1.项目架构说明
最近公司新项目上了很多新技术,如消息队列、微服务、集群、分库分表等,服务才分称多个微服务,关联模块放一起组成一个服务,每个服务还可能使多机的集群模式,通过一个统一的网关控制;
2.遇到的问题
微服务之间,机子集群直接就需要交互,并发竞争问题就不可避免,因为不是同一台机子,项目运行的时候,一个功能并不保证只请求一台机子,而是请求了多台机子,造成并发和竞争的问题;
3.引入分布锁
对于当前的问题,经过会议讨论,得出两个解决方案,一是使用消息队列有序读取,多机按取余序处理;二是使用缓存的分布锁,公司最近用redis,所以就决定用redis作分布锁;因此,对于redis分布锁,简单应用也有两个方式,如下:
1)使用setnx key value
这个方式存在死锁的弊端,因此在操作是需要一些操作,setnx 命令设置值时,返回1为成功,0为已有数据,因此可以key 不变,value 为时间戳,然后在每次setnx 返回0 后,去判断时间戳的超时问题(超时值自己设定),未超时不动作(没有获得锁),超时再用getset key value ,将新的时间戳设置进去,获取旧值判断是否超时(再一次判断),未超时不动作(没得锁),超时了即获得锁;
方式1)的方式仍有弊端,就是分布式多服务间,多台机子直接时间的统一性问题,如果一台机子系统时间比其他机子时间要晚(超过超时时间值),那这台机子将永远获得锁,因此方式2)可以规避这个问题;
2)使用 set key value ex|px nx
这个方式ex 为秒, px 为毫秒,首先设置值时返回ok成功,再设置同样的key值时返回空,返回成功视为获得锁,返回空视为未获得锁,时间统一以redis 内部时间,因此可作为分布锁,消除死锁,也避免多服务系统时间不统一的问题;
代码例子:
/**
* 不存在时插入 ,可用做分布锁
*
* @param key
* @param value
* @param time
* @param unit
* @return
*/
public static boolean setNx(String key, Object value, long time, TimeUnit unit) {
Boolean flag = REDIS_TEMPLATE.opsForValue().setIfAbsent(key, JSONObject.toJSONString(value), time, unit);
if(flag == null || !flag) {
return false;
}else {
return true;
}
}