首页 > 其他分享 > 一、tienchin健身系统技术点复现-注解限流

一、tienchin健身系统技术点复现-注解限流

时间:2023-06-06 23:13:14浏览次数:51  
标签:count redis current lua 限流 复现 key tienchin

一、tienchin健身系统技术点复现-注解限流

这个技术用到的点是 用Java代码执行 redis 的 lua 脚本,采用 请求接口方法 注解@RateLimiter ,前置通知拦截判断请求次数,做出限流操作。

Gitee代码仓库-rate-limiter

1、application.yml 配置 redis参数

在 application.yml 中配置redis基本的参数

spring.redis.host=127.0.0.1
spring.redis.password=choleen
spring.redis.port=6379

2、配置 Redis 序列化key-value

对于 redis 存储或取出时,不配置用的是JDK的序列化,在redis中存储的key和value会有一些其他的东西在前面,比如

name:张三
jdk---> \dsff\dsfg\dfdfname:\dfds\dfds\ggds张三

这种可以用 RedisTemplate 去调用 get(key),但是用命令行就不行了,在后面用 lua 表达式时,也相当于使用命令行,所以需要我们重新设置序列化

创建一个RedisConfig类

/**
 * 功能描述 配置redis 的序列化
 *  配置lua脚本加载
 *
 * @author [山沉]
 * @个人博客 [https://choleen95.github.io/]
 * @博客 [https://www.cnblogs.com/Choleen/]
 * @since [2023/6/5 23:11]
 */
@Configuration
public class RedisConfig {

    @Bean
    RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        // 定义序列化方式,默认JDK序列化,它会:/dd23/f45/443name:xxxx 这种形式
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        redisTemplate.setKeySerializer(serializer);
        redisTemplate.setValueSerializer(serializer);
        // hash类型的序列化也设置一下
        redisTemplate.setHashKeySerializer(serializer);
        redisTemplate.setHashValueSerializer(serializer);
        return redisTemplate;
    }

    @Bean
    DefaultRedisScript<Long> limitScript() {
        //加载lua脚本
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/RateLimiter.lua")));
        return script;
    }

}

3、创建@RateLimiter注解

/**
 * 限制接口访问次数的注解-搭配切面使用
 *
 * @author 山沉
 * @date 2023/6/5 23:00
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimiter {
    /**
     * 限流key,即redis中存入的key前缀 rate_limiter:192.168.246.130:com.yangjiapo.rate_limiter.controller.HelloController.hello
     */
    String key() default "rate_limier:";
    /**
     * 限流次数
     */
    int count() default 100;
    /**
     * 限流时间,即60秒内大于100就限制
     */
    int time() default 60;
    /**
     * 限流类型
     */
    LimitType limitType() default LimitType.DEFAULT;

}

创建LimitType 限流类型

public enum LimitType {
    /**
     *根据请求者Ip进行限流
     */
    IP,
    /**
     * 默认策略全局限流
     */
    DEFAULT

}

4、定义切面RateLimiterAspectj

@Aspect
@Component
public class RateLimterAspectj {

    private static final Logger LOGGER = LoggerFactory.getLogger(RateLimterAspectj.class);
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private DefaultRedisScript<Long> redisScript;


    @Before("@annotation(rateLimiter)")
    public void before(JoinPoint jp, RateLimiter rateLimiter) throws RateLimitException {
        // 拼接 redis 的key
        String key = getCombineKey(jp, rateLimiter);
        int count = rateLimiter.count();
        int time = rateLimiter.time();
        // redis 执行lua脚本
        try {
            Long number = redisTemplate
                    .execute(redisScript, Collections.singletonList(key), time, count);
            if (number == null || number.intValue() > count) {
                // 超过限流阈值
                LOGGER.info("当前接口已达到最大限流次数");
                throw new RateLimitException("访问过于频繁,请稍后访问!");
            }
            LOGGER.info("一个时间窗内请求次数:{}, 当前请求次数:{},缓存的key为:{}", count, number, key);
        } catch (Exception e) {
            throw e;

        }
    }


    /**
     * 这个方法就是获取 redis 存储的接口调用次数的key
     * rate_limiter:192.168.130.221-com.yangjiapo.rate_limiter.controller.HelloController.hello
     *
     * @param jp 参数
     * @param rateLimiter 注解
     * @author [山沉]
     * @date 2023/6/5
     * @return {@link String}
     */
    private String getCombineKey(JoinPoint jp, RateLimiter rateLimiter) {
        MethodSignature signature = (MethodSignature) jp.getSignature();
        StringBuffer buffer = new StringBuffer(rateLimiter.key());
        if (LimitType.IP == rateLimiter.limitType()) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                    .getRequestAttributes()).getRequest();
            // 获取IP
            buffer.append(IpUtils.getIpAddr(request)).append("-");
        }
        String methodName = signature.getMethod().getName();
        Method method = signature.getMethod();
        String classRouting = method.getDeclaringClass().getName();
        buffer.append(classRouting).append("-").append(methodName);
        return buffer.toString();
    }

}

