Redis

    xiaoxiao2025-08-01  17

    目录

    Redis数据结构ZSet底层存储结构Redis的五种数据结构的内部编码 Redis的bitmap结构Redis 操作 BitmapBitmap实现原理 Redis过期策略与淘汰机制

      Redis是一个key-value存储的内存缓存操作,其数据库完全在内存中,使用磁盘仅用于持久性。Redis可以将数据复制到任意数量的从服务器。Redis的优势: 1、速度快 2、支持丰富的数据类型,支持String,List,set,sorted set,hash 3、支持事务,操作都是原子性的,保证了如果两个客户端同时访问的Redis服务器将获得更新后的值 4、Redis是一个多实用的工具,可用于缓存,消息,按key设置过期时间,过期后将会自动删除

    Redis的适用场景:

    缓存   合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。排行榜   很多网站都有排行榜应用的,如月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。计数器   什么是计数器?如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的原子性的自增操作incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。INCR key:将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。分布式会话   集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。分布式锁   利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败。社交网络   点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。最新动态   按照时间顺序排列的最新动态,也是一个很好的应用,可以使用 Sorted Set 类型的分数权重存储 Unix 时间戳进行排序。消息队列   Redis 能作为一个很好的消息队列来使用,除了Redis自身的发布/订阅模式,依赖 List 类型利用 LPUSH 命令将数据添加到链表头部,通过 BRPOP 命令将元素从链表尾部取出。

    Redis数据结构

      Redis支持五种数据类型:String、Hash、List,Set、及ZSet(sorted set:有序集合)。

    Redis 的String 可以包含任何数据,如数字,字符串,jpg图片或者序列化的对象。使用场景:   缓存功能:字符串最经典的使用场景,Redis作为缓存层,MySQL作为储存层,绝大部分请求数据都是Redis中获取,由于Redis具有支撑高并发特性,所以缓存通常能起到加速读写和降低 后端压力的作用   计数器:可以实现快速计数、查询缓存的功能   共享session:出于负载均衡的考虑,分布式服务会将用户信息的访问均衡到不同服务器上,用户刷新一次访问可能会需要重新登录,为避免这个问题可以用Redis将用户session集中管理,在这种模式下只要保证Redis的高可用和扩展性的,每次获取用户更新或查询登录信息都直接从Redis中集中获取Redis Hash 是一个键值(key->value)对集合;是一个 String 类型的 field 和 value 的映射表。使用场景:哈希结构相对于字符串序列化缓存信息更加直观,并且在更新操作上更加便捷,常常用于用户信息等管理List 就是链表(Redis 使用双端链表实现的 List),是有序的,value可以重复,可以通过下标取出对应的value值,左右两边都能进行插入和删除数据。使用场景:消息队列,Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端是用lupsh从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性Redis的Set是String类型的无序集合。和链表一样,在执行插入和删除和判断是否存在某元素时。集合最大的优势在于可以进行交集并集差集操作。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。使用场景:标签(tag),如一个用户对娱乐、体育比较感兴趣,另一个可能对新闻感兴 趣,这些兴趣就是标签,有了这些数据就可以得到同一标签的人,以及用户的共同爱好的标签Redis ZSet和Set一样也是String类型元素的集合且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。Redis正是通过分数来为集合中的成员进行从小到大的排序。ZSet的成员是唯一的,但分数(score)却可以重复。ZSet是插入有序的,即自动排序。使用场景:排行榜。例如视频网站需要对用户上传的视频做排行榜,可以按照时间、按照播放量、按照获得的赞数等进行榜单维护。

    ZSet底层存储结构

      ZSet底层的存储结构包括ziplist或skiplist,在同时满足以下两个条件的时候使用ziplist,其他时候使用skiplist,两个条件如下:

    有序集合保存的元素数量小于128个有序集合保存的所有元素的长度小于64字节

      当ziplist作为ZSet的底层存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。   当skiplist作为ZSet的底层存储结构的时候,使用skiplist按序保存元素及分值,使用dict来保存元素和分值的映射关系。这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。   zskiplist作为skiplist的数据结构,包括指向头尾的header和tail指针,其中level保存的是skiplist的最大的层数。zskiplistNode的数据格式,每个节点有保存数据的robj指针,分值score字段,后退指针backward便于回溯,zskiplistLevel的数组保存跳跃列表每层的指针。 zset存储过程   zset的添加过程以zadd的操作进行分析,整个过程如下:

    解析参数得到每个元素及其对应的分值查找key对应的zset是否存在,不存在则创建如果存储格式是ziplist,那么在执行添加的过程中需要区分元素存在和不存在两种情况,存在情况下先删除后添加;不存在情况下则添加并且需要考虑元素的长度是否超出限制或实际已有的元素个数是否超过最大限制进而决定是否转为skiplist对象如果存储格式是skiplist,那么在执行添加的过程中需要区分元素存在和不存在两种情况,存在的情况下先删除后添加,不存在情况下那么就直接添加,在skiplist当中添加完同时需要更新dict的对象

    Redis的五种数据结构的内部编码

    1.字符串的内部编码 字符串类型的内部编码有3种: int:8个字节的长整型。 embstr:小于等于39个字节的字符串。 raw:大于39个字节的字符串。 Redis会根据当前值的类型和长度决定使用内部编码实现。

    2.哈希的内部编码 哈希类型的内部编码有两种: ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64个字节)时,Redis会使用ziplist作为哈希的内部实现。ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。

    hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现。因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

    3.列表的内部编码 列表类型的内部编码有两种: ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)。同时所有值都小于hash-max-ziplist-value配置(默认64个字节)时,Redis会使用ziplist作为哈希的内部实现。

    linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。

    4.集合的内部编码 集合类型的内部编码有两种: intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合内部实现,从而减少内存的使用。

    hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

    5.有序集合的内部编码 有序集合类型的内部编码有两种 ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个)。同时每个元素的值小于zset-max-ziplist-value配置(默认64个字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存使用。

    skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时zip的读写效率会下降。

    Redis的bitmap结构

      在开发中,可能会遇到这种情况:需要统计用户的某些信息,如果使用普通的 key/value存储,用户量很大,需要的空间也会很大,所以 Redis提供了Bitmap位图,Bitmap是通过操作二进制位来进行记录,节约内存。

    优点: 节省空间: 通过一个bit位来表示某个元素对应的值或者状态,其中key就是对应元素的值。实际上8个bit可以组成一个Byte,所以是极其节省空间的。 效率高: setbit和getbit的时间复杂度都是O(1),其他位运算效率也高。

    Redis 操作 Bitmap

    setbit 设置操作 set key offset value : 设置 key 的第 offset 位为value getbit 获取操作 get key offset 获取某个位上的值 bitcount 统计操作 bitcount key [start, end] 统计指定位值范围内1的个数 bitpos用来查找指定范围内出现的第一个0或者1

    Bitmap实现原理

    Java 是如何表示用 Bitmap 来表示上述的大数组的?首先定义一个 byte 数组,根据初始容量来分配数组的大小 capacity = ( size >> 3 ) + 1:

    public byte[] bitemapArray(int size){ byte[] arr = new byte[(size >> 3) + 1]; return arr; } byte数组的容量为什么是(size>>3)+1?还是因为1个字节=8bit 比如传入的大小size=7,则capacity=(7>>3)+1=1

    之后,向数组中添加值,即把对应的位设置为 1,步骤如下:

    首先找到 byte 中的索引 index,计算公式 index = n / 8 = n >> 3找到 byte[index] 中对应的位置 position ,计算公式 position = n % 8 = n & 0x07 (因为下标从 0 开始)将 byte[inex] 和 1 << positon 做或(|)运算

    Redis过期策略与淘汰机制

    Redis过期策略是:定期删除+惰性删除   定期删除指Redis默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。但是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,所以有了惰性删除,即当获取某个 key 的时候,Redis首先检查取出的键是否过期,若过期删除该键,否则,返回该键。   但是如果定期删除漏掉了很多过期 key,然后也没及时去查,也就没走惰性删除,如果大量过期 key 堆积在内存里,导致 Redis内存块耗尽了,这时候进行内存淘汰机制。

    内存淘汰机制 Redis中的默认的过期策略是noeviction 。

    maxmemory-policy 六种方式 volatile-lru:在设置了过期时间的键空间中,移除最近最少使用的 key allkeys-lru : 在键空间中,移除最近最少使用的 key volatile-random:在设置了过期时间的键空间中,随机移除某个 key。 allkeys-random:直接在键空间中随机移除一个键 volatile-ttl : 在设置了过期时间的键空间中,有更早过期时间的 key 优先移除 noeviction :不做过期处理,只返回一个写操作错误
    最新回复(0)