<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>缓存 on 你怂你mua</title>
        <link>https://liusir521.github.io/tags/%E7%BC%93%E5%AD%98/</link>
        <description>Recent content in 缓存 on 你怂你mua</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <copyright>Example Person</copyright>
        <lastBuildDate>Sat, 11 Oct 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://liusir521.github.io/tags/%E7%BC%93%E5%AD%98/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Redis数据结构之Sorted Sets</title>
        <link>https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bsorted-sets/</link>
        <pubDate>Sat, 11 Oct 2025 00:00:00 +0000</pubDate>
        
        <guid>https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bsorted-sets/</guid>
        <description>&lt;img src="https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bsorted-sets/redis.jpg" alt="Featured image of post Redis数据结构之Sorted Sets" /&gt;&lt;blockquote&gt;
&lt;p&gt;redis 版本7.x&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;有序性与唯一性&#34;&gt;有序性与唯一性
&lt;/h2&gt;&lt;p&gt;Sorted sets与Sets类似，是一种集合类型，这种集合中不会出现重复的member(数据)。Sorted seats中的元素由两部分组成，分别是member和score(分数)。&lt;/p&gt;
&lt;p&gt;member会关联一个double类型的score，Sorted sets默认会根据这个score对member从小到大排序。如果member关联的score相同，则按照字符串的字典顺序排列。&lt;/p&gt;
&lt;p&gt;常见使用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;排行榜：游戏中根据分数排名top10。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;速率限流器：滑动窗口速率限制器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;延迟队列：使用score存储过期时间，从小到大排序，最靠前的就是最先到期的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;skiplist--dict-和-listpack&#34;&gt;skiplist + dict 和 listpack
&lt;/h2&gt;&lt;p&gt;Sorted sets底层通过两种方式存储数据：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;listpack（7.0版本之前是ziplist）：使用条件是集合元素小于或等于128，且member占用字节数小于64。将member和score紧凑排列作为listpack的一个元素存储。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;skiplist + dict：当不满足上述条件时，将数据分别存储在skiplist（跳表）和dict中，是一种空间换时间的思想。散列表key存储的是member，value存储的是关联的score。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;listpack适用于元素个数不多且占用空间不大的场景，使用listpack就是为了节省内存。Sorted Sets能支持高效的范围查询，正是因为采用了skiplist。&lt;/p&gt;
&lt;p&gt;而使用dict的目的是以O(1)的时间复杂度查询单个元素。总之，Sorted Sets在插入或者更新时，会同时向skiplist和dict中插入或更新对应的数据，以保证两者数据的一致性。&lt;/p&gt;
&lt;h3 id=&#34;skiplist--dict&#34;&gt;skiplist + dict
&lt;/h3&gt;&lt;p&gt;skiplist本质是一种可以进行二分查找的有序链表。skiplist在原有的基础上增加了多级索引来实现快速查找。&lt;/p&gt;
&lt;h4 id=&#34;skiplist节点查找&#34;&gt;skiplist节点查找
&lt;/h4&gt;&lt;p&gt;通常数据查找是从顶层开始，如果节点保存的值比待查数据的值小，skipllist就继续访问该层的下一个节点。&lt;/p&gt;
&lt;p&gt;如果比待查数据的值大，就跳到当前节点的下一层的链表继续查找。如下图所示查找节点17：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bsorted-sets/skiplist.png&#34;
	width=&#34;1390&#34;
	height=&#34;205&#34;
	srcset=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bsorted-sets/skiplist_hu_31b0218d11512f2b.png 480w, https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bsorted-sets/skiplist_hu_c4b0b68b7c066a22.png 1024w&#34;
	loading=&#34;lazy&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;678&#34;
		data-flex-basis=&#34;1627px&#34;
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;从level1开始，17大于6，继续与下一个节点比较&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;17&amp;lt;26，回到原节点，跳到当前节点的level0层链表，与下一个节点比较，找到目标17&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;skiplist也是受到这种多层链表的启发设计出来的。根据上面的生成链表，上层节点个数是下层节点个数的一半，查找过程类似二分查找，时间复杂度是O(n)。&lt;/p&gt;
&lt;p&gt;但是，这种设计方式有个问题，就是每次新增一个节点，就会打乱相邻的两层链表节点个数2：1的关系，就需要调整链表结构。&lt;/p&gt;
&lt;p&gt;为了避免这个问题，skiplist不要求上下相邻两层链表之间节点个数有严格的比例关系，而是为每个节点随机出一个层数，这样插入节点时只需要修改前后指针。&lt;/p&gt;
&lt;p&gt;Sorted Sets相关数据结构源码如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zskiplist&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// 头尾指针便于双向遍历
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zskiplistNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tail&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// 当前skiplist包含的元素个数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// 表内节点的最大层级
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;level&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;size_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;alloc_size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zskiplist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zset&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;dict&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dict&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;zskiplist&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zsl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;skiplist中的每个节点，由zskiplistNode结构体表示：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zskiplistNode&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;sds&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ele&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;double&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;score&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zskiplistNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;backward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zskiplistLevel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zskiplistNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;forward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;level&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;zskiplistNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ele和score属性：使用sds类型的ele存储实际数据，score存储分数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;*backward：后退指针，指向该节点的上一个节点，便于倒序查找。每个节点只有一个后退指针，只有level0层的节点是双向链表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;level[]：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;*forward：前进指针。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;span：跨度，记录该层的forward指针指向的下一个节点之间的跨越了level0层的节点数&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;listpack&#34;&gt;listpack
&lt;/h3&gt;&lt;p&gt;listpack的优势的节省内存，但只能按顺序查找元素，时间复杂度是O(n)。正因如此，才能在少量数据的情况下，节省内存同时又不影响性能。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Redis数据结构之Lists</title>
        <link>https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/</link>
        <pubDate>Thu, 11 Sep 2025 00:00:00 +0000</pubDate>
        
        <guid>https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/</guid>
        <description>&lt;img src="https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/redis.jpg" alt="Featured image of post Redis数据结构之Lists" /&gt;&lt;blockquote&gt;
