SpringBoot分散式鎖之redis實現
阿新 • • 發佈:2020-07-18
一、pom檔案配置
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-data-redis</artifactId> 4 </dependency> 5
二、yml檔案配置
spring: redis: host: 192.168.80.88 port: 6379 password: database: 5 timeout: 2000ms jedis: pool: max-idle: 20 max-wait: 1000ms max-active: 100
三、啟動配置
1 import org.springframework.boot.web.servlet.FilterRegistrationBean; 2 import org.springframework.context.annotation.Bean; 3 import org.springframework.context.annotation.Configuration; 4 import org.springframework.core.Ordered; 5 6 import java.util.Collections; 7 import java.util.List;8 9 10 @Configuration 11 public class RedisDistributedLockConfig { 12 13 /** 14 * 新增RequestId 15 * @return 16 */ 17 @Bean 18 public FilterRegistrationBean requestIdFilter() { 19 RequestIdFilter reqestIdFilter = new RequestIdFilter(); 20 FilterRegistrationBean registrationBean = newFilterRegistrationBean(); 21 registrationBean.setFilter(reqestIdFilter); 22 List<String> urlPatterns = Collections.singletonList("/*"); 23 registrationBean.setUrlPatterns(urlPatterns); 24 registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); 25 return registrationBean; 26 } 27 }
四、其它配置
1、註解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Description //標註在方法上的分散式鎖註解 **/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DistributedLockAble { String key(); String prefix() default "disLock:"; long expire() default 10L; // 預設10s過期 }
2、註解aop
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @Description //分散式鎖註解處理切面 **/ @Aspect @Slf4j @Component public class DistributedLockAspect { @Autowired private RedisUtils lock; public DistributedLockAspect(RedisUtils lock) { this.lock = lock; } /** * 在方法上執行同步鎖 */ @Around(value = "@annotation(lockable)") public Object distLock(ProceedingJoinPoint point, DistributedLockAble lockable) throws Throwable { boolean locked = false; String key = lockable.prefix() + lockable.key(); try { locked = lock.lock(key, WebUtil.getRequestId(), lockable.expire()); if(locked) { return point.proceed(); } else { log.error("Did not get a lock for key {}", key); throw new RuntimeException("分散式鎖獲取失敗"); } } catch (Exception e) { throw e; } finally { if(locked) { if(!lock.unLock(key, WebUtil.getRequestId())){ log.warn("Unlock {} failed, maybe locked by another client already. ", lockable.key()); } } } } }
3、redis工具類
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import java.util.Collections; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * @Description //redis工具類 **/ @Component @Slf4j public class RedisUtils { @Autowired private StringRedisTemplate redisTemplate; private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; private static final Long RELEASE_LOCK_SUCCESS_RESULT = 1L; private long waitMillisPer = 1000L; public void cacheValue(String key, String value){ redisTemplate.opsForValue().set(key, value); }
public String getValueByKey(String key){ return redisTemplate.opsForValue().get(key); } public void deleteByKey(String key){ redisTemplate.delete(key); } /** * 嘗試獲取鎖(立即返回) * @param key 鎖的redis key * @param value 鎖的value * @param expire 過期時間/秒 * @return 是否獲取成功 */ public boolean lock(String key, String value, long expire) { return redisTemplate.opsForValue().setIfAbsent(key, value, expire, TimeUnit.SECONDS); } /** * 嘗試獲取鎖,並至多等待timeout時長 * * @param key 鎖的redis key * @param value 鎖的value * @param expire 過期時間/秒 * @param timeout 超時時長 * @param unit 時間單位 * @return 是否獲取成功 */ public boolean lock(String key, String value, long expire, long timeout, TimeUnit unit) { long waitMillis = unit.toMillis(timeout); long waitAlready = 0; while (!redisTemplate.opsForValue().setIfAbsent(key, value, expire, TimeUnit.SECONDS) && waitAlready < waitMillis) { try { Thread.sleep(waitMillisPer); } catch (InterruptedException e) { log.error("Interrupted when trying to get a lock. key: {}", key, e); } waitAlready += waitMillisPer; } if (waitAlready < waitMillis) { return true; } log.warn("<====== lock {} failed after waiting for {} ms", key, waitAlready); return false; } /** * 釋放鎖 * @param key 鎖的redis key * @param value 鎖的value */ public boolean unLock(String key, String value) { DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class); long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value); return Objects.equals(result, RELEASE_LOCK_SUCCESS_RESULT); } }
4、其它工具類
import java.util.UUID; /** * @Description //請求id管理 **/ public class WebUtil { public static final String REQ_ID_HEADER = "Req-Id"; private static final ThreadLocal<String> REQ_ID_THREAD_LOCAL = new ThreadLocal<>(); public static void setRequestId(String requestId) { REQ_ID_THREAD_LOCAL.set(requestId); } public static String getRequestId(){ String requestId = REQ_ID_THREAD_LOCAL.get(); if(requestId == null) { requestId = getUid(); REQ_ID_THREAD_LOCAL.set(requestId); } return requestId; } public static void removeRequestId() { REQ_ID_THREAD_LOCAL.remove(); } public static String getUid(){ return UUID.randomUUID().toString().toUpperCase().replaceAll("-", ""); } }
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @Description //請求id攔截器 **/ public class RequestIdFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String reqId = httpServletRequest.getHeader(WebUtil.REQ_ID_HEADER); //沒有則生成一個 if (StringUtils.isEmpty(reqId)) { reqId = WebUtil.getUid(); } WebUtil.setRequestId(reqId); try { filterChain.doFilter(servletRequest, servletResponse); } finally { WebUtil.removeRequestId(); } } }
五、使用
@DistributedLockAble(key = "distributed_lock", expire = 100) public void test_method(String key){ // TODO }
六、打完收工~