分布式锁【数据库乐观锁实现的分布式锁、Zookeeper分布式锁原理、Redis实现的分布式锁】(三)-全面详解(学习总结---从入门到深化)

简介: 分布式锁【数据库乐观锁实现的分布式锁、Zookeeper分布式锁原理、Redis实现的分布式锁】(三)-全面详解(学习总结---从入门到深化)



 

分布式锁解决方案_数据库乐观锁实现的分布式锁

什么是乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改, 所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有 去更新这个数据,可以使用版本号机制和CAS算法实现。

编写乐观锁更新语句

<update id="decreaseStockForVersion" parameterType="int" >
        UPDATE product SET count = count - # {count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version}
</update>

编写创建订单业务层

/**
     * 创建订单 乐观锁
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String createOrder(Integer productId, Integer count) throws Exception {
        int retryCount = 0;
        int update = 0;
        // 1、根据商品id查询商品信息
        Product product = productMapper.selectById(productId);
        // 2、判断商品是否存在
        if (product == null) {
            throw new RuntimeException("购买商品不存在:" + productId + "不存在");
       }
        // 3、校验库存
        if (count > product.getCount()) {
            throw new Exception("库存不够");
       }
         // 乐观锁更新库存
        // 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
        // 最多重试3次
        while(retryCount < 3 && update == 0){
            update = this.reduceStock(product.getId(),count);
            retryCount++;
       }
        if (update == 0){
            throw new Exception("库存不够");
       }
        // 6、 创建订单
        TOrder order = new TOrder();
        order.setOrderStatus(1);//待处理
        order.setReceiverName("张三");
        order.setReceiverMobile("18587781068");
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
        baseMapper.insert(order);
        // 7、 创建订单和商品关系数据
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProduceId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(count);
        orderItemMapper.insert(orderItem);
        return order.getId();
   }
    /**
     * 减库存
     * <p>
     * 由于默认的事务隔离级别是可重复读,produce.findById()
     * 得到的数据始终是相同的,所以需要提取 reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。
     */
    @Transactional(rollbackFor = Exception.class)
    public int reduceStock(int gid,int count) {
        int result = 0;
        //1、查询商品库存
        Product product = productMapper.selectById(gid);
        //2、判断库存是否充足
        if (product.getCount() >= count) {
            //3、减库存
            // 乐观锁更新库存
            result = productMapper.decreaseStockForVersion(gid,count, product.getVersion());
       }
        return result;
   }

分布式锁解决方案_Redis实现的分布式锁原理

获取锁

互斥:确保只有一个线程获得锁

# 添加锁 利用setnx的互斥性
127.0.0.1:6379> setnx lock thread1

释放锁

1、手动释放锁

2、超时释放:获取锁时设置一个超时时间

#释放锁 删除即可
127.0.0.1:6379> del lock

超时释放

127.0.0.1:6379> setnx lock tread1
127.0.0.1:6379> expire lock 5
127.0.0.1:6379> ttl lock

两步合成一步

help set
 SET key value [EX seconds] [PX milliseconds] [NX|XX]
 summary: Set the string value of a key
 since: 1.0.0
 group: string
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> set lock k1 ex 5 nx
OK
127.0.0.1:6379> set lock k1 ex 5 nx
nil

分布式锁解决方案_Redis实现的分布式锁

引入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

添加Redis配置

spring:
 redis:
   host: localhost
   port: 6379

编写创建订单实现类

