博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
缓存篇-使用Redis进行分布式锁应用
阅读量:4079 次
发布时间:2019-05-25

本文共 6016 字,大约阅读时间需要 20 分钟。

文章目录


前言

本地缓存的问题:每个微服务都得要有自己的缓存服务,数据更新时只能更新本服务的缓存 造成数据不一致。分布式项目时,本地锁只能锁住当前服务。


一、redis分布式锁的原理

redis命令中setnx表示同一时刻只能设置成功一个。

  • nx 表示notexist

二、使用redis进行分布式锁改造

抽取访问数据库的代码

private Map
> getDataFromDB() {
//线程进入之前,再次查缓存 String catelogJson = stringRedisTemplate.opsForValue().get("catelogJson"); if (StringUtils.isNotEmpty(catelogJson)) {
//如果缓存中有数据 //解析成json返回 return JSON.parseObject(catelogJson, new TypeReference
>>() {
}); } //如果缓存中没有,就查库 //查全部 List
categoryEntities = baseMapper.selectList(null); //查询所有一级目录 List
categoryLevel01 = getCategoryEntities(categoryEntities, 0L); Map
> parent_cid = categoryLevel01.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> { //遍历 拿到每个一级分类的二级分类 List
categoryLevel02 = getCategoryEntities(categoryEntities, v.getCatId()); List
catelog2Vos = null; if (categoryLevel02 != null) { catelog2Vos = (List
) categoryLevel02.stream().map(l2 -> { Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), l2.getName(), l2.getCatId().toString(), null); //三级分类 List
categoryLevel03 = getCategoryEntities(categoryEntities, l2.getCatId()); if (categoryLevel03 != null) { List
catelog3Vo = categoryLevel03.stream().map(l3 -> new Catelog3Vo(l3.getCatId().toString(), l3.getName(), l2.getCatId().toString())).collect(Collectors.toList()); catelog2Vo.setCatelog3VoList(catelog3Vo); } return catelog2Vo; }).collect(Collectors.toList()); } return catelog2Vos; })); //在返回数据之前,将数据放入缓存,保证原子性 //将数据转换成json串保存在redis中 stringRedisTemplate.opsForValue().set("catelogJson", JSON.toJSONString(parent_cid)); return parent_cid;}

1.第一阶段

1.使用setnx进行加锁

在redisTemplate中使用setIfAbsent进行setnx操作。

