34.RocketMQ之Broker端消息存储流程详解

news2024/9/20 22:28:12

highlight: arduino-light

Broker消息存储概要设计

RocketMQ主要存储的文件包括Commitlog文件,ConsumeQueue文件,IndexFile文件。

RMQ把所有主题的消息存储在同一个文件中,确保消息发送时顺序写文件。

为了提高消费效率引入了ConsumeQueue消息队列文件,每个消息主题包含多个消息消费队列,每一个消息队列有一个消息文件。

IndexFile索引文件,其主要设计理念就是为了加速消息的检索性能,根据消息的属性快速从Commitlog文件中检索消息。

初识Broker消息存储

消息存储实现类:org.apache.rocketmq.store.DefaultMessageStore,它是存储模块里面最重要的一个类,包含了很多对存储文件的操作API。 java // 消息存储配置属性     private final MessageStoreConfig messageStoreConfig; // CommitLog 文件的存储实现类 private final CommitLog commitLog; // 消息队列存储缓存表,按消息主题分组 private final ConcurrentMap<String/* topic */, ConcurrentMap<Integer/* queueId */, ConsumeQueue>> consumeQueueTable; // ConsumeQueue刷盘线程 private final FlushConsumeQueueService flushConsumeQueueService; // 清除CommitLog文件服务 private final CleanCommitLogService cleanCommitLogService; // 清除ConsumeQueue文件服务 private final CleanConsumeQueueService cleanConsumeQueueService; // 索引文件实现类 private final IndexService indexService; // MappedFile分配服务 private final AllocateMappedFileService allocateMappedFileService; // CommitLog消息分发,根据CommitLog文件构建ConsumeQueue,IndexFile文件 private final ReputMessageService reputMessageService; // 存储HA机制 private final HAService haService; // 消息堆内存缓存 private final TransientStorePool transientStorePool; // 消息拉取长轮询模式消息达到监听器 private final MessageArrivingListener messageArrivingListener; // Broker属性配置类 private final BrokerConfig brokerConfig; // 文件刷盘监测点 private StoreCheckpoint storeCheckpoint; // CommitLog文件转发请求 private final LinkedList<CommitLogDispatcher> dispatcherList;

消息发送存储流程

Step1:如果Broker停止工作或者为Slave,则拒绝消息写入。

如果消息长度超过256个字符,消息属性长度超过65536个字符将拒绝改消息的写入。

Step2:如果消息的延迟级别大于0,将消息的原主题名称与原消息队列ID存入消息属性中,用延迟消息主题SCHEDULE_TOPIC、消息队列ID更新原先消息的主题与队列,这是并发消息消费重试关键的一步。

Step3:获取当前可以写入的Commitlog文件。每一个文件默认1G,一个文件写满后再创建另外一个,以该文件中第一个偏移量为文件名,偏移量小于20位用0补齐。第一个文件初始偏移量为0,第二个文件的偏移量是1073741824,代表该文件中的第一条消息的物理偏移量为1073741824,这样就能快速定位到消息。

