分布式接口冪等性、分布式限流總結(jié)整理

1、Update操作的冪等性
1)根據(jù)唯一業(yè)務(wù)號去更新數(shù)據(jù)
update set version = version +1 ,xxx=${xxx} where id =xxx and version = ${version};
2、使用Token機(jī)制,保證update、insert操作的冪等性
1)沒有唯一業(yè)務(wù)號的update與insert操作
1、分布式限流的幾種維度
時間 限流基于某段時間范圍或者某個時間點(diǎn),也就是我們常說的“時間窗口”,比如對每分鐘、每秒鐘的時間窗口做限定 資源 基于可用資源的限制,比如設(shè)定最大訪問次數(shù),或最高可用連接數(shù)
1)QPS和連接數(shù)控制
2)傳輸速率
3)黑白名單
4)分布式環(huán)境
網(wǎng)關(guān)層限流
中間件限流
2、限流方案常用算法講解
1)令牌桶算法
令牌 獲取到令牌的Request才會被處理,其他Requests要么排隊(duì)要么被直接丟棄 桶用來裝令牌的地方,所有Request都從這個桶里面獲取令牌
2)漏桶算法
3、分布式限流的主流方案
1)Guava RateLimiter客戶端限流
com.google.guava guava 18.0
@RestController@Slf4jpublic class Controller{//每秒鐘可以創(chuàng)建兩個令牌RateLimiter limiter = RateLimiter.create(2.0);//非阻塞限流@GetMapping("/tryAcquire")public String tryAcquire(Integer count){//count 每次消耗的令牌if(limiter.tryAcquire(count)){log.info("成功,允許通過,速率為{}",limiter.getRate());return "success";}else{log.info("錯誤,不允許通過,速率為{}",limiter.getRate());return "fail";}}//限定時間的非阻塞限流@GetMapping("/tryAcquireWithTimeout")public String tryAcquireWithTimeout(Integer count, Integer timeout){//count 每次消耗的令牌 timeout 超時等待的時間if(limiter.tryAcquire(count,timeout,TimeUnit.SECONDS)){log.info("成功,允許通過,速率為{}",limiter.getRate());return "success";}else{log.info("錯誤,不允許通過,速率為{}",limiter.getRate());return "fail";}}//同步阻塞限流@GetMapping("/acquire")public String acquire(Integer count){limiter.acquire(count);log.info("成功,允許通過,速率為{}",limiter.getRate());return "success";}}
2)基于Nginx的限流
1.iP限流
@RestController@Slf4jpublic class Controller{//nginx測試使用@GetMapping("/nginx")public String nginx(){log.info("Nginx success");}}
127.0.0.1 www.test.com
vim /usr/local/nginx/conf/nginx.conf
#根據(jù)IP地址限制速度#1)$binary_remote_addr binary_目的是縮寫內(nèi)存占用,remote_addr表示通過IP地址來限流#2)zone=iplimit:20m iplimit是一塊內(nèi)存區(qū)域(記錄訪問頻率信息),20m是指這塊內(nèi)存區(qū)域的大小#3)rate=1r/s 每秒放行1個請求limit_req_zone $binary_remote_addr zone=iplimit:20m rate=1r/s;server{server_name www.test.com;location /access-limit/ {proxy_pass http://127.0.0.1:8080/;#基于ip地址的限制#1)zone=iplimit 引用limit_rep_zone中的zone變量#2)burst=2 設(shè)置一個大小為2的緩沖區(qū)域,當(dāng)大量請求到來,請求數(shù)量超過限流頻率時,將其放入緩沖區(qū)域#3)nodelay 緩沖區(qū)滿了以后,直接返回503異常limit_req zone=iplimit burst=2 nodelay;}}
www.test.com/access-limit/nginx
2.多維度限流
#根據(jù)IP地址限制速度limit_req_zone $binary_remote_addr zone=iplimit:20m rate=10r/s;#根據(jù)服務(wù)器級別做限流limit_req_zone $server_name zone=serverlimit:10m rate=1r/s;#根據(jù)ip地址的鏈接數(shù)量做限流limit_conn_zone $binary_remote_addr zone=perip:20m;#根據(jù)服務(wù)器的連接數(shù)做限流limit_conn_zone $server_name zone=perserver:20m;server{server_name www.test.com;location /access-limit/ {proxy_pass http://127.0.0.1:8080/;#基于ip地址的限制limit_req zone=iplimit burst=2 nodelay;#基于服務(wù)器級別做限流limit_req zone=serverlimit burst=2 nodelay;#基于ip地址的鏈接數(shù)量做限流 最多保持100個鏈接limit_conn zone=perip 100;#基于服務(wù)器的連接數(shù)做限流 最多保持100個鏈接limit_conn zone=perserver 1;#配置request的異常返回504(默認(rèn)為503)limit_req_status 504;limit_conn_status 504;}location /download/ {#前100m不限制速度limit_rate_affer 100m;#限制速度為256klimit_rate 256k;}}
3)基于Redis+Lua的分布式限流
1.Lua腳本
2.Lua安裝
參考http://www.lua.org/ftp/教程,下載5.3.5_1版本,本地安裝 如果你使用的是Mac,那建議用brew工具直接執(zhí)行brew install lua就可以順利安裝, 有關(guān)brew工具的安裝可以參考https://brew.sh/網(wǎng)站,建議翻墻否則會很慢。 使用brew安裝后的目錄在/usr/local/Cellar/lua/5.3.5_1 安裝IDEA插件,在IDEA->Preferences面板,Plugins, 里面Browse repositories,在里面搜索lua,然后就選擇同名插件lua。安裝好后重啟IDEA 配置Lua SDK的位置:IDEA->File->Project Structure, 選擇添加Lua,路徑指向Lua SDK的bin文件 都配置好之后,在項(xiàng)目中右鍵創(chuàng)建Module,左側(cè)欄選擇lua,點(diǎn)下一步,選擇lua的sdk,下一步,輸入lua項(xiàng)目名,完成
3.編寫hello lua
print?'Hello?Lua'
4.編寫模擬限流
-- 模擬限流-- 用作限流的keylocal key = 'my key'-- 限流的最大閾值local limit = 2-- 當(dāng)前限流大小local currentLimit = 2-- 是否超過限流標(biāo)準(zhǔn)if currentLimit + 1 > limit thenprint 'reject'return falseelseprint 'accept'return trueend
5.限流組件封裝
org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-aop com.google.guava guava 18.0
server.port=8080spring.redis.database=0spring.redis.host=localhostspring.redis.port=6376
-- 獲取方法簽名特征local methodKey = KEYS[1]redis.log(redis.LOG_DEBUG,'key is',methodKey)-- 調(diào)用腳本傳入的限流大小local limit = tonumber(ARGV[1])-- 獲取當(dāng)前流量大小local count = tonumber(redis.call('get',methodKey) or "0")--是否超出限流值if count + 1 >limit then-- 拒絕訪問return falseelse-- 沒有超過閾值-- 設(shè)置當(dāng)前訪問數(shù)量+1redis.call('INCRBY',methodKey,1)-- 設(shè)置過期時間redis.call('EXPIRE',methodKey,1)-- 放行return trueend
@Service@Slf4jpublic class AccessLimiter{@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedisScriptrateLimitLua; public void limitAccess(String key,Integer limit){boolean acquired = stringRedisTemplate.execute(rateLimitLua,//lua腳本的真身Lists.newArrayList(key),//lua腳本中的key列表limit.toString()//lua腳本的value列表);if(!acquired){log.error("Your access is blocked,key={}",key);throw new RuntimeException("Your access is blocked");}}}
@Configurationpublic class RedisConfiguration{public RedisTemplateredisTemplate(RedisConnectionFactory factory){ return new StringRedisTemplate(factory);}public DefaultRedisScript loadRedisScript(){DefaultRedisScript redisScript = new DefaultRedisScript();redisScript.setLocation(new ClassPathResource("rateLimiter.lua"));redisScript.setResultType(java.lang.Boolean.class);return redisScript;}}
@RestController@Slf4jpublic class Controller{@Autowiredprivate AccessLimiter accessLimiter;@GetMapping("test")public String test(){accessLimiter.limitAccess("ratelimiter-test",1);return "success";}}
6.編寫限流注解
@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface AccessLimiterAop{int limit();String methodKey() default "";}
@Slf4j@Aspect@Componentpublic class AccessLimiterAspect{@Autowiredprivate AccessLimiter accessLimiter;//根據(jù)注解的位置,自己修改@Pointcut("@annotation(com.gyx.demo.annotation.AccessLimiter)")public void cut(){log.info("cut");}@Before("cut()")public void before(JoinPoint joinPoint){//獲取方法簽名,作為methodkeyMethodSignature signature =(MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();AccessLimiterAop annotation = method.getAnnotation(AccessLimiterAop.class);if(annotation == null){return;}String key = annotation.methodKey();Integer limit = annotation.limit();//如果沒有設(shè)置methodKey,就自動添加一個if(StringUtils.isEmpty(key)){Class[] type = method.getParameterType();key = method.getName();if (type != null){String paramTypes=Arrays.stream(type).map(Class::getName).collect(Collectors.joining(","));key += "#"+paramTypes;}}//調(diào)用redisreturn accessLimiter.limitAccess(key,limit);}}
@RestController@Slf4jpublic class Controller{@Autowiredprivate AccessLimiter accessLimiter;@GetMapping("test")@AccessLImiterAop(limit =1)public String test(){return "success";}}
作者:敲代碼的旺財
來源:blog.csdn.net/qq_34886352/article/details/104694550

評論
圖片
表情
