本地锁 & 分布式锁

引子: 解决缓存击穿问题

synchronized (this){代码块}

public synchronized Map<String,List<Catelog2Vo>> getCatalog]sonFromDb()

:SpringBoot所有的组件在容器中都是单例的。

优点:速度快

所以这样只能有一个人拿到锁进入逻辑, 但是也是在单体服务下可行,在分布条件下,几个服务就生成几把锁(应该问题不大)

测试1: 有3个人进入拿到了锁

原因:  拿到锁的那个线程确认缓存没有,去查数据库,查询后释放锁 ,刚释放还没来得及往缓存中存数据, 另一个线程就拿到锁再进行操作

解决方案:  把 (往缓存中存数据) 放到锁中    [稍微影响效率]

 

 

 

这里推荐下阿里的jetcache 全部都封装好了

 

redis分布式锁

分布式中,即使多个服务,也只让一个人去查查数据

原理: redis  set hello world NX

 

 先看缓冲中有没有, 没有的话就占分布式锁,执行下面的逻辑

可能遇到问题

1.拿到锁之后,执行完逻辑,在释放锁之前突然断电,无法释放锁导致其他服务无法获取,造成死锁  (解决办法: 锁设置过期时间:30S)

 

 还有问题: 若是刚加锁,还没来得及设置过期时间,就闪断

解决办法:  原子操作

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",30, TimeUnit.SECONDS);

 

 

 

删锁的时候遇到问题

1.假设业务时间过长,锁自动过期了  就会有其他线程进来

2.自己的锁过期了,删掉了其他人正在用的锁

解决办法:可以加V值改为UUID,删锁执行判断UUID进行删除

String uuid = UUID.randomUUID().toString();

    新问题(无原子性): 因为数据传输慢的原因,去redis判断uuid是否是自己,判断后,在然后往回传结果true的路上  ,K过期了,其他人占了锁 , 此时,删除的锁,也是别人的锁

    解决办法: 删锁的原子性,利用lua脚本 

                String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call('del',KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                // 删除锁
                redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("LOCK_KEY_CATALOG_JSON"), uuid);
Arrays.asList("LOCK_KEY_CATALOG_JSON") ==  uid  就删除锁

加锁保证原子性,解锁保证原子性

问题: 锁的自动续期问题,  [把时间放长一点]

 

 

 

Redisson(单机redis)

导入依赖

复制代码
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.3</version>
        </dependency>
View Code
复制代码

配置

复制代码
@Configuration
public class MyRedissonConfig {

    /**
     * 注入客户端实例对象
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson(@Value("${spring.redis.host}") String host, @Value("${spring.redis.port}")String port) throws IOException {
        // 1.创建配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port);// 单节点模式
//        config.useSingleServer().setAddress("rediss://" + host + ":" + port);// 使用安全连接
//        config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");// 集群模式
        // 2.创建redisson客户端实例
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}
View Code
复制代码

测试

RedissonClient redissonClient

实现分布式锁

锁的自动续期+30S (看门狗 : 10秒续一次)   ,一旦设置过期时间,看门狗会失效

 

可重入锁(抢占一把锁)

复制代码
    public Map<String, List<Catalog2VO>> getCatalogJsonFromDBWithRedissonLock() {
        // 1.抢占分布式锁,同时设置过期时间
        RLock lock = redisson.getLock("LOCK_KEY_CATALOG_JSON");
        lock.lock(30, TimeUnit.SECONDS);
        try {
            // 2.查询DB
            Map<String, List<Catalog2VO>> result = getCatalogJsonFromDB();
            return result;
        } finally {
            // 3.释放锁
            lock.unlock();
        }
    }
View Code
复制代码

 

复制代码
RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();
// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     ...
   } finally {
       lock.unlock();
   }
}
复制代码

公平锁(先到先得)

复制代码
//所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,
也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。
RLock fairLock = redisson.getFairLock("anyLock"); // 最常见的使用方法 fairLock.lock(); // 10秒钟以后自动解锁 // 无需调用unlock方法手动解锁 fairLock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS); ... fairLock.unlock();
复制代码

读写锁(写锁控制读锁)

复制代码
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
//
rwlock.writeLock().lock();
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
//
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
//
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock(); //抽象lock(代表读写锁)
复制代码

 

闭锁

等待全部成才能加锁

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(5); //等5个走完才结束
latch.await();

// 在其他线程或其他JVM里()
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();

信号量

做限流

复制代码
提前在redis中存入信号量{"semaphore":"10"}

RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//获取信号量-1,获取不到就一直等待
semaphore.tryAcquire(23, TimeUnit.SECONDS);  /.尝试等待23秒,



//其他线程 semaphore.release(); //释放信号量+1
复制代码

 

可过期性信号量

红锁(大多数锁上就行)

 

注意锁的粒度

 

 

 

 办法:  要么加锁牺牲效率,    要么允许暂时的不一致,有最终一致性就行     丨(加了缓存过期时间)

 

 

 问题: 脏数据问题,  2号慢  , 3号读到了脏数据,也更新了脏数据     丨 (加了缓存过期时间)

 

经常修改的数据,直接读数据库就行

 

posted @   磕伴  阅读(263)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示