Step4:对于rocketmq来说,存储消息的主要文件被称为CommitLog,因此就从该类入手。处理存储请求的入口方法是asyncPutMessage,主要流程如下: java public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) { //可能会有多个线程并发请求,虽然支持集群,但是对于每个单独的broker都是本地存储,所以内存锁就足够了 putMessageLock.lock(); try { //获取最新的文件 MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); ... //如果文件为空,或者已经存满,则创建一个新的commitlog文件 if (null == mappedFile || mappedFile.isFull()) { mappedFile = this.mappedFileQueue.getLastMappedFile(0); } ... //调用底层的mappedFile进行出处,但是注意此时还没有刷盘 result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext); ... } finally { putMessageLock.unlock(); } PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result); } 在写入CommitLog之前,先申请putMessageLock,也就是将消息存储到CommitLog文件中是串行的。

可能会有多个线程并发请求,虽然支持集群,但是对于每个单独的broker都是本地存储,所以单机内存锁就足够。

接口PutMessageLock java public interface PutMessageLock { void lock(); void unlock(); } 重入实现PutMessageReentrantLock ```java public class PutMessageReentrantLock implements PutMessageLock { private ReentrantLock putMessageNormalLock = new ReentrantLock(); // NonfairSync

@Override
    public void lock() {
        putMessageNormalLock.lock();
    }

    @Override
    public void unlock() {
        putMessageNormalLock.unlock();
    }
}

**自旋实现PutMessageSpinLock** java public class PutMessageSpinLock implements PutMessageLock { //true: Can lock, false : in lock. private AtomicBoolean putMessageSpinLock = new AtomicBoolean(true);

@Override
    public void lock() {
        boolean flag;
        do {
            flag = this.putMessageSpinLock.compareAndSet(true, false);
        }
        while (!flag);
    }

    @Override
    public void unlock() {
        this.putMessageSpinLock.compareAndSet(false, true);
    }
}

``` 异步刷盘建议用自旋锁,同步刷盘建议用重入锁:useReentrantLockWhenPutMessage默认false使用自旋锁;

异步刷盘建议开启TransientStorePoolEnable;建议关闭transferMsgByHeap,提高拉消息效率;

同步刷盘建议适当增大sendMessageThreadPoolNums,具体配置需要经过压测。

自旋锁

互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。

如果物理机器有一个以上的处理器或者处理器核心,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只须让线程执行一个循环(自旋),这项技术就是所谓的自旋锁。

自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效果就会非常好,反之如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有价值的工作,这就会带来性能的浪费。

也就是说我们要权衡自旋等待、线程的用户态与内核态切换的开销,哪个更大?

重入锁

当一个线程要获取一个被其他线程持有的独占锁时,该线程会被阻塞,那么当一个线程再次获取它自己己经获取的锁时是否会被阻塞呢?如果不被阻塞,那么我们说该锁是可重入的。

了解了自旋锁和可重入锁,我们在看看如何回答:为什么建议“异步刷盘建议使用自旋锁,同步刷盘建议使用重入锁”?

同步刷盘时,锁竞争激烈,会有较多的线程处于等待阻塞等待锁的状态,如果采用自旋锁会浪费很多的CPU时间,所以“同步刷盘建议使用重入锁”。

异步刷盘是间隔一定的时间刷一次盘,锁竞争不激烈,不会存在大量阻塞等待锁的线程,偶尔锁等待就自旋等待一下很短的时间,不要进行上下文切换了,所以采用自旋锁更合适。

Step5:设置消息的存储时间,如果mappedFile为空,表明 /commitlog 目录下不存在任何文件,说明本次消息是第一次消息发送,用偏移量0创建第一个 commit 文件。 java messageExtBatch.setStoreTimestamp(beginLockTimestamp); if (null == mappedFile || mappedFile.isFull()) { mappedFile = this.mappedFileQueue.getLastMappedFile(0); } if (null == mappedFile) { log.error("Create mapped file1 error"); beginTimeInLock = 0; return new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, null); } Step6:将消息追加到 MappedFile 中。首先先获取 MappedFile 当前写指针,如果currentPos大于或等于文件大小则表明文件已写满,抛出UNKNOWN_ERROR。如果currentPos小于文件大小,通过 slice() 方法创建一个与MappedFile的贡献内存区,并设置position为当前指针。 ```java int currentPos = this.wrotePosition.get();

if (currentPos < this.fileSize) { ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice(); byteBuffer.position(currentPos); AppendMessageResult result; if (messageExt instanceof MessageExtBrokerInner) { result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt); } else if (messageExt instanceof MessageExtBatch) { result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBatch) messageExt); } else { return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } this.wrotePosition.addAndGet(result.getWroteBytes()); this.storeTimestamp = result.getStoreTimestamp(); return result; } **Step7:创建全局唯一消息ID,消息ID有16字节,消息ID前4字节是IP,中间4字节是端口号,最后8字节是消息偏移量。** java long wroteOffset = fileFromOffset + byteBuffer.position(); this.resetByteBuffer(storeHostHolder, storeHostLength); String msgId = MessageDecoder.createMessageId(this.msgIdMemory, msgInner.getStoreHostBytes(storeHostHolder), wroteOffset); ``` 但是为了消息ID可读性,返回给应用程序的msgId为字符类型,可以通过UtilAll.byte2string方法将msgId字节数组转换成字符串,通过UtilAll.string2bytes方法将msgId字符串还原成16个字节的字节数组,从而根据提取消息偏移量,可以快速通过msgId找到消息内容。

