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

news2024/12/23 1:22:58

基于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 {

    @Bean
    public 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 {

    @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:
                    //TODO: 从request中通过header等读取到用户信息,然后参考doIpLock实现
                    //略
                    break;
                case COUNT:
                    //某个方法同时访问的人数限制的实现
                    doCountLock(handlerMethod);
                    break;
                case ANY:
                    //TODO: 该方法同时只允许一个人访问,实现方法与doCountLock一致
                    //略
                    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:
                    //TODO: 略,释放锁
                    break;
                case COUNT:
                    //某个方法同时访问的人数限制的实现
                    releaseCountLock(handlerMethod);
                    break;
                case ANY:
                    //TODO: 略,释放锁
                    break;
            }
        }
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    //访问数量限制lock
    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);
        //设限制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判断和lock
    private void doIpLock(HttpServletRequest request){
        //如果是IP锁,则到redis中读取是否已经存在key
        String 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)
    @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

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

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

相关文章

Python常用图片数据方法

文章目录 1. 常用图片数据类型2. 图片的显示2.1 plt.imshow()2.2 使用 turtle 来绘制图片 3.图片ndarray数据的常用切片操作使用 cv2 来读取图片打印数据R G B 通道的获取BGR 转成 RGBcv2 不支持中文路径的解决方法 4 PIL.Image 转成 QImage 或 QPixmap 1. 常用图片数据类型 使…

使用Flask快速搭建轻量级Web应用【第127篇—Flask】

使用Flask快速搭建轻量级Web应用 在Web开发领域&#xff0c;选择适合项目需求的框架至关重要。Flask&#xff0c;一个轻量级的Python Web框架&#xff0c;以其简洁、灵活和易扩展的特性而备受开发者青睐。本文将介绍如何使用Flask迅速搭建一个轻量级的Web应用&#xff0c;并通过…

【C++教程从0到1入门编程】第九篇:STL中Vector类

一、vector的介绍 1.vector的介绍 vector是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&…

web项目抢购模块测试

web项目抢购模块测试 抢购模块(先测后台,再测前台)流程抢购用例编写测试点--后台抢购用例编写测试点--前台用例设计 面试题1: 当你发现研发实现的结果与需求不一致时怎么办? 需求评审的时候:需要确认所有输入类型的校验是针对单独的输入框做的还是在最终提交时校验 抢购模块 需…

三维天地助力科研实验室提质增效

ELN模板是一种系统化记录实验数据的方式,它可以为实验员提供一个标准化的框架,使其可以快速而准确地记录实验过程和结果。 运用ELN模板,能够保障所有实验记录均依照统一标准进行,有效防止人为差异对实验数据精准度造成不良影响。 作为国内知名的实验室数智化领域软件开发服务…

Redis Java客户端Jedis

Jedis所需要的jar包&#xff1a; <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version> </dependency>连接Redis注意事项&#xff1a; 禁用Linux的防火墙&#xff1a;Linu…

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

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

RUST 每日一省:rust logo收集

rust的logo集合&#xff0c;看看有没有你喜欢的&#xff0c;挑一个吧&#xff1b; GitHub - XuHugo/rust-logo: Collection of logo images for all rust languages 下边只是挑选了几个&#xff0c;更多的还是看github吧。

<2024最新>ChatGPT逆向教程

前言 在使用本篇文章用到的项目以及工具时,需要对其有一定的了解,无法访问以及无法使用的问题作者不承担任何责任,可以自行想办法解决遇到的问题​。 文章若有不合适,有问题的地方,请私聊指出,谢谢~ 准备工具 一台至少 2 核 2G 内存的服务器,推荐是位于香港、新加坡或…

c语言之汉诺塔的实现

思路 汉诺塔问题就是有三个盘子&#xff0c;让我们把其中一个盘子上的东西全移到另一个盘子上&#xff0c;注意的是中途必须满足大东西必须在小东西下面。 这里&#xff0c;我们有A B C三个盘子&#xff0c;假如A上有一个珠子&#xff0c;那我们直接把这一个移到C上就可以&am…

