《别看了,你学不会的》——Redis原理与实战(二)

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的分布式锁

锁的条件

要确保锁的实现同时满⾜以下四个条件:

  1. 互斥性
  2. 不会发⽣死锁
  3. 具有容错性
  4. 解铃还须系铃⼈

使⽤场景
库存超卖场景、⽀付场景、分布式事务、任务调度

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分布式锁实现逻辑
  1. 客户端连接zookeeper,并在/lock下创建临时的且有序的⼦节点,第⼀个客户端对应的⼦节点为/lock/lock-0000000000,第⼆个为/lock/lock-0000000001,以此类推
  2. 客户端获取/lock下的⼦节点列表,判断⾃⼰创建的⼦节点是否为当前⼦节点列表中序号最⼩的⼦节点;如果是则认为获得锁,否则监听/lock的⼦节点变更消息,获得⼦节点变更通知后重复此步骤直⾄获得锁
  3. 执⾏业务代码
  4. 完成业务流程后,删除对应的⼦节点释放锁

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单线程模型为什么性能这么⾼?
  1. 纯内存操作
  2. 核⼼是基于⾮阻塞的IO多路复⽤机制
  3. 单线程反⽽避免了多线程的频繁上下⽂切换问题
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值