上篇主要讲了日常经常使用的哪些redis包及其配置的差异,这篇对基于redis的分布式锁,主要分享下自定义锁和redisson锁的使用和分析。

1.(自定义)Redis分布式锁

对于redis来说,非常适合做分布式锁来控制各个服务并发请求下造成的变量不一致、会串的问题。zookeeper锁其实也可以但不是很推荐,毕竟zk客户端是有某节点宕机自愈后、数据分发不同步的问题。

Redis锁的原理其实就是SETNX的原子性控制,建议一定要 设定超时时间,防止资源竞争过于激烈导致线程长时间获取不到锁、发生timeout最终导致死锁。而像Mysql数据库采用的是InnoDB模式,默认参数innodb_lock_wait_timeout设置锁等待的时间是50s,一旦数据库锁超过这个时间就会报错。

SETNX的完整语法:SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]

必选参数说明:

  • SET:命令
  • key:待设置的key
  • value:设置的key的value,最好为随机字符串

可选参数说明:

  • NX:表示key不存在时才设置,如果存在则返回 null

  • XX:表示key存在时才设置,如果不存在则返回NULL

  • PX millseconds:设置过期时间,过期时间精确为毫秒

  • EX seconds:设置过期时间,过期时间精确为秒

这里提供一个自定义的Redis锁给大家参考使用:

package com.test.common.utils;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.UUID;@Component
@Slf4j
public class RedisLockUtil {@Autowiredprivate RedisTemplate redisTemplate;/*** 默认锁超时时间(毫秒)*/private static final int DEFAULT_EXPIRE = 5*1000;/*** 锁名称前缀*/private static final String LOCK_PREFIX = "Coupon:";private RedisLock() {}/*** 获取锁* @param lockName    锁名称* @param acquireTimeout 等待获取锁的超时时间(毫秒)* @return  加锁成功后返回锁的唯一标识,未获取成功则返回null*/public String lock(String lockName, long acquireTimeout) {return lockWithTimeout(lockName, acquireTimeout, DEFAULT_EXPIRE);}/*** 获取锁* @param lockName   锁名称* @param acquireTimeout 等待获取锁的超时时间(毫秒)* @param timeout   锁超时时间,上锁后超过此时间则自动释放锁* @return   加锁成功后返回锁的唯一标识,未获取成功则返回null*/public String lockWithTimeout(String lockName, long acquireTimeout, long timeout){RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();RedisConnection redisConnection = connectionFactory.getConnection();/** 随机生成一个value */String identifier = UUID.randomUUID().toString();String lockKey = LOCK_PREFIX + lockName;int lockExpire = (int)(timeout / 1000);long end = System.currentTimeMillis() + acquireTimeout;  /** 获取锁的超时时间,超过这个时间则放弃获取锁 */while (System.currentTimeMillis() < end) {if (redisConnection.setNX(lockKey.getBytes(), identifier.getBytes())) {redisConnection.expire(lockKey.getBytes(), lockExpire);/** 获取锁成功,返回标识锁的value值,用于释放锁确认 */RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);return identifier;}/** 返回-1代表key没有设置超时时间,为key设置一个超时时间 */if (redisConnection.ttl(lockKey.getBytes()) == -1) {redisConnection.expire(lockKey.getBytes(), lockExpire);}try {Thread.sleep(10);} catch (InterruptedException e) {log.warn("获取分布式锁:线程中断!");Thread.currentThread().interrupt();}}RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);return null;}/*** 释放锁* @param lockName 锁名称* @param identifier 锁标识* @return*/public boolean releaseLock(String lockName, String identifier) {if (StringUtils.isEmpty(identifier)) return false;RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();RedisConnection redisConnection = connectionFactory.getConnection();String lockKey = LOCK_PREFIX + lockName;boolean releaseFlag = false;while (true) {try{/** 监视lock,准备开始事务 *///redisConnection.watch(lockKey.getBytes());byte[] valueBytes = redisConnection.get(lockKey.getBytes());/**  value为空表示锁不存在或已经被释放*/if(valueBytes == null){//redisConnection.unwatch();releaseFlag = false;break;}/** 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁 */String identifierValue = new String(valueBytes);if (identifier.equals(identifierValue)) {//redisConnection.multi();redisConnection.del(lockKey.getBytes());
//                  List<Object> results = redisConnection.exec();
//                  if (results == null) {
//                      continue;
//                  }releaseFlag = true;}
//              redisConnection.unwatch();break;}catch(Exception e){log.warn("释放锁异常", e);}}RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);return releaseFlag;}
}

使用Redis分布式锁用于领券场景的单元测试类:

package com.test.marketing.svc.client;import com.test.marketing.base.client.RedisLock;
import com.test.marketing.base.entity.CouponSummary;
import com.test.marketing.svc.init.MarketingServiceApplication;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;
import java.util.stream.IntStream;@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MarketingServiceApplication.class)
public class RedisLockTest {@Resourceprivate RedisLock redisLock;/***  测试并发领券/抢券场景下,使用redis分布锁累计领券总数*/@Testpublic void testCountReceive() {CouponSummary couponSummary = new CouponSummary();couponSummary.setReceivedQuantity(0l);IntStream.range(0,50).parallel().forEach( o->countReceiveQuantity(couponSummary) );log.info("Count Received quantities :" + couponSummary.getReceivedQuantity());Assert.assertNotEquals(50l, couponSummary.getReceivedQuantity().longValue());couponSummary.setReceivedQuantity(0l);IntStream.range(0,50).parallel().forEach( o->countReceiveQuantityWithLock("test",1000L, couponSummary) );log.info("Count Received quantities with lock :" + couponSummary.getReceivedQuantity());Assert.assertEquals(50l, couponSummary.getReceivedQuantity().longValue());}private void countReceiveQuantityWithLock(String lockName, Long acquireTimeout, CouponSummary couponSummary)  {String lockIdentify = redisLock.lock(lockName,acquireTimeout);if (StringUtils.isNotEmpty(lockIdentify)){countReceiveQuantity(couponSummary);redisLock.releaseLock(lockName, lockIdentify);}else{log.error("get lock failed.");}}private void countReceiveQuantity(CouponSummary couponSummary){couponSummary.setReceivedQuantity(couponSummary.getReceivedQuantity()+1);try {Thread.sleep(10l);} catch (InterruptedException e) {log.error("count thread was interrupted");}}}

2.Redisson分布式锁

Redisson是比较不错的开源框架,一般用于分布式锁或注册中心控制。Redisson实现的是可重入锁,接口RLock通过RedissonLock类来实现主要逻辑的,

2.1 使用案例

Redisson的使用方式其实很简单:

