分布式锁之-redis

发布于:2024-05-05 ⋅ 阅读:(24) ⋅ 点赞:(0)

什么是分布式锁?
即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
分布式锁应该具备哪些条件?
1:在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
2:高可用的获取锁与释放锁
3:高性能的获取锁与释放锁
4:具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
5:具备锁失效机制,即自动解锁,防止死锁
分布式锁的实现方式有那些?
1.使用关系型mysql数据库实现分布式锁。
2.使用redis非关系型数据库实现分布式锁。
3.使用zookeeper注册中心来实现分布式锁。
本文将详细介绍重要的分布式锁–给予redis的分布式锁。
A:Redisson 实现的分布式锁使用演示
B:自己实现的 Redis 分布式锁使用演示
数据库脚本

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for product_stock
-- ----------------------------
DROP TABLE IF EXISTS `product_stock`;
CREATE TABLE `product_stock`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `stock` int(11) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '产品库存表\n' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of product_stock
-- ----------------------------
INSERT INTO `product_stock` VALUES (1, 20);

SET FOREIGN_KEY_CHECKS = 1;

yml配置信息

spring:
  application:
    name: lock-redis
  # 数据库链接 要改成自己的数据链接信息
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${MYSQL_URL:127.0.0.1}:3306/lock-test?autoReconnect=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true
    username: root
    password: ${MYSQL_PASSWORD:abc123}
    hikari:
      max-lifetime: 500000
  # redis链接,要改成自己的链接配置
  redis:
    host: ${REDIS_URL:127.0.0.1}
    port: 6379
    password: ${REDIS_PASSWORD:abc123}
    database: 11

自己实现的分布式锁的方式。

  /**
     * 模拟减库存操作 - 自己实现 redis 锁接口
     *
     * @return str
     */
    @GetMapping("/reduceStockByMyLock/{id}")
    public String reduceStockByMyLock(@PathVariable("id") Integer id) {
        return productStockService.reduceStockByMyLock(id);
    }
 @SneakyThrows
    @Override
    public String reduceStockByMyLock(Integer id) {
        // requestId 确保每一个请求生成的都不一样,这里使用 uuid,也可以使用其他分布式唯一 id 方案
        String requestId = UUID.randomUUID().toString().replace("-", "");
        int expireTime = 10;
        bulkRedisLock.lock(requestId, expireTime);
        // 开启续命线程,
        Thread watchDog = bulkRedisLock.watchDog(expireTime, requestId);
        watchDog.setDaemon(true);
        watchDog.start();
        try {
            ProductStock stock = productStockMapper.selectById(id);
            if (stock != null && stock.getStock() > 0) {
                productStockMapper.reduceStock(id);
            } else {
                throw new RuntimeException("库存不足!");
            }
        } finally {
            watchDog.interrupt();
            bulkRedisLock.unlock(requestId);
        }
        return "ok";
    }

自己实现的配置锁BulkRedisLock

@Slf4j
@Component
@SuppressWarnings("all")
public class BulkRedisLock {
    private static final String LOCK_PREFIX = "redisLock";
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 尝试获取锁
     *
     * @param requestId  请求id
     * @param expireTime 过期时间  单位毫秒
     * @return true false
     */
    public boolean lock(String requestId, int expireTime) {
        // 也可以使用 lua 脚本 "return redis.call('set',KEYS[1], ARGV[1],'NX','PX',ARGV[2])"
        // 使用redis保证原子操作(判断是否存在,添加key,设置过期时间)
        while (true) {
            if (Boolean.TRUE.equals(stringRedisTemplate.boundValueOps(LOCK_PREFIX).
                    setIfAbsent(requestId, expireTime, TimeUnit.SECONDS))) {
                return true;
            }
        }
    }

    /**
     * 将锁释放掉
     * <p>
     * 为何解锁需要校验 requestId 因为不是自己的锁不能释放
     * 客户端A加锁,一段时间之后客户端A解锁,在执行 lock 之前,锁突然过期了。
     * 此时客户端B尝试加锁成功,然后客户端A再执行 unlock 方法,则将客户端B的锁给解除了。
     *
     * @param requestId 请求id
     * @return true false
     */
    public boolean unlock(String requestId) {
        // 这里使用Lua脚本保证原子性操作
        String script = "if  redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long res = stringRedisTemplate.execute(redisScript, Collections.singletonList(LOCK_PREFIX), requestId);
        return new Long(1).equals(res);
    }

    /**
     * 创建续命子线程
     *
     * @param time      操作预期耗时
     * @param requestId 唯一标识
     * @return 续命线程 Thread
     */
    public Thread watchDog(int time, String requestId) {
        return new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(time * 2 / 3);
                    //重置时间
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                            "return redis.call('expire', KEYS[1],ARGV[2]) " +
                            "else return '0' end";
                    List<Object> args = new ArrayList();
                    args.add(requestId);
                    args.add(time);
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                    stringRedisTemplate.execute(redisScript, Collections.singletonList(LOCK_PREFIX), args);
                } catch (Exception e) {
                    // sleep interrupted 是因为 sleep
                    // log.info("watchDog异常:{}", e.getMessage());
                    return;
                }
            }
        });
    }
}

mapper的xml文件

 <update id="reduceStock">
        update product_stock
        set stock = stock - 1
        where id = #{id}
    </update>

控制层入库contoller

 /**
     * 模拟减库存操作 - 自己实现 redis 锁接口
     *
     * @return str
     */
    @GetMapping("/reduceStockByMyLock/{id}")
    public String reduceStockByMyLock(@PathVariable("id") Integer id) {
        return productStockService.reduceStockByMyLock(id);
    }

使用edisson 实现的分布式锁使用
控制controller

 /**
     * 模拟减库存操作 - redisson 实现
     *
     * @return str
     */
    @GetMapping("/reduceStock/{id}")
    public String reduceStockByRedisson(@PathVariable("id") Integer id) {
        return productStockService.reduceStock(id);
    }

业务实现

@Override
    public String reduceStock(Integer id) {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        try {
            ProductStock stock = productStockMapper.selectById(id);
            if (stock != null && stock.getStock() > 0) {
                productStockMapper.reduceStock(id);
            } else {
                throw new RuntimeException("库存不足!");
            }
        } finally {
            lock.unlock();
        }
        return "ok";
    }

以上的是分布式锁之-redis 若需完整代码 可识别二维码后 给您发代码。
若友友们有更好的分布式锁的实现方式 请在评论区留下你可贵的分享 谢谢!!!!
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到