@Override
    public String createOrderRedis(Integer productId, Integer count) throws Exception {
        log.info("*************** 进入方法 **********");
        String key = "lock:";
        String value = UUID.randomUUID().toString();
        // 获取分布式锁
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key+productId,String.valueOf(Thread.currentThread().getId()),30,TimeUnit.SECONDS);
        // 判断是否获取锁成功
        if (!result){
            log.info("我进入了锁");
            return "不允许重复下单";
       }
        try {
            // 1、根据商品id查询商品信息
            Product product = productMapper.selectById(productId);
            // 2、判断商品是否存在 if (product == null) {
                throw new RuntimeException("购买商品不存在:" + productId + "不存在");
           }
            // 3、校验库存
            if (count > product.getCount()) {
                throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
           }
            // 4、计算库存
            Integer leftCount = product.getCount() - count;
            // 5、更新库存
            product.setCount(leftCount);
            productMapper.updateById(product);
            // 6、 创建订单
            TOrder order = new TOrder();
            order.setOrderStatus(1);//待处理
            order.setReceiverName("张三");
            order.setReceiverMobile("18587781068");
            order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
            baseMapper.insert(order);
            // 7、 创建订单和商品关系数据
            OrderItem orderItem = new OrderItem();
            orderItem.setOrderId(order.getId());
            orderItem.setProduceId(product.getId());
            orderItem.setPurchasePrice(product.getPrice());
            orderItem.setPurchaseNum(count);
            orderItemMapper.insert(orderItem);
            return order.getId();
       }catch (Exception e){
            e.printStackTrace();
       }finally {
            // 释放锁
          stringRedisTemplate.delete(key+productId);
       }
        return "创建失败";
   }

分布式锁解决方案_Redis分布式锁误删除问题

配置锁标识

private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString().replace("-" ,"");

获取锁

//1、获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 2、获得锁 setnx key   value   time   type
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+produceId, threadId, 30,TimeUnit.SECONDS);

释放锁

// 获取锁标识
  String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + produceId);
            // 判断标识是否一致
            if (s.equals(threadId)){
                // 释放锁
               stringRedisTemplate.delete(KEY_PREFIX + produceId);
           }

分布式锁解决方案_Redis分布式锁不可重入问题

不可重入问题

如何解决

 

分布式锁解决方案_基于Redisson实现的分布式锁实现

Redisson介绍

Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分 布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重 对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。

引入Redisson依赖

<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.17.2</version>
</dependency>

添加Reids的配置

spring:
 redis:
   host: localhost
   port: 6379

编写Redis分布式锁工具类

package com.itbaizhan.lock.utils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class DistributedRedisLock {
    @Autowired
    private RedissonClient redissonClient;
    // 加锁
    public Boolean lock(String lockName) {
        if (redissonClient == null) {
            log.info("DistributedRedisLock redissonClient is null");
            return false;
       }
        try {
            RLock lock = redissonClient.getLock(lockName);
            // 锁15秒后自动释放,防止死锁
            lock.lock(15, TimeUnit.SECONDS);
            log.info("Thread [{}] DistributedRedisLock lock [{}] success",Thread.currentThread().getName(), lockName);
            // 加锁成功
            return true;
       } catch (Exception e) {
            log.error("DistributedRedisLocklock [{}] Exception:", lockName, e);
            return false;
       }
   }
    // 释放锁
    public Boolean unlock(String lockName) {
        if (redissonClient == null) {
            log.info("DistributedRedisLock redissonClient is null");
            return false;
       }
        try {
            RLock lock = redissonClient.getLock(lockName);
            lock.unlock();
            log.info("Thread [{}] DistributedRedisLock unlock [{}] success",Thread.currentThread().getName(), lockName);
            // 释放锁成功
            return true;
} catch (Exception e) {
            log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e);
            return false;
       }
   }
}

编写创建订单接口实现

/**
     * Redis锁实现
     *
     * @param productId
     * @param count
     * @return
     * @throws Exception
     */
    @Override
    public String createOrderRedis(Integer productId, Integer count) throws Exception {
        //获取锁对象
        if (distributedRedisLock.lock(String.valueOf(productId))) {
            try {
                // 1、根据商品id查询商品信息
                Product product = productMapper.selectById(productId);
                // 2、判断商品是否存在
                if (product == null) {
                throw new RuntimeException("购买商品不存在:" + productId + "不存在");
               }
                // 3、校验库存
                if (count > product.getCount())
               {
                    throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
               }
                // 4、计算库存
                Integer leftCount = product.getCount() - count;
                // 5、更新库存
                product.setCount(leftCount);
                productMapper.updateById(product);
                // 6、 创建订单
                TOrder order = new TOrder();
                order.setOrderStatus(1);//待处理
                order.setReceiverName("张三");
                order.setReceiverMobile("18587781068");
                order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
                baseMapper.insert(order);
                // 7、 创建订单和商品关系数据
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderId(order.getId());
                orderItem.setProduceId(product.getId());
                orderItem.setPurchasePrice(product.getPrice());
                orderItem.setPurchaseNum(count);
                orderItemMapper.insert(orderItem);
                return order.getId();
           } catch (Exception e) {
                e.printStackTrace();
           } finally {
               distributedRedisLock.unlock(String.valueOf(productId));
           }
       }
        return "创建失败";
   }