这里用到的 RedisTemplate、DefaultRedisScript 都在 RedisConfig 配置类中初始化了。

由于现在 Idea 智能了许多,大家在创建 切面时,容易创建为 aspect 类,这种就要根据它的要求写切点、通知等,若已经创建了 aspect,但想用 class 方式,即把此类删掉重新创建即可。

5、创建RateLimiter.lua脚本

在 Resources 目录下,创建 lua 目录,然后创建 RateLimiter.lua 脚本。

local key = KEYS[1]
local time = tonumber(ARGV[1])
local count = tonumber(ARGV[2])
local current = redis.call('get', key)
if current and tonumber(current) > count then
    return tonumber(current);
end
current = redis.call('incr', key)
if tonumber(current) == 1 then
    redis.call('expire', key, time)
end
return tonumber(current)

这里是 定义了三个参数:redis的key,过期时间 time,单位时间内限流次数 count

流程:

  • 当 current 存在,且大于 count 时,即返回当前从redis中获取的 value 值,即第几次请求。
  • 若 current 小于 count 则 使用 redis 的自增 1
  • 若 当前是第一次请求,则给一个redis的过期时间 time
  • 最后返回当前请求次数 current

标签:count,redis,current,lua,限流,复现,key,tienchin
From: https://www.cnblogs.com/Choleen/p/17462013.html

相关文章

  • SCM Manager XSS漏洞复现(CVE-2023-33829)
    一、漏洞描述漏洞简述SCM-Manager是一款开源的版本库管理软件,同时支持subversion、mercurial、git的版本库管理。安装简单,功能较强,提供用户、用户组的权限管理,有丰富的插件支持。由于在MIT的许可下是开源的,因此它允许被用于商业用途,而且其代码可以在GitHub上获取到。该项目......
  • 饶派杯 XCTF 车联网挑战赛 mqttsvr 复现
    前言IDA和Ghidra对mips64架构的识别貌似不是很友好,赛场上由于反编译实在难看,所以很难静下心来去逆,于是赛后在期末考试前稍微花点时间做了一下复现。准备checksec一下,发现是mips64大端,没开CanaryRELRO,可以溢出,可以覆写got表,这里开了NX和PIE,NX对mips64这种异架构来说好像并不会......
  • 二、tienchin健身系统下的技术点复现--动态数据源
    二、配置动态数据源前面我们已经准备了基础的类,@DynamicDatasource、DruidProperties、DynamicAspect现在我们开始对AbstractRoutingDatasource所需要的数据源,放到对应的map结构中。1、加载数据源/***功能描述加载自定义的数据源,存入到Map<String,Datasource>结构中......
  • 三、tienchin健身系统下的技术点复现--动态数据源
    三、网页手动实现动态数据源切换手动切换数据源,采用HttpSession保存数据源名称,在全局的切面定义service下所有方法,都会切换数据源。1、定义一个html页面<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>切换数据源</title><scriptsrc=&quo......
  • FairMOT复现报错存档
    FairMOT复现使用pip命令单独安装Cython包即可修改下载的cython-bbox包里的setup.py里的代码将#extra_compile_args=['-Wno-cpp'],修改为extra_compile_args={'gcc':['/Qstd=c99']},然后运行pythonsetup.pybuild_extinstall安装DCNv2时安装失败,要尝试一下......
  • NPS之Socks流量分析以及未授权复现
    前言因为想要写一个socks的流量算法去绕过安全设备,所以这里对nps的流量特征总结一下,方便自己后期的魔改。环境ubuntu16.04vpsserverwindowsserver2012R2clinetmkdirnpscdnpswgethttps://github.com/ehang-io/nps/releases/download/v0.26.10/linux_amd64_server.tar.g......
  • 十四、使用Sentinal进行限流熔断
    内容sentinal限流降级:限流是接口流量太大要进行限制,限制后的流量进行降级。sentinal熔断降级:熔断是A调用B,而B不靠谱,就熔断不调用,并降级。sentinal+nacos组合。常见的限流算法静态窗口限流动态窗口限流例如:当前是第2.5秒静态:统计第2秒到现在的请求数......
  • 限流算法
    固定窗口缺陷:最简单,但是不能精确限制,由于是计算的时间差,比如每10秒只能10个请求,8-10秒请求了10个,那么10-18秒就也无法请求了importjava.util.concurrent.atomic.AtomicInteger;importjava.util.concurrent.atomic.AtomicLong;publicclassMyFixedWindow{privatef......
  • 2023ciscn-misc-国粹复现
    说明:此题给出的两个图片,a.png及k.png是分别表示的x坐标和y坐标。然后再用此坐标对题目进行画图,从而得出flag但是由于本人的代码技术有限只能借鉴大佬们的代码然后对其进行分析学习。importnumpyasnpfromPILimportImage classtest():   def__init__(self):......
  • Spring Cloud Alibaba Sentinel实现熔断限流代码示例
    SpringCloudAlibabaSentinel介绍SpringCloudAlibabaSentinel是一个面向分布式服务架构的流量控制组件,是SpringCloudAlibaba的核心组件之一。主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。代码示例以下是一个使用SpringC......