什么是分布式锁?
即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
分布式锁应该具备哪些条件?
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.Driverurl: jdbc:mysql://${MYSQL_URL:127.0.0.1}:3306/lock-test?autoReconnect=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=trueusername: rootpassword: ${MYSQL_PASSWORD:abc123}hikari:max-lifetime: 500000# redis链接,要改成自己的链接配置redis:host: ${REDIS_URL:127.0.0.1}port: 6379password: ${REDIS_PASSWORD:abc123}database: 11
自己实现的分布式锁的方式。
/*** 模拟减库存操作 - 自己实现 redis 锁接口** @return str*/@GetMapping("/reduceStockByMyLock/{id}")public String reduceStockByMyLock(@PathVariable("id") Integer id) {return productStockService.reduceStockByMyLock(id);}
@SneakyThrows@Overridepublic 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";@Resourceprivate 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_stockset stock = stock - 1where 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);}
业务实现
@Overridepublic 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 若需完整代码 可识别二维码后 给您发代码。
若友友们有更好的分布式锁的实现方式 请在评论区留下你可贵的分享 谢谢!!!!