分布式锁解决方案_Zookeeper分布式锁原理

公平锁和可重入锁的原理

这种排队取水模型,就是一种锁的模型。

什么是可重入锁呢?

创建临时顺序节点

[zk: localhost:2181(CONNECTED) 1] create -e  -s
/test 123

ZK分布式锁的实现原理

当第一个客户端请求过来时,Zookeeper 客户端会创建一个持久节 点 locks。如果它(Client1)想获得锁,需要在 locks 节点下创建 一个顺序节点 lock1。

接着,客户端 Client1 会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock1 是不是排序最小的那一个,如果是,则成功获得锁。

这时候如果又来一个客户端 client2 前来尝试获得锁,它会在 locks 下再创建一个临时节点 lock2。

客户端 client2 一样也会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock2 是不是最小的,此时,发现 lock1 才是最小 的,于是获取锁失败。获取锁失败,它是不会甘心的,client2 向它 排序靠前的节点 lock1 注册 Watcher 事件,用来监听 lock1 是否存 在,也就是说 client2 抢锁失败进入等待状态。

此时,如果再来一个客户端Client3来尝试获取锁,它会在 locks 下 再创建一个临时节点 lock3。

同样的,client3 一样也会查找 locks 下面的所有临时顺序子节点, 判断自己的节点 lock3 是不是最小的,发现自己不是最小的,就获 取锁失败。它也是不会甘心的,它会向在它前面的节点 lock2 注册 Watcher 事件,以监听 lock2 节点是否存在。

释放锁

如果是任务完成,Client1 会显式调用删除 lock1 的指令。

如果是客户端故障了,根据临时节点得特性,lock1 是会自动删除的。

lock1 节点被删除后,Client2 可开心了,因为它一直监听着 lock1。lock1 节点删除,Client2 立刻收到通知,也会查找 locks 下面的所有临时顺序子节点,发下 lock2 是最小,就获得锁。

同理,Client2 获得锁之后,Client3 也对它虎视眈眈:

分布式锁解决方案_基于Zookeeper实现分布式锁

简介

Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封 装的一套高级API 简化了ZooKeeper的操作。

引入Curator依赖

<dependency>
         <groupId>org.apache.curator</groupId>
          <artifactId>curator-framework</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>5.2.0</version>
        </dependency>

编写Zookeeper配置

@Configuration
public class ZookeeperConfig {
    @Bean
    public CuratorFramework zookeeperClient() {
        CuratorFramework client = CuratorFrameworkFactory.builder()
               .connectString("127.0.0.1:2181")
               .sessionTimeoutMs(5000)
               .connectionTimeoutMs(5000)
               .retryPolicy(new ExponentialBackoffRetry(1000, 3))
               //.namespace("test")
               .build();
        client.start();
        return client;
   }
}

编写创建订单接口实现

使用InterProcessMutex的acquire和release方法,来获取和释放锁。

@Autowired
    CuratorFramework client;
    @Override
    public String createOrderZookeeper(Integer productId, Integer count) throws Exception 
     {
        // client cruator中zk客户端对象   path 抢锁路径同一个锁path需要一致
        InterProcessMutex lock = new InterProcessMutex(client, "/lockPath");
        //第一个属性:定时的时间数字
        //第二个属性:定义时间的单位
        if (lock.acquire(3, TimeUnit.SECONDS))
          {
            try {
                // 1、根据商品id查询商品信息
                Product product = productMapper.selectById(productId);
                // 2、判断商品是否存在
                if (product == null) {
            throw new RuntimeException("购买商品不存在:" + productId + "不存在");
               }
                // 3、校验库存
                if (count > product.getCount())
                 {
                    throw new RuntimeException("商品" + productId + "仅剩" +
product.getCount() + "件,无法购买");
               }
                // 4、计算库存
                Integer leftCount = product.getCount() - count;
                // 5、更新库存
                product.setCount(leftCount);
                productMapper.updateById(product);
                // 6、 创建订单
                TOrder order = new TOrder();
                order.setOrderStatus(1);//待处理
                order.setReceiverName("张三");
                order.setReceiverMobile("18587781068");
                order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
                baseMapper.insert(order);
                // 7、 创建订单和商品关系数据
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderId(order.getId());
                orderItem.setProduceId(product.getId());
                orderItem.setPurchasePrice(product.getPrice());
                orderItem.setPurchaseNum(count);
                orderItemMapper.insert(orderItem);
                return order.getId();
           } finally {
                lock.release();
           }
       }
        return "创建失败";
   }

