高并发,分布式锁

高并发

优化案例:高并发减库存

@RestController
public class TestController {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redisson;


    /*
     * 1.setIfAbsent,setNx,不存在时设置,执行完删除,这样就保证只有一个线程执行
     * 2.当逻辑代码出现异常,lockkey就无法删除,所以加入try finally
     * 3.但是当第一个线程设置了lockkey后,服务器宕机,这样lockkey就无法删除,别的线程就无法进入,死锁,所以要加一个过期时间,
     * 并且要放在一行代码里执行,保证原子操作,放在两行的话,有可能在设置过期时间之前宕机
     * 4.第一个线程需要15s,10s后锁过期,第二个线程进来设置锁,第一线程执行完删除了锁,但它删的是第二个线程的锁,一次类推
     *   所以可以设置锁的值为唯一,UUID就可以,删除时判断UUID是不是自己的UUID
     * 5.当删除锁之前判断uuid是自己的,时间9.9s,但删除锁时已经过了10s,第二个线程进来,第一个线程删除的又是第二个线程的锁
     *  所以要保证判断和删除时原子操作,要用redisson
     *
     * */
    @GetMapping("test")
    public String test() {
        String lockKey = "good";
/*        String uuid = UUID.randomUUID().toString();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 10, TimeUnit.SECONDS);

        if (!flag) {
            return "error_code";
        }*/

        //1.普通分布式锁
        RLock lock = redisson.getLock(lockKey);

       /* //2.读写锁
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(lockKey);
        RLock readLock = readWriteLock.readLock();
        RLock writeLock = readWriteLock.writeLock();*/


        try {
            lock.lock();
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock").toString());
            if (stock > 0) {
                stock = stock - 1;
                redisTemplate.opsForValue().set("stock", stock + "");
                System.out.println("扣减成功,剩余库存--》" + stock);
            } else {
                System.out.println("库存不足");
            }
        } finally {
            lock.unlock();/*
            if(uuid.equals(redisTemplate.opsForValue().get(lockKey))){
                redisTemplate.delete(lockKey);
            }*/
        }
        return "end";
    }
1、分布式锁解决方案
  • 数据库,利用主键冲突

  • redis setNX单线程,不会有并发问题

  • zookeeper

2、redis和zookeeper主从同步对比

redis

AP 可用,分区容错。只要主节点锁成功就成功,问题,从还有同步主节点数据,主就挂了,从成为新主,但其实里面已经没有了锁key,其他线程就会进入加锁成功。而zookeeper就不会

解决办法:

  • 补偿脚本

  • Redlock 超过半数redis节点加锁成功才算成功,而这些节点没有主从关系,相当于zookeeper了

zookeeper

CP 一致性,分区容错性,

同步时,先从主节点同步到从节点,超过一半成功就会认为加锁成功返回给线程执行业务

zookeeper 选举机制 ZAB 只会选举同步成功的节点成为新主

总结:分布式锁,性能高用redis,一致性高用zookeeper

3、redis解决高并发

优化思路:分段式锁,类似jdk1.7中ConcurrentHashMap底层原理,200个库存分段,例如分成10段,10个key,每个里20个库存

缓存数据库双写不一致

结果:数据库6,缓存10

解决:数据库写后,不是更新缓存而是删除缓存,下次查询时从数据库查询后再放入缓存

结果:数据库6,缓存10

再解决:延迟双删

问题:如果线程3更新缓存在双删之间没问题,如果在双删之后还是出现了老问题,数据库6,缓存10

另外还降低了系统性能,不推荐

真正解决
  • 分布式锁:每一个线程里所有操作加锁,其他线程拿到锁才能执行,这样每个线程操作排队执行,但影响了性能
  • canel
  • TiDB

思考:大多数场景都是读多写少,读写锁

读多写少

//2.读写锁
RReadWriteLock readWriteLock = redisson.getReadWriteLock(lockKey);
RLock readLock = readWriteLock.readLock();
RLock writeLock = readWriteLock.writeLock();

读多写多

真实场景中,数据库和缓存没必要强一致,所以只需要在更新缓存时设置一个过期时间即可

如果非要一致,就不应该使用缓存,因为缓存命中率太低。但是这样数据库压力太大,扛不住。

用分布式数据库TiD

不知道读多还是写多

考虑用阿里的canel,我们不需自己更新缓存,它会自己监听数据库binlog,根据先后顺序更新缓存

posted @   jpy  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示