限流功能的探索和实现

一、需求

有一个接口是30s内只能请求一次,怎么限流。

二、实现

        通过拦截器+注解实现,我们这个接口放在Service方法上,所以我们使用注解+AOP方式实现。

        使用缓存实现,根据参照架构师专栏公众号的文章,将方法的名字+UserID+args进行MD5加密作为Key进行存储。我们使用的是Ehcache,java自带的缓存件,将数据进行缓存。
三、扩展

        如果只是实现固定时间的单个请求很简单就实现了,但是当实现固定时间一定请求量就较难实现。根据漏桶和令牌桶算法可进一步实现,通过Redis+LUA可实现,另外一种是先方式是使用Redis的INCR实现限流器和计数器,我上面的这个实现类似于Redis的setNX实现的限流。查资料显示有三种方式可以进行限流。

注解代码:

package com.sinosoft.app.project.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimitAnnocation {

    /**
     * 每次请求间隔时间
     * @return
     */
    int seconds();

}

AOP代码

package com.sinosoft.app.project.annotation;

import com.alibaba.fastjson.JSON;
import com.sinosoft.app.base.redis.IRedisSerivce;
import com.sinosoft.app.base.utils.*;
import com.sinosoft.app.project.Interface.enums.ResultStatus;
import com.sinosoft.app.project.Interface.util.MyRedisService;
import com.sinosoft.app.project.Interface.util.ResultUtil;
import com.sinosoft.app.project.cloud.utils.StringUtils;
import com.sinosoft.app.project.dao.EsInterfaceMapper;
import com.sinosoft.app.project.dao.EsQcloudInfoMapper;
import com.sinosoft.app.project.entity.EsInterface;
import com.sinosoft.app.project.entity.EsQcloudInfo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 拦截一定时间内一定重复请求
 */
@Component
@Aspect
public class AccessLimitAspect {


    Logger logger = LoggerFactory.getLogger(AccessLimitAspect.class);


    @Pointcut("@annotation(com.sinosoft.app.project.annotation.AccessLimitAnnocation)")
    public void SdkLoginCheckToken() {
    }

    @Resource
    private MyRedisService myRedisService;


    @Around("SdkLoginCheckToken()")
    public Result checkSdkToken(ProceedingJoinPoint joinPoint) throws Throwable {

        Result result = Result.newOne();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String name = signature.getName();
        //获取参数并生产MD5作为Key
        Object[] params = joinPoint.getArgs();
        String jsonParam = "";
        if (params!=null&&params.length>0) {
            jsonParam = JSON.toJSONString(params);
        }
        String value = name + "_" + WebUtil.getUserId() + "_" + jsonParam;
        String md5 = MD5Util.stringMD5(value);
        //判断是否存在
        Boolean exist = myRedisService.isExist(md5);
        if (exist) {
            logger.info("多次重复请求,请求内容:", value);
            return ResultUtil.getFailture(ResultStatus.TOO_MANY_REQUEST_EXCEPTION);
        }
        //不存在,将数据存入到cache中
        //获取注解中的值
        Method method = signature.getMethod();
        AccessLimitAnnocation annotation = method.getAnnotation(AccessLimitAnnocation.class);
        myRedisService.set(md5, value, annotation.seconds(), annotation.seconds());
        return  (Result) joinPoint.proceed();
    }

}

Ehcache实现

package com.sinosoft.app.project.Interface.util;

import com.sinosoft.app.base.redis.IRedisSerivce;
import net.sf.ehcache.Element;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;

/**
 * @ClassName MyRedisUtil
 * @Description 方便操作redis
 * @Author JYJ
 * @Date 2022/5/11 10:52
 * @Version 1.0
 */
@Service
public class MyRedisService implements IRedisSerivce {

    @Resource
    private Cache dictCache;

    public MyRedisService() {
    }

    @Override
    public String getId(String id) {
        return null;
    }

    @Override
    public void set(String key, String value) {

    }

    /**
     * 存储Key和Value,同时设置过期时间
     *
     * @param key
     * @param value
     * @param timeToLive 存活时间
     * @param timeToIdle 空闲时存活时间
     */
    public void set(String key, String value, int timeToLive, int timeToIdle) {
        net.sf.ehcache.Cache cacheUtil = (net.sf.ehcache.Cache) this.dictCache.getNativeCache();
        Element element = new Element(key, value);
        element.setTimeToLive(timeToLive);
        element.setTimeToIdle(timeToIdle);
        cacheUtil.put(element);
    }