    @Resourceprivate RedissonClient redissonClient;//如下为代码片段://若不加锁则会产生超卖RLock lock = redissonClient.getLock("stock:" + productId);try {lock.lock(10, TimeUnit.SECONDS);int stockAmount = stockService.get(productId).getStockAmount();log.info("剩余库存量为:{}", stockAmount);if (stockAmount <= 0) {return false;}String orderNo = UUID.randomUUID().toString().replace(".", "").toUpperCase();/*原子性操作(例如减库存操作等)...*/} catch (Exception ex) {log.error("下单失败", ex);} finally {lock.unlock();}

备注:这里的getLock()默认是指的非公平锁,redisson也提过了公平锁getFairLock和多锁getMultiLock():RedissonMultiLock,也支持异步方式lock.tryLockAsync(100,10,TimeUnit.SECONDS):Future<T>,很方便使用。

其它细节更多可参考:Redisson基本用法 - 废物大师兄 - 博客园

2.2 配置Redisson

Redisson推荐redis集群节点至少为3个(奇数个),redis cluster集群版本个人推荐在5.0.3以上。

YAML配置方式(redisson.yml):

clusterServersConfig:idleConnectionTimeout: 10000pingTimeout: 1000connectTimeout: 10000timeout: 3000retryAttempts: 3retryInterval: 1500reconnectionTimeout: 3000failedAttempts: 3password: nullsubscriptionsPerConnection: 5clientName: nullloadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}subscriptionConnectionMinimumIdleSize: 1subscriptionConnectionPoolSize: 50slaveConnectionMinimumIdleSize: 5slaveConnectionPoolSize: 128masterConnectionMinimumIdleSize: 5masterConnectionPoolSize: 128readMode: "SLAVE"subscriptionMode: "SLAVE"nodeAddresses:- "redis://172.17.17.5:30004"- "redis://172.17.17.5:30005"- "redis://172.17.17.5:30006"scanInterval: 1000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.SnappyCodec> {}

配置类方式:

/*** redisson配置类*/
@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://10.102.5.11:6379","redis://10.102.5.11:6380").addNodeAddress("redis://redis://10.102.5.11:6381");RedissonClient redisson = Redisson.create(config);return redisson;}}

好了,基于redis的分布式锁就讨论到这里,下一讲还是回归大本营,谈谈Spring官方对Cache的顶层设计及其Redis的桥接是如何实现的 多级缓存分析篇(三) Spring本地缓存源码分析,欢迎来交流留言哦~~

