分佈式鎖
分佈式鎖是用來控制分佈式系統對共享資源進行有序的操作,在分佈式系統中,如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那麼訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分佈式鎖。 實現原理包含:
- 互斥性:保證同一時間只有一個客戶端可以拿到鎖,也就是可以對共享資源進行操作
- 安全性:只有加鎖的服務纔能有解鎖權限
- 避免死鎖:通過expire給鎖設置過期時間,避免了線程長期佔用鎖而導致死鎖
- 保證加鎖與解鎖操作是原子性操作:通過setnx加鎖,當key不存在時,進行set操作,若key已經存在,則不做任何操作
在業務中的應用
1.創建分佈式鎖工具類
import java.util.Collections;
import redis.clients.jedis.Jedis;
public class RedisLock {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";//當key不存在時,進行set操作,若key已經存在,則不做任何操作
private static final String SET_WITH_EXPIRE_TIME = "PX";//加一個過期的設置,單位毫秒,具體時間由第五個參數決定
private static final Long RELEASE_SUCCESS = 1L;
/**
* 嘗試獲取分佈式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請求標識
* @param expireTime 超期時間
* @return 是否獲取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 釋放分佈式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請求標識
* @return 是否釋放成功
*/
//參數KEYS[1]賦值爲lockKey,ARGV[1]賦值爲requestId,首先獲取鎖對應的value值,檢查是否與requestId相等,如果相等則刪除鎖(解鎖)
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
2.創建Redis操作工具類
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
@Service
public class RedisService {
@Autowired
private JedisPool jedisPool;
/**
* 獲取對象
*/
public <T> T get(String key,Class<T> clazz){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String sval = jedis.get(key);
//將String轉換爲Bean
T t = stringToBean(sval,clazz);
return t;
}finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 設置對象
*/
public <T> boolean set(String key,T value){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//將Bean轉換爲String
String s = beanToString(value);
if(s == null || s.length() <= 0) {
return false;
}
jedis.set(key, s);
return true;
}finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 減少值
*/
public <T> Long decr(String key){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//返回value減1後的值
return jedis.decr(key);
}finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 將字符串轉換爲Bean對象
*/
@SuppressWarnings("unchecked")
public static <T> T stringToBean(String str,Class<T> clazz) {
if(str == null || str.length() == 0 || clazz == null) {
return null;
}
if(clazz == int.class || clazz == Integer.class) {
return ((T) Integer.valueOf(str));
}else if(clazz == String.class) {
return (T) str;
}else if(clazz == long.class || clazz == Long.class) {
return (T) Long.valueOf(str);
}else if(clazz == List.class) {
return JSON.toJavaObject(JSONArray.parseArray(str), clazz);
}else {
return JSON.toJavaObject(JSON.parseObject(str), clazz);
}
}
/**
* 將Bean對象轉換爲字符串類型
*/
public static <T> String beanToString(T value) {
if(value == null){
return null;
}
Class<?> clazz = value.getClass();
if(clazz == int.class || clazz == Integer.class) {
return ""+value;
}else if(clazz == String.class) {
return (String)value;
}else if(clazz == long.class || clazz == Long.class) {
return ""+value;
}else {
return JSON.toJSONString(value);
}
}
}
3.秒殺業務調用
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import cn.com.app.redis.RedisLock;
import cn.com.app.redis.RedisService;
@Controller
public class RedisLockController implements InitializingBean {
@Autowired
private RedisService redisService;
@Autowired
private JedisPool jedisPool;
//內存標記,減少redis訪問
private HashMap<String, Boolean> localOverMap = new HashMap<String, Boolean>();
/**
* 系統初始化的時把商品庫存加入到緩存中
*/
@Override
public void afterPropertiesSet() throws Exception {
//設置默認商品庫存
redisService.set("goodsStock", "10");
//添加內存標記
localOverMap.put("goodsStock", false);
}
/**
* 請求秒殺,redis分佈式鎖
*/
@RequestMapping(value="/miaoshalock")
@ResponseBody
public String miaoshalock(HttpServletRequest request,@RequestParam("userid")String userid){
boolean over = localOverMap.get("goodsStock");
if(over) {
System.out.println("秒殺結束");
return "秒殺結束";
}
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String requestId = UUID.randomUUID().toString();
boolean isOk = RedisLock.tryGetDistributedLock(jedis,"LOCK:goodsStock",requestId,10000);//獲取鎖
if(isOk){
try{
int count = redisService.get("goodsStock",Integer.class);
if(count <= 0){
localOverMap.put("goodsStock", true);
System.out.println("庫存不足");
return "庫存不足";
}
String orderInfo = redisService.get("order"+userid,String.class);
if(orderInfo != null){
System.out.println("重複下單");
return "重複下單";
}
long stock = redisService.decr("goodsStock");
System.out.println("秒殺成功,剩餘庫存:"+stock);
redisService.set("order"+userid, userid+"_"+UUID.randomUUID().toString());
/**
* 數據庫操作減少庫存,下訂單,在一個事務中
*/
}finally{
RedisLock.releaseDistributedLock(jedis,"LOCK:goodsStock",requestId);//釋放鎖
}
}else{
System.out.println("該線程未拿到鎖");
return "秒殺失敗";
}
}finally {
if(jedis != null) {
jedis.close();
}
}
return "秒殺成功";
}
}