Redis数据类型和应用场景
- String
- 内部实现
- 优势:
1. 获取字符串长度的复杂度为O(1),结构内len属性记录字符串长度
2. 既可以保存文本数据,又可以保存二进制数据;
3. 拼接字符串不会造成缓冲区溢出,先检查空间是否满足要求
4.
- 编码方式为 embstr,int,raw
-
使用场景
- 缓存对象:对象的JSON
- 计数器:文章阅读量 转发次数 库存
- 共享Session信息
- 分布式锁
SETNX key value//不存在则set成功 否则失败 SETEX key seconds value
@Resource
RedisTemplate<String, String> redisTemplate;
public void updateUserWithRedisLock(SysUser sysUser) throws InterruptedException {
// 占分布式锁,去redis占坑
Boolean lock = redisTemplate.opsForValue()
.setIfAbsent("SysUserLock" + sysUser.getId(),
"value");
if(lock) {
//加锁成功... 执行业务
redisTemplate.delete("SysUserLock" + sysUser.getId()); //删除key,释放锁
} else {
Thread.sleep(100); // 加锁失败,重试
updateUserWithRedisLock(sysUser);
}
}
问题:
- 异常导致锁没有释放: 为redis的key设置过期时间
- 锁过期之后被别的线程重新获取与释放: 怎么判断这把锁是不是自己的?加锁时为value赋随机值,加锁的随机值等于解锁时的获取到的值,才能证明这把锁是你的。
- Hash
- 内部实现
压缩列表
哈希表 - 应用场景
- List
-
内部实现
- 双向链表
list封装了双向链表
头结构:typedef struct list { //链表头节点 listNode *head; //链表尾节点 listNode *tail; //节点值复制函数 void *(*dup)(void *ptr); //节点值释放函数 void (*free)(void *ptr); //节点值比较函数 int (*match)(void *ptr, void *key); //链表节点数量 unsigned long len; } list;
- 压缩链表
当个数小于512个,每个元素的值小于64字节
- 节约内存 由连续内存块组成的顺序数据结构
- 数据结构:zlbytes占用字节数 zltail尾部节点距起始地址的字节 zlen节点数 zlend结束符
- 每个节点数据结构:prevlen encoding data 根据不同类型和大小分配空间
- 导致连锁更新问题 更新某个节点超过分配内存则会导致内存重新分配 影响性能
- 双向链表
-
应用场景
消息队列
- Set
哈希表或整数集合
-
哈希表是一个数组(dictEntry **table),数组的每个元素是一个指向「哈希表节点(dictEntry)」的指针。
-
整数集合
一块连续内存空间, contents 数组 -
应用场景
聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
- Zset
压缩列表或跳表
- 跳表的优势是能支持平均 O(logN) 复杂度的节点查找。
Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
- bitmap
用1bit来表示value
线程模型
Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」
「关闭文件、AOF 刷盘、释放内存」是后台线程
Redis快的原因:
- 在内存中完成,CPU不是瓶颈
- 单线程模型没有多线程竞争 以及线程切换的开销
- IO多路复用一个线程处理多个请求
内存淘汰策略
- 删除过期key 定期删除+惰性删除
- 内存淘汰机制
- 在设置了过期时间的数据中进行淘汰:随机淘汰;优先淘汰最早过期的;优先淘汰最近最久未使用的;优先淘汰使用次数最少的
- 所有数据淘汰:随机淘汰任意键值;
淘汰整个键值中最久未使用的键值;淘汰整个键值中最少使用的键值。
Redis持久化方式
- AOF日志
每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里; - RDB快照
将某一时刻的内存数据,以二进制的方式写入磁盘;
用fork()创建子线程来进行快照,此时子进程和父进程是共享同一片内存数据的写时复制,主进程写时会复制一份副本数据,子进程会把该副本数据写入 RDB 文件 - 混合持久化
- RDB 优点是数据恢复速度快,但是快照的频率不好把握。
- AOF 优点是丢失数据少,但是数据恢复不快。RDB+AOF增量部分
缓存雪崩 穿透 击穿
-
如何避免缓存雪崩?
- 将缓存失效时间随机打散:
- 设置缓存不过期:
-
如何避免缓存击穿?
- 互斥锁方案
- 不给热点数据设置过期时间
-
如何避免缓存穿透?
- 非法请求的限制:
- 设置空值或者默认值:
- 使用布隆过滤器快速判断数据是否存在在数据库中。避免查询数据库
数据库和缓存一致性
-
缓存更新策略
- Cache Aside(旁路缓存)策略
不能先删除缓存再更新数据库,否则会有数据库缓存不一致 - Read/Write Through(读穿 / 写穿)策略
应用程序只和缓存交互,不再和数据库交互,而是由缓存和数据库交互,相当于更新数据库的操作由缓存自己代理了。 - Write Back(写回)策略
- Cache Aside(旁路缓存)策略
-
保证缓存和数据库数据的一致性
- 先更新数据库,再删除缓存
因为缓存的写入通常要远远快于数据库的写入
「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。给缓存数据加上了「过期时间」
删除失败解决方法:
- 重试机制。
- 订阅 MySQL binlog,再操作缓存。
- 先删除缓存,再更新数据库」:「延迟双删」
- 「更新数据库 + 更新缓存」:在更新缓存前先加个分布式锁
- 先更新数据库,再删除缓存
Redis高可用 集群方案
-
主从复制
一主多从 读写分离 -
哨兵模式
- 提供主从节点故障转移的功能。
- 监控、选主、通知。
- ping主节点 如果客观下线则从哨兵中选举新的主节点
- 主从故障转移
-
切片集群