多级缓存分析篇(二) 常用分布式锁分析相关推荐

  1. Java基础学习总结(147)——Java常用分布式锁技术方案

    前言 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题.所以自己结合实际工作中的一些经验和网上看到的一些资料,做一个讲解 ...

  2. 【分布式缓存系列】Redis实现分布式锁的正确姿势

    一.前言 在我们日常工作中,除了Spring和Mybatis外,用到最多无外乎分布式缓存框架--Redis.但是很多工作很多年的朋友对Redis还处于一个最基础的使用和认识.所以我就像把自己对分布式缓 ...

  3. CP模式(ZK)的分布式锁分析

    目录 JAVA的锁 死锁 分布式锁 CP模型的分布式锁 zk分布式锁的原理 zk节点操作 zk分布式锁代码实现 CP模式分布式锁总结 JAVA的锁 锁是用来控制多个线程访问共享资源的方式,一般来说,一 ...

  4. [安全攻防进阶篇] 二.如何学好逆向分析、逆向路线推荐及吕布传游戏逆向案例

    从2019年7月开始,我来到了一个陌生的专业--网络空间安全.初入安全领域,是非常痛苦和难受的,要学的东西太多.涉及面太广,但好在自己通过分享100篇"网络安全自学"系列文章,艰难 ...

  5. 我的物联网项目(二十七) 分布式锁粗心导致大量阻塞

    有天项目中某个业务出现了异常,查询相关日志显示如下: ### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQ ...

  6. 精尽 Redisson 源码分析 —— 可重入分布式锁 ReentrantLock

    1. 概述 在 Redisson 中,提供了 8 种分布锁的实现,具体我们可以在 <Redisson 文档 -- 分布式锁和同步器> 中看到.绝大数情况下,我们使用可重入锁(Reentra ...

  7. Redisson分布式锁分析

    2019独角兽企业重金招聘Python工程师标准>>> RedissonLock继承结构: RLock接口中定义的方法:主要分析tryLock()实现. **(一) Redisson ...

  8. 学习模式上的记录之统计篇二 Sigmaplot 非线性回归报告分析

    先说下线性回归的前提假设(LINE): L:Linearity 线性 因变量和每个自变量都是线性关系 I:Independence 独立性 对于所有的观测值,他们的误差项相互之间是独立的 N:Norm ...

  9. 【嵌入式】Libmodbus源码分析(二)-常用接口函数分析

    00. 目录 文章目录 00. 目录 01. modbus常用接口函数概述 02. modbus辅助接口函数 03. modbus功能接口函数 04. modbus数据处理 05. 附录 01. mo ...

最新文章

  1. python中idx是什么意思_在python中滚动idxmax()?
  2. js获取当前日期_vue项目中获取前后N天日期
  3. PostgreSQL 安装配置 (亲测可用)
  4. jndi mysql数据库_数据库连接池技术中dbcp、c3p0、jndi
  5. 后端系统架构 听课笔记
  6. 【牛客 - 302哈尔滨理工大学软件与微电子学院第八届程序设计竞赛同步赛(低年级)】小乐乐吃糖豆 (fIb博弈)
  7. MySQL 之 performance_schema
  8. 【耿老师公开课】反转!物联网火爆,开发者却很难入门?
  9. (解决)can't connect to redis-server
  10. c语言的各种类型的指针,简单总结C语言中各种类型的指针的概念
  11. SpringCloud工作笔记081---SpringCloud Hystrix Turbine(断路器聚合监控)的基本使用
  12. 远程服务器 上传公钥,SSH远程连接报错Permission denied (publickey)
  13. [轉]asp解析json
  14. 对话Man Group:对冲基金的达尔文式竞争
  15. php是什么币种的外汇英文,外汇币种英文_我要最常用的10个外汇币种和英文简称!_合拍网...
  16. 《加密与解密》笔记五(二)
  17. 红杉、IDG、北极光、顺为等投资大咖怎么看智能硬件
  18. 稻城亚丁徒步攻略,与你共同感受这片净土
  19. 火狐浏览器播放媒体没有声音怎么办?
  20. 对偶理论,敏感性分析(方述诚 笔记4

热门文章

  1. 专业之选—汇川技术钟情Redash
  2. mysql 网络io_如何在MySQL源码中看其网络IO模型
  3. 佩奇的数据分析学习之excel(三)
  4. 【JavaScript】localStorage的介绍和使用
  5. linux的账号权限管理
  6. 【历史上的今天】6 月 2 日:苹果推出了 Swift 编程语言;电信收购联通 C 网;OS X Yosemite 发布
  7. Keil uVision5修改工程名字
  8. 三维深度学习中的目标分类与语义分割
  9. plsql developer建立数据库
  10. 一键生成,LOGO免费设计小技巧