三种分布式锁对比

数据库分布式锁实现

优点:简单,使用方便,不需要引入 Redis、Zookeeper 等中间件。

缺点:1、不适合高并发的场景 2、db 操作性能较差

Redis 分布式锁实现

优点:1、性能好,适合高并发场景 2、较轻量级 3、有较好的框架支持,如 Redisson

缺点:1、过期时间不好控制 2、需要考虑锁被别的线程误删场景

Zookeeper 分布式锁实现

优点:1、有较好的性能和可靠性 2、有封装较好的框架,如 Curator

缺点:1、性能不如 Redis 实现的分布式锁  2、比较重的分布式锁。

汇总对比

1、从性能角度:Redis > Zookeeper >= 数据库

2、从实现的复杂性角度:Zookeeper > Redis > 数据库

3、从可靠性角度:Zookeeper > Redis > 数据库

目录
相关文章
|
12月前
|
NoSQL 算法 安全
分布式锁—1.原理算法和使用建议
本文主要探讨了Redis分布式锁的八大问题,包括非原子操作、忘记释放锁、释放其他线程的锁、加锁失败处理、锁重入问题、锁竞争问题、锁超时失效及主从复制问题,并提供了相应的优化措施。接着分析了Redis的RedLock算法,讨论其优缺点以及分布式专家Martin对其的质疑。此外,文章对比了基于Redis和Zookeeper(zk)的分布式锁实现原理,包括获取与释放锁的具体流程。最后总结了两种分布式锁的适用场景及使用建议,指出Redis分布式锁虽有性能优势但模型不够健壮,而zk分布式锁更稳定但部署成本较高。实际应用中需根据业务需求权衡选择。
|
运维 NoSQL 算法
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
本文深入探讨了基于Redis实现分布式锁时遇到的细节问题及解决方案。首先,针对锁续期问题,提出了通过独立服务、获取锁进程自己续期和异步线程三种方式,并详细介绍了如何利用Lua脚本和守护线程实现自动续期。接着,解决了锁阻塞问题,引入了带超时时间的`tryLock`机制,确保在高并发场景下不会无限等待锁。最后,作为知识扩展,讲解了RedLock算法原理及其在实际业务中的局限性。文章强调,在并发量不高的场景中手写分布式锁可行,但推荐使用更成熟的Redisson框架来实现分布式锁,以保证系统的稳定性和可靠性。
988 0
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
|
存储 Dubbo Java
分布式 RPC 底层原理详解,看这篇就够了!
本文详解分布式RPC的底层原理与系统设计,大厂面试高频,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 RPC 底层原理详解,看这篇就够了!
|
机器学习/深度学习 存储 运维
分布式机器学习系统:设计原理、优化策略与实践经验
本文详细探讨了分布式机器学习系统的发展现状与挑战,重点分析了数据并行、模型并行等核心训练范式,以及参数服务器、优化器等关键组件的设计与实现。文章还深入讨论了混合精度训练、梯度累积、ZeRO优化器等高级特性,旨在提供一套全面的技术解决方案,以应对超大规模模型训练中的计算、存储及通信挑战。
853 4
|
12月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
7月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
736 25
|
12月前
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
1513 0
|
8月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
341 1
Redis专题-实战篇二-商户查询缓存
|
7月前
|
缓存 运维 监控
Redis 7.0 高性能缓存架构设计与优化
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Redis 7.0高性能缓存架构,探索函数化编程、多层缓存、集群优化与分片消息系统,用代码在二进制星河中谱写极客诗篇。
1258 3
|
8月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。

热门文章

最新文章