Step8:获取该消息在消息队列的偏移量,CommitLog中保存了当前所有消息队列的当前待写入偏移量 java keyBuilder.setLength(0); keyBuilder.append(msgInner.getTopic()); keyBuilder.append('-'); keyBuilder.append(msgInner.getQueueId()); String key = keyBuilder.toString(); Long queueOffset = CommitLog.this.topicQueueTable.get(key); if (null == queueOffset) { queueOffset = 0L; CommitLog.this.topicQueueTable.put(key, queueOffset); } Step9:根据消息体的长度、主题的长度、属性的长度结合消息存储格式计算消息的总长度。 ```java protected static int calMsgLength(int sysFlag, int bodyLength, int topicLength, int propertiesLength) {

final int msgLen =
            4   //TOTALSIZE:该消息条目总长度,4字节
            + 4 //MAGICCODE:魔数,4字节。固定值0xdaa320a7
            + 4 //BODYCRC:消息体crc校验码,4字节
            + 4 //QUEUEID:消息消息队列ID,4字节
            + 4 //FLAG:消息FLAG,RMQ不做处理,供应用程序使用,默认4字节
            + 8 //QUEUEOFFSET:消息在消息消费队列的偏移量,8字节
            + 8 //PHYSICALOFFSET:消息在CommitLog文件中偏移量,8字节
            + 4 //SYSFLAG:消息系统Flag,例如是否压缩,是否是事务消息等,4字节
            + 8 //BORNTIMESTAMP:消息生产者调用消息发送API的时间戳,8字节
            + bornhostLength //BORNHOST:发送者IP,端口,8字节
            + 8 //STORETIMESTAMP:消息存储时间戳,8字节
            + storehostAddressLength //STOREHOSTADDRESS:BrokerIP和端口,8字节
            + 4 //RECONSUMETIMES:消息重试次数,4字节
            + 8 //Prepared Transaction Offset:事务消息物理偏移量,8字节
            + 4 + (bodyLength > 0 ? bodyLength : 0) //BODY:消息体长度,4字节
            //TOPIC:主题存储长度,1字节
            + 1 + topicLength
             //propertiesLength:消息属性,长度为PropertiesLength中存储的值
            + 2 + (propertiesLength > 0 ? propertiesLength : 0)
            + 0;
        return msgLen;
    }

``` 上述表明CommitLog条目是不定长的,每一个条目的长度存储在前4个字节中。

Step10:如果消息长度 + END_FILE_MIN_BLANK_LENGTH 大于 CommitLog文件的空闲空间,则返回AppendMessageStatus.END_OF_FILE,Broker会创建一个新的CommitLog文件来存储该消息。

从这里可以看出,每个CommitLog文件最少会空闲8字节,高4字节存储当前文件剩余空间,低四字节存储魔数:CommitLog.BLANK_MAGIC_CODE。 ```java final long beginTimeMills = CommitLog.this.defaultMessageStore.now(); // Write messages to the queue buffer byteBuffer.put(this.msgStoreItemMemory.array(), 0, msgLen);

AppendMessageResult result = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, msgLen, msgId, msgInner.getStoreTimestamp(), queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills); Step11:将消息内容存储到ByteBuffer中,然后创建AppendMessageResult。把消息存储在MappedFile对应的内存映射Buffer中,并没有刷写到磁盘。 java // Return code private AppendMessageStatus status; // Where to start writing private long wroteOffset; // Write Bytes private int wroteBytes; // Message ID private String msgId; // Message storage timestamp private long storeTimestamp; // Consume queue's offset(step by one) private long logicsOffset; private long pagecacheRT = 0; private int msgNum = 1; //批量发送消息条数

/**
 * When write a message to the commit log, returns code
 */
public enum AppendMessageStatus {
    PUT_OK,
    END_OF_FILE, //超过文件大小
    MESSAGE_SIZE_EXCEEDED, //消息长度超过最大允许长度
    PROPERTIES_SIZE_EXCEEDED, //消息属性超过最大允许长度
    UNKNOWN_ERROR,
}

``` Step12:更新消息队列逻辑偏移量

Step13:处理完消息追加逻辑后将释放putMessageLock锁

Step14:DefaultAppendMessageCallback#doAppend只是将消息追加在内存中,需要根据同步刷盘还是异步刷盘方式,将内存中的数据持久化道磁盘,然后执行HA主从同步复制。

存储文件组织与内存映射 RMQ通过使用内存映射文件来提高IO访问性能,无论是CommitLog、ConsumeQueue还是IndexFile,单个文件都被设计成固定长度,如果一个文件写满后再创建一个新文件,文件名就是第一条消息的偏移量。

