Doris数据库BE——LoadChannelMgr原理解析

news2024/9/17 7:31:12

数据在经过清洗过滤后,会通过Open/AddBatch请求分批量将数据发送给存储层的BE节点上。在一个BE上支持多个LoadJob任务同时并发写入执行。LoadChannelMgr负责管理这些任务,并对数据进行分发。

internal service

Open/AddBatch请求接口使用BRPC,定义在be/src/service/internal_service.h文件中。如下列出了tablet_writer接口的调用流程。tablet_writer_open函数会调用LoadChannelMgr类的open函数;tablet_writer_add_batch函数调用 _tablet_writer_add_batch函数,tablet_writer_add_block函数调用_tablet_writer_add_block函数;tablet_writer_cancel函数会调用LoadChannelMgr类的cancel函数。

class PInternalServiceImpl : public PBackendService {
public:
    PInternalServiceImpl(ExecEnv* exec_env);
    virtual ~PInternalServiceImpl();

    void tablet_writer_open(google::protobuf::RpcController* controller, const PTabletWriterOpenRequest* request, PTabletWriterOpenResult* response, google::protobuf::Closure* done) override;
          auto st = _exec_env->load_channel_mgr()->open(*request); st.to_protobuf(response->mutable_status());

    void tablet_writer_add_batch(google::protobuf::RpcController* controller, const PTabletWriterAddBatchRequest* request, PTabletWriterAddBatchResult* response, google::protobuf::Closure* done) override;
          google::protobuf::Closure* new_done = new NewHttpClosure<PTransmitDataParams>(done);
          _tablet_writer_add_batch(cntl_base, request, response, new_done);
    void tablet_writer_add_batch_by_http(google::protobuf::RpcController* controller, const ::doris::PEmptyRequest* request, PTabletWriterAddBatchResult* response, google::protobuf::Closure* done) override;
          PTabletWriterAddBatchRequest* new_request = new PTabletWriterAddBatchRequest();
          google::protobuf::Closure* new_done = new NewHttpClosure<PTabletWriterAddBatchRequest>(new_request, done);
          brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
          Status st = attachment_extract_request_contain_tuple<PTabletWriterAddBatchRequest>(new_request, cntl);
          _tablet_writer_add_batch(cntl_base, new_request, response, new_done);

    void tablet_writer_add_block(google::protobuf::RpcController* controller, const PTabletWriterAddBlockRequest* request, PTabletWriterAddBlockResult* response, google::protobuf::Closure* done) override;
          google::protobuf::Closure* new_done = new NewHttpClosure<PTransmitDataParams>(done);
          brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
          attachment_transfer_request_block<PTabletWriterAddBlockRequest>(request, cntl);
          _tablet_writer_add_block(cntl_base, request, response, new_done);
    void tablet_writer_add_block_by_http(google::protobuf::RpcController* controller, const ::doris::PEmptyRequest* request, PTabletWriterAddBlockResult* response, google::protobuf::Closure* done) override;
          PTabletWriterAddBlockRequest* new_request = new PTabletWriterAddBlockRequest();
          google::protobuf::Closure* new_done = new NewHttpClosure<PTabletWriterAddBlockRequest>(new_request, done);
          brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
          Status st = attachment_extract_request_contain_block<PTabletWriterAddBlockRequest>(new_request, cntl);
          _tablet_writer_add_block(cntl_base, new_request, response, new_done);                                                                                                         
                                                                                       
    void tablet_writer_cancel(google::protobuf::RpcController* controller, const PTabletWriterCancelRequest* request, PTabletWriterCancelResult* response, google::protobuf::Closure* done) override;
          auto st = _exec_env->load_channel_mgr()->cancel(*request);
private:
    void _tablet_writer_add_batch(google::protobuf::RpcController* controller, const PTabletWriterAddBatchRequest* request, PTabletWriterAddBatchResult* response, google::protobuf::Closure* done);
    void _tablet_writer_add_block(google::protobuf::RpcController* controller, const PTabletWriterAddBlockRequest* request, PTabletWriterAddBlockResult* response, google::protobuf::Closure* done);

private:
    ExecEnv* _exec_env;
    PriorityThreadPool _tablet_worker_pool;
    PriorityThreadPool _slave_replica_worker_pool;
}

LoadChannelMgr