&lt;p&gt;redis中的list和java中的linkedlist类似，是一种线性有序结构，按照元素被推入列表中的顺序存储元素，满足先进先出的需求。可以把它当作队列、栈来使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;内部结构演进&#34;&gt;内部结构演进
&lt;/h2&gt;&lt;h3 id=&#34;linkedlist&#34;&gt;linkedlist
&lt;/h3&gt;&lt;p&gt;在redis3.2之前List底层数据结构由linkedlist或者ziplist实现，优先使用ziplist存储。当List对象满足以下两个条件时，将使用ziplist存储，否则使用linkedlist。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;链表中的每个元素占用的字节数小于64&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;链表中的元素数量小于512个&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关键源码：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/* 链表节点 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;listNode&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// prev与next字段形成双端链表
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;listNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prev&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;listNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;listNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/* 链表结构体 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;list&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;listNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;listNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tail&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;free&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;redis链表的特性&#34;&gt;redis链表的特性
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;双端：链表节点带有prev和next指针，获取某个节点的前置和后置节点的时间复杂度都是O(1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无环：链表头节点的prev和尾节点的next指针指向的都是null。对链表的访问以null结尾。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;带表头指针和表尾指针：list结构体有head和tail指针，获取链表头尾节点的时间复杂度为O(1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;len属性：list结构体有len属性，获取节点数量的时间复杂度为O(1)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;ziplist&#34;&gt;ziplist
&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;linkedlist中存在prev、next两个指针，在数据很小的情况下，指针占用的空间会超过数据占用的空间。&lt;/p&gt;
&lt;p&gt;linkedlist是链表结构，在内存中不是连续的，遍历效率低下。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为了解决上述的两个问题，创建了ziplist，ziplist是一种内存紧凑的数据结构，占用一块连续的内存空间，能够提高内存利用率。&lt;/p&gt;
&lt;p&gt;当一个Lists只有少量数据，并且每个列表项要么是小整数型，要么是比较短的字符串时，就会使用ziplist来作为List的底层数据结构存储数据。&lt;/p&gt;
&lt;p&gt;ziplist是一块连续的内存，结构如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/ziplist.png&#34;
	width=&#34;1219&#34;
	height=&#34;364&#34;
	srcset=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/ziplist_hu_c1a7c844a0e5db96.png 480w, https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/ziplist_hu_9255aeb9a6e510be.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;ziplist结构图&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;334&#34;
		data-flex-basis=&#34;803px&#34;
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;zlbytes：占用4字节，记录整个ziplist占用的总字节数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;zltail：占用4字节，只想最后一个entry偏移量，用于快速定位最后一个entry&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;zllen：占用2字节，记录entry总数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;entry：Lists的元素&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;zlend：ziplist结束的标志，占用1字节，值等于255&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为ziplist头尾元数据大小是固定的，并且zllen记录了ziplist头部最后一个元素的位置，所以可以用O(1)的时间复杂度找到ziplist中第一个或最后一个元素。而在查找中间元素时，只能从Lists头部或尾部开始遍历，时间复杂度O(n)。&lt;/p&gt;
&lt;p&gt;存储数据的entry结构如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/entry.png&#34;
	width=&#34;768&#34;
	height=&#34;385&#34;
	srcset=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/entry_hu_73134cf5b7b32e54.png 480w, https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/entry_hu_a4ed8201857157e4.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;entry结构图&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;199&#34;
		data-flex-basis=&#34;478px&#34;
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;prevlen：记录前一个entry占用的字节数，逆序遍历就是通过这个字段确定的向前移动多少字节拿到上一个entry的 &lt;code&gt;首地址&lt;/code&gt; 的。这部分会根据上一个entry的长度进行变长编码。变长方式如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;前一个entry的字节数小于254（255用于zlend），prevlen的长度为1字节，值等于上一个entry的长度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;前一个entry的字节数大于等于254，prevlen占用5字节，第一字节配置为254作为一个标识，后面4字节组成一个32位int值，用于存放上一个entry的字节长度。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;encoding：表示当前entry的类型和长度，前两位用于表示类型，前两位为11时表示存储的是int类型，其他情况表示存储的字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;entry-data：实际存放数据的地方，但是当entry存储的是int类型时，encoding和entry-data会合并到encoding中，并没有entry-data字段。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;linkedlist与ziplist对比&#34;&gt;linkedlist与ziplist对比
&lt;/h3&gt;&lt;p&gt;为什么说ziplist节省内存？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;与linkedlist相比，少了prev和next指针。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过encoding字段针对编码进行细化存储，尽可能做到按需分配。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ziplist的不足:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;不能保存过多的元素，否则查询性能下降，导致O(n)时间复杂度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ziplist存储空间是连续的，当插入新的entry时，内存空间不足就需要重新分配一块连续的内存空间，引发连锁更新。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;连锁更新问题:&lt;/p&gt;
&lt;p&gt;每个entry都用prevlen记录上一个entry的长度，在当前entry B 前面插入一个新的entry A 时，会导致 B 的prevlen发生变化，也会导致 B 的大小发生改变。同理后面的entry C 也会发生改变。以此类推，就可能导致连锁更新问题。&lt;/p&gt;
&lt;p&gt;连锁更新会导致多次重新分配ziplist的存储空间，直接影响ziplist的查询性能。所以在redis3.2引入了quicklist。&lt;/p&gt;
&lt;h3 id=&#34;quicklist&#34;&gt;quicklist
&lt;/h3&gt;&lt;p&gt;quicklist结合了linkedlist与ziplist的优势，本质还是一个双向链表，只不过链表的每一个节点都是一个ziplist。&lt;/p&gt;
&lt;p&gt;结构体源码如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;quicklistNode&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;quicklistNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prev&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;quicklistNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sz&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;count&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;encoding&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;container&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;recompress&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;attempted_compress&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;extra&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;quicklistNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;quicklist&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;quicklistNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;quicklistNode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tail&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;        &lt;span class=&#34;cm&#34;&gt;/* total entries */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;          &lt;span class=&#34;cm&#34;&gt;/* number of nodes */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;fill&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;              &lt;span class=&#34;cm&#34;&gt;/* ziplist size limit */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;compress&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* compression depth */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;quicklist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;quicklist是ziplist的升级版，优化的关键点在于控制好每个ziplist的大小或者元素个数。&lt;/p&gt;
&lt;p&gt;但是存在两个极端情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;quicklist node的ziplist越小，可能造成越多的内存碎片。极端情况是每个ziplist只有一个entry，退化成了linkedlist。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;quicklist node的ziplist过大，极端情况下会造成一个quicklist只有一个ziplist, 退化成了ziplist, 连锁更新的问题就会暴露出来。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以在5.0版本的时候设计了另一个数据结构listpack, 并在7.0版本中替换掉了ziplist。&lt;/p&gt;
&lt;h3 id=&#34;listpack&#34;&gt;listpack
&lt;/h3&gt;&lt;p&gt;listpack的结构如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/listpack.png&#34;
	width=&#34;1138&#34;
	height=&#34;404&#34;
	srcset=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/listpack_hu_d8adf7c493870101.png 480w, https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/listpack_hu_cb1bb6b843b36b45.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;listpack结构图&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;281&#34;
		data-flex-basis=&#34;676px&#34;
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;tot-bytes：即total bytes，占用4字节，记录listpack占用的总字节数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;num-elements：占用2字节，记录listpack elements的个数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;elements：listpack元素，保存数据的部分&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;listpack-end-byte：结束标志，占用1字节，固定值为255&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;element结构如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/listpackele.png&#34;
	width=&#34;664&#34;
	height=&#34;340&#34;
	srcset=&#34;https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/listpackele_hu_47f3919b9ae45cdd.png 480w, https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Blists/listpackele_hu_4a57c64aa151b132.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;listpackele结构图&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;195&#34;
		data-flex-basis=&#34;468px&#34;
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;encoding-type：存储实际数据的编码类型和长度，是一个变长字段。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;element-data：存放实际数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;element-tot-len：前两个字段的总长度，不包括自身长度。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每个element只记录自身长度，修改或新增元素时，不会影响后续element的长度，解决了连锁更新的问题。&lt;/p&gt;
&lt;h2 id=&#34;消息队列实战&#34;&gt;消息队列实战
&lt;/h2&gt;&lt;h3 id=&#34;消息队列介绍&#34;&gt;消息队列介绍
&lt;/h3&gt;&lt;p&gt;消息队列是一种异步的服务间通信方式，适合用于分布式和微服务架构。消息在未被处理和删除之前一直在队列上。&lt;/p&gt;
&lt;p&gt;消息队列基于先进先出(FIFO)的设计原则，允许发送者(生产者)向队列中发送消息，而接收者(消费者)则可以从队列中获取消息进行处理。通常消息队列被用于解耦应用程序的各个组件，实现异步通信、削峰填谷、解耦合、流量控制等。&lt;/p&gt;
&lt;p&gt;消息队列特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;消息有序性：消息是异步处理的，但消费者需要按生产者发送消息的顺序来消费，避免出现后发送的消息被先处理的情况。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重复消息处理：当网络问题出现消息重传时，消费者可能收到多条重复消息，可能造成同一业务逻辑被多次执行。在这种情况下，应用系统需要确保幂等性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可靠性：保证一次性传递消息。如果发送消息时接收者不可用，消息队列会保留消息，直到成功传递它，消费者重启后可以继续读取消息进行处理，防止消息遗漏。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;redis实现&#34;&gt;redis实现
&lt;/h3&gt;&lt;h4 id=&#34;实时消费问题&#34;&gt;实时消费问题
&lt;/h4&gt;&lt;p&gt;生产者可以使用LPUSH key element[element&amp;hellip;] 的形式将消息插入队列头部，如果key不存在则会创建一个空的队列再插入消息。&lt;/p&gt;
&lt;p&gt;消费者可以通过RPOP key 的形式依次读取队列的消息，以此实现先进先出的消息队列。&lt;/p&gt;
&lt;p&gt;但是LPUSH、RPOP存在性能风险，生产者向队列插入消息时，Lists并不会主动通知消费者及时消费。程序需要不断轮询并判断是否为空再执行消费逻辑，这就会导致即使没有新的信息写入，消费者也在不停的调用RPOP命令占用CPU资源。&lt;/p&gt;
&lt;p&gt;redis提供了BLPOP、BRPOP的阻塞读取的命令，消费者在读取队列没有数据时会自动阻塞，直到有新的消息写入队列，才继续读取新消息执行业务逻辑。&lt;/p&gt;
&lt;h4 id=&#34;重复消费解决方案&#34;&gt;重复消费解决方案
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;消息队列自动为每一条消息生成一个全局ID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;生产者为每条消息创建一个全局ID，消费者把处理过的消息ID记录下来，判断是否重复&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实这就是密等，对于同一条消息，消费者收到后处理一次的结果和处理多次的结果是一样的。&lt;/p&gt;
&lt;h4 id=&#34;消息可靠性解决方案&#34;&gt;消息可靠性解决方案
&lt;/h4&gt;&lt;p&gt;场景：消费者读取消息处理过程中宕机了，就会导致消息没有处理完成，可是数据已经不在队列中了。&lt;/p&gt;
&lt;p&gt;这种现象的本质是消费者处理消息时崩溃了，无法再读取消息，缺乏一个消息确认的可靠机制。&lt;/p&gt;
&lt;p&gt;redis提供了BRPOPLPUSH source destination timeout 命令，含义是以阻塞的方式从source队列读取消息，同时把这个消息复制到另一个destination队列中(备份)，并且是原子操作。&lt;/p&gt;
&lt;p&gt;不过这个命令在redis6.2版本被BLMOVE取代。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BLMOVE op1 op2 RIGHT LEFT 0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;消费者在消费时在while循环中使用BLMOVE，以阻塞的方式从队列 op1 队尾消费消息，同时把消息复制到队列 op2 队头(备份队列)，该操作是原子性的，最后一个参数 timeout=0 表示持续等待。&lt;/p&gt;
&lt;p&gt;如果上述命令消费成功，就使用 LREM 命令把队列 op2 中的对应消息删除，从而实现 ACK 确认机制。如果消费异常，使用 BRPOP op2 从备份队列中再次读取消息即可。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Redis数据结构之Set</title>
        <link>https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bset/</link>
        <pubDate>Thu, 11 Sep 2025 00:00:00 +0000</pubDate>
        
        <guid>https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bset/</guid>
        <description>&lt;img src="https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bset/redis.jpg" alt="Featured image of post Redis数据结构之Set" /&gt;&lt;h2 id=&#34;无序和唯一&#34;&gt;无序和唯一