//进行分布式锁的改造public Map
> getCatelogJsonFromWithDistributeLock() {
//设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111"); if(lock){
//加锁成功.执行业务 Map
> dataFromDB = getDataFromDB(); //业务执行成功,需要解锁 stringRedisTemplate.delete("lock"); return dataFromDB; }else {
//加锁失败 //进行重试,自旋 //休眠 return getCatelogJsonFromWithDistributeLock(); }}

在这里插入图片描述

2.出现的问题(死锁)

在这里插入图片描述

2.第二阶段(解决第一阶段死锁问题)

1.设置过期时间

public Map
> getCatelogJsonFromWithDistributeLock() {
//设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111"); if(lock){
//加锁成功.执行业务 //设置过期时间 stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS); Map
> dataFromDB = getDataFromDB(); //业务执行成功,需要解锁 stringRedisTemplate.delete("lock"); return dataFromDB; }else {
//加锁失败 //进行重试,自旋 //休眠 return getCatelogJsonFromWithDistributeLock(); }}

2.出现问题(设置key和过期时间不是原子性)

在这里插入图片描述

3.解决key和过期时间不是原子性

使用setnx ex

Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",300,TimeUnit.SECONDS);

在这里插入图片描述

3.第三阶段(出现删锁问题)

当设置过期时间小于业务执行代码时间时,业务代码未执行完,锁被释放,其他线程应用此时再次抢到lock锁,之前线程执行代码完成后,需要删除锁,此时删除的就不再是自己的锁了,是其他线程的锁。导致问题出现。

在这里插入图片描述

1.解决删掉其他线程锁的问题(指定值为uuid)

//进行分布式锁的改造public Map
> getCatelogJsonFromWithDistributeLock() {
//指定值为uuid,确保删除的是当前锁 String redisValue = UUID.randomUUID().toString(); //设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", redisValue,300,TimeUnit.SECONDS); if(lock){
//加锁成功.执行业务 Map
> dataFromDB = getDataFromDB(); //业务执行成功,需要解锁 //判断是否是当前锁 if(redisValue.equals(stringRedisTemplate.opsForValue().get("lock"))){
//如果匹配,就执行删除 stringRedisTemplate.delete("lock"); } return dataFromDB; }else {
//加锁失败 //进行重试,自旋 //休眠 return getCatelogJsonFromWithDistributeLock(); }}

2.出现问题(依然不是原子性删除)

如果判断为true之后,redis中lock时间到期,此时删除的依然不是自己的key

在这里插入图片描述

4.第四阶段(原子操作解决删锁问题)

使用redis+lua脚本保证原子性

if redis.call("get",KEYS[1]) == ARGV[1]then    return redis.call("del",KEYS[1])else    return 0end
String luaScript="if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
//进行分布式锁的改造public Map
> getCatelogJsonFromWithDistributeLock() {
//指定值为uuid,确保删除的是当前锁 String redisValue = UUID.randomUUID().toString(); //设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", redisValue,300,TimeUnit.SECONDS); if(lock){
//加锁成功.执行业务 Map
> dataFromDB = getDataFromDB(); //业务执行成功,需要解锁 //判断是否是当前锁 String luaScript="if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";// if(redisValue.equals(stringRedisTemplate.opsForValue().get("lock"))){
// //如果匹配,就执行删除// stringRedisTemplate.delete("lock");// } Integer lock1 = stringRedisTemplate.execute(new DefaultRedisScript
(luaScript, Integer.class), Arrays.asList("lock"), redisValue); return dataFromDB; }else {
//加锁失败 //进行重试,自旋 //休眠 return getCatelogJsonFromWithDistributeLock(); }}

最后在finally执行删锁命令

//进行分布式锁的改造public Map
> getCatelogJsonFromWithDistributeLock() {
//指定值为uuid,确保删除的是当前锁 String redisValue = UUID.randomUUID().toString(); //设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", redisValue,300,TimeUnit.SECONDS); Map
> dataFromDB = null; if(lock){
try {
dataFromDB = getDataFromDB(); }catch (Exception e){
String luaScript="if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end"; Integer lock1 = stringRedisTemplate.execute(new DefaultRedisScript
(luaScript, Integer.class), Arrays.asList("lock"), redisValue); } return dataFromDB; }else {
return getCatelogJsonFromWithDistributeLock(); }}

总结

不论是加锁还是删锁,都得保证原子性。

转载地址:http://pexni.baihongyu.com/

你可能感兴趣的文章
微信小程序_石头剪刀布
查看>>
Webpack 实践技巧与建议
查看>>
微信小程序实战之天气预报
查看>>
微信小程序 wx.uploadFile(object), wx.downloadFile(object) API
查看>>
微信小程序 Image API
查看>>
微信小程序把玩 Record API
查看>>
微信小程序 Audio API
查看>>
微信小程序 Video API
查看>>
微信小程序 Storage API
查看>>
微信小程序 location API
查看>>
微信小程序 获取设备信息 API
查看>>
微信小程序 navigation API
查看>>
微信小程序 animation API
查看>>
微信小程序 canvas API
查看>>
微信小程序实战 《跨时空》旅行日记小程序
查看>>
微信小程序精品demo:电影推荐:位置,浏览记录历史,相册
查看>>
微信小程序完整精品demo:移动小商城:基于node,包含前后台
查看>>
微信小应用-小程序-demo-仿芒果TV (持续更新)
查看>>
微信小程序-自定义组件
查看>>
微信小程序学习用demo推荐:列表项左滑删除效果
查看>>