Redis相关问题-2

简介: 本文介绍了Redis的内存淘汰策略和分布式锁实现。内存淘汰策略包括淘汰易失数据和全库数据,具体方法如volatile-lru、volatile-lfu、allkeys-random等,以及no-eviction不淘汰策略。同时详细讲解了Redis分布式锁的实现,通过SETNX命令实现锁机制,解决死锁、锁超时、归一性和可重入性问题,提供代码示例说明加锁与解锁的绑定及可重入锁的实现方式。

Q:数据淘汰策略

淘汰易失数据(具有过期时间的数据)

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-lfu(least frequently used:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  3. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  4. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

淘汰全库数据

  1. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  2. allkeys-lfu(least frequently used:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
  3. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

不淘汰

  1. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!



Q:Redis分布式锁如何实现

Redis分布式锁主要依靠一个SETNX指令实现的 , 这条命令的含义就是“SET if Not Exists”,即不存在的时候才会设置值。

只有在key不存在的情况下,将键key的值设置为value。如果key已经存在,则SETNX命令不做任何操作。

这个命令的返回值如下。

  • 命令在设置成功时返回1。
  • 命令在设置失败时返回0。

假设此时有线程A和线程B同时访问临界区代码,假设线程A首先执行了SETNX命令,并返回结果1,继续向下执行。而此时线程B再次执行SETNX命令时,返回的结果为0,则线程B不能继续向下执行。只有当线程A执行DELETE命令将设置的锁状态删除时,线程B才会成功执行SETNX命令设置加锁状态后继续向下执行

Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "binghe");

当然我们在使用分布式锁的时候也不能这么简单, 会考虑到一些实际场景下的问题 , 例如 :

  1. 死锁问题在使用分布式锁的时候, 如果因为一些原因导致系统宕机, 锁资源没有被释放, 就会产生死锁解决的方案 : 上锁的时候设置锁的超时时间 Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "binghe", 30, TimeUnit.SECONDS);
  2. 锁超时问题如果业务执行需要的时间, 超过的锁的超时时间 , 这个时候业务还没有执行完成, 锁就已经自动被删除了其他请求就能获取锁, 操作这个资源 , 这个时候就会出现并发问题 , 解决的方案 :
  1. 引入Redis的watch dog机制, 自动为锁续期
  2. 开启子线程 , 每隔20S运行一次, 重新设置锁的超时时间
  1. 归一问题如果一个线程获取了分布式锁, 但是这个线程业务没有执行完成之前 , 锁被其他的线程删掉了 , 又会出现线程并发问题 , 这个时候就需要考虑归一化问题就是一个线程执行了加锁操作后,后续必须由这个线程执行解锁操作,加锁和解锁操作由同一个线程来完成。为了解决只有加锁的线程才能进行相应的解锁操作的问题,那么,我们就需要将加锁和解锁操作绑定到同一个线程中,可以使用ThreadLocal来解决这个问题 , 加锁的时候生成唯一标识保存到ThreadLocal , 并且设置到锁的值中 , 释放锁的时候, 判断线程中的唯一标识和锁的唯一标识是否相同, 只有相同才会释放
public class RedisLockImpl implements RedisLock{
 @Autowired
 private StringRedisTemplate stringRedisTemplate;
 private ThreadLocal<String> threadLocal = new ThreadLocal<String>();
 @Override
 public boolean tryLock(String key, long timeout, TimeUnit unit){
     String uuid = UUID.randomUUID().toString();
     threadLocal.set(uuid);
     return stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
 }
 @Override
 public void releaseLock(String key){
     //当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作
     if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){
       stringRedisTemplate.delete(key);   
     }
 }
}
  1. 可重入问题当一个线程成功设置了锁标志位后,其他的线程再设置锁标志位时,就会返回失败。还有一种场景就是在一个业务中, 有个操作都需要获取到锁, 这个时候第二个操作就无法获取锁了 , 操作会失败例如 : 下单业务中, 扣减商品库存会给商品加锁, 增加商品销量也需要给商品加锁 , 这个时候需要获取二次锁第二次获取商品锁就会失败 , 这就需要我们的分布式锁能够实现可重入实现可重入锁最简单的方式就是使用计数器 , 加锁成功之后计数器 + 1 , 取消锁之后计数器 -1 , 计数器减为0 , 真正从Redis删除锁
