重新认识Redis
谈到数据,大多数业务数据经常被存储在传统数据库上,而这些数据库,包含SQL和NoSQL,都是以磁盘为介质的,这也就意味着磁盘IO是数据库的一大瓶颈。比方说,一些高并发业务,比如秒杀活动,实时股票/金融计算服务,如果只用数据库,那么必定会出现数据库的查询速度远远满足不了实际业务的需求速度的情况,这本质上也是由硬件规律决定的,毕竟RAM的速度要远远大于磁盘速度。
所以很多时候,我们往往需要在业务和底层存储之间加多一个缓存层,这个缓存层,可以程序自己实现加载在内存的缓存,当然也可以用现有的解决方案,比如Redis。
大都数人谈到Redis,一想到的大概也就是"Redis是内存式缓存数据库,单线程,性能高",这样说其实也没错,但有些不全面。今天我们来全面地谈一谈Redis。
Redis如何存字符串
来一个最简单的例子: SET demo 1
, 设置一个key,那redis是如何来存储的呢?在redis底层,key和value都是以同样的方式来存储的,也就是SDS(Sample Dynamic String),简单动态字符串,简单来说,就是维护一个结构体,存储长度len和数据数组,这样做的意义是什么呢?当然就是为了O(1)复杂度的来获取字符串的长度,所以,Redis采用了用空间换时间的策略来保证O(1)的复杂度来获取字符串的长度。
另外,SDS对内存分配也做了优化,一次性申请大于本身的内存,然后通过len
和free
来标记申请内存中的已使用和未使用的字节数,这样,如果下次对这个SDS进行修改且长度依然小于free
的长度,那么就可以直接写入free的内存,否则申请扩展SDS内存,这样做的意义就是,减少内存的申请次数,减少CPU消耗,优化读写性能。
Redis的底层存储类型
在Redis中,我们经常遇到的就是普通的字符串类型,列表,哈希,集合,有序集合等,这些数据类型其实都有自己底层的实现方式,如下:
Redis数据类型对应的底层类型REDIS_STRINGSDSREDIS_LISTlinkedlistREDIS_HASHhashmapREDIS_SETarray & ziplistREDIS_ZSETskiplist & dictionary
压缩列表
当列表中的值都比较小且数量不大时,redis会采用压缩列表的方式来节省内存,所谓压缩列表,即通过定义特定的数据格式,使用连续的内存空间来存储数据的一种数据结构
zlbytes:存储一个无符号整数,固定四个字节长度,用于存储压缩列表所占用的字节,当重新分配内存的时候使用,不需要遍历整个列表来计算内存大小
zltail:存储一个无符号整数,固定四个字节长度,代表指向列表尾部的偏移量,偏移量是指压缩列表的起始位置到指定列表节点的起始位置的距离。
zllen:压缩列表包含的节点个数,固定两个字节长度,源码中指出当节点个数大于2^16-2个数的时候,该值将无效,此时需要遍历列表来计算列表节点的个数。
entryX:列表节点区域,长度不定,由列表节点紧挨着组成。
zlend:一字节长度固定值为255,用于表示列表结束。
在Redis的五种数据类型中,其中有三种会在某些特定条件下使用这种压缩列表,那压缩列表的意义是什么呢? 答案就是节省内存,减少内存访问次数,提升读写效率。
TTL的实现
定时任务在redis的底层实现上也是一个链表,比如TTL自动过期,其实是有个任务列表存储在链表中的,通过秒级或者毫秒级轮询遍历链表的方式来实现。
有序列表的实现
有序列表使用跳跃表来实现快速查找和删除。跳跃表本质上就是一种多层级的双向有序链表,即skiplist
,每次遍历从层级高的地方先遍历,插入时查找插入位置类似于N分插入。跳跃表是一种可以比肩平衡树的数据结构,效率极高。
高可用的实现
Redis在部署上分为单机版和集群版,单机版本就是单节点部署,节点挂了,Redis也就挂了。所以在生产环境一般都是部署Redis集群。
Redis之所以高可用,除了自身的数据类型设计上的保证了性能之外,在多机部署上也很大程度提升了服务的高可用性。
Redis有集群模式,我们称之为cluster
,还有一种主从模式,我们称之为master-slave
,还有一种称之为哨兵模式sentinal
。
主从是为了实现当主节点挂了之后可以快速使用从节点来替代主节点,哨兵则是一种专门用来监听主节点和从节点状态的节点,并实现主从的自动切换,而集群模式则可以看成是主从+哨兵的横向扩展。
在主从模式中,每个节点存储的数据都是全量的,而在集群模式中,每个节点存储的数据是非全量的,即通过分槽的方式,将数据平摊到各个子节点中,从而从水平方向上横向扩展了redis的存储能力。当然,集群模式下,对一些多key的原子操作将变得不可用。
数据持久化的实现
都说Redis是内存式数据库,但是也有持久化的功能,和其他DB一样,都是可以对数据进行备份和恢复的。
Redis有两种持久化方式,一种是直接备份内存中的数据,即RDB
备份。另外一种则是备份写命令的方式,即AOF
备份。
主从之间的数据复制采用的是RDB
方式备份,而AOF则是实时性更高,类似于将所有写入log备份到磁盘文件中,当需要恢复时,直接再执行一遍这些写入命令即可。
业务上对redis的实践优化
-
大Key拆分成多个小Key,在集群中一个Key太大,或者访问频率过高,都可能导致大Key所在slot的节点的IO流量过大,从而影响其他业务访问的速度。解决方式则通过程序合并的方式将一个大Key拆分成N个小Key,来均衡集群各个节点的流量峰值。
-
禁止使用从不过期的Key,因为Redis是内存型数据库,若永久不过期,长久以往,对浪费Redis越来越多的内存资源,从机器成本以及代码原则上,我们应该给所有Key都加上TTL。
-
禁止使用keys这种全遍历操作,原因是会block住其他请求,可以使用scan来替代,相当于分页处理。
-
哈希表的扩容会导致redis的内存出现短时间内增长,当扩容结束,会释放掉ht[1]的数据,内存回归正常。
原文链接: https://mp.weixin.qq.com/s/gM2FWRtVybT5ZzDpbZf6Aw
有疑问加站长微信联系(非本文作者)