<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于redis的計(jì)數(shù)器限流算法實(shí)現(xiàn)

          共 7672字,需瀏覽 16分鐘

           ·

          2021-11-03 22:56

          前言

          昨天我們已經(jīng)預(yù)告了今天的內(nèi)容——實(shí)現(xiàn)計(jì)數(shù)器限流算法,所以今天不需要過(guò)多說(shuō)明,我們直接開始正文。

          計(jì)數(shù)器限流算法

          關(guān)于計(jì)數(shù)器限流算法的實(shí)現(xiàn)原理,我們昨天已經(jīng)介紹過(guò)了,今天的內(nèi)容算是基于我們昨天所說(shuō)的原理的一種應(yīng)用和實(shí)現(xiàn),當(dāng)然還是有必要說(shuō)下我們的實(shí)現(xiàn)思路的:

          在接口內(nèi)部最開始的地方,設(shè)置調(diào)用方的計(jì)數(shù)器(key為調(diào)用方唯一的身份信息),第一次調(diào)用時(shí)將其值設(shè)置為1并放進(jìn)緩存中,同時(shí)緩存設(shè)置過(guò)期時(shí)間,有效期內(nèi)每次調(diào)用計(jì)數(shù)器+1,時(shí)間過(guò)期,緩存會(huì)自動(dòng)刪除。可以把相關(guān)邏輯封裝成自定義注解,搞成通用組件,這樣只需要在需要限速的接口上加上對(duì)應(yīng)的的注解即可,明天我們可以來(lái)實(shí)現(xiàn)下。

          創(chuàng)建項(xiàng)目

          這里我們直接創(chuàng)建一個(gè)spring bootweb項(xiàng)目,然后引入redis客戶端的依賴:

          ?<dependency>
          ?????<groupId>org.springframework.datagroupId>
          ?????<artifactId>spring-data-redisartifactId>
          ?????<version>2.3.6.RELEASEversion>
          dependency>

          <dependency>
          ????<groupId>redis.clientsgroupId>
          ????<artifactId>jedisartifactId>
          dependency>

          redis用的是spring bootRedisTemplate,當(dāng)然你也可以用其他的,沒(méi)有任何限制,然后是redis客戶端設(shè)置:

          spring:
          ??redis:
          ????database:?0
          ????host:?127.0.0.1
          ????port:?6379
          ????password:?redis1234567
          ????#?連接超時(shí)時(shí)間(ms)
          ????timeout:?5000
          ????#?高版本springboot中使用jedis或者lettuce
          ????jedis:
          ??????pool:
          ????????#?連接池最大連接數(shù)(負(fù)值表示無(wú)限制)
          ????????max-active:?8
          ????????#?連接池最大阻塞等待時(shí)間(負(fù)值無(wú)限制)
          ????????max-wait:?5000
          ????????#?最大空閑鏈接數(shù)
          ????????max-idle:?8
          ????????#?最小空閑鏈接數(shù)
          ????????min-idle:?1

          redis配置類:

          @Configuration
          public?class?RedisConfig?{

          ????private?static?Logger?logger?=?LoggerFactory.getLogger(RedisConfig.class);

          ????@Value("${spring.redis.host}")
          ????private?String?host;
          ????@Value("${spring.redis.password}")
          ????private?String?password;
          ????@Value("${spring.redis.port}")
          ????private?int?port;
          ????@Value("${spring.redis.database}")
          ????private?int?database;

          ????@SuppressWarnings("all")
          ????@Bean
          ????public?StringRedisTemplate?redisTemplate(RedisConnectionFactory?factory)?{
          ????????StringRedisTemplate?template?=?new?StringRedisTemplate(factory);
          ????????Jackson2JsonRedisSerializer?jackson2JsonRedisSerializer?=?new?Jackson2JsonRedisSerializer(Object.class);
          ????????ObjectMapper?om?=?new?ObjectMapper();
          ????????om.setVisibility(PropertyAccessor.ALL,?JsonAutoDetect.Visibility.ANY);
          ????????om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
          ????????jackson2JsonRedisSerializer.setObjectMapper(om);
          ????????RedisSerializer?stringSerializer?=?new?StringRedisSerializer();
          ????????template.setKeySerializer(stringSerializer);
          ????????template.setValueSerializer(jackson2JsonRedisSerializer);
          ????????template.setHashKeySerializer(stringSerializer);
          ????????template.setHashValueSerializer(jackson2JsonRedisSerializer);
          ????????template.afterPropertiesSet();
          ????????return?template;
          ????}


          ????@Bean
          ????public?JedisConnectionFactory?jedisConnectionFactory()?{
          ????????logger.info("jedisConnectionFactory:初始化了");
          ????????RedisStandaloneConfiguration?configuration?=?new?RedisStandaloneConfiguration();
          ????????configuration.setHostName(host);
          ????????configuration.setPassword(RedisPassword.of(password));
          ????????configuration.setPort(port);
          ????????configuration.setDatabase(database);
          ????????return?new?JedisConnectionFactory(configuration);
          ????}
          }

          至此,項(xiàng)目的基本環(huán)境基本上搭建完成,下面開始編寫業(yè)務(wù)代碼。

          限流業(yè)務(wù)實(shí)現(xiàn)

          為了能夠?qū)崿F(xiàn)業(yè)務(wù)層面的低耦合,同時(shí)也為了便于應(yīng)用到實(shí)際業(yè)務(wù)中,這里我將限流器封裝到攔截器中,然后通過(guò)自定義注解的方式實(shí)現(xiàn)攔截器的業(yè)務(wù)去耦合。

          限速注解組件

          我的第一步是定義一個(gè)計(jì)數(shù)器限流注解組件:

          @Retention(RetentionPolicy.RUNTIME)
          @Target(ElementType.METHOD)
          public?@interface?CounterLimit?{

          ????/**
          ?????*?調(diào)用方唯一key的名字
          ?????*?
          ?????*?@return
          ?????*/

          ????String?name();
          ????/**
          ?????*?限制訪問(wèn)次數(shù)
          ?????*?@return
          ?????*/

          ????int?limitTimes();

          ????/**
          ?????*?限制時(shí)長(zhǎng),也就是計(jì)數(shù)器的過(guò)期時(shí)間
          ?????*
          ?????*?@return
          ?????*/

          ????long?timeout();

          ????/**
          ?????*?限制時(shí)長(zhǎng)單位
          ?????*
          ?????*?@return
          ?????*/

          ????TimeUnit?timeUnit();

          }

          注解包括四個(gè)屬性,name表示調(diào)用方身份唯一性的參數(shù)名,比如userIdlimitTimes表示限制訪問(wèn)次數(shù),也就是他在指定時(shí)間內(nèi)可以訪問(wèn)多少次;timeout表示限制訪問(wèn)次數(shù)的有效期,一分鐘還是一個(gè)小時(shí);timeUnit表示限速實(shí)際的單位,秒、分鐘、小時(shí)等。

          限速攔截器

          沒(méi)做之前,考慮的是通過(guò)切面來(lái)實(shí)現(xiàn),但是今天實(shí)際實(shí)踐的時(shí)候,發(fā)現(xiàn)之前想偏了(竟然會(huì)犯入?yún)⒌图?jí)錯(cuò)誤,說(shuō)明最近輪子造的有點(diǎn)少),最終是通過(guò)攔截器實(shí)現(xiàn)的(忠告:沒(méi)事還是要多造輪子,不然容易手生):

          @Component
          public?class?CounterLimiterHandlerInterceptor?implements?HandlerInterceptor?{

          ????@Autowired
          ????private?RedisTemplate?redisTemplate;

          ????@Override
          ????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?Exception?{
          ????????if?(handler?instanceof?HandlerMethod)?{
          ????????????HandlerMethod?handlerMethod?=?(HandlerMethod)?handler;
          ????????????//?判斷方法是否包含CounterLimit,有這個(gè)注解就需要進(jìn)行限速操作
          ????????????if?(handlerMethod.hasMethodAnnotation(CounterLimit.class))?{
          ????????????????CounterLimit?annotation?=?handlerMethod.getMethod().getAnnotation(CounterLimit.class);
          ????????????????JSONObject?result?=?new?JSONObject();
          ????????????????String?token?=?request.getParameter(annotation.name());
          ????????????????response.setContentType("text/json;charset=utf-8");
          ????????????????result.put("timestamp",?System.currentTimeMillis());
          ????????????????BoundValueOperations?boundGeoOperations?=?redisTemplate.boundValueOps(token);
          ????????????????//?如果用戶身份唯一key為空,直接返回錯(cuò)誤
          ????????????????if?(StringUtils.isEmpty(token))?{
          ????????????????????result.put("result",?"token?is?invalid");
          ????????????????????response.getWriter().print(JSON.toJSONString(result));
          ????????????????//?如果限速校驗(yàn)通過(guò),則將請(qǐng)求放行
          ????????????????}?else?if?(checkLimiter(token,?annotation))?{
          ????????????????????result.put("result",?"請(qǐng)求成功");
          ????????????????????Long?expire?=?boundGeoOperations.getExpire();
          ????????????????????logger.info("result:{}, expire:?{}",??result,?expire);
          ????????????????????return?true;
          ????????????????//?否則告知調(diào)用方達(dá)到限速上線
          ????????????????}?else?{
          ????????????????????result.put("result",?"達(dá)到訪問(wèn)次數(shù)限制,禁止訪問(wèn)");
          ????????????????????Long?expire?=?boundGeoOperations.getExpire();
          ????????????????????logger.info("result:{}, expire:?{}",??result,?expire);
          ????????????????????response.getWriter().print(JSON.toJSONString(result));
          ????????????????}
          ????????????????return?false;
          ????????????}
          ????????}
          ????????return?true;
          ????}

          ????/**
          ????*?限速校驗(yàn)
          ????*/

          ????private?Boolean?checkLimiter(String?token,?CounterLimit?annotation)?{
          ????????BoundValueOperations?boundGeoOperations?=?redisTemplate.boundValueOps(token);
          ????????Integer?count?=?boundGeoOperations.get();
          ????????if?(Objects.isNull(count))?{
          ????????????redisTemplate.boundValueOps(token).set(1,?annotation.timeout(),?annotation.timeUnit());
          ????????}?else?if?(count?>=?annotation.limitTimes())?{
          ????????????return?Boolean.FALSE;
          ????????}?else?{
          ????????????redisTemplate.boundValueOps(token).set(count?+?1,?boundGeoOperations.getExpire(),?annotation.timeUnit());
          ????????}
          ????????return?Boolean.TRUE;
          ????}
          }

          代碼邏輯也比較簡(jiǎn)單:

          • 首先判斷接口方法是否包含CounterLimit注解,有這個(gè)注解就需要進(jìn)行限速操作
          • 如果用戶身份唯一key為空,直接返回錯(cuò)誤
          • 如果限速校驗(yàn)通過(guò),則將請(qǐng)求放行,否則告知調(diào)用方達(dá)到限速上線
          • 在校驗(yàn)限速方法中,如果count為空,表示首次訪問(wèn),則存放一個(gè)count,并設(shè)置過(guò)期時(shí)間
          • 如果達(dá)到訪問(wèn)限制上限,直接拒絕,未達(dá)到則count+1,過(guò)期時(shí)間設(shè)置為剩余時(shí)間

          代碼也有比較詳細(xì)的注解,各位小伙伴也應(yīng)該能看懂。

          注意: 當(dāng)然如果你的項(xiàng)目本身已經(jīng)有了完善的全局異常處理機(jī)制,這里的攔截器可以直接拋出對(duì)應(yīng)的異常,這里為了方便我偷了個(gè)懶,并沒(méi)有做全局異常處理,而是直接通過(guò)response返回了異常信息,實(shí)際項(xiàng)目開發(fā)中,這種寫法肯定是不合理的,各位小伙伴一定要注意哦!

          攔截器配置

          這一塊就屬于復(fù)習(xí)內(nèi)容了,也屬于比較入門級(jí)別的spring boot操作了,這里不再過(guò)多贅述,詳細(xì)代碼如下:

          @Configuration
          public?class?WebConfig?implements?WebMvcConfigurer?{

          ????@Autowired
          ????private?CounterLimiterHandlerInterceptor?counterLimiterHandlerInterceptor;

          ????@Override
          ????public?void?addInterceptors(InterceptorRegistry?registry)?{
          ????????//?計(jì)數(shù)器限速
          ????????registry.addInterceptor(counterLimiterHandlerInterceptor).addPathPatterns("/**");
          ????????WebMvcConfigurer.super.addInterceptors(registry);
          ????}
          }
          接口配置

          接口這塊也比較簡(jiǎn)單,就是簡(jiǎn)單的controller方法,然后方法上多了我們的自定義限速器注解CounterLimit,這個(gè)注解的參數(shù)我們上面已經(jīng)解釋過(guò)了,所以這里也就不再贅述:

          ?@CounterLimit(name?=?"token",limitTimes?=?5,?timeout?=?60,?timeUnit?=?TimeUnit.SECONDS)
          ????@GetMapping("/limit/count-test")
          ????public?Object?counterLimiter(String?name,?String?token)?{
          ????????JSONObject?result?=?new?JSONObject();
          ???????result.put("data",?"success");
          ????????return?result;
          ????}

          測(cè)試

          完成以上內(nèi)容之后,我們就可以進(jìn)行相關(guān)測(cè)試了,首先將我們的項(xiàng)目啟動(dòng)起來(lái),然后直接訪問(wèn)我們的接口即可,訪問(wèn)接口的時(shí)候記得帶著我們的token(唯一key),最終訪問(wèn)結(jié)果如下:

          從結(jié)果中我們可以看出來(lái),在第一次訪問(wèn)的時(shí)候,token的過(guò)期時(shí)間為60,我們連續(xù)訪問(wèn)5次之后,接口限制我們?cè)L問(wèn)的,然后等到限制過(guò)期之后(token過(guò)期),又可以繼續(xù)訪問(wèn)了。至此,我們的計(jì)數(shù)器限流的算法實(shí)現(xiàn)也算是完美達(dá)成,是不是很簡(jiǎn)單呢?

          總結(jié)

          本次demo總體來(lái)說(shuō)很簡(jiǎn)單,除了算法本身之外,基本上都是java或者spring boot的簡(jiǎn)單知識(shí)點(diǎn)應(yīng)用,但是從我自己實(shí)踐的感受來(lái)說(shuō),我覺(jué)得以后還是得多造輪子,因?yàn)橹氨容^熟悉的好多配置和寫法都生疏了,好多都要翻看之前的demo才能想起來(lái)。當(dāng)然,話句話說(shuō)就是,很多看起來(lái)很簡(jiǎn)單的實(shí)例或者demo,其實(shí)在真正實(shí)踐的時(shí)候并不簡(jiǎn)單,因?yàn)槲覀兺倳?huì)高估自己的能力……

          項(xiàng)目完整代碼:

          https://github.com/Syske/learning-dome-code/tree/dev/spring-boot-counter-limiter

          好了,各位小伙伴,晚安吧!

          - END -


          瀏覽 57
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费1级黄色片 | 国产黄色电影一区 | 麻酥酥在线观看 | 韩国啪啪网站 | 特级西西人体444w w w |