RocketMQ使用MappedFile、MappedFileQueue来封装存储文件,一个MappedFileQueue可以包含多个MappedFile。

Broker收到消息之后的处理流程:简单来说,roker通过Netty网络服务器获取到条消息,接着就会把这条消息写入到一个Commitlog文件里去,一个Broker机器上就只有一个CommitLog文件,所有Topic的消息都会写入到一个文件里去。

然后还会以异步的方式把消息写入到ConsumeQueue文件里去,因为一个Topic有多个MessageQueue,任何条消息都是写入一个MessageQueue的,那个MessaqeQueue其实就是对应了一个ConsumeQueue文件 所以一条写入MessageQueue的消息,必然会异步进入对应的ConsumeQueue文件。

同时还会异步把消息写入一个ndexFile里,在里面主要就是把每条消息的key和消息在CommitLog中的offset偏移量做一个索引,这样后续如果要根据消息key从CommitLog文件里查询消息,就可以根据IndexFile的索引来了。

接着我们来一步一步分析一下这里写入这几个文件的一个流程。

首先Broker收到一个消息之后,必然是先写入CommitLog文件的,那么这个CommitLog文件在磁盘上的目录结构大致如何呢? 看下面 CommitLog文件的存储目录是在SROCKETMQ HOME/store/commitlog下的,里面会有很多的CommitLog文件,每个文件默认是1GB大小,一个文件写满了就创建一个新的文件,文件名的话,就是文件中的第一个偏移量,如人面所示。文件名如果不足20位的话,就用0来补齐就可以了。 000000000000000000000 000000000003052631924 在把消息写入CommitLog文件的时候,会申请 个putMessageLock锁 也就是说,在Broker上写入消息到CommitLog文件的时候,都是事行的,不会让你并发的写入,并发写入文件必然会有数据错乱的问题,下面是源码片段 java protected final PutMessageLock putMessageLock; putMessageLock .lock()

接着其实会对消息做出一通处理,包括设置消息的存储时间、创建全局唯一的消息D、计算消息的总长度,然后会走段很关键的源码,把消息写入到MappedFile里去,这个其实我们之前还讲解过里面的黑科技,看下面的源码。

java if (messageExt instanceof MessapeExtBrokerInner) { result = cb.doAppend(this.getFileFromoffset(), byteBuffer, this.filesize - currentpos, (MessapeExtBrokerInner) messageExt); }

上面源码片段中,其实最关键的是cb.doAppend这行代码,这行代码其实是把消息追加到MappedFile映射的一块内存里去,并没有直接刷入磁盘中.

至于具体什么时候才会把内存里的数据刷入磁盘,其实要看我们配置的刷盘策略,另外就是不管是同步刷盘还是异步刷盘,假设你配置了主从同步,一旦你写入完消息到CommitLog之后,接下来都会进行主从同步复制的。

Broker收到一条消息之后,其实就会直接把消息写入到Commitlod里去,但是他写入刚开始仅仅是写入到MappedFile映射的一块内存里去,后续是根据刷盘策略去决定是否立即把数据从内存刷入磁盘的.

这个消息写入CommitLog之后,然后消息是如何进入ConsumeQueue和IndexFile的。 实际上,Broker启动的时候会开启一个线程,ReputMessageService,他会把CommitLog更新事件转发出去,然后让任务处理器去更新ConsumeQueue和IndexFile.

我们看下面的源码片段,在DefaultMessageStore的start方法里,在里面就是启动了这个ReputMessageService线程。 这个DefaultMessageStore的start方法就是在Broker启动的时候调用的,所以相当于是Broker启动就会启动这个线程.

image.png

下面我们看这个ReputlMessageService线程的运行逻辑,源码片段如下所示

image.png

也就是说,在这个线程里,每隔1毫秒,就会把最近写入CommitLog的消息进行一次转发,转发到ConsumeQueue和ndexFile里去,通过的是doReput0方法来实现的,我们再看doReput方法里的实现逻辑,先看下面源码片段.

image.png

这段代码意思非常的清晰明了,就是从commitLog中去获取到一DispatchRequest拿到了一份需要进行转发的消息,也就是从CommitLog中读取的。

接着他就会通过下面的代码,调用doDispatch0方法去把消息进行转发,一个是转发到ConsumeQueue里去,一个是转发到IndexFile里去 大家看下面的源码片段,里面走了CommitLogDispatcher的循环

