基于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.mzph.cn/news/740939.shtml

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

相关文章

《MySQL数据库》day2--连接查询、子查询、union、limit、DML语句

文章目录 1.把查询结果去除重复记录 -》distinct2.连接查询2.1什么是连接查询&#xff1f;2.2连接查询的分类2.3笛卡尔积现象2.4内连接2.4.1内连接之等值连接。2.4.2内连接之非等值连接2.4.3内连接之自连接 2.5外连接2.6三张表&#xff0c;四张表怎么连接&#xff1f; 3.子查询…

SA3D:基于 NeRF 的三维场景分割方法

Paper: Cen J, Zhou Z, Fang J, et al. Segment anything in 3d with nerfs[J]. Advances in Neural Information Processing Systems, 2024, 36. Introduction: https://jumpat.github.io/SA3D/ Code: https://github.com/Jumpat/SegmentAnythingin3D SA3D 是一种用于 NeRF 表…

Java项目:48 ssm008医院门诊挂号系统+jsp(含文档)

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本选题则旨在通过标签分类管理等方式实现 管理员&#xff1b;个人中心、药房管理、护士管理、医生管理、病人信息管理、科室信息管理、挂号管…

【每日一题】2864. 最大二进制奇数-2024.3.13

题目&#xff1a; 2864. 最大二进制奇数 给你一个 二进制 字符串 s &#xff0c;其中至少包含一个 1 。 你必须按某种方式 重新排列 字符串中的位&#xff0c;使得到的二进制数字是可以由该组合生成的 最大二进制奇数 。 以字符串形式&#xff0c;表示并返回可以由给定组合…

基于GIS技术的智慧农业大数据平台解决方案

1.建设背景 国务院促进大数据发展纲要 纲要提出大力推动政府信息系统和公共数据互联开放共享&#xff0c;加快政府信息平台整合&#xff0c;推动数据资源整合&#xff0c;提升治理水平。按照“五个统一”建设“大平台“&#xff0c;融合“大数据”&#xff0c;构建“大系统”落…

Leetcode-热题100-持续更新

时间不够&#xff0c;只能背诵哎&#xff0c;没办法&#xff0c;难题不写&#xff0c;简单题和中等题。 def majorityElement(nums):# 使用字典 count 来记录每个元素的出现次数count {}for num in nums:if num in count:count[num] 1else:count[num] 1length len(nums)#…

喜报!聚铭网络实力入选2024年度扬州市网络安全技术支撑服务机构

近日&#xff0c;中共扬州市委网络安全和信息化委员会办公室正式公布了“2024年度扬州市网络安全技术支撑服务机构”名单&#xff0c;聚铭网络凭借其卓越的技术实力与优质的安服能力&#xff0c;在众多竞争者中脱颖而出&#xff0c;光荣上榜&#xff01; 为了健全扬州市网络安…

zabbix 7.0编译部署教程

zabbix 7.0编译部署教程 2024-03-08 16:50乐维社区 zabbix7.0 alpha版本、beta版本已经陆续发布&#xff0c;Zabbix7.0 LTS版本发布时间也越来越近。据了解&#xff0c;新的版本在性能提升、架构优化等新功能方面有非常亮眼的表现&#xff0c;不少小伙伴对此也已经跃跃欲试。心…

JavaWeb实验 AJAX技术基本应用

实验目的 认识Ajax的作用&#xff1b;能在JSP中应用Ajax与Servlet进行交互。 实验内容 创建一个Java Web应用&#xff0c;综合利用JSP、Ajax和Servlet技术实现中英文互译功能&#xff1a; 在输入框输入英文单词时&#xff0c;在后面即时显示翻译为中文的结果&#xff1b;在输…

下载文件,无法获取header中的Content-Disposition

问题&#xff1a;axios跨域请求时&#xff0c;无法获取header中的Content-Disposition&#xff0c;并且network中已显示Content-Disposition 在使用CORS方式跨域时&#xff0c;浏览器只会返回默认的头部Header 解决&#xff1a; 后端在返回时&#xff0c;需要设置公开的响应…

【项目】C++ 基于多设计模式下的同步异步日志系统

前言 一般而言&#xff0c;业务的服务都是周而复始的运行&#xff0c;当程序出现某些问题时&#xff0c;程序员要能够进行快速的修复&#xff0c;而修复的前提是要能够先定位问题。 因此为了能够更快的定位问题&#xff0c;我们可以在程序运行过程中记录一些日志&#xff0c;通…

选股就用河北源达“财源滚滚”选股软件

在股市投资的道路上&#xff0c;选股无疑是至关重要的一环。然而&#xff0c;面对海量的个股信息和复杂的市场环境&#xff0c;如何科学、准确地选股&#xff0c;成为了投资者必须面对的难题。河北源达信息技术股份有限公司推出的“财源滚滚”选股软件&#xff0c;以其独特的优…

11 vector的实现

注意 实现仿cplus官网的的string类&#xff0c;对部分主要功能实现 实现 文件 #pragma once #include <string> #include <assert.h>namespace myvector {template <class T>class vector{public://iteratortypedef T* iterator;typedef const T* const_…

【AI绘画】AI绘画免费网站推荐

人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是指一种模拟人类智能的技术。它是通过计算机系统来模拟人的认知、学习和推理能力&#xff0c;以实现类似于人类智能的行为和决策。人工智能技术包含多个方面&#xff0c;包括机器学习、深度学习、自…

第42期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

【C++11】来感受lambda表达式的魅力~

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

redis题库详解

1 什么是Redis Redis(Remote Dictionary Server) 是一个使用 C 语言编写的&#xff0c;开源的&#xff08;BSD许可&#xff09;高性能非关系型&#xff08;NoSQL&#xff09;的键值对数据库。 Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串&#xff0c;…

《OWASP TOP10漏洞》

0x01 弱口令 产生原因 与个人习惯和安全意识相关&#xff0c;为了避免忘记密码&#xff0c;使用一个非常容易记住 的密码&#xff0c;或者是直接采用系统的默认密码等。 危害 通过弱口令&#xff0c;攻击者可以进入后台修改资料&#xff0c;进入金融系统盗取钱财&#xff0…

ENVI 如何批量拆分多波段栅格

在处理遥感图像时&#xff0c;需要将多波段栅格进行拆分是很常见的需求。下面介绍一种方法&#xff0c;可以实现图像批量拆分并重命名。 打开ENVI的App Store 搜索并下载应用 在ENVI的App Store中搜索"将多波段图像拆分成多个单波段文件"&#xff0c;并下载安装。 打…

OceanBase4.2版本 Docker 体验

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…