&lt;/h2&gt;&lt;p&gt;Sets是字符串类型的无序集合，集合中的元素是唯一的，不会出现重复的数据。Sets底层是用散列表实现的，散列表的key存储的是Sets元素中的value，散列表的value指向null。&lt;/p&gt;
&lt;h3 id=&#34;使用场景&#34;&gt;使用场景
&lt;/h3&gt;&lt;p&gt;当你需要存储多个元素，且不允许出现重复数据，无需考虑元素有序性时，可以使用Sets。Sets还支持在集合之间做交集、并集、差集操作&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;共同关注：通过交集实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每日新增关注数：对近两天的总注册用户量集合取差集&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打标签：为自己收藏的文章打标签，例如微信收藏功能，这样可以快速找到被添加了某个标签的所有文章。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;intset&#34;&gt;intset
&lt;/h2&gt;&lt;p&gt;当元素内容是64位以内的10进制整数，且元素个数不超过512时，Sets会使用更加省内存的intset来存储。相关结构源码如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;intset&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;uint32_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;encoding&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;uint32_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;int8_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;contents&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;intset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;length：记录整数集合存储的元素个数，其实就是contents数组的长度&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;contents：真正存储整数集合的数组，是一块连续的内存区域。数组中的元素会按照值的大小从小到大存储，并且不会有重复元素&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;encoding：编码格式，决定数组类型，一共有三种不同的值。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;INTSET_ENC_INT16：表示contents数组的存储元素是int16_t类型的，每2字节表示一个整数元素&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;INTSET_ENC_INT32：表示contents数组的存储元素是int32_t类型的，每4字节表示一个整数元素&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;INTSET_ENC_INT64：表示contents数组的存储元素是int64_t类型的，每8字节表示一个整数元素&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;intset升级&#34;&gt;intset升级
&lt;/h3&gt;&lt;p&gt;当往一个int16_t类型的intset中插入一个int64_t类型的值时会触发升级。也就是Sets的所有数据类型会转换成int64_t类型。步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;根据新元素的类型和Sets元素数量，计算包括新添加的元素在内的新空间大小，对底层数组空间扩容，重新分配空间。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将intset中原有的元素转换成新元素类型，按从大到小的顺序放到正确位置，需要保障intset元素的有序性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将encoding的值修改为length+1&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，每次向intset添加新元素可能引起升级，升级又会对原始数据进行类型转换，时间复杂度是O(n)。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;intset不支持降级操作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;散列表原理&#34;&gt;散列表原理
&lt;/h2&gt;&lt;p&gt;Redis的散列表的底层数据结构通常是dict，由数组和链表构成，数组元素占用的槽位叫做哈希桶。当出现散列冲突时，会在桶下挂一个链表，用 &lt;code&gt;拉链法&lt;/code&gt; 解决散列冲突的问题。&lt;/p&gt;
&lt;h3 id=&#34;存储结构&#34;&gt;存储结构
&lt;/h3&gt;&lt;p&gt;散列表的底层存储数据结构实际上有两种:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;dict数据结构&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;listpack(7.0之前使用ziplist)数据结构&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常使用dict数据结构存储数据，每个field-value pairs构成一个dictEntry节点。只有 &lt;code&gt;同时满足&lt;/code&gt; 以下两个条件，才会使用listpack数据结构替代dict。按照field在前，value在后紧密相连的方式，依次把每个field-value pairs放到列表的表尾。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;每个field-value pairs中的field和value的字符串的字节数都小于64&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;field-value pairs数量小于512&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每次向散列表写数据时，都会调用相关函数来判断是否需要转换底层数据结构。&lt;/p&gt;
&lt;p&gt;当插入和修改的数据不满足以上两个条件时，就把散列表底层存储的数据结构转换为dict。虽然使用了listpack无法实现O(1)时间复杂度操作数据，但能大大减少内存占用，由于数据量比较小，性能不会有太大差异。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;需要注意的是，不能由dict退化成listpack。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;dict结构体主要源码如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dict&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;dictType&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;dictEntry&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ht_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ht_used&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;rehashidx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pauserehash&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;signed&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ht_size_exp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;dictType *type：存放函数的结构体，定义了一些函数指针。可以通过配置自定义函数，实现在dict的key和value中存放任何类型的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;dictEntry **ht_table[2]：存放大小为2的散列表指针数组，每个指针指向一个dictEntry类型的散列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ht_used[2]：记录每个散列表使用了多少槽位。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rehashidx：标记是否正在执行rehash操作，-1表示没有，如果正在执行rehash操作，那么其实表示当前执行rehash操作的ht_table[0]的dictEntry数组的索引。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pauserehash：表示rehash的状态，大于0表示rehash暂停，等于0时表示继续执行，小于0时表示出错。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数组中每个元素都是dictEntry类型的，就是它存放了field-value pairs。其结构如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dictEntry&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dictEntry&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;  &lt;span class=&#34;cm&#34;&gt;/* Must be first */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;               &lt;span class=&#34;cm&#34;&gt;/* Must be second */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;union&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;uint64_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;u64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;int64_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;s64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;double&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;*key指针指向field-value pairs中的field，实际上指向一个SDS实例&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;v 是一个union联合体，表示field-value pairs中的value，同一时刻只有一个字段有value，用联合体的目的是节省内存。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;*val：value是非数字类型时使用该指针存储&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;uint64_t u64：value是无符号整数时使用该字段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;int64_t s64：value是有符号整数时使用该字段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;double d：value是浮点数时使用该字段&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;*next指针指向下一个节点。当发生哈希冲突时，使用此链表（链表法）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;扩容和缩容&#34;&gt;扩容和缩容
&lt;/h3&gt;&lt;p&gt;扩容和缩容的步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;为了提高性能，减少哈希冲突，会创建一个大小等于ht_used[0] * 2的散列表ht_used[1]，也就是每次扩容时根据散列表ht_table [0]已使用空间扩大一倍创建一个新散列表ht_table [1]。反之，如果是缩容操作，就根据ht_table [0]已使用空间缩小一半创建一个新的散列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重新计算field-value pairs的哈希值，得到这个field-value pairs在新散列表ht_table [1]中的桶位置，将field-value pairs迁移到新的散列表上。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所有field-value pairs迁移完成后，修改指针，释放空间。把ht_table [0]指针指向扩容后的散列表，回收原来的小的散列表空间，把ht_table [1]指针指向null，为下次扩容\缩容准备。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;扩容缩容时机&#34;&gt;扩容\缩容时机
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当前没有执行bgsave或者BGREWRITEAOF命令，同时负载因子大于等于1。也就是当前没有RDB子进程和AOF重写子进程在工作，这两个操作容易对性能造成影响。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;正在执行bgsave或者BGREWRITEAOF命令，负载因子大于或等于5。这时哈希冲突比较严重，再不扩容，查询效率就太低了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;负载因子 = 散列表存储的dictEntry节点数量 / 哈希桶个数。理想情况下每个哈希桶存储一个dictEntry节点，这时负载因子 = 1。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;扩容过程&#34;&gt;扩容过程
&lt;/h3&gt;&lt;p&gt;为了防止阻塞主线程造成性能问题，不是一次性把全部key迁移，而是分多次将迁移操作分散到每次请求中，避免集中式rehash造成长时间阻塞。&lt;/p&gt;
&lt;p&gt;在渐进式rehash期间，dict会同时使用ht_table[0]和ht_table[1]两个散列表，具体步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;将rehashidx配置为0，表示rehash开始执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在rehash期间，服务端每次处理客户端对dict散列表的增删改查操作时，除了执行指定操作外，还会检查当前dict是否处于rehash状态，如果是，就把散列表ht_table[0]上索引位置为rehashidx的哈希桶的链表的所有field-value pairs rehash到散列表ht_table[1]上，并将rehashidx加1。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当所有field-value pairs迁移完成后将rehashidx配置为-1，表示操作已完成。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;删除、修改和查找可能会在两个散列表上进行，第一个没找到就去第二个，但是增加操作只会在新的散列表上进行。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
        </item>
        <item>
        <title>Go三方缓存库对比</title>
        <link>https://liusir521.github.io/p/go%E4%B8%89%E6%96%B9%E7%BC%93%E5%AD%98%E5%BA%93%E5%AF%B9%E6%AF%94/</link>
        <pubDate>Wed, 10 Sep 2025 00:00:00 +0000</pubDate>
        
        <guid>https://liusir521.github.io/p/go%E4%B8%89%E6%96%B9%E7%BC%93%E5%AD%98%E5%BA%93%E5%AF%B9%E6%AF%94/</guid>
        <description>&lt;img src="https://liusir521.github.io/p/go%E4%B8%89%E6%96%B9%E7%BC%93%E5%AD%98%E5%BA%93%E5%AF%B9%E6%AF%94/go.png" alt="Featured image of post Go三方缓存库对比" /&gt;&lt;h2 id=&#34;前言&#34;&gt;前言