image.png

实际上F常来说这个CommitLoaDispatcher的实现类有两,分别是CommitLoaDispatcherBuildConsumeQueue和CommitLoaDispatcherBuildlindex,他们俩分别会负责把消息转发到ConsumeQueue和IndexFile。

接着我们看一下ConsumeQueueDispatche的源码实现逻辑,其实非常的简单,就是找到当前Topic的messageQueueld对应的一个ConsumeQueue文件 个MessageQueue会对应多个ConsumeQueue文件,找到一个即可,然后消息写入其中

image.png

再来看看indexFile的写入逻辑,其实也很简单,无非就是在ndexFile里去构建对应的索引罢了,如下面的源码片段

image.png

因此到这里为止,我想大家基本就看明白了,当我们把消息写入到CommitLog之后,有一个后台线程每隔1毫秒就会去拉取CommitLog中最新更新的一批消息,然后分别转发到ConsumeQueue和ndexFile里去,这就是他底层的实现原理

ConsumerQueue 消息消费队列是专门为消息订阅构建的索引文件,提高根据主题与消息队列检索消息的速度。

IndexFile主要用于根据Message Key和Unique Key检索消息

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

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

相关文章

Android studio 引入不了R包,手动引入显示红色。可以跑起来却没问题

之前在这个问题踩坑2次&#xff0c;遂记录一下。 问题是&#xff1a;工程里找不到自己包名的R&#xff0c;手动导入显示红色&#xff0c;Run起来倒是没问题 尝试过Clean&#xff0c;Rebuild&#xff0c;清缓存&#xff0c;重启&#xff0c;都没用。 最终发现是没有在 Android…

gitlab:(二)gitlab注册用户和邀请进入项目的问题

.新注册用户无法在管理员界面激活 在管理员界面无法进行管理&#xff0c;找不到相关的操作界面 当前gitlab版本 尝试降低gitlab版本 卸载之后删除残留文件 安装指定的低版本&#xff1a;gitlab-ce-12.7.5-ce.0.el7.x86_64.rpm yum install policycoreutils openssh-server op…

网络应用基础ENSP环境的安装(NETBASE第三课)

网络应用基础ENSP环境的安装(NETBASE第三课) eNSP(Enterprise Network Simulation Platform)是一款由华为提供的、可扩展的、图形化操作的网络仿真工具平台&#xff0c;主要对企业网络路由器、交换机进行软件仿真&#xff0c;完美呈现真实设备实景&#xff0c;支持大型网络模拟…

Markdown编辑器Vditor的基本使用以及在Vue3中使用

介绍 官网 GitHub 帮助文档 Vditor是一个Markdown编辑器组件&#xff08;也支持富文本编辑器&#xff09;&#xff0c;可以嵌入到自己的Web应用中。 此Markdown编辑器支持三种模式&#xff08;几乎没有一个Markdown编辑器同时支持这三种模式&#xff09;&#xff1a; 所见即所…

基于matlab消除视频流中摄像机运动的影响(附源码)

一、前言 此示例演示如何从视频流中删除摄像机运动的影响。 在此示例中&#xff0c;我们首先定义要跟踪的目标。在这种情况下&#xff0c;它是汽车的后部和车牌。我们还建立了一个动态搜索区域&#xff0c;其位置由最后一个已知的目标位置确定。然后&#xff0c;我们仅在此搜…

ChatGPT引导下的编程起航:零基础学会Python编程(ChatGPT版)

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言如何使用 ChatGPT对…

【C++】C++11之线程库

一、thread类 在 C11 之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如 windows 和 linux 下各有自己的接 口&#xff0c;这使得代码的可移植性比较差 。 C11 中最重要的特性就是对线程进行支持了&#xff0c;使得 C 在 并行编程时不需要依赖第三…

刚发offer,明天就让上班!原公司离职手续还没办,说晚两天报到,hr竟取消offer,录用其他人!...

这年头找工作有多难&#xff1f;一位网友吐槽&#xff1a;一瞬间从天堂掉进地狱&#xff01; 楼主说&#xff0c;hr今天刚发了offer&#xff0c;就让明天来上班。自己说明天不可能&#xff0c;原公司离职手续还没办完&#xff0c;hr就取消了offer&#xff0c;转而录用了其他候选…

MySQL基本查询与内置函数

