基于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 {
@Bean
public 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 {
@Autowired
RedisTemplate<String,String> redisTemplate;
@Override
public void set(String k, String value) {
redisTemplate.opsForValue().set(k,value);
}
@Override
public void set(String k, String value, int expireMinute) {
redisTemplate.opsForValue().set(k,value,expireMinute, TimeUnit.MINUTES);
}
@Override
public String get(String k) {
return redisTemplate.opsForValue().get(k);
}
@Override
public long getLong(String k) {
String str = get(k);
return str == null ? 0 : Long.parseLong(str);
}
@Override
public long increment(String k) {
return redisTemplate.opsForValue().increment(k);
}
@Override
public String getAndDelete(String k) {
return redisTemplate.opsForValue().getAndDelete(k);
}
@Override
public 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 {
@Autowired
RedisLockInterceptor redisLockInterceptor;
@Override
public 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 {
@Autowired
RedisService redisService;
private static final String CACHE_KEY_REDIS_LOCK = "system:distributed:lock:";
@Override
public 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);
}
@Override
public 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)
@ResponseBody
public String exceptionHandler(Exception e){
e.printStackTrace();
return e.getMessage();
}
}
5 Controller上进行注解
- 示例如下,一看就懂
- 需要加锁/限流的方法,只需要添加一个注解就像了
@RestController
@RequestMapping("/lock")
public class LockController {
@Autowired
RedisService 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?