基于Redis实现分布式锁、限流操作——基于SpringBoot实现
- 本文总结了一种利用Redis实现分布式锁、限流的较优雅的实现方式
- 本文原理介绍较为通俗,希望能帮到有需要的人
- 本文的demo地址:https://gitee.com/rederxu/lock_distributed.git
一、本文基本实现
- 利用redis的key是否存在判断锁是否存在
- 利用redis的increment/decrement方法进行计数,从而实现限流
- 利用注解,对需要锁定/限流的方法进行配置(用起来超简单!!!)
- 利用SpringBoot的拦截器,在访问前判断并加锁,访问完成后释放锁
- 利用@ControllerAdvice捕获全局异常和终止访问
二、为什么选redis
- 由于redis的原子性(即其操作属于最基本的操作,要么执行要么不执行,要么全部成功,要么全部不成功),因此,用来计数、记录值是否存在具有较高优势。
- 此外,redis作为效率极高的程序外应用,能有效地独立保存数据,即使是多个服务,也能保持数据一致性(进而实现分布式控制)
- 本文总结的分布式锁、限流操作都基于redis实现
- 分布式锁:利用redis的key-value存储结构,判断key是否存在,key在即锁在
- 限流操作:利用redis的原子性,通过increment/decrement方法进行计数,超出则抛出异常,终止访问。
三、Spring Boot的实现方式
- 这里的Spring Boot可以理解为微服务中的一个子服务
- 以下实现是编码过程,运行效果见第四部分,核心实现见本节第3、5部分
1. Reids配置和服务实现
1.1 redis配置
server.port=8080
spring.redis.host=localhost
spring.redis.database=0
spring.redis.port=6379
package tech.xujian.lock.distributed.lockdistributed.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new StringRedisSerializer());return redisTemplate;}
}
1.2 redis服务实现
1.3 RedisService
package tech.xujian.lock.distributed.lockdistributed.service;public interface RedisService {void set(String k,String value);void set(String k,String value,int expireMinute);String get(String k);long getLong(String k);long increment(String k);long decrement(String k);String getAndDelete(String k);
}
1.4 RedisServiceImpl
package tech.xujian.lock.distributed.lockdistributed.service.impl;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import tech.xujian.lock.distributed.lockdistributed.service.RedisService;import java.util.concurrent.TimeUnit;@Service
public class RedisServiceImpl implements RedisService {@AutowiredRedisTemplate<String,String> redisTemplate;@Overridepublic void set(String k, String value) {redisTemplate.opsForValue().set(k,value);}@Overridepublic void set(String k, String value, int expireMinute) {redisTemplate.opsForValue().set(k,value,expireMinute, TimeUnit.MINUTES);}@Overridepublic String get(String k) {return redisTemplate.opsForValue().get(k);}@Overridepublic long getLong(String k) {String str = get(k);return str == null ? 0 : Long.parseLong(str);}@Overridepublic long increment(String k) {return redisTemplate.opsForValue().increment(k);}@Overridepublic String getAndDelete(String k) {return redisTemplate.opsForValue().getAndDelete(k);}@Overridepublic long decrement(String k) {return redisTemplate.opsForValue().decrement(k);}
}
2 注解实现
2.1 锁类型枚举
package tech.xujian.lock.distributed.lockdistributed.annotation;public enum RedisLockType {IP(1),USERNAME(2),COUNT(3),ANY(4);private int value;RedisLockType(int value){this.value = value;}public int getValue() {return value;}
}
2.2 注解声明
package tech.xujian.lock.distributed.lockdistributed.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {RedisLockType type() default RedisLockType.IP;
}
3 拦截去实现
3.1拦截器配置
package tech.xujian.lock.distributed.lockdistributed.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import tech.xujian.lock.distributed.lockdistributed.interceptor.RedisLockInterceptor;@Component
@Configuration
public class WebConfig implements WebMvcConfigurer {@AutowiredRedisLockInterceptor redisLockInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(redisLockInterceptor).addPathPatterns("/**");}
}
3.2拦截器实现
package tech.xujian.lock.distributed.lockdistributed.interceptor;import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import tech.xujian.lock.distributed.lockdistributed.annotation.RedisLock;
import tech.xujian.lock.distributed.lockdistributed.annotation.RedisLockType;
import tech.xujian.lock.distributed.lockdistributed.service.RedisService;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
@Slf4j
public class RedisLockInterceptor implements HandlerInterceptor {@AutowiredRedisService redisService;private static final String CACHE_KEY_REDIS_LOCK = "system:distributed:lock:";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if(handler instanceof HandlerMethod){HandlerMethod handlerMethod = (HandlerMethod) handler;RedisLock redisLock = handlerMethod.getMethodAnnotation(RedisLock.class);if(redisLock == null){return HandlerInterceptor.super.preHandle(request, response, handler);}switch (redisLock.type()){case IP:doIpLock(request);break;case USERNAME:break;case COUNT:doCountLock(handlerMethod);break;case ANY:break;}}return HandlerInterceptor.super.preHandle(request, response, handler);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {if(handler instanceof HandlerMethod){HandlerMethod handlerMethod = (HandlerMethod) handler;RedisLock redisLock = handlerMethod.getMethodAnnotation(RedisLock.class);if(redisLock == null){HandlerInterceptor.super.afterCompletion(request, response, handler, ex);return;}switch (redisLock.type()){case IP:releaseIpLock(request);break;case USERNAME:break;case COUNT:releaseCountLock(handlerMethod);break;case ANY:break;}}HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}private void doCountLock(HandlerMethod handlerMethod){String redisKey = CACHE_KEY_REDIS_LOCK + "count:" + handlerMethod.getMethod().getDeclaringClass() + ":" + handlerMethod.getMethod().toGenericString();redisKey = redisKey.replaceAll(" ","");log.info(redisKey);long countNow = redisService.getLong(redisKey);log.info("当前方法访问人数:" + countNow);Assert.isTrue(countNow < 10,"系统拥堵,请稍后重试!当前访问人数:" + countNow);redisService.increment(redisKey);}private void releaseCountLock(HandlerMethod handlerMethod) {String redisKey = CACHE_KEY_REDIS_LOCK + "count:" + handlerMethod.getMethod().getDeclaringClass() + ":" + handlerMethod.getMethod().toGenericString();redisKey = redisKey.replaceAll(" ","");long countNow = redisService.decrement(redisKey);log.info("当前方法访问人数:" + countNow);}private void doIpLock(HttpServletRequest request){String ip = ServletUtil.getClientIP(request);String redisKey = CACHE_KEY_REDIS_LOCK + "ip:" + ip;String value = redisService.get(redisKey);if(StrUtil.isEmpty(value)){redisService.set(redisKey,ip,1);return;}else{throw new RuntimeException("操作太快,请稍后重试");}}private void releaseIpLock(HttpServletRequest request) {String ip = ServletUtil.getClientIP(request);String redisKey = CACHE_KEY_REDIS_LOCK + "ip:" + ip;redisService.getAndDelete(redisKey);}}
4 全局异常拦截
package tech.xujian.lock.distributed.lockdistributed.exception;import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
public class RedisLockExceptionHandler {@ExceptionHandler(value =Exception.class)@ResponseBodypublic String exceptionHandler(Exception e){e.printStackTrace();return e.getMessage();}
}
5 Controller上进行注解
- 示例如下,一看就懂
- 需要加锁/限流的方法,只需要添加一个注解就像了
@RestController
@RequestMapping("/lock")
public class LockController {@AutowiredRedisService redisService;@RedisLock(type = RedisLockType.IP)@GetMapping("/test/ip")public String lockTest() throws InterruptedException {Thread.sleep(20000);return "succeed.";}@RedisLock(type = RedisLockType.COUNT)@GetMapping("/test/count")public String testCount() throws InterruptedException {Thread.sleep(20000);return "succeed.";}
}
四、运行效果
- 上文controller中使用sleep使接口卡顿20秒,用来示意接口访问需要时间
1. 正常访问
- 访问中

- 访问结束后

2. 锁
- 如果在访问的时候再次发起访问,则提示错误(前者访问继续执行)

3. 限流
- 超出流量限制时:

- 本文的demo地址:https://gitee.com/rederxu/lock_distributed.git