Redis进阶:数据结构、持久化、跳跃表、事务

Redis进阶:数据结构、持久化、跳跃表、事务什么是跳跃表跳跃表是一种有序的数据结构,目的是为了降低单向链表查找的时间复杂度,可降低到O(log(n))~O(n);相比于红黑树来讲,跳跃表实现更简单,插入、删除操作时间复杂度更低。那些地方在使用跳跃表实现有序集合键(SortedSet)集群节点中用作内部数据结构跳跃表的实现跳跃表通过多层存储的不同数据,从顶层向下类似于二分法查找法,查找目标数据更多Redis跳跃表介绍…

Redis 数据类型与应用:

字符串:String(SET key value)

  • 按二进制存储,也就是可以接受任何类型格式的数据;
  • 规定了一个字符串 value 最大可为 512M

应用场景:

  1. 存储热点的单个字符串或者 JSON 串

  2. 由于是二进制安全的,可以存储图片文件到 value 中

  3. 线程安全的计数器(如使用 INCR key 来完成点赞或文章阅读数的修改)

可重复列表:List (LSET key index value):

简单的字符串列表,按照插入顺序排序(可用该特性完成需要的数据结构),允许重复元素;

可以 添加 / 删除 一个元素在列表的头部(LPUSH key value [value …] 、 LPOP key)或者列表的尾部(RPUSH key value [value …] 、 RPOP key),也可以删除列表中的值(LREM key count value)

应用场景:

  1. 使用 BLPOP key timeout 在timeout时间内等待弹出队首元素,来实现一个任务队列、不太成熟的 消息队列。

  2. 在分布式机器中,将日志记录到 Redis 中的 List 中,然后按顺序记录到一个日志文件中。

哈希表:Hashs(HSET key field value):

可以看成具有键值对的map容器

应用场景:

  1. 用来存储对象的键值对。如果使用字符串类型来存储 JSON 字符串对象,JSON 的序列化/反序列化会带来性能上的消耗,且在修改 JSON 字符串对象时,需考虑线程安全问题。如果使用哈希表来存储对象中的键值对,不会有序列化和线程安全问题,且占用内存小。

hash 表的数据结构与扩容

不重复集合:Set(SADD key member [member …]):

没有排序的字符集合,集合中不允许出现重复的元素

应用场景:

  1. 使用 SRANDMEMBER key [count] 从集合中随机取出 count 个元素,可用作热点数据分类的随机推送

  2. 使用 SINTER / SUNION / SDIFF 求两个集合的交、并、差,来实现对应的数据要求。

有序不重复集合:SortedSet(ZADD key score member [score] [member]):

带有分数的set集合,并按升序排列;

其中的元素是唯一的, 但分数(score)是可以重复的;

score使用64位存储,整数范围为 正负9*10^18,双精度 double 需要注意精度的丢失

应用场景:

  1. 对数据进行一个排序,并可以在列表中拿到最大,最小的多少个元素等操作。

  2. 实现定时的消息发送,使用 score 存储需要发送消息的时间戳,服务端轮询 SortedSet 中的数据判断是否要发送通知。

位图:SETBIT key offset value

将数据的唯一标识信息,直接或通过某一个函数间接 映射到位图二进制中的某一位上,用 0和1表示一个状态。

该类型的数据结构,可用于大量数据的统计计算。

应用场景:

  1. 对在全年内没有播放的电影数量进行统计:将每一条电影数据创建一个位图数据,如果某一天有播放记录,则将对应位图中的位置修改为1,然后对每部电影产生的一个 BitMaps 进行或运算(BITOP or storeKey key1 key2…),得到的 storeKey 中位为 0 的就是没播放的电影。

对 BitMap 的升级数据结构: Bloom Filter、Cuckoo Filter

HyperLogLog

HyperLogLog是用作基数(集合中,不重复元素的个数)统计的,内部使用了loglog算法。该种数据结构的操作只有添加元素、获取基数个数、合并多个HyperLogLog。