在这里插入图片描述
在一个BE上支持多个LoadJob任务同时并发写入执行。LoadChannelMgr负责管理这些任务,并对数据进行分发。每次导入任务LoadJob会建立一个LoadChannel来执行,LoadChannel维护了一次导入通道,LoadChannel可以将数据分批量写入操作直到导入完成。因此LoadChannelMgr需要维护一个loadjob和load channel映射关系(std::mutex _lock; // lock protect the load channel mapstd::unordered_map<UniqueId, std::shared_ptr<LoadChannel>> _load_channels; // load id -> load channel)。LoadChannelMgr还需要为导入通道维护内存(_mem_usage统计所有导入通道内存消耗大小;导入内存软硬限制等),用于cache导入通道的LRU缓存(LastestSuccessChannelCache: Used to cache the LoadChannel of the import receiver,完成数据导入的通道后缓存于此),创建thread执行_start_load_channels_clean函数清理超时导入通道timeout load channels

Status LoadChannelMgr::init(int64_t process_mem_limit) {
    _load_hard_mem_limit = calc_process_max_load_memory(process_mem_limit);
    _load_soft_mem_limit = _load_hard_mem_limit * config::load_process_soft_mem_limit_percent / 100;    
    _load_channel_min_mem_to_reduce = _load_hard_mem_limit * 0.1; // If a load channel's memory consumption is no more than 10% of the hard limit, it's not worth to reduce memory on it. Since we only reduce 1/3 memory for one load channel, for a channel consume 10% of hard limit, we can only release about 3% memory each time, it's not quite helpfull to reduce memory pressure. In this case we need to pick multiple load channels to reduce memory more effectively.
    _mem_tracker = std::make_unique<MemTrackerLimiter>(MemTrackerLimiter::Type::LOAD, "LoadChannelMgr");
    REGISTER_HOOK_METRIC(load_channel_mem_consumption, [this]() { return _mem_tracker->consumption(); });
    
    _last_success_channel = new_lru_cache("LastestSuccessChannelCache", 1024);
    
    RETURN_IF_ERROR(_start_bg_worker()); 
    return Status::OK();
}

LoadChannelMgr主要负责导入通道维护和内存超限管理两大任务,包含在open时为请求创建导入通道;cancel时关闭通道;add_batch时使用LRU缓存cache和清理已完成的导入通道,内存超限时清理内存。后台thread执行_start_load_channels_clean函数清理超时导入通道timeout load channels
open函数为导入数据请求创建导入通道,首先提取loadid,查找或创建导入通道。

Status LoadChannelMgr::open(const PTabletWriterOpenRequest& params) {
    UniqueId load_id(params.id()); std::shared_ptr<LoadChannel> channel; std::lock_guard<std::mutex> l(_lock);
        auto it = _load_channels.find(load_id);
        if (it != _load_channels.end()) { channel = it->second;  // 查找到正使用中的loadid->loadchannel
        } else { // create a new load channel
            int64_t timeout_in_req_s = params.has_load_channel_timeout_s() ? params.load_channel_timeout_s() : -1;
            int64_t channel_timeout_s = calc_channel_timeout_s(timeout_in_req_s);
            bool is_high_priority = (params.has_is_high_priority() && params.is_high_priority());
            // Use the same mem limit as LoadChannelMgr for a single load channel
            auto channel_mem_tracker = std::make_unique<MemTracker>(fmt::format("LoadChannel#senderIp={}#loadID={}", params.sender_ip(), load_id.to_string()));
            channel.reset(new LoadChannel(load_id, std::move(channel_mem_tracker), channel_timeout_s, is_high_priority, params.sender_ip(), params.is_vectorized())); // 创建导入通道
            _load_channels.insert({load_id, channel});
        }
    }
    RETURN_IF_ERROR(channel->open(params)); // 打开导入通道
    return Status::OK();
}

add_batch函数是向导入通道里添加数据的入口,其也包含了使用LRU缓存cache和清理已完成的导入通道,内存超限时清理内存功能。_get_load_channel函数从loadjob和load channel映射和cache导入通道的LRU缓存查询导入通道,如果从cache导入通道的LRU缓存查到则需判定是否导入已经完成,否则继续该次导入数据;如果是低等级导入任务需要参与内存超限处理任务;向导入通道中加载数据;最终判定导入通道是否完成导入,完成需要从loadjob和load channel映射去除,加入cache导入通道的LRU缓存。

