存储结构
string
list
dict
set
zset zset:有序集合,不能有重复元素,有序集合中的每个元素都需要指定一个分数,根据分数对元素进行升序排序。可以做排行榜
跳表
ziplist
三种特殊数据类型
- 1.geospatial: Redis 在 3.2 推出 Geo 类型,该功能可以推算出地理位置信息,两地之间的距离。
- 2.hyperloglog:基数:数学上集合的元素个数,是不能重复的。这个数据结构常用于统计网站的 UV。
- 3.bitmap: bitmap 就是通过最小的单位 bit 来进行0或者1的设置,表示某个元素对应的值或者状态。一个 bit 的值,或者是0,或者是1;也就是说一个 bit 能存储的最多信息是2。bitmap 常用于统计用户信息比如活跃粉丝和不活跃粉丝、登录和未登录、是否打卡等。
内部原理
官方数据 redis 可以做到每秒近10w的并发,这么快的原因主要总结为以下几点:
- 1:完全基于内存操作
- 2:使用单线程模型来处理客户端的请求,避免了上下文的切换
- 3:IO 多路复用机制
- 4:自身使用 C 语言编写,有很多优化机制,比如动态字符串 sds
redis 6.0之后又使用了多线程,但redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程,所以是不会有线程安全的问题。之所以加入了多线程因为 redis 的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能
redis客户端
jedis
Jedis 的连接实例是线程不安全的,于是需要维护一个连接池,每个线程需要时从连接池取出连接实例,完成操作后或者遇到异常归还实例。当连接数随着业务不断上升时,对物理连接的消耗也会成为性能和稳定性的潜在风险点。
Lettuce 使用 Netty 作为通信层组件,其连接实例是线程安全的,并且在条件具备时可访问操作系统原生调用 epoll, kqueue 等获得性能提升。
lettuce
- 高级客户端
- 线程安全
- 支持同步、异步和反应式 API
- 支持集群、哨兵、管道和编解码 引用
jetcache缓存框架
github.com/alibaba/jet… 需要集群、读写分离、异步等特性支持请使用lettuce客户端 不需要 则使用jedis
1.自动load 在缓存未命中的情况下,会调用loader,如果loader抛出异常,get和getAll会抛出CacheInvokeException。 2.预防缓存雪崩自动刷新缓存 RefreshCache基于decorator模式提供了自动刷新的缓存的能力,目的是为了防止缓存失效时造成的雪崩效应打爆数据库。同时设置了loader和refreshPolicy的时候,CacheBuilder的buildCache方法返回的Cache实例经过了RefreshCache的包装。 3.异步API
caffeine cache是guava cache的后续作品 可了解下 监控统计 github.com/alibaba/jet…
分布式锁
Redisson是用于在 Java 程序中操作 Redis 的库,可用于分布式锁 Redis 实现分布式锁主要步骤 指定一个 key 作为锁标记,存入 Redis 中,指定一个 唯一的用户标识 作为 value。 当 key 不存在时才能设置值,确保同一时间只有一个客户端进程获得锁,满足 互斥性 特性。 设置一个过期时间,防止因系统异常导致没能删除这个 key,满足 防死锁 特性。 当处理完业务之后需要清除这个 key 来释放锁,清除 key 时需要校验 value 值,需要满足 只有加锁的人才能释放锁 。
借助 Redisson 的 WatchDog机制能够很好的解决锁续期的问题 127.0.0.1:6379> HGETALL myLock
- "285475da-9152-4c83-822a-67ee2f116a79:52"
- "1" hash 结构的 key 是锁的名称,field 是客户端 ID,value 是该客户端加锁的次数
加锁机制、锁互斥机制、Watch dog 机制、可重入加锁机制、锁释放机制、等五个方面对 Redisson 实现分布式锁的底层原理进行分析。
1.加锁其实是通过一段 lua 脚本实现的
2.锁互斥 如果客户端 2 来尝试加锁,首先,第一个if判断会执行 exists myLock,发现 myLock 这个锁 key 已经存在了。接着第二个 if 判断,判断myLock 锁 key 的 hash 数据结构中,是否包含客户端 2 的 ID,这里明显不是,因为那里包含的是客户端 1 的 ID。所以,客户端 2 会执行: return redis.call('pttl', KEYS[1]); 返回的一个数字,这个数字代表了 myLock 这个锁 key 的剩余生存时间。最终通过subscribeFuture订阅锁释放事件
3.锁续期机制 watch dog 客户端 1 加锁的锁 key 默认生存时间才 30 秒,如果超过了 30 秒,客户端 1 还想一直持有这把锁 Watch Dog 机制其实就是一个后台定时任务线程,获取锁成功之后,会将持有锁的线程放入到一个 RedissonLock.EXPIRATION_RENEWAL_MAP里面,然后每隔 10 秒
4.可重入加锁机制 加锁那段 lua 代码
- 5.释放锁机制 1.删除锁(这里注意可重入锁,在上面的脚本中有详细分析)。 2.广播释放锁的消息,通知阻塞等待的进程(向通道名为 redisson_lock__channel publish 一条 UNLOCK_MESSAGE 信息)。 3.取消 Watch Dog 机制,即将 RedissonLock.EXPIRATION_RENEWAL_MAP 里面的线程 id 删除,并且 cancel 掉 Netty 的那个定时任务线程。 注意 释放锁保证原子性操作
zhuanlan.zhihu.com/p/135864820
过期淘汰策略
- noeviction:不淘汰任何数据,当内存不足时,新增操作会报错,Redis 默认内存淘汰策略;
- allkeys-lru:淘汰整个键值中最久未使用的键值;
- allkeys-random:随机淘汰任意键值;
- volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值;
- volatile-random:随机淘汰设置了过期时间的任意键值;
- volatile-ttl:优先淘汰更早过期的键值。
在 Redis 4.0 版本中又新增了 2 种淘汰策略:
- volatile-lfu:淘汰所有设置了过期时间的键值中,最少使用的键值;
- allkeys-lfu:淘汰整个键值中最少使用的键值。
Redis 过期键值删除使用的是贪心策略,它每秒会进行 10 次过期扫描,此配置可在 redis.conf 进行配置,默认值是 hz 10,Redis 会随机抽取 20 个值,删除这 20 个键中过期的键,如果过期 key 的比例超过 25% ,重复执行此流程
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
- 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
热key
- 可以将结果缓存到本地内存中
- 将热 key 分散到不同的服务器中
- 设置永不过期
LRU/LFU
缓存击穿、缓存穿透、缓存雪崩
- 缓存穿透是指用户请求的数据在缓存中不存在并且在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍,然后返回空。解决方案:布隆过滤器 ; 返回空对象
- 缓存击穿,是指一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。解决方案:互斥锁; 永不过期 如果热key QPS>10w超过单机,可使用服务本地缓存,负载均衡到不同的机器上
- 缓存雪崩是指缓存中不同的数据大批量到过期时间,而查询数据量巨大,请求直接落到数据库上导致宕机。解决方案:均匀过期;加互斥锁;缓存永不过期;双层缓存策略
RDB&AOF持久化
在 Redis 4.0 之后,Redis 有 3 种持久化的方式:
- RDB(Redis DataBase,快照方式,是redis默认的持久化方式)将某一个时刻的内存数据,以二进制的方式写入磁盘;采用 fork 子进程的方式来写时同步的
- RDB的优点:
-
- 1.它是将某一时间点redis内的所有数据保存下来,所以当我们做「大型的数据恢复时,RDB的恢复速度会很快」
- 2.由于RDB的FROK子进程这种机制,队友给客户端提供读写服务的影响会非常小
- RDB的缺点:
-
- 举个例子假设我们定时5分钟备份一次,在10:00的时候 redis 备份了数据,但是如果在10:04的时候服务挂了,那么我们就会丢失在10:00到10:04的整个数据
- 1:「有可能会产生长时间的数据丢失」
- 2:可能会有长时间停顿:我们前面讲了,fork 子进程这个过程是和 redis 的数据量有很大关系的,如果「数据量很大,那么很有可能会使redis暂停几秒」
- AOF(Append Only File,文件追加方式),记录所有的操作命令,并以文本的形式追加到文件中;
- AOF 的「优点」:
-
- 1.AOF可以「更好的保护数据不丢失」,一般AOF会以每隔1秒,通过后台的一个线程去执行一次fsync操作,如果redis进程挂掉,最多丢失1秒的数据
- 2.AOF是将命令直接追加在文件末尾的,「写入性能非常高」
- 3.AOF日志文件的命令通过非常可读的方式进行记录,这个非常「适合做灾难性的误删除紧急恢复」,如果某人不小心用 flushall 命令清空了所有数据,只要这个时候还没有执行 rewrite,那么就可以将日志文件中的 flushall 删除,进行恢复
- AOF 的「缺点」:
-
- 1.对于同一份数据源来说,一般情况下AOF 文件比 RDB 数据快照要大
- 2.由于 .aof 的每次命令都会写入,那么相对于 RDB 来说「需要消耗的性能也就更多」,当然也会有 aof 重写将 aof 文件优化。
- 3.「数据恢复比较慢」,不适合做冷备。
- 混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能减低数据丢失的风险
RDB 和 AOF 持久化各有利弊,RDB 可能会导致一定时间内的数据丢失,而 AOF 由于文件较大则会影响 Redis 的启动速度,为了能同时拥有 RDB 和 AOF 的优点,Redis 4.0 之后新增了混合持久化的方式,因此我们在必须要进行持久化操作时,应该选择混合持久化的方式。
部署方式
-
单机模式:这也是最基本的部署方式,只需要一台机器,负责读写
-
哨兵模式:哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。它具备自动故障转移、集群监控、消息通知等功能。
-
cluster集群模式:在redis3.0版本中支持了cluster集群部署的方式,这种集群部署的方式能自动将数据进行分片,每个master上放一部分数据,提供了内置的高可用服务,即使某个master挂了,服务还可以正常地提供。
主从复制
在主从复制这种集群部署模式中,我们会将数据库分为两类,第一种称为主数据库(master),另一种称为从数据库(slave)。主数据库会负责我们整个系统中的读写操作,从数据库会负责我们整个数据库中的读操作。我们会让主数据库只负责写操作,让从数据库只负责读操作,就是为了读写分离,减轻服务器的压力。
- 1.当一个从数据库启动时,它会向**主数据库发送一个SYNC命令**,master收到后,在后台保存快照,也就是我们说的RDB持久化,当然保存快照是需要消耗时间的,并且redis是单线程的,在保存快照期间redis受到的命令会缓存起来
- 2.快照完成后会**将缓存的命令以及快照一起打包发给slave节点**,从而保证主从数据库的一致性。
- 3.从数据库接受到快照以及缓存的命令后会将这部分数据**写入到硬盘上的临时文件当中**,写入完成后会用这份文件去替换掉RDB快照文件,当然,这个操作是不会阻塞的,可以继续接收命令执行,具体原因其实就是fork了一个子进程,用子进程去完成了这些功能。
主从复制的方式
持续复制. 全量复制 部分复制
无硬盘复制
1.master禁用了RDB快照时,发生了主从同步(复制初始化)操作,也会生成RDB快照,但是之后如果master发成了重启,就会用RDB快照去恢复数据,这份数据可能已经很久了,中间就会丢失数据
2.在这种一主多从的结构中,master每次和slave同步数据都要进行一次快照,从而在硬盘中生成RDB文件,会影响性能
为了解决这种问题,redis在后续的更新中也加入了无硬盘复制功能,也就是说**直接通过网络发送给slave**,避免了和硬盘交互,但是也是有io消耗
性能问题排查调优
存在集中过期 key 的逻辑存在,但这种逻辑又是业务所必须的,那此时如何优化,同时又不对 Redis 有性能影响呢? 一般有两种方案来规避这个问题:
集中过期 key 增加一个随机过期时间,把集中过期的时间打散,降低 Redis 清理过期 key 的压力
使用的 Redis 是 4.0 以上版本,可以开启 lazy-free 机制,当删除过期 key 时,把释放内存的操作放到后台线程中执行,避免阻塞主线程
REDIS 5.0
全新数据类型streams lolwut lua改进 动态hz support docker