JVM是如何运行的

JVM&#xff08;Java Virtual Machine&#xff0c;Java虚拟机&#xff09;是 Java 程序的运行环境&#xff0c;它负责将 Java 字节码翻译成机器代码并执行。也就是说 Java 代码之所以能够运行&#xff0c;主要是依靠 JVM 来实现的。 JVM 整体的大概执行流程是这样的&#xff1…

数据结构:详解【顺序表】的实现

1. 顺序表的定义 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。动态顺序表与数组的本质区别是——根据需要动态的开辟空间大小。 2. 顺序表的功能 动态顺序表的功能一般有如下几个&#xff1a; 初始化顺序表打印顺序…

【网络安全】【密码学】【北京航空航天大学】实验七、流密码【Python实现】

实验七、流密码 实验目的 1、 了解常用的流密码算法&#xff0c;并对其进行实现&#xff1b; 2、 了解常用的伪随机数生成算法&#xff0c;并对其进行实现&#xff1b; 原理简介 流密码&#xff08;Stream Cipher&#xff09;也称为序列密码&#xff0c;它是对称密码算法的…

亚马逊扣店租/注册店铺可以使用虚拟卡吗?

亚马逊扣店租/注册店铺可以使用虚拟卡吗&#xff1f; 可以 一、亚马逊店铺类型 亚马逊提供了不同类型的店铺&#xff0c;以满足不同卖家的需求。以下是最常见的两种店铺类型&#xff1a; 1、亚马逊个人卖家店铺&#xff1a; 这是适合个人卖家的选项&#xff0c;通常称为&qu…

【相关问题解答2】bert中文文本摘要代码:结果输出为一些重复的标点符号和数字

【相关问题解答2】bert中文文本摘要代码 写在最前面问题1&#xff1a;tokenizer.py中encode函数&#xff0c;不能使用lower操作关于提问问题描述1一些建议1问题更新2&#xff1a;结果输出为一些重复的标点符号和数字一些建议21. 数据检查和预处理2. 模型和训练配置3. 过拟合和欠…

Anzo Capital昂首资本基础知识分享:货币对有几组

基础知识大分享&#xff0c;今天Anzo Capital昂首资本分享&#xff1a;外汇市场中的货币对有几组&#xff0c;都是哪些货币对&#xff1f;首先&#xff0c;货币对分为三组: 第一组&#xff1a;主要货币对 包括七种金融工具&#xff0c;占外汇市场所有交易业务的70%以上。 美…

大数据疑难问题2024

问题一&#xff1a; 集群部署一主一备&#xff0c;初始化操作没有问题&#xff0c;有两个namenode,再次重启显示只有node01有namenode 原因&#xff1a;Journalde服务需要在启动启动hdfs和yarn前再次启动 再次启动步骤&#xff1a; 1.启动3台节点的zookeeper&#xff0c;在3…

Thymeleaf 基本使用

01、Thymeleaf 官网地址&#xff1a;Thymeleafhttps://www.thymeleaf.org/ 简介 Thymeleaf是一种服务器端Java模板引擎&#xff0c;用于将数据渲染为HTML、XML、JavaScript等格式&#xff0c;并在Web浏览器中呈现给用户。 具体来说&#xff0c;Thymeleaf充当着视图层的角色&…

代码随想录day19(1)二叉树:对称二叉树(leetcode101)

题目要求&#xff1a;判断一棵二叉树是否轴对称 思路&#xff1a;如果二叉树是对称二叉树&#xff0c;说明此二叉树是可以左右翻转的&#xff0c;所以判断的时候我们同时遍历两棵子树&#xff0c;比较两棵子树的内、外侧是否相等&#xff0c;比较的时候我们实际上比较的是左孩…

Leedcode刷题——5 DFS+回溯

注&#xff1a;以下代码均为c 1. 电话号码的字母组合 思路&#xff1a; 例如&#xff1a;”23“ //1. 我自己写的 vector<string> letterCombinations(string digits) {if(digits "")return {};vector<string> state {""};string s;vecto…