template <typename TabletWriterAddRequest, typename TabletWriterAddResult>
Status LoadChannelMgr::add_batch(const TabletWriterAddRequest& request, TabletWriterAddResult* response) {
    UniqueId load_id(request.id());
    // 1. get load channel
    std::shared_ptr<LoadChannel> channel; bool is_eof;
    auto status = _get_load_channel(channel, is_eof, load_id, request);
    if (!status.ok() || is_eof) { return status; }

    if (!channel->is_high_priority()) { // 2. check if mem consumption exceed limit If this is a high priority load task, do not handle this. because this may block for a while, which may lead to rpc timeout.
        _handle_mem_exceed_limit();
    }

    // 3. add batch to load channel batch may not exist in request(eg: eos request without batch), this case will be handled in load channel's add batch method.
    Status st = channel->add_batch(request, response);
    if (UNLIKELY(!st.ok())) { channel->cancel(); return st; }

    // 4. handle finish
    if (channel->is_finished()) {
        _finish_load_channel(load_id);
    }
    return Status::OK();
}

template <typename Request>
Status LoadChannelMgr::_get_load_channel(std::shared_ptr<LoadChannel>& channel, bool& is_eof, const UniqueId& load_id, const Request& request) {
    is_eof = false; std::lock_guard<std::mutex> l(_lock);
    auto it = _load_channels.find(load_id);
    if (it == _load_channels.end()) {
        auto handle = _last_success_channel->lookup(load_id.to_string()); // success only when eos be true
        if (handle != nullptr) {
            _last_success_channel->release(handle);
            if (request.has_eos() && request.eos()) { is_eof = true; return Status::OK(); }
        }
        return Status::InternalError("fail to add batch in load channel. unknown load_id={}", load_id.to_string());
    }
    channel = it->second;
    return Status::OK();
}

void LoadChannelMgr::_finish_load_channel(const UniqueId load_id) {
    VLOG_NOTICE << "removing load channel " << load_id << " because it's finished";
    {
        std::lock_guard<std::mutex> l(_lock);
        _load_channels.erase(load_id);
        auto handle = _last_success_channel->insert(load_id.to_string(), nullptr, 1, dummy_deleter);
        _last_success_channel->release(handle);
    }
    VLOG_CRITICAL << "removed load channel " << load_id;
}

其实add_batch函数都是由线程池中线程执行,因此需要_wait_flush_cond信号量保证同时只有一个线程在进行内存清理,其他线程必须等待该线程进行唤醒。