目录 聚合函数 分组查询 内置函数 日期函数 字符串函数 数学函数 聚合函数 COUNT&#xff1a;返回查询到的数据的数量 SUM&#xff1a;返回查询到的数据的总和&#xff08;数字&#xff09; AVG&#xff1a;返回数据的平均值 MAX&#xff1a;返回查询到的数据的最大值 MIN&a…

毫米波雷达系列 | 毫米波雷达测角原理-补档

毫米波雷达测角原理 角度分辨率 当估算角度分辨率最少需要两个RX天线&#xff0c;利用的是物体相对每个天线的差分距离。 假设雷达具有一个发射天线TX和两根接受天线RX1和RX2之间的间距为d&#xff0c; θ \theta θ为目标相对天线的角度&#xff0c;那么接受天线之间的相位…

OpenCV 入门教程:颜色空间转换

OpenCV 入门教程&#xff1a;颜色空间转换 导语一、颜色空间的基本概念1.1 RGB颜色空间1.2 灰度颜色空间1.3 其他颜色空间 二、颜色空间转换三、示例应用3.1 提取图像的色彩通道3.2 调整图像的亮度和对比度 总结 导语 在图像处理和计算机视觉领域&#xff0c;颜色空间转换是一…

学无止境·MySQL③

单表查询 题一创建表并插入数据薪水修改为5000将姓名为张三的员工薪水修改为3000元将姓名为李四的员工薪水修改为4000元&#xff0c;gener改为女 题一 1.创建表&#xff1a; 创建员工表employee&#xff0c;字段如下&#xff1a; id&#xff08;员工编号&#xff09;&#xff…

CVE 官网提交流程

CVE 官网提交流程 一、进入官网申请 https://cveform.mitre.org/ 选择申请CVE ID即可。 Vulnerability type翻译&#xff08;chatgpt-3.5&#xff09;buffer overflow缓冲区溢出是一种软件漏洞&#xff0c;攻击者在向缓冲区写入超出其容量的数据时&#xff0c;可能导致数据覆…

一文搞懂常见的加密算法

加密算法在互联网技术领域中几乎是无处不在&#xff0c;而密码学也是网络安全的重要基础&#xff0c;这篇文章我们就一起来学习下常见的加密算法。 1 为什么要研究加密算法&#xff1f; 在技术方面&#xff0c;加密算法的研究具有重要的意义&#xff0c;主要体现在以下几个方…

【LeetCode】HOT 100(25)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

从零开始 Spring Boot 66:JPA 查询参数

从零开始 Spring Boot 66&#xff1a;JPA 查询参数 图源&#xff1a;简书 (jianshu.com) JPA 的查询参数分为两种&#xff1a; 命名参数&#xff08;Named Parameters&#xff09;位置参数&#xff08;Positional Parameters&#xff09; 类似于 Python 中的函数的位置参数和…

Linux的网络配置与远程连接与文件传输

&#xff08;该图由AI绘制 关注我 学习AI画图&#xff09; 目录 网络配置 1、ifconfig查看网络信息 2、与网卡相关的配置文件 3、查询计算机的网络状态 4、systemctl启动/重启/停止网络 Linux远程连接与文件传输 1、为什么需要远程连接 2、SSH协议 3、sshd服务 4、…

2023CCF CAT- 热身赛

NOIP普及组 字符串 排序2017 动态规划 递推 USACO 2001 贪心 牛客小白月赛12 说实话还是很喜欢打比赛&#xff0c;喜欢AC的感觉&#xff0c;但是这玩意咋越来越难了那。。。。。 扎心了&#xff0c;不是~~~~~ 当个爱好吧&#xff0c;还是很喜欢当年打比赛和队友相视一笑的样子…

宇凡微2.4g无线合封芯片,高集成内置九齐单片机

2.4GHz是指一段频率范围&#xff0c;用来表示无线通信中的特定频率范围。在无线传输产品和设备中&#xff0c;2.4GHz被广泛应用&#xff0c;用于传输固定频率的波形以实现接收和发射功能。 需要注意的是&#xff0c;2.4GHz和蓝牙功能在基本上是相似的&#xff0c;但并不是所有…

前端面试刷题整理

第一题&#xff1a;es6 class语法 题目&#xff1a;现有三种菜单&#xff0c;button属性&#xff0c;select属性&#xff0c;model属性 class Mune{constructor(title,icon){this.title titlethis.icon icon}isDisabled(){return false}exec(){} } class Button extends Mun…