- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > 編程語(yǔ)言 >
- springboot+redis 實(shí)現分布式限流令牌桶的示例代碼
網(wǎng)上找了很多redis分布式限流方案,要不就是太大,需要引入第三方j(luò )ar,而且還無(wú)法正常運行,要不就是定時(shí)任務(wù)定時(shí)往key中放入數據,使用的時(shí)候調用,嚴重影響性能,所以著(zhù)手自定義實(shí)現redis令牌桶。
只用到了spring-boot-starter-data-redis包,并且就幾行代碼。
a、idea新建springboot項目,引入spring-data-redis包
b、編寫(xiě)令牌桶實(shí)現方法RedisLimitExcutor
c、測試功能,創(chuàng )建全局攔截器,測試功能
maven添加依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
令牌桶實(shí)現方法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í)現 */ @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í)現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í)現 */ @Configuration public class RequestInterceptor implements HandlerInterceptor { @Autowired private RedisLimitExcutor redisLimitExcutor; /** * 只有返回true才會(huì )繼續向下執行,返回false取消當前請求 * * @param request * @param response * @param handler * @return */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { /** * 根據實(shí)際情況設置QPS */ String url = request.getRequestURI(); String ip = getIpAdd(request); //QPS設置為5,手動(dòng)刷新接口可以測試出來(lái) if (!redisLimitExcutor.tryAccess(ip+url, 5, 1)) { throw new RuntimeException("調用頻繁"); } 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")) { // 根據網(wǎng)卡取本機配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { return null; } ipAddress = inet.getHostAddress(); } } // 如果通過(guò)代理訪(fǎng)問(wèn),可能獲取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 寫(xiě)業(yè)務(wù)邏輯 return "aaaaa"; } }
http://localhost:8080/demo/limit
當刷新頻率高了以后,就會(huì )報錯
5、(GitHub經(jīng)常訪(fǎng)問(wèn)不到)
備注:
1、 redis的key可以根據實(shí)際情況設置,入例子中的ip+url,可以將全部流量進(jìn)行控制,防止惡意刷接口,但需要注意的是,使用ip方式,要將QPS設置大一些,因為會(huì )出現整個(gè)大廈公用一個(gè)ip的情況。也可以使用url+userName,將QPS設置小一點(diǎn),可以更加精準的限制api的訪(fǎng)問(wèn)。
2、可以將拋出異常進(jìn)行全局捕獲和統一返回。
到此這篇關(guān)于springboot+redis 實(shí)現分布式限流令牌桶的示例代碼的文章就介紹到這了,更多相關(guān)springboot redis分布式限流令牌桶內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站