RPC遠程調用 可以跨平台 一般采用HTTP協議 底層使用socket技術 只要語言支持socket技術,就可以進行通訊
開放平台一般采用http協議,因為支持更多的語言
本地調用只支持Java語言與Java語言開發使用虛擬機與虛擬機之間通訊 rmi
高並發限流解決方案
為啥要限流?
秒殺 雙十一 服務安全(流量攻擊 DDOS) 雪崩效應
限流為了保護服務
高並發限流解決方案限流算法(令牌桶、漏桶、計數器)、應用層解決限流(Nginx)
限流算法
常見的限流算法有:令牌桶、漏桶。計數器也可以進行粗暴限流實現。
計數器
它是限流算法中最簡單最容易的一種算法,比如我們要求某一個接口,1分鍾內的請求不能超過10次,我們可以在開始時設置一個計數器,每次請求,該計數器+1;如果該計數器的值大於10並且與第一次請求的時間間隔在1分鍾內,那么說明請求過多,如果該請求與第一次請求的時間間隔大於1分鍾,並且該計數器的值還在限流范圍內,那么重置該計數器
public class LimitService { private int limtCount = 60;// 限制最大訪問的容量 AtomicInteger atomicInteger = new AtomicInteger(0); // 每秒鍾 實際請求的數量 private long start = System.currentTimeMillis();// 獲取當前系統時間 private int interval = 60;// 間隔時間60秒 public boolean acquire() { long newTime = System.currentTimeMillis(); if (newTime > (start + interval)) { // 判斷是否是一個周期 start = newTime; atomicInteger.set(0); // 清理為0 return true; } atomicInteger.incrementAndGet();// i++; return atomicInteger.get() <= limtCount; } static LimitService limitService = new LimitService(); public static void main(String[] args) { ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); for (int i = 1; i < 100; i++) { final int tempI = i; newCachedThreadPool.execute(new Runnable() { public void run() { if (limitService.acquire()) { System.out.println("你沒有被限流,可以正常訪問邏輯 i:" + tempI); } else { System.out.println("你已經被限流呢 i:" + tempI); } } }); } } }
滑動窗口計數
滑動窗口計數有很多使用場景,比如說限流防止系統雪崩。相比計數實現,滑動窗口實現會更加平滑,能自動消除毛刺。
滑動窗口原理是在每次有訪問進來時,先判斷前 N 個單位時間內的總訪問量是否超過了設置的閾值,並對當前時間片上的請求數 +1。
相對於傳統的 對於臨界值的打破 傳統計數器缺點:臨界問題 可能違背定義固定速率原則
每隔10s移動一次
令牌桶算法
Token guava限流 令牌算法
令牌桶算法是一個存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:
假設限制2r/s,則按照500毫秒的固定速率往桶中添加令牌;
桶中最多存放b個令牌,當桶滿時,新添加的令牌被丟棄或拒絕;
當一個n個字節大小的數據包到達,將從桶中刪除n個令牌,接着數據包被發送到網絡上;
如果桶中的令牌不足n個,則不會刪除令牌,且該數據包將被限流(要么丟棄,要么緩沖區等待)。
令牌桶算法(Token)
令牌桶分為兩個動作
動作1 固定速錄往桶中存入令牌
動作2 客戶端如果想訪問請求,先從桶中獲取token
客戶端只有拿到令牌 才可以 訪問服務器端 這是關鍵哦,獲取不到就走降級
桶中的令牌也會滿了哦,滿了就不在裝了。不能無限裝的,有容量。
一個存 一個取
使用RateLimiter實現令牌桶限流
RateLimiter是guava提供的基於令牌桶算法的實現類,可以非常簡單的完成限流特技,並且根據系統的實際情況來調整生成token的速率。
通常可應用於搶購限流防止沖垮系統;限制某接口、服務單位時間內的訪問量,譬如一些第三方服務會對用戶訪問量進行限制;限制網速,單位時間內只允許上傳下載多少字節等。
pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.toov5</groupId> <artifactId>springboot-guava</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.1-jre</version> </dependency> </dependencies> </project>
service
package com.toov5.service; import org.springframework.stereotype.Service; @Service public class OrderService { public boolean addOrder() { System.out.println("db...正在操作訂單表數據庫"); return true; } }
controller
package com.toov5.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.google.common.util.concurrent.RateLimiter; import com.toov5.service.OrderService; @RestController public class IndexController { @Autowired private OrderService orderService; //create方法中傳入一個參數 以秒為單位固定的速率值 1r/s 往桶中存入一個令牌 RateLimiter rateLimiter = RateLimiter.create(1); //獨立線程!它自己是個線程 //相當於接口每秒只能接受一個客戶端請求 @RequestMapping("/addOrder") public String addOrder() { //限流放在網關 獲取到當前 客戶端從桶中獲取對應的令牌 結果表示從桶中拿到令牌等待時間 //如果獲取不到令牌 就一直等待 double acquire = rateLimiter.acquire(); System.out.println("從桶中獲取令牌等待時間"+acquire); //業務邏輯處理 boolean addOrderResult = orderService.addOrder(); if (addOrderResult) { return "恭喜搶購成功!"; } return "搶購失敗!"; } }
啟動:
package com.toov5; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
運行訪問:
加速點擊 后面就有等待了 存放到token 不夠用了就得等待了
這樣一直等待也不好 設置服務降級處理
規定時間內沒有獲取到臨牌 走降級
修改添加:
package com.toov5.controller; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.google.common.util.concurrent.RateLimiter; import com.toov5.service.OrderService; @RestController public class IndexController { @Autowired private OrderService orderService; //create方法中傳入一個參數 以秒為單位固定的速率值 1r/s 往桶中存入一個令牌 RateLimiter rateLimiter = RateLimiter.create(1); //獨立線程!它自己是個線程 //相當於接口每秒只能接受一個客戶端請求 @RequestMapping("/addOrder") public String addOrder() { //限流放在網關 獲取到當前 客戶端從桶中獲取對應的令牌 結果表示從桶中拿到令牌等待時間 //如果獲取不到令牌 就一直等待 double acquire = rateLimiter.acquire(); System.out.println("從桶中獲取令牌等待時間"+acquire); boolean tryAcquire=rateLimiter.tryAcquire(500,TimeUnit.MILLISECONDS); //如果在500sms沒有獲取到令牌 直接走降級 if (!tryAcquire) { System.out.println("別搶了,等等吧!"); return "別搶了,等等吧!"; } //業務邏輯處理 boolean addOrderResult = orderService.addOrder(); if (addOrderResult) { System.out.println("恭喜搶購成功!"); return "恭喜搶購成功!"; } return "搶購失敗!"; } }
運行結果:
獲取后就從桶中刪除了token
漏桶算法
漏桶作為計量工具(The Leaky Bucket Algorithm as a Meter)時,可以用於流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:
一個固定容量的漏桶,按照常量固定速率流出水滴;
如果桶是空的,則不需流出水滴;
可以以任意速率流入水滴到漏桶;
如果流入水滴超出了桶的容量,則流入的水滴溢出了(被丟棄),而漏桶容量是不變的。
令牌桶和漏桶對比:
令牌桶是按照固定速率往桶中添加令牌,請求是否被處理需要看桶中令牌是否足夠,當令牌數減為零時則拒絕新的請求;
漏桶則是按照常量固定速率流出請求,流入請求速率任意,當流入的請求數累積到漏桶容量時,則新流入的請求被拒絕;
令牌桶限制的是平均流入速率(允許突發請求,只要有令牌就可以處理,支持一次拿3個令牌,4個令牌),並允許一定程度突發流量;
漏桶限制的是常量流出速率(即流出速率是一個固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),從而平滑突發流入速率;
令牌桶允許一定程度的突發,而漏桶主要目的是平滑流入速率;
兩個算法實現可以一樣,但是方向是相反的,對於相同的參數得到的限流效果是一樣的。
另外有時候我們還使用計數器來進行限流,主要用來限制總並發數,比如數據庫連接池、線程池、秒殺的並發數;只要全局總請求數或者一定時間段的總請求數設定的閥值則進行限流,是簡單粗暴的總數量限流,而不是平均速率限流。
應用層可以 Nginx http_limit 應用層限流 (這一種運維解決,前三種 網關里面加代碼)
以固定的速率從桶中流出水滴 以任意的速率往桶中放水滴 桶中的容量大小是不會發生改變的
令牌桶算法以平均速率訪問,漏桶算法平滑訪問。