void LoadChannelMgr::_handle_mem_exceed_limit() {
    // Check the soft limit.
    int64_t process_mem_limit = MemInfo::soft_mem_limit();
    if (_mem_tracker->consumption() < _load_soft_mem_limit && MemInfo::proc_mem_no_allocator_cache() < process_mem_limit) { return; }
    // Indicate whether current thread is reducing mem on hard limit.
    bool reducing_mem_on_hard_limit = false;
    std::vector<std::shared_ptr<LoadChannel>> channels_to_reduce_mem;
    {
        std::unique_lock<std::mutex> l(_lock);
        while (_should_wait_flush) { // _should_wait_flush被设置,因此该线程需要等待condition
            LOG(INFO) << "Reached the load hard limit " << _load_hard_mem_limit << ", waiting for flush";
            _wait_flush_cond.wait(l);
        }
        bool hard_limit_reached = _mem_tracker->consumption() >= _load_hard_mem_limit || MemInfo::proc_mem_no_allocator_cache() >= process_mem_limit;
        // Some other thread is flushing data, and not reached hard limit now, we don't need to handle mem limit in current thread.
        if (_soft_reduce_mem_in_progress && !hard_limit_reached) { return; }

        // Pick LoadChannels to reduce memory usage, if some other thread is reducing memory due to soft limit, and we reached hard limit now, current thread may pick some duplicate channels and trigger duplicate reducing memory process. But the load channel's reduce memory process is thread safe, only 1 thread can reduce memory at the same time, other threads will wait on a condition variable, after the reduce-memory work finished, all threads will return. 正式清理内存
        using ChannelMemPair = std::pair<std::shared_ptr<LoadChannel>, int64_t>;
        std::vector<ChannelMemPair> candidate_channels;
        int64_t total_consume = 0;
        for (auto& kv : _load_channels) {
            if (kv.second->is_high_priority()) {  // do not select high priority channel to reduce memory to avoid blocking them.
                continue;
            }
            int64_t mem = kv.second->mem_consumption();            
            candidate_channels.push_back(std::make_pair(kv.second, mem)); // save the mem consumption, since the calculation might be expensive.
            total_consume += mem;
        }

        if (candidate_channels.empty()) { // should not happen, add log to observe
            LOG(WARNING) << "All load channels are high priority, failed to find suitable" << "channels to reduce memory when total load mem limit exceed";
            return;
        }

        // sort all load channels, try to find the largest one.
        std::sort(candidate_channels.begin(), candidate_channels.end(), [](const ChannelMemPair& lhs, const ChannelMemPair& rhs) { return lhs.second > rhs.second; });

        int64_t mem_consumption_in_picked_channel = 0;
        auto largest_channel = *candidate_channels.begin();
        // If some load-channel is big enough, we can reduce it only, try our best to avoid reducing small load channels.
        if (_load_channel_min_mem_to_reduce > 0 && largest_channel.second > _load_channel_min_mem_to_reduce) { // Pick 1 load channel to reduce memory.
            channels_to_reduce_mem.push_back(largest_channel.first);
            mem_consumption_in_picked_channel = largest_channel.second;
        } else { // Pick multiple channels to reduce memory.
            int64_t mem_to_flushed = total_consume / 3;
            for (auto ch : candidate_channels) {
                channels_to_reduce_mem.push_back(ch.first);
                mem_consumption_in_picked_channel += ch.second;
                if (mem_consumption_in_picked_channel >= mem_to_flushed) { break; }
            }
        }

        std::ostringstream oss;
        if (MemInfo::proc_mem_no_allocator_cache() < process_mem_limit) {
            oss << "reducing memory of " << channels_to_reduce_mem.size() << " load channels (total mem consumption: " << mem_consumption_in_picked_channel << " bytes), because total load mem consumption " << PrettyPrinter::print(_mem_tracker->consumption(), TUnit::BYTES)  << " has exceeded";
            if (_mem_tracker->consumption() > _load_hard_mem_limit) {
                _should_wait_flush = true; reducing_mem_on_hard_limit = true;
                oss << " hard limit: " << PrettyPrinter::print(_load_hard_mem_limit, TUnit::BYTES);
            } else {
                _soft_reduce_mem_in_progress = true;
                oss << " soft limit: " << PrettyPrinter::print(_load_soft_mem_limit, TUnit::BYTES);
            }
        } else {
            _should_wait_flush = true;
            reducing_mem_on_hard_limit = true;
            oss << "reducing memory of " << channels_to_reduce_mem.size()  << " load channels (total mem consumption: " << mem_consumption_in_picked_channel << " bytes), because " << PerfCounters::get_vm_rss_str() << " has exceeded limit " << PrettyPrinter::print(process_mem_limit, TUnit::BYTES) << " , tc/jemalloc allocator cache " << MemInfo::allocator_cache_mem_str();
        }
        LOG(INFO) << oss.str();
    }

    for (auto ch : channels_to_reduce_mem) {
        uint64_t begin = GetCurrentTimeMicros();
        int64_t mem_usage = ch->mem_consumption();
        ch->handle_mem_exceed_limit();  // 调用导入通道的内存清理函数
        LOG(INFO) << "reduced memory of " << *ch << ", cost " << (GetCurrentTimeMicros() - begin) / 1000 << " ms, released memory: " << mem_usage - ch->mem_consumption() << " bytes";
    }

    {
        std::lock_guard<std::mutex> l(_lock);
        // If a thread have finished the memtable flush for soft limit, and now the hard limit is already reached, it should not update these variables.
        if (reducing_mem_on_hard_limit && _should_wait_flush) {
            _should_wait_flush = false; _wait_flush_cond.notify_all(); // 唤醒其他线程
        }
        if (_soft_reduce_mem_in_progress) {
            _soft_reduce_mem_in_progress = false;
        }        
        _refresh_mem_tracker_without_lock(); // refresh mem tacker to avoid duplicate reduce
    }
    return;
}
class LoadChannelMgr {
public:
    LoadChannelMgr(); ~LoadChannelMgr();

    Status init(int64_t process_mem_limit);    
    Status open(const PTabletWriterOpenRequest& request); // open a new load channel if not exist

    template <typename TabletWriterAddRequest, typename TabletWriterAddResult>
    Status add_batch(const TabletWriterAddRequest& request, TabletWriterAddResult* response);
    
    Status cancel(const PTabletWriterCancelRequest& request); // cancel all tablet stream for 'load_id' load

    void refresh_mem_tracker() {std::lock_guard<std::mutex> l(_lock); _refresh_mem_tracker_without_lock(); }
    MemTrackerLimiter* mem_tracker() { return _mem_tracker.get(); }

private:
    template <typename Request>
    Status _get_load_channel(std::shared_ptr<LoadChannel>& channel, bool& is_eof, const UniqueId& load_id, const Request& request);
    void _finish_load_channel(UniqueId load_id);
    