&lt;/h2&gt;&lt;h3 id=&#34;原生map设计的缺陷&#34;&gt;原生map设计的缺陷
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;不支持并发，需要加锁操作，原生库提供的有sync.map，底层存储所使用的容器还是map。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不会自动缩容。内置的map有自动扩容的功能，但是当删除大量数据之后不会自动缩容，删除之后桶本身占用的内存并不会被回收。(可以通过定时新建map拷贝来实现，但是比较麻烦)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果存放的数据的类型是指针时，GC会扫描map的所有元素，效率不高。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;三方库主流解决方案&#34;&gt;三方库主流解决方案
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;三方库通过 &lt;code&gt;预分配内存&lt;/code&gt;（创建时指定map大小，当插入时如果没有空位就通过淘汰算法清除老数据）+ &lt;code&gt;分片hash&lt;/code&gt;（将一个大map拆分成多个小map，分片减少了key进入同一个hash shard的概率，同时，当一个分片加锁时不会影响别的分片）+ &lt;code&gt;缓存淘汰算法&lt;/code&gt;（lfu、lru、fifo等）通过控制数量来减少map不缩容带来的影响。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;也有三方库底层通过map+slice的形式来减少不缩容的影响，其中slice用来真正的存储value，map[key][index]用来查找key在slice中对应的下标。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;三方库对比&#34;&gt;三方库对比
&lt;/h2&gt;&lt;h3 id=&#34;bigcache&#34;&gt;bigcache
&lt;/h3&gt;&lt;h4 id=&#34;存储结构&#34;&gt;存储结构
&lt;/h4&gt;&lt;p&gt;分片hash+[]byte ，每一个分片称为一个shard（map[int64]int32，避免了存储指针），在get或者set时会先对key进行hash，根据hash值判断操作哪一个shard，之后的操作都是在shard上进行的。（shard之间互不影响，每个都有读写锁）真正的item（set的value）是存储在[]byte（bytesqueue，gc对于切片当作一个变量扫描，无需遍历整个数组）中的，分片（shard）中的value其实就是该item对应的下标。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://liusir521.github.io/p/go%E4%B8%89%E6%96%B9%E7%BC%93%E5%AD%98%E5%BA%93%E5%AF%B9%E6%AF%94/bigcache.png&#34;
	width=&#34;1068&#34;
	height=&#34;861&#34;
	srcset=&#34;https://liusir521.github.io/p/go%E4%B8%89%E6%96%B9%E7%BC%93%E5%AD%98%E5%BA%93%E5%AF%B9%E6%AF%94/bigcache_hu_426e797d30a3a70f.png 480w, https://liusir521.github.io/p/go%E4%B8%89%E6%96%B9%E7%BC%93%E5%AD%98%E5%BA%93%E5%AF%B9%E6%AF%94/bigcache_hu_90ffac5c4ea11054.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;bigcache结构图&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;124&#34;
		data-flex-basis=&#34;297px&#34;
	
