一.为什么要用redis缓存
1.它把数据存到内存而不是硬盘中,操作缓存就是操作内存,具有高性能。
2.直接操作缓存能够承受的请求是远远大于数据库的,具有高并发
二.为什么要用 redis 而不用 map/guava 做缓存
缓存分为分布式缓存和本地缓存。
map是本地缓存,具有轻量快捷的特点,生命周期随着JVM的销毁而结束,并且在多实例的情况下,每个实例都有一份自己的缓存,缓存的数据不一致。
redis是分布式缓存,在多实例情况下,各个实例用同一份缓存,保证了缓存的一致性。
三.redis 的线程模型
redis内部使用的文件事件处理器是单线程的,所以redis是单线程的。
文件事件处理器包含四个部分:sorket,IO多路复用程序,文件事件分派器,事件处理器
多个sorket可能会并发产生不同的操作,每个操作对应不同的文件事件,ID多路复用同时监听着多个sorket,将sorket产生的文件事件放入队列中,文件事件分派器从队列中取得一个事件交给对应的事件处理器处理
四.redis 和 memcached 的区别
1.redis支持数据持久化,支持将数据保存到磁盘中,重启的时候可以进行再次加载,但memcached将数据全部缓存在内存中
2.redis有多种数据类型,memcached只支持string
3.redis支持事物
4.redis是IO多路复用模型,memcached是非堵塞IO复用模型
五.redis 设置过期时间
定期删除+惰性删除
定期删除:redis默认100ms随机抽取设置了过期时间的key,检查其是否过期,过期就删除
惰性删除:当查询到的key已经过期,就删除
六.redis内存淘汰机制
最近最少使用,随机,最不经常使用*2 即将过期,写入报错
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
4.0版本后增加以下两种:
volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
七.redis 持久化机制
RDB快照 ,AOF只追加文件
混合持久化:AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头,优点:快速加载同时避免丢失过多数据
八.redis 事务
watch key1 key2 ... : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi : 标记一个事务块的开始( queued )
exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard : 取消事务,放弃事务块中的所有命令
unwatch : 取消watch对所有key的监控
事务总是具有原子性、一致性和隔离性,不能回滚
九.缓存雪崩和缓存穿透问题解决方案
缓存雪崩:缓存同一时间大量失效,造成大量请求落到数据库上,造成数据库崩溃
解决方法:
事前:尽量保证redis的高可用性,选择合适的内存淘汰机制
事中:加上本地缓存+限流,降低mysql压力
事后:用redis持久化机制,尽快恢复数据
缓存穿透:大量请求不存在的数据,导致请求落到数据库上
解决方法:
1.使用布隆过滤器,将所有有可能存在的数据哈希到一个足够大的bitmap中,一定不存在的数据会被这个map拦截掉
2.如果查询返回的数据是空,仍把空结果进行缓存,但过期时间设置的很短
十.单线程的redis为什么这么快
1.纯内存操作
2.单线程,避免了不必要的上下文切换和竞争条件
3.非堵塞IO多路复用
十一.Redis 架构模式
1.单机版
缺点:内存能力有限
处理能力有限
无法高可用
2.主从复制
一类是主数据库,一类是从数据库,主数据库进行读写操作,当进行写数据操作,数据同步到从数据库,从数据库只能读数据和接收主数据库同步过来的数据。
一个主数据库可以有多个从数据库,一个从数据库只能有一个主数据库
优点:降低了主数据库的读压力
缺点:无法保证高可用,没有降低主数据库的写压力
3.哨兵
哨兵是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移
优点:保证高可用
监控各个结点
自动进行故障转移
缺点:没有降低主服务器的写压力
主从复用,切换需要时间
丢数据
4.集群(proxy型)
优点:支持失败结点自动删除
后端分片逻辑对业务透明,业务方的读写方式跟操作单个redis一致
缺点:需要维护proxy
不支持自动故障转移
可扩展性差,进行扩缩容需要手动干预
5.集群(直连型)Redis-Cluster
Redis-Cluster采用无中心结构,每个节点保存数据且与其他节点相连。
优点:无中心架构,不存在哪个节点影响性能瓶颈,没有proxy层
数据存储分布在多个节点,节点间数据共享,可动态调整数据分布
具有可扩展性,节点可动态增加或删除
具有高可用性,部分节点不可用时,集群仍可用
也有主从结构,若主数据库failover,则用投票机制选择从数据库变为主数据库
缺点:资源隔离性较差,容易出现相互影响的情况
数据通过异步复制,不保证数据的强一致性
十二.什么是一致性哈希算法?什么是哈希槽?
一致性hash https://www.cnblogs.com/lpfuture/p/5796398.html
哈希槽 https://blog.csdn.net/z15732621582/article/details/79121213
十三.redis分布式锁
分布式锁可靠性的四个条件:
1.加索和解锁的是同一人
2.不能造成死锁
3.具有容错性,只要大部分redis结点正常运行,客户就可以进行加锁和解锁
4.互斥性。在任意时刻,只能有一人持有锁
Jedis.set()函数
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
用lua脚本
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}