public class RedisLockImpl implements RedisLock{
 @Autowired
 private StringRedisTemplate stringRedisTemplate;
 private ThreadLocal<String> threadLocal = new ThreadLocal<String>();
 private ThreadLocal<Integer> threadLocalInteger = new ThreadLocal<Integer>();
 @Override
 public boolean tryLock(String key, long timeout, TimeUnit unit){
     Boolean isLocked = false;
     if(threadLocal.get() == null){
         String uuid = UUID.randomUUID().toString();
      threadLocal.set(uuid);
         isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
     }else{
         isLocked = true;   
     }
     //加锁成功后将计数器加1
     if(isLocked){
         Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();
         threadLocalInteger.set(count++);
     }
     return isLocked;
 }
 @Override
 public void releaseLock(String key){
     //当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作
     if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){
         Integer count = threadLocalInteger.get();
         //计数器减为0时释放锁
         if(count == null || --count <= 0){
           stringRedisTemplate.delete(key);      
         }
     }
 }
}
目录
相关文章
|
Java 存储
线程池的核心参数有哪些?
线程池七大核心参数:核心/最大线程数、线程保持时间及单位、阻塞队列、线程工厂与拒绝策略。
762 79
|
5月前
|
Java Spring 容器
DI依赖注入的几种手段
本内容介绍了依赖注入的四种方式:构造器注入、接口注入、Setter注入和注解注入,并重点比较了Spring中的@Autowired与Java标准注解@Resource的区别,包括来源和依赖查找策略。
288 0
|
25天前
|
数据采集 缓存 数据可视化
Android 无侵入式数据采集:从手动埋点到字节码插桩的演进之路
本文深入探讨Android无侵入式埋点技术,通过AOP与字节码插桩(如ASM)实现数据采集自动化,彻底解耦业务代码与埋点逻辑。涵盖页面浏览、点击事件自动追踪及注解驱动的半自动化方案,提升数据质量与研发效率,助力团队迈向高效、稳定的智能化埋点体系。(238字)
433 158
|
人工智能 关系型数据库 OLAP
聚光灯已就位!阿里云瑶池数据库邀你征战Cursor首届实战征文大赛
阿里云AnalyticDB携手Cursor中文社区,正式发起首届实战征文大赛!我们诚邀开发者融合Cursor的智能编程能力与AnalyticDB PostgreSQL提供的Supabase服务进行项目开发,让优秀项目被专家看见、被机遇拥抱!
|
4月前
|
SQL Arthas 关系型数据库
MySQL相关问题
当SQL语句执行缓慢时,可通过Skywalking等工具定位慢SQL,再使用Explain分析执行计划。重点关注possible_keys、key、key_len、type和extra字段,判断索引使用情况及是否回表。可通过优化索引、使用覆盖索引等方式提升性能。此外,还可开启MySQL慢日志或使用Arthas、Prometheus等工具辅助定位问题。
84 0
|
2月前
|
人工智能 Cloud Native 关系型数据库
云栖重磅|瑶池数据库:从云原生数据底座向“AI就绪”的多模态数据底座演进
瑶池数据库:从云原生数据底座向“AI就绪”的多模态数据底座演进
|
2月前
|
运维 Kubernetes API
如何理解K8s自定义资源(CRD)——从nacos的K8s集群部署开始
通过部署Nacos实践,解析Kubernetes中自定义资源(CRD)的核心概念。借助Nacos Operator,阐述CRD如何扩展API、实现声明式管理有状态服务,并揭示Operator模式自动化运维的底层逻辑,助力理解云原生应用管理机制。
198 0
|
3月前
|
CDN
阿里云CDN收费标准,不同计费模式价格表(基础服务费和增值服务费用整理)
阿里云CDN费用包含基础服务费和增值服务费。基础费用按流量、带宽峰值或月结95带宽计费,默认按流量计费;增值服务如HTTPS、QUIC、WAF、实时日志等按使用量收费,不使用不收费。支持资源包抵扣,降低成本。
602 3