當前所做的項目有這樣一個場景:新增數據的時候生成的流水號,是查詢數據庫表最大流水號加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");//釋放鎖
}
}