redis哨兵
哨兵模式是⼀种⾃动选择⽼⼤的模式,即在⽼⼤宕机之后,哨兵模式会根据哨兵们的内部投票,⾃动的重新选出⼀个新的⽼⼤。哨兵模式是⼀种特殊的模式,⾸先Redis提供了哨兵的命令,哨兵是⼀个独⽴的进程,作为进程,它会独⽴运⾏。其原理是哨兵通过发送命令,等待Redis服务器响应,如果Redis服务器⼀直没有响应,说明这个Redis服务器可能已经宕机了,从⽽监控运⾏的多个Redis实例
哨兵作⽤:
- 通过发送命令,让Redis服务器返回监控其运⾏状态,包括主服务器和从服务器
- 当哨兵监测到master宕机,会⾃动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置⽂件,让它们切换主机
然⽽⼀个哨兵进程对Redis服务器进⾏监控,可能会出现问题。为此,我们可以使⽤多个哨兵进⾏监控。各个哨兵之间还会进⾏监控,这样就形成了多哨兵模式
哨兵模式实际上是一个集群
⽤⽂字描述⼀下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会⻢上进⾏failover过程,仅仅是哨兵1主观的认为主服务器不可⽤,这个现象成为主观下线
当后⾯的哨兵也检测到主服务器不可⽤,并且数量达到⼀定值时,那么哨兵之间就会进⾏⼀次投票,投票的结果由⼀个哨兵发起,进⾏failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把⾃⼰监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端⽽⾔,⼀切都是透明的
优点
- 能自动切换主服务器
- 系统健壮性更高
缺点
- 部署+运维复杂
- 依旧还是master单点模式
cluster
无中心的架构,多主多从
优点
- 没有master单点问题
- 动态添加、删除node
- 高性能
- 高可用
- 自动切换主服务器
redis分布式锁
分布式锁⼀般有三种实现⽅式:
1 . 数据库乐观锁
2. 基于Redis的分布式锁
3. 基于ZooKeeper的分布式锁
锁的条件
要确保锁的实现同时满⾜以下四个条件:
- 互斥性
- 不会发⽣死锁
- 具有容错性
- 解铃还须系铃⼈
使⽤场景
库存超卖场景、⽀付场景、分布式事务、任务调度
redis分布式锁
import cn.com.tpig.cache.redis.RedisService;
import cn.com.tpig.utils.SpringUtils;
//redis分布式锁
public final class RedisLockUtil {
private static final int defaultExpire = 60;
private RedisLockUtil() {
//
}
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
public static boolean lock(String key, int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key, "1");
if(status == 1) {
redisService.expire(key, expire);
return true;
}
return false;
}
public static boolean lock(String key) {
return lock2(key, defaultExpire);
}
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
public static boolean lock2(String key, int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long value = System.currentTimeMillis() + expire;
long status = redisService.setnx(key, String.valueOf(value));
if(status == 1) {
return true;
}
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime < System.currentTimeMillis()) {
//超时
long newExpireTime = System.currentTimeMillis() + expire;
long currentExpireTime = Long.parseLong(redisService.getSet(key,String.valueOf(newExpireTime)));
if(currentExpireTime == oldExpireTime) {
return true;
}
}
return false;
}
public static void unLock1(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
redisService.del(key);
}
public static void unLock2(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime > System.currentTimeMillis()) {
redisService.del(key);
}
}
}
实现方式二
基于开源框架Redission
RedissonClient redisson = Redisson.create(config);
RLock lock1 = redisson.getFairLock("lock1");
RLock lock2 = redisson.getFairLock("lock2");
RLock lock3 = redisson.getFairLock("lock3");
RedissonRedLock multiLock = new RedissonRedLock(lock1, lock2, lock3);
multiLock.lock();
multiLock.unlock();
扩展zk分布式锁
zookeeper特性
- 有序节点:
如果是第⼀个创建的⼦节点,那么⽣成的⼦节点为 /lock/node-0000000000 ,下⼀
个节点则为 /lock/node-0000000001 ,依次类推 - 临时节点:客户端可以建⽴⼀个临时节点,在会话结束或者会话超时后,zookeeper会⾃动删除该节点
- 事件监听:
在读取数据时,我们可以同时对节点设置事件监听- 节点创建
- 节点删除
- 节点数据修改
- ⼦节点变更
zookeeper分布式锁实现逻辑
- 客户端连接zookeeper,并在/lock下创建临时的且有序的⼦节点,第⼀个客户端对应的⼦节点为/lock/lock-0000000000,第⼆个为/lock/lock-0000000001,以此类推
- 客户端获取/lock下的⼦节点列表,判断⾃⼰创建的⼦节点是否为当前⼦节点列表中序号最⼩的⼦节点;如果是则认为获得锁,否则监听/lock的⼦节点变更消息,获得⼦节点变更通知后重复此步骤直⾄获得锁
- 执⾏业务代码
- 完成业务流程后,删除对应的⼦节点释放锁
redis高并发
缓存穿透
当用户的请求发送过来,首先会打到redis上,如果对应的数据在redis中并不存在,那么每次针对此key的请求从缓存获取不到,请求都会到数据源,从⽽可能压垮数据源
缓存击穿
key可能会在某些时间点被超⾼并发地访问,是⼀种⾮常“热点”的数据。在缓存失效的瞬间,有⼤量线程来重建缓存,造成后端负载加⼤,甚⾄可能会让应⽤崩溃
缓存雪崩
缓存层由于某些原因不可⽤(宕机)或者⼤量缓存由于超时时间相同在同⼀时间段失效(⼤批key失效/热点数据失效),⼤量请求直接到达存储层,存储层压⼒过⼤导致系统雪崩
redis缓存⼀致性
- 写数据只写DB
- 更新数据先更新DB,在失效缓存
- 读数据,先读redis,读取不到再从DB读,再回写redis
Redis过期策略
- 定时删除
在设置key的过期时间的同时,为该key创建⼀个定时器,让定时器在key的过期时间来临时,对key进⾏删除
优点:保证内存被尽快释放
缺点:若过期key很多,删除这些key会占⽤很多的CPU时间
定时器的创建耗时,若为每⼀个设置过期时间的key创建⼀个定时器(将会有⼤量的定时器产⽣),性能影响严重 - 懒汉式删除
key过期的时候不删除,每次通过key获取值的时候去检查是否过期,若过期,则删除,返回null
优点:删除操作只发⽣在通过key取值的时候发⽣,⽽且只删除当前key,所以对CPU时间的占⽤是⽐较少的
缺点:若⼤量的key在超出超时时间后,很久⼀段时间内,都没有被获取过,那么可能发⽣内存泄露 - 定期删除
每隔⼀段时间执⾏⼀次删除过期key操作
优点: 通过限制删除操作的时⻓和频率,来减少删除操作对CPU时间的占⽤
缺点:会造成⼀定的内存占⽤,但是没有懒汉式那么占⽤内存;会定期的去进⾏⽐较和删除操作,cpu⽅⾯不如懒汉式,但是⽐定时好
Redis采⽤的是:懒汉式删除+定期删除
懒汉式删除流程:
- 在进⾏get或setnx等操作时,先检查key是否过期
- 若过期,删除key,然后执⾏相应操作
- 若没过期,直接执⾏相应操作
定期删除流程:
- 遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
- 检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执⾏20次,循环体是下边的描述)
- 如果当前库中没有⼀个key设置了过期时间,直接执⾏下⼀个库的遍历
- 随机获取⼀个设置了过期时间的key,检查该key是否过期,如果过期,删除key
- 判断定期删除操作是否已经达到指定时⻓,若已经达到,直接退出定期删除
Redis缓存淘汰策略
Redis的内存淘汰策略是指在Redis的⽤于缓存的内存不⾜时,怎么处理需要新写⼊且需要申请额外空间的数据
- noeviction:当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错
- allkeys-lru:当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的key
- allkeys-random:当内存不⾜以容纳新写⼊数据时,在键空间中,随机移除某个key
- volatile-lru:当内存不⾜以容纳新写⼊数据时,在设置了过期时间的键空间中,移除最近最少使⽤的key
- volatile-random:当内存不⾜以容纳新写⼊数据时,在设置了过期时间的键空间中,随机移除某个key
- volatile-ttl:当内存不⾜以容纳新写⼊数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
Redis线程模型
- ⽂件事件处理器
- ⽂件事件
- ⽂件事件处理器
redis单线程模型为什么性能这么⾼?
- 纯内存操作
- 核⼼是基于⾮阻塞的IO多路复⽤机制
- 单线程反⽽避免了多线程的频繁上下⽂切换问题