&gt;&lt;/p&gt;
&lt;h4 id=&#34;添加操作&#34;&gt;添加操作
&lt;/h4&gt;&lt;p&gt;添加操作分为三种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;要添加的key已经存在（也可能是hash碰撞）：由于使用的是FIFO淘汰策略，所以即使有旧值的情况下，新值也不会复用其内存，而是push新的value到队列中。之前的旧值并未从内存中移除，仅将其偏移量从hashmap中移除，使得外部读不到。&lt;/p&gt;
&lt;p&gt;旧值何时淘汰：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;设置了CleanWindow，且旧值刚好过时，会被清理器自动淘汰。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置了MaxEntrySize或者HardMaxCacheSize，当内存满时，也会出发旧数据的淘汰。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;previousIndex&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hashmap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hashedKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;previousIndex&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;previousEntry&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;entries&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;previousIndex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;resetHashFromEntry&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;previousEntry&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;//remove hashkey&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;delete&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hashmap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hashedKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;
&lt;p&gt;bytesqueue已满：&lt;/p&gt;
&lt;p&gt;a. 如果bytesqueue未达到设定的HardMaxCacheSize上限，或者HardMaxCacheSize没有设置，则直接扩容直到切片上限。&lt;/p&gt;
&lt;p&gt;b. 如果已达上限，则会删除最旧的数据（无论是否过期），知道可以将当前数据添加进去。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;正常情况下，加当前shard的写锁，直接添加并更新索引。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;获取操作&#34;&gt;获取操作
&lt;/h4&gt;&lt;p&gt;添加操作的逆过程。需要注意的是，如果获取时数据到达了过期时间，但还没有被清理掉，这时也是可以成功获取到的。是符合大多数需求场景的。&lt;/p&gt;
&lt;h4 id=&#34;删除操作&#34;&gt;删除操作
&lt;/h4&gt;&lt;p&gt;和添加时清除旧值类似。&lt;/p&gt;
&lt;h4 id=&#34;淘汰策略&#34;&gt;淘汰策略
&lt;/h4&gt;&lt;p&gt;采用FIFO淘汰策略。新增数据，以及对老数据进行修改，都是直接append到[]byte（bytesqueue）中的。基本不对内存进行修改删除。同时，每个数据项不能单独设置缓存时长，必须全部保持一致。这样对数据淘汰比较友好。&lt;/p&gt;
&lt;h3 id=&#34;freecache&#34;&gt;freecache
&lt;/h3&gt;&lt;h4 id=&#34;存储结构-1&#34;&gt;存储结构
&lt;/h4&gt;&lt;p&gt;freecache和bigcache类似，也是分片的思想。freecache内部包含256个分段（segment），每个分段都有一把锁。每个分段内部包含两个指针rb（ringbuffer，底层是切片，存储真实数据）和slotsData（也是切片，存储数据在rb中的下标，查找时二分法遍历）。指针固定512（rb和slotData）个，所以号称0GC。但是freecache的key和value都是[]byte类型的。需要进行序列化操作。&lt;/p&gt;
&lt;h4 id=&#34;添加操作-1&#34;&gt;添加操作
&lt;/h4&gt;&lt;p&gt;先对key进行hash，低8位对应segment数组，低8-15位选取slot下标， 同时取高 16 位做 hash16 用于 slot 内快速比较。  然后通过二分法寻找小于等于当前hash16，如果找到等于的还要继续对比key的信息。如果找到相同的key，则是更新操作，没找到就是插入。更新时会比较新value和老value的大小，如果比老的小就可以直接覆盖写入，否则就在rb的末尾寻找空间写入。插入时如果rb空间不足就会触发淘汰机制。&lt;/p&gt;
&lt;h4 id=&#34;查找操作&#34;&gt;查找操作
&lt;/h4&gt;&lt;p&gt;与添加操作的查找类似，先通过key定位segment，然后继续定位slot，然后根据hash16进行二分查找。如果找到了继续对存储的key进行对比。完全命中才算。命中时读取其设置的过期时间，如果已经到了，返回未命中，并将此项标记为删除。没过期就更新相关的命中统计。&lt;/p&gt;
&lt;h4 id=&#34;删除操作-1&#34;&gt;删除操作
&lt;/h4&gt;&lt;p&gt;value的定位和上面的一样，找到之后会将索引从slot中移除， 并把该 entry 的 header 标记 deleted = true（逻辑删除标识，写在 header 里）。这意味着索引已经不再指向该条目，外部 Get/Set 不会再找到它。&lt;/p&gt;
&lt;h4 id=&#34;淘汰机制&#34;&gt;淘汰机制
&lt;/h4&gt;&lt;p&gt;当 RingBuf 空间不足时，FreeCache 会从缓冲区的头部开始主动淘汰数据以释放空间。其淘汰策略是一个精巧的、近似 LRU 的策略，综合考虑了访问时间和过期时间。 freecache 不是精确维护一个 LRU 链表，而是用分段 + 环形缓冲 + “平均访问时间”比较的方法：当某个段要腾空间时，优先删除过期或“比段平均访问时间更旧（更冷）”的条目；如果条目相对较新（热），则把它搬到环形缓冲的尾部以保留。这个策略因此被称为 Nearly LRU（近似 LRU）。&lt;/p&gt;
&lt;h3 id=&#34;ristretto&#34;&gt;ristretto
&lt;/h3&gt;&lt;h4 id=&#34;存储结构-2&#34;&gt;存储结构
&lt;/h4&gt;&lt;p&gt;与前面的框架类似，也是分片hash的设计思想（256个分片+读写锁）（最底层使用的map存储的value），但是最底层存储的key是经过计算后的hash值（该框架对key计算了两个hash值&amp;lt;两个uint64&amp;gt;，keyhash和 conflict ，后者用于判断冲突，使用的是xxhash算法）。该框架在初始化实例时会自动启动一个协程processItems来进行增删改的操作，并周期性的进行GC回收。&lt;/p&gt;
&lt;h4 id=&#34;添加操作-2&#34;&gt;添加操作
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先会检查设置的过期时间，如果小于0直接返回false代表失败，为0表示永不过期。然后根据key计算出两个hash值。然后会先尝试更新操作，如果返回更新成功，会直接修改存储的value，并将当前item的flag修改为更新操作。然后尝试将这个item放入到setbuff的缓冲队列中（channel），如果队列未满或者满了但是当前操作是更新会返回true，视为写入成功，否则返回false并记录指标。然后processItems协程会从setbuff中获取数据（select）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取到数据之后，首先会判断当前item是否是特殊item（内置一个空结构体类型的channel字段wait）， 由于是协程监听进行的增删改操作，不保证强一致性，Ristretto提供了一个Wait函数来等待之前设置的item全部设置成功，该函数会进行阻塞（读取wait），直到setbuff执行到这个特殊的item，然后关闭这个特殊item的wait，结束Wait函数，表示数据已经完全录入完毕。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果是普通的item，会先计算这个item的消耗cost，如果初始化时提供了Coster函数，并且当前flag不是删除，且item的cost为0，就会调用提供的函数进行计算真实的cost，然后还会加上内部存储占用 itemSize，除非在 Config 中设置 IgnoreInternalCost = true。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后判断当前item的flag是否是添加操作（这里默认是），然后调用函数进行决策，通过TinyLFU算法来判断当前item是否值得被接受，以及如果接受之后超出内存上限需要通过 Sampled LFU  算法决定要淘汰哪些数据。如果值得添加，就调用函数将item添加到底层map中并记录指标，然后记录当前key的时间戳，方便后续计算寿命。如果不值得添加，就调用 onReject 回调函数，直接丢弃。然后对需要淘汰的数据进行删除。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;更新操作&#34;&gt;更新操作
&lt;/h4&gt;&lt;p&gt;同理从setbuff中获取，未进入setbuff的添加的item参考添加操作。外部暴露的函数与添加操作一致，都是Set。进入setbuff的flag为更新操作的item仅更新相关指标即可。&lt;/p&gt;
&lt;h4 id=&#34;删除操作-2&#34;&gt;删除操作
&lt;/h4&gt;&lt;p&gt;同理从setbuff中获取（有序队列，避免了先执行删除后又被恢复的场景），执行相关的指标更新，然后从底层删除，并调用相关的回调函数。&lt;/p&gt;
&lt;h4 id=&#34;获取操作-1&#34;&gt;获取操作
&lt;/h4&gt;&lt;p&gt;先获取key的两个hash值，在从map寻找数据之前，会先执行一个Push操作，将访问计数的采集推入缓冲再进行读取，缓冲是自定义的一个ring-buffer，内部有sync.pool字段存储获取的key的切片，目的是合成批量操作降低开销。然后才会在真正的分片hash中查找对应的数据， 如果传入了 conflictHash 且不匹配就认为不存在，检查 item 是否过期（TTL），如果过期也当作不存在。&lt;/p&gt;
&lt;h4 id=&#34;淘汰策略-1&#34;&gt;淘汰策略
&lt;/h4&gt;&lt;p&gt;采用的是 Sampled LFU（随机采样找出最不常用的候选）淘汰算法，当添加时发现空间不足时会先用 TinyLFU  估算新key的频率，然后从已有的item中随机抽取5个（源码写死，研究表明，5 个样本的概率足以近似全局最低频项）估算key的频率，如果新key最低，就不值得进入缓存，不是最低就将要淘汰的key添加到相关队列中，重复上述步骤，直到能把新key放进去。&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结
&lt;/h2&gt;&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;对比&lt;/th&gt;
          &lt;th&gt;数据类型限制&lt;/th&gt;
          &lt;th&gt;淘汰策略&lt;/th&gt;
          &lt;th&gt;一致性&lt;/th&gt;
          &lt;th&gt;过期设置&lt;/th&gt;
          &lt;th&gt;GC&lt;/th&gt;
          &lt;th&gt;内存利用率&lt;/th&gt;
          &lt;th&gt;适用场景&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;bigcache&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;value 必须是 &lt;code&gt;[]byte&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;FIFO&lt;/td&gt;
          &lt;td&gt;强一致性&lt;/td&gt;
          &lt;td&gt;仅支持 &lt;strong&gt;全局过期时间&lt;/strong&gt;，不支持单个 key 设置过期；获取时若过期按成功处理&lt;/td&gt;
          &lt;td&gt;0 GC&lt;/td&gt;
          &lt;td&gt;支持动态扩展，利用率较高，但没有淘汰策略，过期数据可能占用内存较久&lt;/td&gt;
          &lt;td&gt;数据生命周期短，不太在意命中率；适合 &lt;strong&gt;短时缓存、临时存储&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;freecache&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;key 和 value 都必须是 &lt;code&gt;[]byte&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Nearly LRU（近似 LRU）&lt;/td&gt;
          &lt;td&gt;强一致性&lt;/td&gt;
          &lt;td&gt;支持 &lt;strong&gt;单个 key 设置过期时间&lt;/strong&gt;；获取时若过期按失败处理&lt;/td&gt;
          &lt;td&gt;0 GC&lt;/td&gt;
          &lt;td&gt;内存固定分配，利用率高；但 &lt;strong&gt;key 小 value 大&lt;/strong&gt; 时可能产生空间碎片&lt;/td&gt;
          &lt;td&gt;命中率要求不高；适合 &lt;strong&gt;日志、监控指标等大量 KV 缓存&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;ristretto&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;无限制&lt;/td&gt;
          &lt;td&gt;Sampled LFU（随机采样选出最不常用候选）&lt;/td&gt;
          &lt;td&gt;最终一致性&lt;/td&gt;
          &lt;td&gt;支持 &lt;strong&gt;单个 key 设置过期时间&lt;/strong&gt;；获取时若过期按失败处理&lt;/td&gt;
          &lt;td&gt;Go GC&lt;/td&gt;
          &lt;td&gt;有容量上限且控制精细，结合淘汰策略，整体 &lt;strong&gt;内存利用率最高&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;需要 &lt;strong&gt;高命中率&lt;/strong&gt;；适合 &lt;strong&gt;业务缓存（用户会话、推荐系统、热点数据）&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
