基于Redis实现分布式锁、限流操作(基于SpringBoot)的实现

基于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配置
  • properties中的配置
server.port=8080
#redis
spring.redis.host=localhost
spring.redis.database=0
spring.redis.port=6379
  • Java代码配置:
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){// 1.创建一个redis模板对象RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);// 设置连接器// 2.配置redis模板的普通键值对的序列化策略redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));// 3.配置redis模板的Hash键值对的序列化策略redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new StringRedisSerializer());// 4.返回该redis模板对象,加入到spring容器中return redisTemplate;}
}
1.2 redis服务实现
  • 包含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://TODO: 从request中通过header等读取到用户信息,然后参考doIpLock实现//略break;case COUNT://某个方法同时访问的人数限制的实现doCountLock(handlerMethod);break;case ANY://TODO: 该方法同时只允许一个人访问,实现方法与doCountLock一致//略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://TODO: 略,释放锁break;case COUNT://某个方法同时访问的人数限制的实现releaseCountLock(handlerMethod);break;case ANY://TODO: 略,释放锁break;}}HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}//访问数量限制lockprivate 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);//设限制100人,也可以考虑在注解中设置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);}//ip判断和lockprivate void doIpLock(HttpServletRequest request){//如果是IP锁,则到redis中读取是否已经存在keyString ip = ServletUtil.getClientIP(request);String redisKey = CACHE_KEY_REDIS_LOCK + "ip:" + ip;String value = redisService.get(redisKey);if(StrUtil.isEmpty(value)){//redis自然过期时间为1分钟,即为在一分钟内完成的请求会自动解锁,也可以考虑设置得更短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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/534966.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

迪杰斯特拉算法 代码

参考链接&#xff1a; 【路径规划】全局路径规划算法——Dijkstra算法&#xff08;含python实现 | c实现&#xff09;-CSDN博客 算法图解&#xff1a; 代码 def dijkstra(matrix, source):"""迪杰斯特拉算法实现Args:matrix (_type_): 用邻接矩阵表示带权图s…

SpringBoot(源码解析 + 实现底层机制)

文章目录 1.搭建SpringBoot底层机制开发环境1.创建maven项目2.使用Git管理项目&#xff08;可以略过&#xff09;1.创建一个github存储库2.克隆到本地&#xff0c;复制文件夹的内容3.粘贴到idea项目文件夹&#xff0c;将其作为本地仓库与远程仓库关联 3.pom.xml 引入父工程和场…

AI壁纸号一周增加上千粉丝,轻松变现的成功案例分享

前言 随着AI绘画技术的发展&#xff0c;传统的互联网副业壁纸号在新的技术加持下迎来了第二春。本文将分享一位壁纸号创作者的成功案例&#xff0c;并为大家提供创作门槛和硬件要求等相关信息。 该项目的创作门槛极低&#xff0c;基本上可以由AI完成内容创作。不过&#xff0…

LM358P/LM358DR/LM358DT/LM358DR2G运算放大器中文资料PDF数据手册引脚图功能

产品概述&#xff1a; LM358B 和 LM2904B 器件是行业标准运算放大器 LM358 和 LM2904 的下一代版本&#xff0c;其中包括两个高压 (36V) 运算放大器。这些器件为成本敏感型应用提供了卓越的价值&#xff0c;其特性包括低偏移&#xff08;300V&#xff0c;典型值&#xff09;、…

C++11新特性【右值引用】

文章目录 1. 什么是左值2. 什么是右值3. 左值引用4. 左值引用使用场景5. 右值引用6. 右值引用使用场景6.1 场景16.2 场景2 7. 完美转发 1. 什么是左值 左值不能根据字面意思来理解&#xff0c;不是说在左边的就是左值&#xff0c;例如&#xff1a; int main() {int a 0;int …

Windows11安装NodeJS18并配置环境变量

从官网下载&#xff0c;或者从百度网盘下载 解压下载的zip包&#xff1a; 重命名为nodejs&#xff1a; 在nodejs中添加cache和global两个目录&#xff1a; 将nodejs和nodejs\global添加到环境变量&#xff1a; 打开终端&#xff0c;输入&#xff1a; node -v接着配置…

高效备考2024年AMC10:吃透2000-2023年1250道真题(限时免费送)

我们今天继续来随机看5道AMC10真题&#xff0c;以及详细解析&#xff0c;这些题目来自1250道完整的官方历年AMC10真题库。通过系统研究和吃透AMC10的历年真题&#xff0c;参加AMC10的竞赛就能拿到好名次。 即使不参加AMC10竞赛&#xff0c;初中和高中数学一定会学得比较轻松、…

(开源项目)OpenHarmony、社区共建Sample合入要求

1.新增Sample功能不能重复于当前已有Sample的功能&#xff1b; 2.新增Sample的工程推荐使用ArkTS语言编写&#xff1b; 3.新增Sample的工程推荐使用Stage模型编写&#xff1b; 4.新增Sample的工程中需要包含UI自动化用例&#xff08;ohosTest工程模块&#xff09;&#xff0…

浅谈船舶岸电系统绝缘监测及故障定位需求及应用

彭姝麟 Acrelpsl 0 项目背景 随着现代船舶发展&#xff0c;船舶电气化程度越来越高&#xff0c;船舶电站的的容量也越来越大&#xff0c;随之而来的是电网的绝缘问题更加复杂化。船舶电力系统一般采用IT系统&#xff0c;即不接地系统。IT系统的优点是发生单相接地时不会出现TN…

2014

1,写出计算Ack(m,n)的递归算法 #include<iostream> using namespace std; int A(int m,int n){if(m0){return n1;}else if(m>0&&n0){return A(m-1,1);}else{return A(m-1,A(m,n-1));} }int main(){int m,n;cout<<"please input two number"&l…

SpringCloud Hystrix 断路器

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第五篇&#xff0c;即介绍 Hystrix 断路器。 二、概述 2.1 分布式系统面临的问题 复杂分布式体系结构中…

了解融云敏感词过滤规则和匹配效果

使用即时通讯&#xff08;IM&#xff09;服务内置了开箱即用的敏感词机制时&#xff0c;可能需要在在开发者后台 配置内置敏感词服务 2 添加敏感词。本文描述了敏感词字符串的设置规则和匹配效果。 简体、繁体智能过滤 设置中文简体敏感词后&#xff0c;对应繁体敏感词也会自…