redis缓存 实现分布式锁

解决方案
在数据库中查询没有 为null 那么将这个查询字段作为key,
value 为null 存储到redis 中 并为这个key 加入过期时间
------------------------------------------------------------------
缓存没有命中.....
------->查询数据库操作.
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
------->查询数据库操作
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
缓存没有命中.....
------->查询数据库操作
但是此时发现 查询数据库操作输出了多次数据库查询
这是因为在 锁住代码块的同时 其他请求也在访问,但是锁里面的代码还在运行 这期间是获取不到数据的
就会一直出现查询数据库期间 一起出现缓存没有命中的情况
当去数据库查询完成返回数据的时候 且返回的数据不为空
但是在向缓存中写入数据的同时 其他数据已经抢到 之前释放的锁了 然后继续查询数据库操作 所以才会出现以上情况

释放锁和查询缓存结果时序问题

如果product 服务有多个 当请求打在相同的服务 不同的节点的时候 本地锁无法做到
针对本地锁的问题 我们需要通过分布式锁来解决 但是不能说本地锁就没用了
我们需要结合本地锁和分布式锁来解决 并发量大的情况下 确保分布式锁的压力较小 且访问数据库的操作 安全 不被其他线程篡改
如果大量访问直接打到分布式锁,分布式锁难以支撑这么多的并发 所以需要用本地锁 只放一个线程抢到锁
多个相同服务 都只放一个请求去抢分布式的锁 那么就大大降低了分布式锁的压力
分布式锁的原理

.分布式锁的常用解决方案
可以利用MySQL隔离性:唯一索引 ``` ```sql use test; CREATE TABLE `DistributedLock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁名', `desc` varchar(1024) NOT NULL DEFAULT '备注信息', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成', PRIMARY KEY (`id`), UNIQUE KEY `uidx_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法'; //数据库中的每一条记录就是一把锁,利用的mysql唯一索引的排他性 lock(name,desc){ insert into DistributedLock(`name`,`desc`) values (#{name},#{desc}); } unlock(name){ delete from DistributedLock where name = #{name} } ``` 可以利用拍他说来实现 select .... where ... for update; 乐观锁:乐观的任务数据不会出现数据安全问题,如果出现了就重试一次 ```sql select ...,version; update table set version+1 where version = xxx ``` ### 2.2 Redis setNX: setNX(key,value) :如果key不存在那么就添加key的值,否则添加失败,Redisson ### 2.3Zookeeper
zookeeper 可以创建临时有序节点 并且可以相互监听
判断当前节点是不是整个节点中最小的
如果是 获取锁
当该节点使用完毕 释放锁 删除该节点
因为其他节点会监听节点的删除事件 然后判断002是否是所有节点中最小的
如果是最小的 获取锁
如果当前节点突然下先 那么其他节点就会监听比他们小的节点

/**
* 根据父编号 获取对应的子菜单信息
* @param list
* @param parentCid
* @return
*/
private List<CategoryEntity> queryByParentCid(List<CategoryEntity> list,Long parentCid){
List<CategoryEntity> collect = list.stream().filter(item -> {
return item.getParentCid().equals(parentCid);
}).collect(Collectors.toList());
return collect;
}
/* //本地缓存
// private Map<String,Map<String,List<Catalog2VO>>> cache=new HashMap<>();*/
/**
* 查询出所有的二级和三级分类的数据
* 并封装为Map<String, Catalog2VO>对象
* @return
*/
@Override
public Map<String, List<Catalog2VO>> getCatelog2JSON() {
String key = "catalogJSON";
// 从Redis中获取分类的信息
String catalogJSON = stringRedisTemplate.opsForValue().get(key);
if(StringUtils.isEmpty(catalogJSON)){
System.out.println("缓存没有命中.....");
// 缓存中没有数据,需要从数据库中查询
Map<String, List<Catalog2VO>> catelog2JSONForDb = getCatelog2JSONDbWithRedis();
return catelog2JSONForDb;
}
System.out.println("缓存命中了....");
// 表示缓存命中了数据,那么从缓存中获取信息,然后返回
Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
});
return stringListMap;
}
public Map<String, List<Catalog2VO>> getCatelog2JSONDbWithRedis() {
String keys = "catalogJSON";
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
if(lock){
System.out.println("获取分布式锁成功....");
Map<String, List<Catalog2VO>> data = null;
try {
data = getDataForDB(keys);
} catch (Exception e) {
e.printStackTrace();
} finally {
String srctip="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
"then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";//定义redis 删除脚本
//通过redis 的lua脚本实现查询和删除的原子性
stringRedisTemplate.execute(new DefaultRedisScript<Long>(srctip,Long.class)
,Arrays.asList("lock"),uuid);
}
return data;
}else {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获取锁失败.....");
return getCatelog2JSONDbWithRedis();
}
}
/* public Map<String, List<Catalog2VO>> getCatelog2JSONDbWithRedisXXXXXXXXXXXXXXX() {
String keys = "catalogJSON";
//加锁 在执行插入操作的同时设置了过期时间相当于 setNX+ttl
String uuid = UUID.randomUUID().toString();
//如果设置30秒 操作数据库还是没有结束 第二个请求过来 设置了可以 当你业务完成后删除key 时可能删除的是别人的key---->设置查询锁的标识UUID来区分
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
if(lock){
//给对应的key设置过期时间 即使出现异常 到了10秒后也会将key删除 但是如果在expire之前就中断了 那么还是会造成死锁的问题----》这时我们希望setNx和expire 之间保证原子性 相当于setNx
// stringRedisTemplate.expire("lock", 10,TimeUnit.SECONDS);
//加锁成功 如果这里查询数据库出现异常的话 就会抛出异常下面的代码不会去执行 那么就会造成死锁
Map<String, List<Catalog2VO>> data = null;
try {
data = getDataForDB(keys);