</description>
        </item>
        <item>
        <title>Redis数据结构之String(SDS)</title>
        <link>https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bstringsds/</link>
        <pubDate>Wed, 10 Sep 2025 00:00:00 +0000</pubDate>
        
        <guid>https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bstringsds/</guid>
        <description>&lt;img src="https://liusir521.github.io/p/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8Bstringsds/redis.jpg" alt="Featured image of post Redis数据结构之String(SDS)" /&gt;&lt;blockquote&gt;
&lt;p&gt;redis版本7.x&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;原生c语言字符串的问题&#34;&gt;原生C语言字符串的问题
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;C语言使用char * 字符数组来实现字符串，在创建时就需要手动检查和分配空间。由于没有 &lt;code&gt;length属性&lt;/code&gt; 记录长度，想要获取字符串的长度就需要从头遍历，直到遇到\0。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无法做到“安全的二进制格式数据存储”，图片等二进制格式数据无法保存。无法存储\0这种特殊字符，\0在C语言中代表字符串结尾。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;字符串的扩容和缩容。char数组的长度在创建字符串的时候就确定下来，如果要追加数据，则要重新申请一块空间，把追加后的字符串内容拷贝进去，再释放旧的空间，十分消耗资源。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;redis中sds设计&#34;&gt;Redis中SDS设计
&lt;/h2&gt;&lt;p&gt;SDS也遵循C语言的以空字符串 &lt;code&gt;\0&lt;/code&gt; 结尾的惯例，但是空字符串不计入SDS内部的len字段中。&lt;/p&gt;
&lt;p&gt;SDS主要字段如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;len：数组已使用长度&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;alloc：数组总长度&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;flags：SDS类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;char buf[]：存储的实际内容&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;o1时间复杂度获取字符串长度&#34;&gt;O(1)时间复杂度获取字符串长度
&lt;/h3&gt;&lt;p&gt;SDS中的len字段保存了字符串的长度，实现了O(1)时间复杂度获取字符串长度。&lt;/p&gt;
&lt;p&gt;SDS结构有一个flags字段，表示的是SDS类型。实际上SDS一共设计了5种类型，分别是sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64，区别在于数组的len长度和分配空间长度alloc不同。&lt;/p&gt;
&lt;h3 id=&#34;节省内存&#34;&gt;节省内存
&lt;/h3&gt;&lt;p&gt;之所以这么设计，是因为SDS使用不同的类型保存不同大小的字符串可以节省内存。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;redis内部限制最大的字符串长度为512MB&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;编码格式&#34;&gt;编码格式
&lt;/h3&gt;&lt;p&gt;SDS内部采用了三种编码格式来存储，分别是int、embstr和raw。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;int编码：8字节的长整型，值是数字类型且数字的长度小于20。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;embstr编码：长度小于或等于44字节的字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;raw编码：长度大于44字节的字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;作用：代替字节对齐的方式来节省内存。&lt;/p&gt;
&lt;h3 id=&#34;二进制格式的数据安全&#34;&gt;二进制格式的数据安全
&lt;/h3&gt;&lt;p&gt;因为SDS并不是通过 &lt;code&gt;\0&lt;/code&gt; 来判断字符串结束的，而是采用len标志结束，所以可以直接存储二进制格式数据。&lt;/p&gt;
&lt;h3 id=&#34;空间预分配&#34;&gt;空间预分配
&lt;/h3&gt;&lt;p&gt;在需要对SDS的空间进行扩容时，不仅仅分配所需的空间，还会分配额外的未使用空间，通过预分配策略，减少了执行字符串增长所需的内存重新分配次数。&lt;/p&gt;
&lt;h3 id=&#34;惰性空间释放&#34;&gt;惰性空间释放
&lt;/h3&gt;&lt;p&gt;当对SDS进行缩短操作时，程序并不会回收多余的内存空间，如果后面需要append追加操作，则直接使用buf数组alloc-len中未使用的空间。通过惰性空间释放策略，避免了减小字符串所需的内存重新分配操作。&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
