分布式微服务架构服务间对资源进行操作时由于服务多且使用分布式数据库,很难保证并发情况下资源操作顺序执行,需要提供一种安全可靠,统一管理的分布式锁组件及运维方式。

需具备功能点

  • 提供可配置分布式锁切面,减少微服务改造,尽量使用配置完成
  • 提供redis分布式锁加、解锁对应api
  • 提供分布式锁追踪功能记录加锁服务业务标识、请求txid、服务host信息、执行时间戳
  • 提供服务分布式参数获取及修改接口
  • 提供服务分布式锁参数配置管理服务及ui
  • 提供分布式锁监控及锁查询、解锁服务及ui
  • 分布式锁异常未释放钉钉消息告警功能(支持微信、钉钉、电话及web方式)

分布式锁的实现方式

  • 基于数据库的乐观锁实现分布式锁
  • 基于 zookeeper 临时节点的分布式锁(支持CP)
  • 基于 Redis 的分布式锁
  • 基于ETCD(最好)

分布式锁特性 

 

  • 互斥性:在任意时刻,只有一个客户端能持有锁
  • 同一性:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  • 可重入性:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

实现分布式锁

原理:利用Redis的单线程特性对共享资源进行串行化处理

什么是lua

lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Redis中使用lua的好处

  1. 减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行
  2. 原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件
  3. 复用性,客户端发送的脚本会永远存储在redis中,在内存中生成一个sha1 标识 (script load ) ,这意味着其他客户端可以复用这一脚本来完成同样的逻辑

 

代码片段

@Resource(name = "dlockRedisTemplate")
private StringRedisTemplate dlockRedisTemplate;

private static final String LOCK_SCRIPT_LUA = "return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) ";
private static RedisScript<String> scriptLock = new DefaultRedisScript<String>(LOCK_SCRIPT_LUA, String.class);

private static final String UNLOCK_SCRIPT_LUA = "if (redis.call('GET', KEYS[1]) == ARGV[1]) " +
        "then return redis.call('DEL',KEYS[1]) " +
        "else return 0 end";
private static RedisScript<Long> scriptUnlock = new DefaultRedisScript<Long>(UNLOCK_SCRIPT_LUA, Long.class);

//加锁
String execute = dlockRedisTemplate.execute(scriptLock, Collections.singletonList(lockKey), lockValue, String.valueOf(expire));
//解锁
Long result = dlockRedisTemplate.execute(scriptUnlock, Collections.singletonList(lockKey), lockValue);

分布式锁使用方式

引入组件依赖

<dependency>
    <groupId>com.xxxx.distributed.enhance</groupId>
    <artifactId>distributed-enhance-dlock</artifactId>
    <version>1.0.0</version>
</dependency>

application.yml配置

dlock:
  enable: true 
  implementMode: 1  #校验方式,1开启,0关闭
  redisMode: #分布式锁功能配置
    binderId: CLUSTER_REDIS
    timeToLive: 60s # 默认失效时间10分钟
    retryTimes: 3 # 重试次数,默认3次
    retryInterval: 100ms #重试间隔,默认200毫秒
    failAction: continue #异常处理方式,默认continue继续执行,可选择break抛出异常中断执行
/**
 * 公共方法进行加锁
 *
 * @param accountReq
 * @return
 */
@ApiOperation(value = "分布式加锁示例", notes = "使用redis加锁")
@RequestMapping(value = "/dlock", method = RequestMethod.POST)
public String dlock(@RequestBody AccountReq accountReq) {
    DLocksResult lock = redisDLock.lock(accountReq.getAcctId());
    return "lock";
}

/**
 * 公共方法进行解锁
 *
 * @param dLockResult
 * @return
 */
@ApiOperation(value = "分布式解锁示例", notes = "使用redis解锁")
@RequestMapping(value = "/undlock", method = RequestMethod.POST)
public BaseRsp undlock(@RequestBody DLocksResult dLockResult) {
    boolean unlock = redisDLock.unlock(dLockResult);
    return "unlock"
}

提供服务分布式参数获取及修改接口

http://localhost:8080/dlocksparam

锁集群异常,可以动态关闭分布式锁功能

{
"dLocksProperties-recordTxid": "false",
"implementMode": "1",
"dLocksProperties-enable": "false",
"dLocksProperties-implementMode": "1"
}