摘要:自定義注解實(shí)現(xiàn)基于接口限流仔細(xì)看會(huì)發(fā)現(xiàn)上面的簡(jiǎn)單實(shí)現(xiàn)會(huì)造成我每個(gè)接口都要寫一次限流方法代碼很冗余所以采用來使用自定義注解來實(shí)現(xiàn)。
服務(wù)限流 -- 自定義注解基于RateLimiter實(shí)現(xiàn)接口限流
令牌桶限流算法
圖片來自網(wǎng)上
令牌桶會(huì)以一個(gè)恒定的速率向固定容量大小桶中放入令牌,當(dāng)有瀏覽來時(shí)取走一個(gè)或者多個(gè)令牌,當(dāng)發(fā)生高并發(fā)情況下拿到令牌的執(zhí)行業(yè)務(wù)邏輯,沒有獲取到令牌的就會(huì)丟棄獲取服務(wù)降級(jí)處理,提示一個(gè)友好的錯(cuò)誤信息給用戶。
2. RateLimiter簡(jiǎn)單實(shí)現(xiàn)
maven依賴
com.google.guava guava 18.0
本人使用的是SpringBoot 2.0.4.RELEASE,Jdk1.8環(huán)境下編寫,部分代碼貼出:
/** * 以1r/s往桶中放入令牌 */ private RateLimiter limiter = RateLimiter.create(1.0); private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @GetMapping("/indexLimiter") public String indexLimiter() { // 如果用戶在500毫秒內(nèi)沒有獲取到令牌,就直接放棄獲取進(jìn)行服務(wù)降級(jí)處理 boolean tryAcquire = limiter.tryAcquire(500, TimeUnit.MILLISECONDS); if (!tryAcquire) { log.info("Error ---時(shí)間:{},獲取令牌失敗.", sdf.format(new Date())); return "系統(tǒng)繁忙,請(qǐng)稍后再試."; } log.info("Success ---時(shí)間:{},獲取令牌成功.", sdf.format(new Date())); return "success"; }
調(diào)用結(jié)果如下:
使用RateLimiter注意的地方:
允許先消費(fèi),后付款,意思就是它可以來一個(gè)請(qǐng)求的時(shí)候一次性取走幾個(gè)或者是剩下所有的令牌甚至多取,但是后面的請(qǐng)求就得為上一次請(qǐng)求買單,它需要等待桶中的令牌補(bǔ)齊之后才能繼續(xù)獲取令牌。
3.自定義注解實(shí)現(xiàn)基于接口限流
仔細(xì)看會(huì)發(fā)現(xiàn)上面的簡(jiǎn)單實(shí)現(xiàn)會(huì)造成我每個(gè)接口都要寫一次限流方法代碼很冗余,所以采用aop來使用自定義注解來實(shí)現(xiàn)。
maven依賴
org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-web com.google.guava guava 18.0 org.projectlombok lombok true
首先定義一個(gè)自定義注解:
package com.limiting.annotation; import java.lang.annotation.*; import java.util.concurrent.TimeUnit; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface AnRateLimiter { //以固定數(shù)值往令牌桶添加令牌 double permitsPerSecond () ; //獲取令牌最大等待時(shí)間 long timeout(); // 單位(例:分鐘/秒/毫秒) 默認(rèn):毫秒 TimeUnit timeunit() default TimeUnit.MILLISECONDS; // 無法獲取令牌返回提示信息 默認(rèn)值可以自行修改 String msg() default "系統(tǒng)繁忙,請(qǐng)稍后再試."; }
然后使用aop的環(huán)繞通知來攔截注解,使用了一個(gè)ConcurrentMap來保存每個(gè)請(qǐng)求對(duì)應(yīng)的令牌桶,key是沒有url請(qǐng)求,防止出現(xiàn)每個(gè)請(qǐng)求都會(huì)新建一個(gè)令牌桶這么會(huì)達(dá)不到限流效果.
package com.limiting.aspect; import com.google.common.collect.Maps; import com.google.common.util.concurrent.RateLimiter; import com.limiting.annotation.AnRateLimiter; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Method; import java.util.Map; import java.util.Objects; /** * * 描述: * * @author 只寫B(tài)UG的攻城獅 * * @date 2018-09-12 12:07 */ @Slf4j @Aspect @Component public class RateLimiterAspect { /** * 使用url做為key,存放令牌桶 防止每次重新創(chuàng)建令牌桶 */ private MaplimitMap = Maps.newConcurrentMap(); @Pointcut("@annotation(com.limiting.annotation.AnRateLimiter)") public void anRateLimiter() { } @Around("anRateLimiter()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // 獲取request,response HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); // 或者url(存在map集合的key) String url = request.getRequestURI(); // 獲取自定義注解 AnRateLimiter rateLimiter = getAnRateLimiter(joinPoint); if (rateLimiter != null) { RateLimiter limiter = null; // 判斷map集合中是否有創(chuàng)建有創(chuàng)建好的令牌桶 if (!limitMap.containsKey(url)) { // 創(chuàng)建令牌桶 limiter = RateLimiter.create(rateLimiter.permitsPerSecond()); limitMap.put(url, limiter); log.info("<<================= 請(qǐng)求{},創(chuàng)建令牌桶,容量{} 成功!!!", url, rateLimiter.permitsPerSecond()); } limiter = limitMap.get(url); // 獲取令牌 boolean acquire = limiter.tryAcquire(rateLimiter.timeout(), rateLimiter.timeunit()); if (!acquire) { responseResult(response, 500, rateLimiter.msg()); return null; } } return joinPoint.proceed(); } /** * 獲取注解對(duì)象 * @param joinPoint 對(duì)象 * @return ten LogAnnotation */ private AnRateLimiter getAnRateLimiter(final JoinPoint joinPoint) { Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods(); String name = joinPoint.getSignature().getName(); if (!StringUtils.isEmpty(name)) { for (Method method : methods) { AnRateLimiter annotation = method.getAnnotation(AnRateLimiter.class); if (!Objects.isNull(annotation) && name.equals(method.getName())) { return annotation; } } } return null; } /** * 自定義響應(yīng)結(jié)果 * * @param response 響應(yīng) * @param code 響應(yīng)碼 * @param message 響應(yīng)信息 */ private void responseResult(HttpServletResponse response, Integer code, String message) { response.resetBuffer(); response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); PrintWriter writer = null; try { writer = response.getWriter(); writer.println("{"code":" + code + " ,"message" :"" + message + ""}"); response.flushBuffer(); } catch (IOException e) { log.error(" 輸入響應(yīng)出錯(cuò) e = {}", e.getMessage(), e); } finally { if (writer != null) { writer.flush(); writer.close(); } } } }
最后來試試自己定義的注解是否生效,能否達(dá)到限流效果.
@GetMapping("/index") @AnRateLimiter(permitsPerSecond = 1, timeout = 500, timeunit = TimeUnit.MILLISECONDS,msg = "親,現(xiàn)在流量過大,請(qǐng)稍后再試.") public String index() { return System.currentTimeMillis() + ""; }
訪問請(qǐng)求(按F5狂刷新瀏覽器)效果如下圖:
總結(jié)
至此已基本上使用注解實(shí)現(xiàn)了接口限流,后期可以根據(jù)自己需求自行修改,這個(gè)只適于單個(gè)應(yīng)用進(jìn)行接口限流,如果是分布式項(xiàng)目或者微服務(wù)項(xiàng)目可以采用redis來實(shí)現(xiàn),后期有時(shí)間來一個(gè)基于redis自定義注解來實(shí)現(xiàn)接口限流。
本人也是剛?cè)隞ava開發(fā)行業(yè)沒多久的小菜鳥,在文章中可能存在一些說的不對(duì),代碼不嚴(yán)謹(jǐn)?shù)牡胤綒g迎各位大神指出,本人表示由衷的感謝和耐心的學(xué)習(xí),希望能在開發(fā)中給大家一些幫助。
參考
https://crossoverjie.top/2017...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77105.html
摘要:下面是幾種常見的限流技術(shù)一限流算法常用的限流算法有令牌桶,漏桶令牌桶令牌桶算法是網(wǎng)絡(luò)流量整形和速率限制中最常使用的一種算法。 就秒殺接口來說,當(dāng)訪問頻率或者并發(fā)請(qǐng)求超過其承受范圍的時(shí)候,這時(shí)候我們就要考慮限流來保證接口的可用性,以防止非預(yù)期的請(qǐng)求對(duì)系統(tǒng)壓力過大而引起的系統(tǒng)癱瘓。通常的策略就是拒絕多余的訪問,或者讓多余的訪問排隊(duì)等待服務(wù)。下面是幾種常見的限流技術(shù) 一、限流算法常用的限流算...
摘要:計(jì)數(shù)限流算法無論固定窗口還是滑動(dòng)窗口核心均是對(duì)請(qǐng)求進(jìn)行計(jì)數(shù),區(qū)別僅僅在于對(duì)于計(jì)數(shù)時(shí)間區(qū)間的處理。令牌桶限流實(shí)現(xiàn)原理令牌桶限流的實(shí)現(xiàn)原理在有詳細(xì)說明。因此由此為入口進(jìn)行分析。目前可返回的實(shí)現(xiàn)子類包括及兩種,具體不同下文詳細(xì)分析。 限流 限流一詞常用于計(jì)算機(jī)網(wǎng)絡(luò)之中,定義如下: In computer networks, rate limiting is used to control t...
摘要:令牌桶算法漏桶算法漏桶漏桶的出水速度是恒定的,那么意味著如果瞬時(shí)大流量的話,將有大部分請(qǐng)求被丟棄掉也就是所謂的溢出。 工作中對(duì)外提供的API 接口設(shè)計(jì)都要考慮限流,如果不考慮限流,會(huì)成系統(tǒng)的連鎖反應(yīng),輕者響應(yīng)緩慢,重者系統(tǒng)宕機(jī),整個(gè)業(yè)務(wù)線崩潰,如何應(yīng)對(duì)這種情況呢,我們可以對(duì)請(qǐng)求進(jìn)行引流或者直接拒絕等操作,保持系統(tǒng)的可用性和穩(wěn)定性,防止因流量暴增而導(dǎo)致的系統(tǒng)運(yùn)行緩慢或宕機(jī)。 在開發(fā)高并發(fā)...
摘要:關(guān)于如何限速,有兩個(gè)比較出名的算法,漏桶算法與令牌桶算法,這里對(duì)其簡(jiǎn)單介紹一下,最后再實(shí)踐在我發(fā)郵件的中以下是發(fā)送郵件的,已限制為一分鐘兩次,你可以通過修改進(jìn)行試驗(yàn)。 前段時(shí)間,我使用了 jwt 來實(shí)現(xiàn)郵箱驗(yàn)證碼的校驗(yàn)與用戶認(rèn)證與登錄,還特別寫了一篇文章作為總結(jié)。 在那篇文章中,提到了一個(gè)點(diǎn),如何限速。 在短信驗(yàn)證碼和郵箱驗(yàn)證碼,如果不限速,被惡意攻擊造成大量的 QPS,不僅拖垮了服務(wù)...
閱讀 2340·2021-11-22 14:56
閱讀 1478·2021-09-24 09:47
閱讀 913·2019-08-26 18:37
閱讀 2832·2019-08-26 12:10
閱讀 1528·2019-08-26 11:55
閱讀 3150·2019-08-23 18:07
閱讀 2306·2019-08-23 14:08
閱讀 612·2019-08-23 12:12