基本数据类型,底层实现
string:int、raw或者embstr
hash:ziplist,hashtable dict字典实现
set:intset 整数集合 O(logn),hashtable
zset,ziplist,skiplist跳表+字典 head tail 头尾节点 节点数量 最大层数,类似平衡树
list:ziplist 压缩链表,连续内存存储,存储不大的对象,O(n)
linkedlist:双向链表 pre next指针,每增加一个节点,就分配一次内存
Redis快的原因
-
基于内存操作
-
数据结构简单,优化过的数据结构
-
单线程,无上下文切换和竞争
-
多路复用IO
过期策略
定期删除:redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
惰性删除:获取key的同时redis检查key是否过期,过期就删除,不返回内容
内存淘汰
-
noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
-
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
-
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
-
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
-
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
-
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
基于HashMap 双向链表,key存储,value指向LRU Node节点
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
/*** 传递进来最多能缓存多少数据
** @param cacheSize 缓存大小
*/public LRUCache(int cacheSize) {
// true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
return size() > CACHE_SIZE;
}
}
Redis cluster
-
自动将数据进行分片,每个 master 上放一部分数据
-
提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的
节点通信原理(gossip)
所有节点持有一份元数据(节点信息等),不同的节点出现元数据的变更,不断将元数据发送给其他节点,让其他节点也进行元数据变更。
meet :某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信
ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。
pong:返回 ping 和 meet,包含自己的状态和其它信息,也用于信息广播和更新。
fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
hash slot
redis cluster 有固定的 16384个 hash slot,对每个 key计算 CRC16值,然后对 16384 取模,可以获取 key 对应的 hash slot。
redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 hash tag来实现。
任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。
主备切换
判读节点宕机:在cluster-node-timeout时间范围内,如果某个节点没有返回pong,就默认为pfail,之后在ping消息中发送给其他节点,如果超过半数的节点都认为pfail,就变成fail宕机
节点过滤:对于宕机的master node,从slave node中选择一个切换为master
节点选举:和master断开连接的时长、本身节点数据量多少,超过半数投票则选举成功
Redis高可用
redis主从复制原理
(无法故障自动恢复)
异步复制:slave启动,发送pysnc命令到master,如果第一次连接master,触发一次全量复制,生成RDB快照,发送给slave,同时内存里的写命令也会同步给slave,slave收到文件,先写入磁盘,再从磁盘加载到内存
复制过程网络中断:支持断点续传,从上次复制的地方继续复制,master保存backlog
哨兵集群实现高可用
(无法负载均衡)
集群监控:负责监控master slave是否正常运行
消息通知:redis实例故障,发送消息警报
故障转移:master挂掉,自动转移到slave
配置中心:如果故障转移发生,通知client新的master地址
集群节点必须>2,如果只有两个,quorum=1
选举算法
-
跟 master 断开连接的时长
-
slave 优先级
-
复制 offset,哪个slave复制数据越多、offset越靠后,优先级越高
-
run id,选择run id较小的
主备切换数据丢失问题
异步复制:master挂了,数据没有复制到slave导致丢失
脑裂:(master脱离了正常网络,但是实际还在运行,导致从新选举了master,出现2个master),client没有来得及切换到新的master继续向旧master写数据,旧master恢复网络变成slave,清空自己数据,这部分数据未同步导致丢失
解决方案:min-slaves-to-write 1,min-slaves-max-lag 10。至少有一个slave,数据复制的延迟不能超过10秒,如果脑裂,最多丢失10秒数据。只能减少数据丢失,不能完全避免
持久化机制
RDB就是快照,AOF就是binlog。
RDB:对redis数据进行周期性的持久化,将当前内存中的数据快照保存到磁盘,rdbSave 生成RDB文件到磁盘,rdbLoad RDB文件重新载入内存
优点:对redis影响小,fork出字进程执行磁盘IO读写,相比AOF,重启恢复更快
缺点:快照文件生成间隔时间长(5分钟),生成文件时如果文件过大,可能导致服务暂停
AOF:每条写入命令,append模式写入日志文件
优点:间隔时间短(一般1秒),最多丢失1秒数据,append写入,性能高,可以通过可读的方式记录,适合做灾难时误删除紧急恢复
缺点:对于同一份数据来说,AOF比RDB更大,开启后,写QPS比RDB低,因为一般配置成1秒一次异步同步
分布式锁
利用底层setnx expire,设置锁的过期时间,防止客户端崩溃导致死锁
zookeeper:创建临时节点,找到最小的序列节点,创建成功则获得锁,创建释放需要创建和删除节点比较耗费资源
热key
所谓热key问题就是,突然有几十万的请求去访问redis上的某个特定key。那么,这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机,缓存击穿,可能导致DB崩溃,雪崩
解决方案:
-
二级缓存,提前加载热key数据到内存中,直接走内存查询,设置过期时间
-
热key打散到各个redis服务器
-
高可用,支持redis服务器扩展
缓存击穿
单个key并发访问过高,过期时导致所有请求直接到db上
- 加锁更新,double check
保证只有单个线程真正请求到db,拿到数据后进行缓存
后续并发线程会拿到缓存中的值
- 永不过期
将过期时间,组合在value中
通过异步方式更新刷新过期时间
缓存穿透
查询不存在缓存和DB中的数据,每次请求都会打到DB
- 布隆过滤器 (误判,集合小,数据量大,所有点都变成1就会发生误判)。布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了,如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。可能的实现方式是将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
- 缓存空值,直接返回null
缓存雪崩
当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上
-
集群保证高可用
-
限流、二级缓存
-
过期时间随机
一致性hash
-
整个hash值空间组成虚拟的圆环,将master节点的ip或者主机名hash取模确定节点的位置。
-
节点太少容易导致缓存热点问题,容易导致缓存击穿
-
虚拟节点,数据均匀分布