RocketMQ 作为一款优秀的分布式消息中间件,可以为业务方提供高性能低延迟的稳定可靠的消息服务。其核心优势是可靠的消费存储、消息发送的高性能和低延迟、强大的消息堆积能力和消息处理能力。

从存储方式来看,主要有几个方面:

  • 文件系统
  • 分布式KV存储
  • 关系型数据库

从效率上来讲,文件系统高于KV存储,KV存储又高于关系型数据库。因为直接操作文件系统肯定是最快的,那么业界主流的消息队列中间件,如 RocketMQ 、RabbitMQ 、kafka 都是采用文件系统的方式来存储消息。

今天,我们就从它的存储文件入手,来探索一下 RocketMQ 消息存储的机制。

一、CommitLog

CommitLog ,消息存储文件,所有主题的消息都存储在 CommitLog 文件中。

我们的业务系统向 RocketMQ 发送一条消息,不管在中间经历了多么复杂的流程,最终这条消息会被持久化到 CommitLog 文件。

我们知道,一台 Broker服务器 只有一个 CommitLog 文件(组), RocketMQ 会将所有主题的消息存储在同一个文件中,这个文件中就存储着一条条Message,每条Message都会按照顺序写入。

也许有时候,你会希望看看这个 CommitLog 文件中,存储的内容到底长什么样子?

1、消息发送

当然,我们需要先往 CommitLog 文件中写入一些内容,所以先来看一个消息发送的例子。

public static void main(String[] args) throws Exception {    MQProducer producer = getProducer();    for (int i = 0;i<10;i++){        Message message = new Message();        message.setTopic("topic"+i);        message.setBody(("清幽之地的博客").getBytes());        SendResult sendResult = producer.send(message);    }    producer.shutdown();}

我们向10个不同的主题中发送消息,如果只有一台 Broker 机器,它们会保存到同一个 CommitLog 文件中。此时,这个文件的位置处于 C:/Users/abc/store/commitlog/00000000000000000000 。

2、读取文件内容

这个文件我们不能直接打开,因为它是一个二进制文件,所以我们需要通过程序来读取它的字节数组。

public static ByteBuffer read(String path)throws Exception{    File file = new File(path);    FileInputStream fin = new FileInputStream(file);    byte[] bytes = new byte[(int)file.length()];    fin.read(bytes);    ByteBuffer buffer = ByteBuffer.wrap(bytes);    return buffer;}

如上代码,可以通过传入文件的路径,读取该文件所有的内容。为了方便下一步操作,我们把读取到的字节数组转换为 java.nio.ByteBuffer 对象。

3、解析

在解析之前,我们需要弄明白两件事:

  • 消息的格式,即一条消息包含哪些字段;
  • 每个字段所占的字节大小。

在上面的图中,我们已经看到了消息的格式,包含了19个字段。关于字节大小,有的是 4 字节,有的是 8 字节,我们不再一一赘述,直接看代码。

