分布式核心技术-高并发下分布式锁知识
产生背景:需要保证同一时间只有一个客户端可以对共享资源进行操作。比如优惠卷领取限制次数,商品库存超卖等。
其核心技术是为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。利用互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
避免共享资源并发操作导致数据问题可以用加锁来实现,本地锁(synchronize,lock等),分布式锁(redis,zookeeper等)。
设计分布式锁应该考虑的东西:
- 排他性:在分布式应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
- 容错性:分布式锁一定能得到释放,比如客户端崩溃或者网络中断。
- 满足可重入,高性能,高可用。
- 注意分布式锁的开销,锁粒度。
Redis实现分布式锁的坑
Redis实现分布式锁文档:http://www.redis.cn/commands.html#string
加锁 SETNX key value
sentx 的含义就是 SET if Not Exists , 有两个参数setnx(key,value),该方法是原则操作。
如果key不存在,则设置当前key成功,返回1。
如果存在,则设置失败,返回0。
解锁 del(key)
得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用del(key)。
配置锁超时expire(key,30s)
客户端崩溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也在一定时间后自动释放。
伪代码
methodA(){
String key = "coupon_66"
if(setnx(key,1) == 1){
expire(key,30,TimeUnit.MILLISECONDS)
try {
//做对应的业务逻辑
//查询用户是否已经领券
//如果没有则扣减库存
//新增领劵记录
} finally {
del(key)
}
}else{
//睡眠100毫秒,然后自旋调用本方法
methodA()
}
}
当前这个房多个命令之间不是原子操作,如setnx和expire之间,如果setnx成功,但是expire失败了,且宕机了,则这个资源就是死锁。
加锁:配置过期时间:保证原子性操作。
解锁:防止误删除,也要保证原子性操作。
分布式锁Lua脚本+redis原生代码编写
多个命令的原子性操作:采用lua脚本+redis,由于判断和删除是lua脚本执行,所以要么全部成功,要么全部失败。
//获取lock的值和传递的值一样,调用删除操作返回1,否则返回0
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//Arrays.asList(lockKey)是key列表,uuid是参数
Integer result = redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Arrays.asList(lockKey), uuid);
实例代码
import com.guslegend.util.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/coupon")
public class CouponController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/add")
public JsonData saveCoupon(@RequestParam(value = "couponId",required = true)int countId){
//防止被其他线程误删
String uuid = UUID.randomUUID().toString();
String lockKey = "lock:coupon:"+countId;
lock(countId,uuid,lockKey);
return JsonData.buildSuccess("添加成功");
}
private void lock(int countId, String uuid, String lockKey) {
//lua脚本
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, Duration.ofSeconds(30));
System.out.println(uuid+"加锁状态"+nativeLock);
if (nativeLock){
try {
// TODO 业务相关的逻辑
TimeUnit.SECONDS.sleep(10L);
}catch (Exception e){
}finally {
//解锁
Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(script,Long.class), Arrays.asList(lockKey,uuid));
System.out.println("解锁状态"+result);
}
}else {
// TODO 获取锁失败
try {
System.out.println("获取锁失败,睡眠5秒,进行自旋");
}catch (Exception e){
}
lock(countId,uuid,lockKey);
}
}
}
官方推荐的分布式锁方式:https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/


被折叠的 条评论
为什么被折叠?