注意点:

  1. HyperLogLog中,并不会存储添加的具体数据,只是记录集合的基数

  2. 基数是算法估算的结果,会有0.81%的误差,但是该数据结构一般用在数据量非常大的场景,故该误差对系统影响可忽略

  3. HyperLogLog占用的空间一定不会超过12KB,占用空间少

地理位置:GEOADD key longitude latitude member [longitude …]

上述 api 中,key表示地图集、longitude表示经度、latitude表示纬度、member表示当前坐标点名称。

应用场景:

  1. 使用 GEORADIUSBYMEMBER key member radius m|km|ft|mi 求得已存在点 member 附近 radius 内所有的点,可用于查找附近的人。同样也可以使用 GEORADIUS 命令实现。

发布订阅模型:publish / subscribe

它不基于任何数据类型,也没有做任何的数据存储(即不会用 AOF 或 RDB),仅仅是一个实时的数据转发通道
在这里插入图片描述
应用场景:

  1. 支持发布 / 订阅,支持多组生产者、多组消费者处理实时的消息,在哨兵集群、Redissioin红锁下有使用到
  2. 有以下几种消息丢失情况,不适合能做消息队列:
    • 该模型没有持久化,Redis 宕机后消息会丢失
    • 消息堆积到上限后,会丢失
    • 同一个消费者无法重复消费同一条消息

Stream

Redis 5.0 及之后推出的较为成熟的消息队列结构。

优缺点:

  1. 支持消费者阻塞获取消息,发布 / 订阅模式
  2. 支持重复消费
  3. 避免因服务宕机,而导致的消息丢失(但AOF的间隔写入日志或Redis集群节点切换可能会丢失少部分数据)
  4. 消息堆积会导致消息丢失

Redis API 文档
5种数据类型对应的底层结构参考

Hash 数据结构

在这里插入图片描述
上图是 Redis 中的 hash 结构,其对应关系为:

   一个 RedisDB 中,对应一个 dict,一个 dict 对应两个 dictht(扩容期间使用两个),一个 dictht 包含一个散列通(存放多个 dictEntry)。

扩、缩容机制与其带来的影响

rehash:
   在 Java 中 HashMap 扩容是个很耗时的操作,需要先申请新的数组空间,然后在一个循环中更新所有元素到新数组中。在 Redis 中为了追求高性能扩、缩容,采用了 渐进式 rehash 策略,来改变 dictht 大小。

   渐进式 rehash 采取分而治之的方式, 将 rehash 键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。渐进式 rehash 的详细流程可参看 这里

扩、缩容条件:
   当元素的个数等于数组的长度时,就会开始扩容,扩容的新数组是原数组大小的 2 倍。当 dictht 中元素个数少于数组长度的 10%时,Redis 会进行缩容来减少空间占用。

影响:
   从 rehash 的详细流程中可以看到,rehash 第一步需要申请内存空间用于存放 dictht[1],dictht[1] 申请的内存大小如下图。如果 rehash 时内存不够,Redis 将执行驱逐策略,Master/Slave 将会把大量的 Key 驱逐淘汰,导致 Master/Slave 主从不一致,带来其他业务影响。

在这里插入图片描述

   该问题的解决办法之一,可以监控集群内存的使用情况,及时处理或提前运营规避,其他关于渐进式 rehash 的问题可参考这里

基于 BitMap 实现的两种过滤器

Bloom Filter(布隆过滤器)

结构:
   BF 由一串二进制(0或1)向量和一系列随机映射函数组成
功能:
   用于检索一个元素是否在一个集合中,BF 可以确定一个数据 一定不在集合中
