当前位置:首页 > 问答 > 正文

Redis编码类型那些事儿,怎么用才算是最合适的实践分享

说到Redis,很多人觉得它就是个简单的键值对存储,把数据往里一扔就行了,但其实,Redis为了节省内存和提高效率,给每种数据类型都准备了不止一种“内部包装方式”,这就是所谓的“编码类型”,你要是能稍微了解一下这些门道,用起Redis来就能更得心应手,让它在你的系统里真正发挥出高性能的优势。

这事儿得从一个最基础的说起:Redis的五大基本数据类型,比如String、List、Hash、Set、Sorted Set,它们只是暴露给咱们用户的操作接口,就像你买了一台智能电视,遥控器上只有“开机”、“换台”、“调音量”这几个大按钮,但电视内部其实有非常复杂的电路和软件来处理你的指令,Redis也是一样,你用一个命令,比如HSET往Hash里加个字段,Redis底层会根据你存的数据情况,自动选择一种最省内存或者处理速度最快的编码方式来存储它,这个选择过程是自动的,但对咱们的性能影响很大。

那这些“内部包装”到底有哪些呢?我来挑几个最常见、最影响性能的说说。

String类型,你以为所有的字符串都是同一种方式存的?不是的,如果字符串的内容可以解释成一个长整型数字,比如你执行SET age 30,那么Redis就不会傻乎乎地用存普通文本的方式去存,它会用一个叫int的编码,直接把这个数字30当成整数存起来,这样占用的空间就极小,但如果你存的是SET name "张三",那它就会用另一种叫embstrraw的编码来存,简单理解,短字符串用embstr更紧凑,长字符串就用raw,这个转换的阈值是可以配置的。(来源:Redis官方文档关于字符串类型的介绍)

再说说List列表,早期版本里,List底层用的是标准的链表,这叫linkedlist编码,链表的好处是头尾插入删除元素特别快,但缺点是每个元素都得带上前后指针,比较占内存,而且查找中间某个元素比较慢,后来Redis引入了ziplist(压缩列表)这种编码,你可以把它想象成一个“打包好的包裹”,它把列表里的所有元素(包括值和指向下一个元素的偏移量)一个紧挨一个地塞进一块连续的内存里,就像把东西紧密打包进一个箱子,极大地减少了内存碎片和指针的开销,但缺点是这个“包裹”如果太大或者里面的元素太大,查询和修改的效率就会下降,因为可能需要对整个“包裹”重新打包,所以Redis给了我们两个配置参数:list-max-ziplist-sizelist-compress-depth,意思是,当列表的元素个数少于某个值(比如512个),并且每个元素的大小都小于多少字节(比如64字节)时,Redis就优先使用节省内存的ziplist编码,一旦超过这个限制,它就会自动转换成linkedlist编码,以保证操作的效率。(来源:Redis官方文档关于列表类型的介绍,以及《Redis设计与实现》一书)

接下来是Hash哈希,也就是键值对集合,这个在实际应用中超级常用,比如存一个用户对象:user:1000 => { name: "李四", age: 35, city: "北京" },它的底层编码也有两种主力军:ziplisthashtable,和List的逻辑很像,当哈希表中的字段数量很少(默认小于512个),且所有字段的名字和值都比较短(默认小于64字节)时,Redis会使用ziplist编码,把字段和值交替着塞进那个“压缩包裹”里,非常省内存,一旦字段数量或者某个值变大了,超过了阈值,它就会自动升级成标准的hashtable哈希表结构,哈希表的查询速度是O(1),非常快,但内存开销会比小规模的ziplist大一些。(来源:Redis官方文档关于哈希类型的介绍)

Set集合和Sorted Set有序集合也遵循类似的自动转换规则,比如Set会在元素少且全是整数时用intset(整数集合)编码,否则用hashtable;Sorted Set也会在小规模时使用类似ziplist的结构。

怎么用才算是最合适的实践呢?核心思想就是:引导Redis在绝大多数情况下,为你当前的数据规模和使用场景,选择最高效的那种编码。

第一,理解并合理配置转换阈值,Redis的默认配置是比较通用的,但你的业务可能很特殊,你明确知道你的Hash结构永远只会存不到100个字段,但每个字段的值可能是一个几KB的文本,这时候,默认的64字节的阈值就太小了,会导致Redis很快就转换成hashtable,虽然查询没问题,但内存可能多用了一些,你就可以根据实际情况,适当调大hash-max-ziplist-value这个参数,让你的大数据依然享受ziplist的内存节省优势,反过来,如果你的Hash字段成千上万,那就别想着用ziplist了,老老实实用hashtable保证性能才是正道。

第二,考虑数据的主要操作,如果你的List主要是在两端进行推送和弹出操作(比如做消息队列),那么即使元素多一点,只要不超过ziplist的个数上限,用ziplist编码效率依然很高,但如果你需要频繁查找列表中间的元素,那么可能linkedlist会更合适(虽然List本来也不擅长随机查找)。

第三,善用小型化数据,既然小数据能触发高效的编码,那我们就有意识地在设计键值时,尽量让它们“小而美”,用多个小Hash代替一个巨大的Hash;把长的字段名适当缩短(但要保证可读性);如果可能,把浮点数转换成整数存储等等,这些细微的优化,在海量数据下效果非常明显。

第四,使用监控命令,当你对性能有疑虑时,可以用Redis的OBJECT ENCODING key命令,去看看某个键当前底层到底用的是哪种编码,这能帮你验证你的配置是否生效,你的数据是否按照你预期的方式在存储。

了解Redis的编码类型,不是为了让我们去手动指定编码(实际上你也指定不了),而是让我们成为一个“明白人”,我们知道数据在Redis内部是怎么被“打扮”的,就能通过合理的数据结构设计和参数配置,去“哄着”Redis,让它自动为我们选择最优的存储方案,这才是真正的高阶玩法,能让你的Redis应用在性能和成本之间找到最佳的平衡点。

Redis编码类型那些事儿,怎么用才算是最合适的实践分享