    public Boolean isExist(String key) {
        net.sf.ehcache.Cache cacheUtil = (net.sf.ehcache.Cache) this.dictCache.getNativeCache();
        Element element = cacheUtil.get(key);
        return element != null;
    }

    @Override
    public void setObj(String key, Object obj) throws IOException {

    }

    @Override
    public void setHash(String key, String field, String value) {

    }

    @Override
    public String get(String key) {
        return null;
    }

    @Override
    public Object getObj(String key) throws IOException {
        return null;
    }

    @Override
    public String getHash(String key, String field) {
        return null;
    }

    @Override
    public boolean del(String key) {
        return false;
    }

    @Override
    public void delHash(String key, String field) {

    }

    @Override
    public Long expire(String key, int seconds) {
        return null;
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课程背景:如果赶上一个语言火的时候,您想不赚钱都难,android ,苹果,大数据,python我们没有赶上第一批,现在go语言您还想错过吗?现在go语言已经在大公司开始使用,在做服务这块慢慢已经走上热点,现在go语言视频很少而且很基础,我们早已经带着学员开始做实战了。go语言大神班为有一定基础且想深入学习go的学员量身打造,拒绝平庸,与众不同!专技术:对整个大数据生态圈的相关技术都有一定的研究,深入理解Go的原理,熟练使用GO技术解决各种业务需求。通过我们课程中的企业级项目和通俗易懂的知道点分析让你更加深了的掌握Go技术!懂架构:对业务有一定的了解,并且可以根据不同的业务场景设计出最优的技术架构。通过我们课程中的企业真实项目,全方位掌握项目的整个开发周期,达到触类旁通的目的!擅调优:一般其他语言开发项目一般都有一定的性能瓶颈,使用GO需要深入掌握项目技术架构特点和技术原理方可对项目中的瓶颈进行调优。通过项目中的调优经验让你掌握该技能!善沟通:GO在项目中扮演一个非常重要的角色,一般是在企业里做服务这块,需要跟各个部门进行协调沟通,所以要具备良好的沟通能力,业务对接能力! 课程研发环境及内容简介:1.课程研发环境项目源代码以Go1.9.2为基准,数据库以mysql为基准,以下环境都适用于项目。开发工具:VScode;数据库工具:mysql  2.内容简介什么是秒杀秒杀场景一般会在电商网站举行一些活动或者节假日在12306网站上抢票时遇到。对于网站中一些稀缺或者特价的产品,电商网站一般会在约定的时间对其进行限量销售,因为这些产品的特殊性,会吸引大量用户前来抢购,并且会在约定时间同时在秒杀页面进行抢购。设计思路将请求拦截在系统上游,降低下流压力;秒杀系统特点就是并发量极大,但实际秒杀成功的请求数量确很少,所以如果不在前端拦截可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时,甚至导致系统崩溃充分利用缓存:利用缓存可以极大提高系统读写速度消息队列:消息队列可以削峰,将拦截大量并发的请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理前端方案浏览器端(js):页面静态化:将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素,通过CDN来抗峰值禁止重复提交:用户提交之后按钮置灰,禁止重复提交用户限流:在某一时间内只允许用户提交一次请求,比如可以采取IP限流后端方案服务器控制器层(网关层)限制UID(userID)访问频率:我们上面拦截了浏览器的访问请求,但准对某些恶意请求和攻击或者其他插件,在服务器控制层要准对同一个uid,限制访问频率 服务层上面只拦截了一部分请求,当秒杀的用户量非常大时,即使每个用户只有一个请求,到服务层的请求数量还是很大。比如我们有100w用户同时抢购100台手机,服务层并发请求压力至少为100w。1.采用消息队列缓存请求:既然服务器层知道库存只有100台手机,那完全没有必要把100w个请求都传递到数据库里,那么可以先把这些请求都写到消息队列里面缓存一下,数据库层订阅消息减少库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束2.利用缓存应对读请求:对类似12306等购票业务,是典型的读多写少业务,大部分请求时查询请求,所以可以利用缓存分担数据库压力3.利用缓存对写请求:缓存也是可以应对写请求,比如我们可以把数据库中库存数据迁移到Redis缓存中,所有减库存操作都在Redis中进行,然后通过后台进程把Redis中的用户秒杀请求同步到数据库中数据库层数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截,数据库层只承担“能力范围内”的访问请求。所以,上面通过在服务层引入的队列和缓存,让底层的数据库高枕无忧

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值