springboot+redis 實(shí)現(xiàn)分布式限流令牌桶的示例代碼
1、前言
網(wǎng)上找了很多redis分布式限流方案,要不就是太大,需要引入第三方j(luò)ar,而且還無法正常運(yùn)行,要不就是定時(shí)任務(wù)定時(shí)往key中放入數(shù)據(jù),使用的時(shí)候調(diào)用,嚴(yán)重影響性能,所以著手自定義實(shí)現(xiàn)redis令牌桶。
只用到了spring-boot-starter-data-redis包,并且就幾行代碼。
2、環(huán)境準(zhǔn)備
a、idea新建springboot項(xiàng)目,引入spring-data-redis包
b、編寫令牌桶實(shí)現(xiàn)方法RedisLimitExcutor
c、測試功能,創(chuàng)建全局?jǐn)r截器,測試功能
3、上代碼
maven添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
令牌桶實(shí)現(xiàn)方法RedisLimitExcutor
package com.example.redis_limit_demo.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * 令牌桶實(shí)現(xiàn) */ @Component public class RedisLimitExcutor { private StringRedisTemplate stringRedisTemplate; @Autowired public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } /** * 令牌的 * * @param key key值 * @param limitCount 容量 * @param seconds 時(shí)間間隔 * @return */ public boolean tryAccess(String key, int limitCount, int seconds) { String luaScript = buildLuaScript(); RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class); List<String> keys = new ArrayList<>(); keys.add(key); Long count = stringRedisTemplate.execute(redisScript, keys, String.valueOf(limitCount), String.valueOf(seconds)); if (count != 0) { return true; } else { return false; } } /** * 腳本 * * @return */ private static final String buildLuaScript() { StringBuilder lua = new StringBuilder(); lua.append(" local key = KEYS[1]"); lua.append("\nlocal limit = tonumber(ARGV[1])"); lua.append("\nlocal curentLimit = tonumber(redis.call('get', key) or \"0\")"); lua.append("\nif curentLimit + 1 > limit then"); lua.append("\nreturn 0"); lua.append("\nelse"); lua.append("\n redis.call(\"INCRBY\", key, 1)"); lua.append("\nredis.call(\"EXPIRE\", key, ARGV[2])"); lua.append("\nreturn curentLimit + 1"); lua.append("\nend"); return lua.toString(); } }
攔截器配置WebAppConfig
package com.example.redis_limit_demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * 攔截器配置 */ @Configuration public class WebAppConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getRequestInterceptor()).addPathPatterns("/**"); } @Bean public RequestInterceptor getRequestInterceptor() { return new RequestInterceptor(); } }
攔截器實(shí)現(xiàn)RequestInterceptor
package com.example.redis_limit_demo.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.InetAddress; import java.net.UnknownHostException; /** * 攔截器實(shí)現(xiàn) */ @Configuration public class RequestInterceptor implements HandlerInterceptor { @Autowired private RedisLimitExcutor redisLimitExcutor; /** * 只有返回true才會(huì)繼續(xù)向下執(zhí)行,返回false取消當(dāng)前請(qǐng)求 * * @param request * @param response * @param handler * @return */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { /** * 根據(jù)實(shí)際情況設(shè)置QPS */ String url = request.getRequestURI(); String ip = getIpAdd(request); //QPS設(shè)置為5,手動(dòng)刷新接口可以測試出來 if (!redisLimitExcutor.tryAccess(ip+url, 5, 1)) { throw new RuntimeException("調(diào)用頻繁"); } else { return true; } } public static final String getIpAdd(HttpServletRequest request) { String ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) { // 根據(jù)網(wǎng)卡取本機(jī)配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { return null; } ipAddress = inet.getHostAddress(); } } // 如果通過代理訪問,可能獲取2個(gè)IP,這時(shí)候去第二個(gè)(代理服務(wù)端IP) if (ipAddress.split(",").length > 1) { ipAddress = ipAddress.split(",")[1].trim(); } return ipAddress; } }
測試controller
package com.example.redis_limit_demo.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("demo") @RestController public class DemoController { @RequestMapping("limit") public String demo() { //todo 寫業(yè)務(wù)邏輯 return "aaaaa"; } }
4、運(yùn)行項(xiàng)目,訪問接口
http://localhost:8080/demo/limit
當(dāng)刷新頻率高了以后,就會(huì)報(bào)錯(cuò)
5、碼云地址(GitHub經(jīng)常訪問不到)
備注:
1、 redis的key可以根據(jù)實(shí)際情況設(shè)置,入例子中的ip+url,可以將全部流量進(jìn)行控制,防止惡意刷接口,但需要注意的是,使用ip方式,要將QPS設(shè)置大一些,因?yàn)闀?huì)出現(xiàn)整個(gè)大廈公用一個(gè)ip的情況。也可以使用url+userName,將QPS設(shè)置小一點(diǎn),可以更加精準(zhǔn)的限制api的訪問。
2、可以將拋出異常進(jìn)行全局捕獲和統(tǒng)一返回。
到此這篇關(guān)于springboot+redis 實(shí)現(xiàn)分布式限流令牌桶的示例代碼的文章就介紹到這了,更多相關(guān)springboot redis分布式限流令牌桶內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot+Redis實(shí)現(xiàn)API接口防刷限流的項(xiàng)目實(shí)踐
- SpringBoot整合Redis并且用Redis實(shí)現(xiàn)限流的方法 附Redis解壓包
- 基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡單的限流器
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的項(xiàng)目實(shí)踐
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的示例詳解
- SpringBoot Redis用注釋實(shí)現(xiàn)接口限流詳解
- 使用SpringBoot?+?Redis?實(shí)現(xiàn)接口限流的方式
- SpringBoot中使用Redis對(duì)接口進(jìn)行限流的實(shí)現(xiàn)
- SpringBoot整合redis實(shí)現(xiàn)計(jì)數(shù)器限流的示例
相關(guān)文章
springmvc中進(jìn)行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析
這篇文章主要介紹了springmvc中進(jìn)行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Java中l(wèi)ength,length(),size()詳解及區(qū)別
這篇文章主要介紹了Java中l(wèi)ength,length(),size()詳解及區(qū)別的相關(guān)資料,需要的朋友可以參考下2016-11-11Java中的反射,枚舉及l(fā)ambda表達(dá)式的使用詳解
這篇文章主要為大家詳細(xì)介紹了Java的反射,枚舉及l(fā)ambda表達(dá)式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03Spring Boot + Jpa(Hibernate) 架構(gòu)基本配置詳解
本篇文章主要介紹了Spring Boot + Jpa(Hibernate) 架構(gòu)基本配置詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05