当前所做的项目有这样一个场景:新增数据的时候生成的流水号,是查询数据库表最大流水号加1,并发情况下流水号有可能会重复,这时候我们首先想到的是方法上加synchronized,一个单词搞定,但是如果项目是做了集群部署,就相当于一个项目部署到了多台服务器上,还是会出现并发的情况的,因为synchronized是jvm层面的它只对单机服务有作用。
解决方案:使用分布式锁,有基于redis的有基于zookper的,这里采用了redis的框架Redisson
直接上代码
1、引入Jar
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.7.0</version>
</dependency>
import jxl.demo.Demo;
import org.redisson.Redisson;
import org.redisson.config.Config;
import com.yonyou.me.utils.PropertiesUtils;
import com.yonyou.me.utils.StringUtils;
public class RedissonManager {
private static Config config = new Config();
// 声明redisso对象
private static Redisson redisson = null;
private static String realPath = Demo.class.getClassLoader()
.getResource("application.properties").getPath();
// 实例化redisson
static {
String path = PropertiesUtils.readValue(realPath, "redis.url");
path = StringUtils.subString(path, "//", "?");
config.useSingleServer().setAddress(path)
.setPassword("yonyou123");
// 得到redisson对象
redisson = (Redisson) Redisson.create(config);
}
// 获取redisson对象的方法
public static Redisson getRedisson() {
return redisson;
}
}
这里是RedissonManager用到的读取配置文件properties的路径类
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
public class PropertiesUtils {
// 根据key读取value
public static String readValue(String filePath, String key) {
Properties props = new Properties();
try {
InputStream in = new BufferedInputStream(new FileInputStream(
filePath));
props.load(in);
String value = props.getProperty(key);
return value;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
这里是RedissonManager用到的截取字符串的公共类
public class StringUtils {
/**
* 判断是否为null 或空值
* @param str
* @return
*/
public static boolean isBlank(String str){
return str == null || str.trim().equals("");
}
/**
* 判断是否非null 或非空值
* @param str
* @return
*/
public static boolean isNotBlank(String str){
return str != null && !str.trim().equals("");
}
public static boolean equals(String a, String b) {
if (a == null) {
return b == null;
}
return a.equals(b);
}
public static boolean equalsIgnoreCase(String a, String b) {
if (a == null) {
return b == null;
}
return a.equalsIgnoreCase(b);
}
/**
* 截取字符串str中指定字符 strStart、strEnd之间的字符串
*
* @param string
* @param str1
* @param str2
* @return
*/
public static String subString(String str, String strStart, String strEnd) {
/* 找出指定的2个字符在 该字符串里面的 位置 */
int strStartIndex = str.indexOf(strStart);
int strEndIndex = str.indexOf(strEnd);
/* index 为负数 即表示该字符串中 没有该字符 */
if (strStartIndex < 0) {
return "字符串 :---->" + str + "<---- 中不存在 " + strStart + ", 无法截取目标字符串";
}
if (strEndIndex < 0) {
return "字符串 :---->" + str + "<---- 中不存在 " + strEnd + ", 无法截取目标字符串";
}
/* 开始截取 */
String result = str.substring(strStartIndex, strEndIndex).substring(strStart.length());
return result;
}
}
3、锁的获取和释放
import java.util.concurrent.TimeUnit;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Component;
@Component
public class DistributedRedisLock {
//从配置类中获取redisson对象
private static Redisson redisson = RedissonManager.getRedisson();
private static final String LOCK_TITLE = "redisLock_";
//加锁
public static boolean acquire(String lockName){
//声明key对象
String key = LOCK_TITLE + lockName;
//获取锁对象
RLock mylock = redisson.getLock(key);
//加锁,并且设置锁过期时间,防止死锁的产生
mylock.lock(2, TimeUnit.MINUTES);
//加锁成功
return true;
}
//锁的释放
public static void release(String lockName){
//必须是和加锁时的同一个key
String key = LOCK_TITLE + lockName;
//获取所对象
RLock mylock = redisson.getLock(key);
//释放锁(解锁)
mylock.unlock();
}
}
4,业务逻辑中使用分布式锁
/**
* 模拟获取最大流水号+1生成新的流水号,分布式高并发处理
*/
@RequestMapping(value = "/test", method = RequestMethod.GET)
public void test() {
DistributedRedisLock.acquire("mylock");//加锁
String sql = "select * from (select * from qcjs_erpinspecinter where dr=0 order by netweight desc) where rownum=1";
ErpInspecInterVO vo;
try {
vo = iBaseQueryBS.queryVOsBySql(sql, ErpInspecInterVO.class).get(0);
ErpInspecInterVO newVo = new ErpInspecInterVO();
BeanUtils.copyProperties(vo, newVo);
newVo.setId(null);
newVo.setTs(null);
newVo.setStatus(2);
newVo.setNetweight(vo.getNetweight().add(new BigDecimal(1)));
ErpInspecInterBillVO billVO = new ErpInspecInterBillVO();
billVO.setHead(newVo);
erpInspecInterService.save(billVO);
} catch (Exception e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}finally{
DistributedRedisLock.release("mylock");//释放锁
}
}