/** * commitlog 文件解析 * @param byteBuffer * @return * @throws Exception */public static MessageExt decodeCommitLog(ByteBuffer byteBuffer)throws Exception {MessageExt msgExt = new MessageExt();// 1 TOTALSIZEint storeSize = byteBuffer.getInt();msgExt.setStoreSize(storeSize);if (storeSize<=0){    return null;}// 2 MAGICCODEbyteBuffer.getInt();// 3 BODYCRCint bodyCRC = byteBuffer.getInt();msgExt.setBodyCRC(bodyCRC);// 4 QUEUEIDint queueId = byteBuffer.getInt();msgExt.setQueueId(queueId);// 5 FLAGint flag = byteBuffer.getInt();msgExt.setFlag(flag);// 6 QUEUEOFFSETlong queueOffset = byteBuffer.getLong();msgExt.setQueueOffset(queueOffset);// 7 PHYSICALOFFSETlong physicOffset = byteBuffer.getLong();msgExt.setCommitLogOffset(physicOffset);// 8 SYSFLAGint sysFlag = byteBuffer.getInt();msgExt.setSysFlag(sysFlag);// 9 BORNTIMESTAMPlong bornTimeStamp = byteBuffer.getLong();msgExt.setBornTimestamp(bornTimeStamp);// 10 BORNHOSTint bornhostIPLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 4 : 16;byte[] bornHost = new byte[bornhostIPLength];byteBuffer.get(bornHost, 0, bornhostIPLength);int port = byteBuffer.getInt();msgExt.setBornHost(new InetSocketAddress(InetAddress.getByAddress(bornHost), port));// 11 STORETIMESTAMPlong storeTimestamp = byteBuffer.getLong();msgExt.setStoreTimestamp(storeTimestamp);// 12 STOREHOSTint storehostIPLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 : 16;byte[] storeHost = new byte[storehostIPLength];byteBuffer.get(storeHost, 0, storehostIPLength);port = byteBuffer.getInt();msgExt.setStoreHost(new InetSocketAddress(InetAddress.getByAddress(storeHost), port));// 13 RECONSUMETIMESint reconsumeTimes = byteBuffer.getInt();msgExt.setReconsumeTimes(reconsumeTimes);// 14 Prepared Transaction Offsetlong preparedTransactionOffset = byteBuffer.getLong();msgExt.setPreparedTransactionOffset(preparedTransactionOffset);// 15 BODYint bodyLen = byteBuffer.getInt();if (bodyLen > 0) {    byte[] body = new byte[bodyLen];    byteBuffer.get(body);    msgExt.setBody(body);}// 16 TOPICbyte topicLen = byteBuffer.get();byte[] topic = new byte[(int) topicLen];byteBuffer.get(topic);msgExt.setTopic(new String(topic, CHARSET_UTF8));// 17 propertiesshort propertiesLength = byteBuffer.getShort();if (propertiesLength > 0) {    byte[] properties = new byte[propertiesLength];    byteBuffer.get(properties);    String propertiesString = new String(properties, CHARSET_UTF8);    Map map = string2messageProperties(propertiesString);}int msgIDLength = storehostIPLength + 4 + 8;ByteBuffer byteBufferMsgId = ByteBuffer.allocate(msgIDLength);String msgId = createMessageId(byteBufferMsgId, msgExt.getStoreHostBytes(), msgExt.getCommitLogOffset());msgExt.setMsgId(msgId);return msgExt;}

4、输出消息内容

public static void main(String[] args) throws Exception {    String filePath = "C:甥敳獲abcstorecommitlog00000000000000000000";    ByteBuffer buffer = read(filePath);    List messageList = new ArrayList<>();    while (true){        MessageExt message = decodeCommitLog(buffer);        if (message==null){            break;        }        messageList.add(message);    }    for (MessageExt ms:messageList) {        System.out.println("主题:"+ms.getTopic()+" 消息:"+            new String(ms.getBody())+"队列ID:"+ms.getQueueId()+" 存储地址:"+ms.getStoreHost());    }}

运行这段代码,我们就可以直接看到 CommitLog 文件中的内容:

主题:topic0 消息:清幽之地的博客 队列ID:1 存储地址:/192.168.44.1:10911主题:topic1 消息:清幽之地的博客 队列ID:0 存储地址:/192.168.44.1:10911主题:topic2 消息:清幽之地的博客 队列ID:1 存储地址:/192.168.44.1:10911主题:topic3 消息:清幽之地的博客 队列ID:0 存储地址:/192.168.44.1:10911主题:topic4 消息:清幽之地的博客 队列ID:3 存储地址:/192.168.44.1:10911主题:topic5 消息:清幽之地的博客 队列ID:1 存储地址:/192.168.44.1:10911主题:topic6 消息:清幽之地的博客 队列ID:2 存储地址:/192.168.44.1:10911主题:topic7 消息:清幽之地的博客 队列ID:3 存储地址:/192.168.44.1:10911主题:topic8 消息:清幽之地的博客 队列ID:2 存储地址:/192.168.44.1:10911主题:topic9 消息:清幽之地的博客 队列ID:0 存储地址:/192.168.44.1:10911

不用过多的文字描述,通过上面这些代码,相信你对 CommitLog 文件就有了更进一步的了解。

此时,我们再考虑另外一个问题:

CommitLog 文件保存了所有主题的消息,但我们消费时,更多的是订阅某一个主题进行消费。 RocketMQ 是怎么样进行高效的检索消息的呢 ?

二、ConsumeQueue

为了解决上面那个问题, RocketMQ 引入了 ConsumeQueue 消费队列文件。

在继续往下说 ConsumeQueue 之前,我们必须先了解到另外一个概念,即 MessageQueue 。

1、MessageQueue

我们知道,在发送消息的时候,要指定一个Topic。那么,在创建Topic的时候,有一个很重要的参数 MessageQueue 。简单来说,就是你这个Topic对应了多少个队列,也就是几个 MessageQueue ,默认是4个。那它的作用是什么呢 ?

它是一个数据分片的机制。比如我们的Topic里面有100条数据,该Topic默认是4个队列,那么每个队列中大约25条数据。 然后,这些 MessageQueue 是和 Broker 绑定在一起的,就是说每个 MessageQueue 都可能处于不同的 Broker 机器上,这取决于你的队列数量和Broker集群。

我们来看上面的图片,Topic名称为order的主题,一共有4个 MessageQueue ,每个里面都有25条数据。因为在笔者的本地环境只有一个 Broker ,所以它们的 brokerName 都是指向同一台机器。

既然 MessageQueue 是多个,那么在消息发送的时候,势必要通过某种方式选择一个队列。默认的情况下,就是通过轮询来获取一个消息队列。

public MessageQueue selectOneMessageQueue() {    int index = this.sendWhichQueue.getAndIncrement();    int pos = Math.abs(index) % this.messageQueueList.size();    if (pos < 0)        pos = 0;    return this.messageQueueList.get(pos);}

当然, RocketMQ 还有一个故障延迟机制,在选择消息队列的时候会复杂一些,我们今天先不讨论。

2、ConsumeQueue

说完了 MessageQueue ,我们接着来看 ConsumerQueue 。上面我们说,它是为了高效检索主题消息的。

ConsumerQueue 也是一组组文件,它的位置在 C:/Users/abc/store/consumequeue 。该目录下面是以Topic命名的文件夹,然后再下一级是以 MessageQueue 队列ID命名的文件夹,最后才是一个或多个文件。

这样分层之后, RocketMQ 至少可以得到以下几个讯息:

  • 先通过主题名称,可以定位到具体的文件夹;
  • 然后根据消息队列ID找到具体的文件;
  • 最后根据文件内容,找到具体的消息。

那么,这个文件里面存储的又是什么内容呢 ?

3、解析文件

为了加速 ConsumerQueue 的检索速度和节省磁盘空间,文件中不会存储消息的全量消息。其存储的格式如下:

同样的,我们先写一段代码,按照这个格式输出一下 ConsumerQueue 文件的内容。

public static void main(String[] args)throws Exception {    String path = "C:甥敳獲abcstoreconsumequeueorder000000000000000000000";    ByteBuffer buffer = read(path);    while (true){        long offset = buffer.getLong();        long size = buffer.getInt();        long code = buffer.getLong();        if (size==0){            break;        }        System.out.println("消息长度:"+size+" 消息偏移量:" +offset);    }    System.out.println("--------------------------");}

在前面,我们已经向 order 这个主题中写了100条数据,所以在这里它的 order#messagequeue#0 里面有25条记录。

消息长度:173 消息偏移量:2003消息长度:173 消息偏移量:2695消息长度:173 消息偏移量:3387消息长度:173 消息偏移量:4079消息长度:173 消息偏移量:4771消息长度:173 消息偏移量:5463消息长度:173 消息偏移量:6155消息长度:173 消息偏移量:6847消息长度:173 消息偏移量:7539消息长度:173 消息偏移量:8231消息长度:173 消息偏移量:8923消息长度:173 消息偏移量:9615消息长度:173 消息偏移量:10307消息长度:173 消息偏移量:10999消息长度:173 消息偏移量:11691消息长度:173 消息偏移量:12383消息长度:173 消息偏移量:13075消息长度:173 消息偏移量:13767消息长度:173 消息偏移量:14459消息长度:173 消息偏移量:15151消息长度:173 消息偏移量:15843消息长度:173 消息偏移量:16535消息长度:173 消息偏移量:17227消息长度:173 消息偏移量:17919消息长度:173 消息偏移量:18611--------------------------

细心的朋友,肯定发现了。上面输出的结果中,消息偏移量的差值等于 = 消息长度 * 队列长度。

4、查询消息

现在我们通过 ConsumerQueue 已经知道了消息的长度和偏移量,那么查找消息就比较容易了。

public static MessageExt getMessageByOffset(ByteBuffer commitLog,long offset,int size) throws Exception {    ByteBuffer slice = commitLog.slice();    slice.position((int)offset);    slice.limit((int) (offset+size));    MessageExt message = CommitLogTest.decodeCommitLog(slice);    return message;}

然后,我们可以依靠这种方法,来实现通过 ConsumerQueue 获取消息的具体内容。

public static void main(String[] args) throws Exception {//consumerqueue根目录String consumerPath = "C:甥敳獲abcstoreconsumequeue";//commitlog目录String commitLogPath = "C:甥敳獲abcstorecommitlog00000000000000000000";//读取commitlog文件内容ByteBuffer commitLogBuffer = CommitLogTest.read(commitLogPath);//遍历consumerqueue目录下的所有文件File file = new File(consumerPath);File[] files = file.listFiles();for (File f:files) {if (f.isDirectory()){File[] listFiles = f.listFiles();for (File queuePath:listFiles) {String path = queuePath+"/00000000000000000000";//读取consumerqueue文件内容ByteBuffer buffer = CommitLogTest.read(path);while (true){//读取消息偏移量和消息长度long offset = (int) buffer.getLong();int size = buffer.getInt();long code = buffer.getLong();if (size==0){break;}//根据偏移量和消息长度,在commitloh文件中读取消息内容MessageExt message = getMessageByOffset(commitLogBuffer,offset,size);if (message!=null){System.out.println("消息主题:"+message.getTopic()+" MessageQueue:"+message.getQueueId()+" 消息体:"+new String(message.getBody()));}}}}}}

运行这段代码,就可以得到之前测试样例中,10个主题的所有消息。

消息主题:topic0 MessageQueue:1 消息体:清幽之地的博客消息主题:topic1 MessageQueue:0 消息体:清幽之地的博客消息主题:topic2 MessageQueue:1 消息体:清幽之地的博客消息主题:topic3 MessageQueue:0 消息体:清幽之地的博客消息主题:topic4 MessageQueue:3 消息体:清幽之地的博客消息主题:topic5 MessageQueue:1 消息体:清幽之地的博客消息主题:topic6 MessageQueue:2 消息体:清幽之地的博客消息主题:topic7 MessageQueue:3 消息体:清幽之地的博客消息主题:topic8 MessageQueue:2 消息体:清幽之地的博客消息主题:topic9 MessageQueue:0 消息体:清幽之地的博客

5、消费消息

消息消费的时候,其查找消息的过程也是差不多的。不过值得注意的一点是, ConsumerQueue 文件和 CommitLog 文件可能都是多个的,所以会有一个定位文件的过程,我们来看源码。

首先,根据消费进度来查找对应的 ConsumerQueue ,获取其文件内容。

public SelectMappedBufferResult getIndexBuffer(final long startIndex) {    //ConsumerQueue文件大小    int mappedFileSize = this.mappedFileSize;    //根据消费进度,找到在consumerqueue文件里的偏移量    long offset = startIndex * CQ_STORE_UNIT_SIZE;    if (offset >= this.getMinLogicOffset()) {        //返回ConsumerQueue映射文件        MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset);        if (mappedFile != null) {            //返回文件里的某一块内容            SelectMappedBufferResult result = mappedFile.selectMappedBuffer((int) (offset % mappedFileSize));            return result;        }    }    return null;}

然后拿到消息在 CommitLog 文件中的偏移量和消息长度,获取消息。

public SelectMappedBufferResult getMessage(final long offset, final int size) {    //commitlog文件大小    int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog();    //根据消息偏移量,定位到具体的commitlog文件    MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset, offset == 0);    if (mappedFile != null) {        //根据消息偏移量和长度,获取消息内容        int pos = (int) (offset % mappedFileSize);        return mappedFile.selectMappedBuffer(pos, size);    }    return null;}

三、Index

上面我们看到了通过消息偏移量来查找消息的方式,但 RocketMQ 还提供了其他几种方式可以查询消息。

  • 通过Message Key 查询;
  • 通过Unique Key查询;
  • 通过Message Id查询。

在这里, Message Key和Unique Key 都是在消息发送之前,由客户端生成的。我们可以自己设置,也可以由客户端自动生成, Message Id 是在 Broker 端存储消息的时候生成。

1、通过 Message Id 查询

Message Id 总共 16 字节,包含消息存储主机地址和在 CommitLog 文件中的偏移量offset。有源码为证:

/** * 创建消息ID * @param input      * @param addr      Broker服务器地址 * @param offset    正在存储的消息,在Commitlog中的偏移量 * @return */public static String createMessageId(final ByteBuffer input, final ByteBuffer addr, final long offset) {    input.flip();    int msgIDLength = addr.limit() == 8 ? 16 : 28;    input.limit(msgIDLength);    input.put(addr);    input.putLong(offset);    return UtilAll.bytes2string(input.array());}

当我们根据 Message Id 向Broker查询消息时,首先会通过一个 decodeMessageId 方法,将Broker地址和消息的偏移量解析出来。

public static MessageId decodeMessageId(final String msgId) throws Exception {    SocketAddress address;    long offset;    int ipLength = msgId.length() == 32 ? 4 * 2 : 16 * 2;    byte[] ip = UtilAll.string2bytes(msgId.substring(0, ipLength));    byte[] port = UtilAll.string2bytes(msgId.substring(ipLength, ipLength + 8));    ByteBuffer bb = ByteBuffer.wrap(port);    int portInt = bb.getInt(0);    //解析出来Broker地址    address = new InetSocketAddress(InetAddress.getByAddress(ip), portInt);    //偏移量    byte[] data = UtilAll.string2bytes(msgId.substring(ipLength + 8, ipLength + 8 + 16));    bb = ByteBuffer.wrap(data);    offset = bb.getLong(0);    return new MessageId(address, offset);}

所以通过 Message Id 查询消息的时候,实际上还是直接从特定Broker上的 CommitLog 指定位置进行查询,属于精确查询。

这个也没问题,但是如果通过 Message Key 和 Unique Key 查询的时候, RocketMQ 又是怎么做的呢?

2、index索引文件

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

另外, RocketMQ 引入Hash索引机制,为消息建立索引,它的键就是 Message Key 和 Unique Key 。

那么,我们先看看index索引文件的结构:

为了便于理解,我们还是以代码的方式,来解析这个文件。

public static void main(String[] args) throws Exception {    //index索引文件的路径    String path = "C:甥敳獲abcstoreindex20200506224547616";    ByteBuffer buffer = CommitLogTest.read(path);    //该索引文件中包含消息的最小存储时间    long beginTimestamp = buffer.getLong();    //该索引文件中包含消息的最大存储时间    long endTimestamp = buffer.getLong();    //该索引文件中包含消息的最大物理偏移量(commitlog文件偏移量)    long beginPhyOffset = buffer.getLong();    //该索引文件中包含消息的最大物理偏移量(commitlog文件偏移量)    long endPhyOffset = buffer.getLong();    //hashslot个数    int hashSlotCount = buffer.getInt();    //Index条目列表当前已使用的个数    int indexCount = buffer.getInt();    //500万个hash槽,每个槽占4个字节,存储的是index索引    for (int i=0;i<5000000;i++){        buffer.getInt();    }    //2000万个index条目    for (int j=0;j<20000000;j++){        //消息key的hashcode        int hashcode = buffer.getInt();        //消息对应的偏移量        long offset = buffer.getLong();        //消息存储时间和第一条消息的差值        int timedif = buffer.getInt();        //该条目的上一条记录的index索引        int pre_no = buffer.getInt();    }    System.out.println(buffer.position()==buffer.capacity());}

我们看最后输出的结果为true,则证明解析的过程无误。

3、构建索引

我们发送的消息体中,包含 Message Key 或 Unique Key ,那么就会给它们每一个都构建索引。

这里重点有两个:

  • 根据消息Key计算Hash槽的位置;
  • 根据Hash槽的数量和Index索引来计算Index条目的起始位置。

将当前 Index条目 的索引值,写在Hash槽 absSlotPos 位置上;将 Index条目的具体信息 (hashcode/消息偏移量/时间差值/hash槽的值) ,从起始偏移量 absIndexPos 开始,顺序按字节写入。

public boolean putKey(final String key, final long phyOffset, final long storeTimestamp) {    if (this.indexHeader.getIndexCount() < this.indexNum) {        //计算key的hash        int keyHash = indexKeyHashMethod(key);        //计算hash槽的坐标        int slotPos = keyHash % this.hashSlotNum;        int absSlotPos = IndexHeader.INDEX_HEADER_SIZE + slotPos * hashSlotSize;        //计算时间差值        long timeDiff = storeTimestamp - this.indexHeader.getBeginTimestamp();        timeDiff = timeDiff / 1000;        //计算INDEX条目的起始偏移量        int absIndexPos =            IndexHeader.INDEX_HEADER_SIZE + this.hashSlotNum * hashSlotSize                + this.indexHeader.getIndexCount() * indexSize;        //依次写入hashcode、消息偏移量、时间戳、hash槽的值        this.mappedByteBuffer.putInt(absIndexPos, keyHash);        this.mappedByteBuffer.putLong(absIndexPos + 4, phyOffset);        this.mappedByteBuffer.putInt(absIndexPos + 4 + 8, (int) timeDiff);        this.mappedByteBuffer.putInt(absIndexPos + 4 + 8 + 4, slotValue);        //将当前INDEX中包含的条目数量写入HASH槽        this.mappedByteBuffer.putInt(absSlotPos, this.indexHeader.getIndexCount());        return true;    }    return false;}

这样构建完Index索引之后,根据 Message Key 或 Unique Key 查询消息就简单了。

比如我们通过 RocketMQ 客户端工具,根据 Unique Key 来查询消息。

adminImpl.queryMessageByUniqKey("order", "FD88E3AB24F6980059FDC9C3620464741BCC18B4AAC220FDFE890007");

在 Broker 端,通过 Unique Key 来计算Hash槽的位置,从而找到Index索引数据。从Index索引中拿到消息的物理偏移量,最后根据消息物理偏移量,直接到 CommitLog 文件中去找就可以了。

总结

本文探讨了 RocketMQ 中消息存储和消息查找的基本思路。源码中间过程都很复杂,但是通过这种自下而上的方式,直接从文件入手,剖析它们的文件结构,从而梳理清楚它们的关系和作用,希望能对朋友们产生积极作用。

rocketmq原理_RocketMQ消息存储和查询原理相关推荐

  1. rocketmq python消息堆积_RocketMQ消息存储和查询原理

    前言 RocketMQ 作为一款优秀的分布式消息中间件,可以为业务方提供高性能低延迟的稳定可靠的消息服务.其核心优势是可靠的消费存储.消息发送的高性能和低延迟.强大的消息堆积能力和消息处理能力. 从存 ...

  2. mysql+join的原理,Mysql连接join查询原理知识点

    Mysql连接join查询原理知识点 Mysql连接(join)查询 1.基本概念 将两个表的每一行,以"两两横向对接"的方式,所得到的所有行的结果. 假设: 表A有n1行,m1列 ...

  3. rocketmq消息存储原理_RocketMQ到底快在哪里?深入探索RocketMQ消息存储和查询原理...

    RocketMQ 作为一款优秀的分布式消息中间件,可以为业务方提供高性能低延迟的稳定可靠的消息服务.其核心优势是可靠的消费存储.消息发送的高性能和低延迟.强大的消息堆积能力和消息处理能力. 从存储方式 ...

  4. 2021年大数据Kafka(九):kafka消息存储及查询机制原理

    全网最详细的大数据Kafka文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 系列历史文章 kafka消息存储及查询机制原理 一.Kafka数据存储机制 ...

  5. 一起学DNS系列(十)图、例详解DNS递归和迭代查询原理及过程 (1)

    上节中提到了一些有关递归查询的内容,但说的很少,也很笼统,本节将会从原理和实例两方面入手分析DNS的递归以及迭代查询. 在此之前,我们需要了解一些背景知识,以便于更好的理解今天的主题内容. 在互联网中 ...

  6. mysql 缓存监控_MySql 缓存查询原理与缓存监控 和 索引监控

    MySql缓存查询原理与缓存监控 And 索引监控 by:授客 QQ:1033553122 查询缓存 1.查询缓存操作原理 mysql执行查询语句之前,把查询语句同查询缓存中的语句进行比较,且是按字节 ...

  7. MySql 缓存查询原理与缓存监控 和 索引监控

    MySql缓存查询原理与缓存监控 And 索引监控 by:授客 QQ:1033553122 查询缓存 1.查询缓存操作原理 mysql执行查询语句之前,把查询语句同查询缓存中的语句进行比较,且是按字节 ...

  8. 用简单例子带你了解联合索引查询原理及生效规则

    摘要:一般都是设计联合索引,很少用单个字段做索引,因为还是要尽可能让索引数量少,避免磁盘占用太多,影响增删改性能. 本文分享自华为云社区<联合索引查询原理及生效规则>,作者:JavaEdg ...

  9. 中国联通物联卡ICCID号码查询原理

    近年来,中国联通物联卡因为种种原因爆红于通信市场,越来越多人开始加入中国联通物联卡办理大军,在推动物联网市场发展的同时也利用中国联通物联卡来实现自身产业的变革.随着中国联通物联卡使用人数增多,各种物联 ...

  10. ES查询原理:倒排索引

    项目场景: 提示:这里简述项目相关背景: 前端数据搜索:针对各种查询条件,需要快速.准确的响应数据结果.但是底层数据表很大,如果按照正常逻辑直接去表查询,这样查询效率很慢,而且是关联多表进行查询.针对 ...

最新文章

  1. 【Qt】Qt Plugin:Qt插件创建与使用
  2. Google App Engine 学习和实践
  3. java 操作 ES 的方式 整理总结
  4. Docker实战部署JavaWeb项目-基于SpringBoot
  5. flex 颜色16进制对照表
  6. Learn OpenGL(二)——顶点输入(Vertex Input)
  7. 转:VS2005的DataGridView 多维合并标题 功能拓展
  8. 帝国cms怎么搭建python环境_自己写的帝国cms后台文章添加增加二级或多级联动功能...
  9. Snake算法与遥感影像应用,python matlab对比
  10. RabbitVCS安装
  11. Luogu P3110 [USACO14DEC]驮运Piggy Back
  12. 谈谈Python的Flask框架学习与福利分享
  13. 对数据库中的数据(用户名/密码)加密解密
  14. CPP全面总结(涵盖C++11标准)
  15. 虚幻4游戏开发_3_创建与继承材质
  16. 最新:基于MAXENT模型的生物多样性生境模拟与保护优先区甄选、自然保护区布局优化评估及论文写作技巧
  17. C/C++日志库-log4cplus(log4j的C++版本)
  18. 企业密钥管理(EKM)行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  19. mysql内核架构_热血江湖mysql内核技术之门派数据库表结构说明
  20. linux下安装redis和phpredis扩展

热门文章

  1. 替换Ecshop系统中产品描述部分的图片的alt图片描述
  2. 基于拥挤距离与变异支配的多目标PSO算法
  3. 【LeetCode】【数组】题号:56,重塑矩阵
  4. 【Sigmoid】操作对象(对数据结果进行验证)
  5. lxml.etree.SerialisationError: IO_ENCODER
  6. python中解释说明符号_python注释以什么符号开始
  7. 实习成长之路: MySQL三 : 事务隔离:为什么你改了我还看不见?
  8. git 提交代码的好习惯
  9. 直播首屏耗时400ms以下的优化实践
  10. OpenGL ES总结(二)OpenGL坐标变换之平移及旋转