优 / 缺点:
   空间效率高,占用空间少; 查询时间短

   有一定的误判率; 元素不能删除(因为不能判断某一个位具体被多少个函数映射到了

BF 主要有两种实现方式,依赖 Redis(Redission) 和不依赖 Redis(即应用内存,Guava) 。

   借助 Redis 实现的 Bloom Filter 与 BitMap 类似,但 BitMap 的数据映射关系由用户定义,Bloom Filter 的映射关系函数由算法内部定义,并有多个函数共同工作。

Cuckoo Filter(布谷鸟过滤器)

   上文提到 BF 存在一个致命的缺点,不能删除元素,只能在程序认为误差率已经不能接受时进行重建。

   CF 使用特殊的函数,解决了这个问题,让集合中的元素可以删除,但也存在一定的误差率。


持久化机制

RDB持久化机制

  • 可以在配置文件中修改刷入的规则,或者存储的文件路径和文件名。dump.rdb 路径默认为 “./”,即在那里启动,就在那里生成文件,一般固定一个文件夹。
  • 是Redis默认的持久化方式,使用快照方式,按刷入的规则,使用bgsave,将对数据的增删改操作记录自动刷入到磁盘文件中
  • 也可以手动调用 save / bgsave 命令来手动触发,两个方式使用主进程 / fork产生的子进程完成数据的存储;如果使用的save,那么单进程单进程的 Redis 会阻塞其他 I/O 请求;如果使用的是bgsave,则需再分配与父进程相同大小的资源空间,并产生一个临时文件,数据写入完成后,临时文件替换掉 dump.rdb。

Redis默认配置情况下,开启的是 RDB Snapshotting,只有满足了配置的刷入规则,才会在配置的路径,向/*.rdb的文件记录数据操作

Q&A:

什么时候触发 RDB 机制?

  1. 当未开启 AOF 时,客户端执行 shutdown 命令时,触发 bgsave 的保存操作
  2. 满足 redis.conf 中配置的规则时,触发保存操作
  3. 执行 save、bgsave 命令时,触发保存操作

AOF持久化机制

  • 默认是不开启的,需要配置appendonly yes,使用日志记录所有的修改操作
  • 三种触发方式
appendfsync always     # 每次都同步(最安全但是最慢)
appendfsync everysec  #每秒同步(AOF默认的同步策略)
appendfsync no          #不主动同步,由操作系统来决定(最快但是不安全)
  • AOF 中有对日志重写的机制(bgrewriteaof命令),改操作将对 *.aof 文件中的记录进行一个合并,如当文件中记录有两条 set k v,set k v2 时,将被合并为一条。相关配置如下:
auto-aof-rewrite-percentage 100		
auto-aof-rewrite-min-size 64mb		当文件大小达到64MB的时候,触发bgrewriteaof命令。生产环境可设置GB为单位。

Redis默认的AOF是关闭,开启后,默认每秒记录操作到磁盘上的数据文件,向 /*.aof 文件按固定的格式追加 Redis 操作命令,如 set 等。

RDB与AOF的对比

  1. 在发生意外宕机的情况下,RDB丢失的数据多于AOF,AOF在默认的每秒同步情况下,最多丢失1秒的数据
  2. AOF的文件存储可阅读的字符串,RDB产生不可阅读的二进制文件。故 RDB 恢复速度快,存储效率高。
  3. *.rdb 文件和 *.aof 文件同时存在时,采用 *.aof 文件中的记录

事务

使用 multi 开启事务,使用 exec 提交事务,使用 discard 丢弃事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name snail
QUEUED
127.0.0.1:6379> get nane
QUEUED
127.0.0.1:6379> set name snail111
QUEUED
127.0.0.1:6379> exec
1) OK
2) (nil)
3) OK
  1. 开启 multi 后面的指令,都会被添加到一个队列中,在遇到 exec 时,将期间的操作一起提交执行
  2. 在开启 multi 后,如果遇到语法错误的指令,那么事务将被自动丢弃;如果是语法正确的指令在执行过程中出现问题,那么事务会被成功执行,错误指令返回对应的提示。

SortedSet 中的跳跃表

什么是跳跃表

   跳跃表是一种类似链表的有序的数据结构,目的是为了降低单向链表查找的时间复杂度,类似于红黑树,但跳跃表的范围查找效率较高。

跳跃表的实现

链表内每一个结点包含多个指向后续元素的指针,是通过随机得到的。每次查找从顶层开始向下对比查找,便可以跳过一些元素

今天的文章Redis进阶:数据结构、持久化、跳跃表、事务分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/34030.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注