    void _handle_mem_exceed_limit(); // check if the total load mem consumption exceeds limit. If yes, it will pick a load channel to try to reduce memory consumption.

    Status _start_bg_worker();

    // lock should be held when calling this method
    void _refresh_mem_tracker_without_lock() {
        _mem_usage = 0;
        for (auto& kv : _load_channels) {
            _mem_usage += kv.second->mem_consumption();
        }
        THREAD_MEM_TRACKER_TRANSFER_TO(_mem_usage - _mem_tracker->consumption(),  _mem_tracker.get());
    }

protected:        
    std::mutex _lock; // lock protect the load channel map    
    std::unordered_map<UniqueId, std::shared_ptr<LoadChannel>> _load_channels; // load id -> load channel
    Cache* _last_success_channel = nullptr;

    // check the total load channel mem consumption of this Backend
    int64_t _mem_usage = 0; int64_t _load_hard_mem_limit = -1; int64_t _load_soft_mem_limit = -1;
    std::unique_ptr<MemTrackerLimiter> _mem_tracker;    
    // By default, we try to reduce memory on the load channel with largest mem consumption, but if there are lots of small load channel, even the largest one consumes very small memory, in this case we need to pick multiple load channels to reduce memory more effectively. `_load_channel_min_mem_to_reduce` is used to determine whether the largest load channel's memory consumption is big enough.
    int64_t _load_channel_min_mem_to_reduce = -1;
    bool _soft_reduce_mem_in_progress = false;    
    bool _should_wait_flush = false; std::condition_variable _wait_flush_cond; // If hard limit reached, one thread will trigger load channel flush, other threads should wait on the condition variable.

    CountDownLatch _stop_background_threads_latch;
    
    scoped_refptr<Thread> _load_channels_clean_thread; // thread to clean timeout load channels
    Status _start_load_channels_clean();
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/565849.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STP 生成树协议

STP&#xff08;Spanning-Tree Protocol&#xff09;的来源 在网络三层架构中&#xff0c;我们会使用冗余这一技术&#xff0c;也就是对三层架构中的这些东西进行备份。冗余包含了设备冗余、网关冗余、线路冗余、电源冗余。 在二层交换网络中进行线路冗余&#xff0c;如图&am…

Linux :: 【基础指令篇 :: 用户管理:(2)】::设置用户密码(及本地Xshell 登录云服务器操作演示) :: passwd

前言&#xff1a;本篇是 Linux 基本操作篇章的内容&#xff01; 笔者使用的环境是基于腾讯云服务器&#xff1a;CentOS 7.6 64bit。 目录索引&#xff1a; 1. 基本语法 2. 基本用法 3. 注意点 4. 补充&#xff1a;指定用户设置密码操作实例测试及登录本地 Xshell 登录演…

Linux命令(18)之file

Linux命令之file 1.file介绍 file命令用来识别文件类型。 2.file用法 file [参数] [文件或目录] file常用参数 参数说明-L显示符号廉洁所指向文件的类别-i显示MIME类别-b显示结果时&#xff0c;不显示文件名称 3.实例 3.1显示ztj.sh文件类型 3.2显示ztj.sh文件类型(不显示…

记一次docker中的oracle连接问题

起因是客户登陆时报错TNS-12537 登陆上上服务器后&#xff0c;发现了几个特点。 1、没有oracle用户 2、数据文件的位置和spfile里面写的不一样 3、pmon进程存在&#xff0c;但是父进程ID不是1 4、配置oracle用户及环境变量&#xff0c;但是as sysdba无法登录到数据库 查看…

Linux命令(19)之userdel

Linux命令之userdel 1.userdel介绍 userdel命令用来说删除useradd命令创建的Linux账户。 useradd创建用户&#xff1a; Linux命令(15)之useradd_小黑要上天的博客-CSDN博客 2.userdel用法 userdel [参数] [用户账户名称] userdel常用参数 参数说明-r递归删除&#xff0c;…

【网络编程】协议定制+Json序列化与反序列化

目录 一、序列化与反序列化的概念 二、自定义协议设计一个网络计算器 2.1TCP协议&#xff0c;如何保证接收方收到了完整的报文呢&#xff1f; 2.2自定义协议的实现 2.3自定义协议在客户端与服务器中的实现 三、使用Json进行序列化和反序列化 3.1jsoncpp库的安装 3.2改造…

学系统集成项目管理工程师(中项)系列26_新兴信息技术

1. 云计算 1.1. 基于互联网的超级计算模式&#xff0c;通过互联网来提供大型计算能力和动态易扩展的虚拟化资源 1.2. 通过网络提供可动态伸缩的廉价计算能力 1.3. 特点 1.3.1. 【19上选23】 1.3.2. 超大规模 1.3.3. 虚拟化 1.3.4. 高可靠性 1.3.5. 通用性 1.3.6. 高可…

linux共享内存总结

共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成 头文件&#xff1a; #include <sys/ipc..h> #include<sys/shm.h> // 创建或获取一个共享内存: 成功返回共享内存ID&#xff0c;失败返回-1 int shmget (key_t key, size_t_size, int flag); // 连接共享内…

Java修饰符

4 修饰符(static关键字) 4.1 权限修饰符 4.2 状态修饰符 final(最终态)static(静态)4.2.1 final的特点 final 关键字是最终的意思,可以修饰成员变量,成员方法,类final修饰的特点: 1.修饰方法:表示该方法是最终方法,不能被重写2.修饰变量:表示该变量是常量,不能被…

深入了解Nginx:高性能的开源Web服务器与反向代理

一、Nginx是什么 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;也可以作为负载均衡器和HTTP缓存服务器使用。它采用事件驱动、异步非阻塞的处理方式&#xff0c;能够处理大量并发连接和高流量负载&#xff…

推荐试试这个简单好用的手机技巧

技巧一&#xff1a;一键锁屏 除了按住手机电源键进行锁屏外&#xff0c;还有其他一些快捷方法可以实现锁屏操作。 对于苹果手机用户&#xff0c;可以按照以下步骤进行设置&#xff1a; 1.打开手机的设置应用&#xff0c;通常可以在主屏幕或应用列表中找到该图标。 2.在设置…

chatgpt赋能python:Pythonunittest跳过用例:使用unittest中跳过测试用例的方法

Python unittest 跳过用例&#xff1a;使用unittest中跳过测试用例的方法 如果你正在开发一个Python项目&#xff0c;你可能已经使用了Python的unittest模块来编写并运行测试用例。在编写测试用例时&#xff0c;有些情况下你可能不想运行某些测试用例&#xff0c;这时就需要使…

Window10安装SQL Server

一、安装SQL Server 1、进入官网根据个人所需下载对应版本即可&#xff0c;本文是基于SQL Server 2022 Express的安装过程 SQL Server 下载 | Microsofthttps://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2、下载完毕&#xff0c;运行安装指引程序 如若不熟悉…

LC-3机器码编程实验 求成绩等级

一、实验目的 分析和理解指定的需解决问题。利用LC-3的汇编代码设计实现相关程序。通过LC-3仿真器调试和运行相关程序并得到正确的结果。 二、实验内容 对学生的成绩使用数组进行排序。 背景&#xff1a;一位老师需要你帮忙决定学生的成绩&#xff0c;她想要根据学生分数在…

【分立元件】MOSFET如何用于同步整流

在电力电子中我们会使用二极管做开关,当二极管导时,相当于开关闭合,当二极管截止时,相当于开关断开。但是二极管在导通时的管压降在低压电源电路中是一个损耗来源,所以一般我们首选使用的是肖特基二极管,因为肖特基二极管的管压降比较低。 如下所示为非同步BUCK电源拓朴…

小黑坐等政审,论文成果毕业事项基本提交,着手眼睛手术明天准备体检然后出发独自夜爬华山的leetcode之旅:81. 搜索旋转排序数组 II

去除相等的二分法 class Solution:def search(self, nums: List[int], target: int) -> bool:# 数组长度n len(nums)# 双指针left 0right n-1# 二分法迭代while left < right:mid (left right) // 2if nums[mid] target:return Trueif nums[left] nums[mid]:left…

原来CSS的登录界面可以变得这么好看

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大一在校生&#xff0c;web前端开发专业 &#x1f921; 个人主页&#xff1a;几何小超 &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习目…

Python之将日志写入到文件(二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

【MySQL 数据库】4、MySQL 事务学习

目录 一、事务简介二、事务相关 SQL 语句(1) 事务提交方式(2) 开启、提交、回滚事务 三、事务的四大特性四、并发事务问题五、事务的隔离级别 一、事务简介 二、事务相关 SQL 语句 (1) 事务提交方式 # 查看当前数据库事务的提交方式 # 1: 自动提交 # 0: 手动提交 select autoc…