kubernetes實(shí)現(xiàn)分布式限流
一、概念
限流(Ratelimiting)指對(duì)應(yīng)用服務(wù)的請(qǐng)求進(jìn)行限制,例如某一接口的請(qǐng)求限制為 100 個(gè)每秒,對(duì)超過(guò)限制的請(qǐng)求則進(jìn)行快速失敗或丟棄。
1.1 使用場(chǎng)景
限流可以應(yīng)對(duì):
- 熱點(diǎn)業(yè)務(wù)帶來(lái)的突發(fā)請(qǐng)求;
- 調(diào)用方 bug 導(dǎo)致的突發(fā)請(qǐng)求;
- 惡意攻擊請(qǐng)求。
1.2 維度
對(duì)于限流場(chǎng)景,一般需要考慮兩個(gè)維度的信息:
時(shí)間限流基于某段時(shí)間范圍或者某個(gè)時(shí)間點(diǎn),也就是我們常說(shuō)的“時(shí)間窗口”,比如對(duì)每分鐘、每秒鐘的時(shí)間窗口做限定
資源基于可用資源的限制,比如設(shè)定最大訪問(wèn)次數(shù),或最高可用連接數(shù)。
限流就是在某個(gè)時(shí)間窗口對(duì)資源訪問(wèn)做限制,比如設(shè)定每秒最多100個(gè)訪問(wèn)請(qǐng)求。

1.3 分布式限流
分布式限流相比于單機(jī)限流,只是把限流頻次分配到各個(gè)節(jié)點(diǎn)中,比如限制某個(gè)服務(wù)訪問(wèn)100qps,如果有10個(gè)節(jié)點(diǎn),那么每個(gè)節(jié)點(diǎn)理論上能夠平均被訪問(wèn)10次,如果超過(guò)了則進(jìn)行頻率限制。
二、分布式限流常用方案
基于Guava的客戶端限流Guava是一個(gè)客戶端組件,在其多線程模塊下提供了以RateLimiter為首的幾個(gè)限流支持類。它只能對(duì)“當(dāng)前”服務(wù)進(jìn)行限流,即它不屬于分布式限流的解決方案。
網(wǎng)關(guān)層限流服務(wù)網(wǎng)關(guān),作為整個(gè)分布式鏈路中的第一道關(guān)卡,承接了所有用戶來(lái)訪請(qǐng)求。我們?cè)诰W(wǎng)關(guān)層進(jìn)行限流,就可以達(dá)到了整體限流的目的了。目前,主流的網(wǎng)關(guān)層有以軟件為代表的Nginx,還有Spring Cloud中的Gateway和Zuul這類網(wǎng)關(guān)層組件,也有以硬件為代表的F5。
中間件限流將限流信息存儲(chǔ)在分布式環(huán)境中某個(gè)中間件里(比如Redis緩存),每個(gè)組件都可以從這里獲取到當(dāng)前時(shí)刻的流量統(tǒng)計(jì),從而決定是拒絕服務(wù)還是放行流量。
限流組件目前也有一些開(kāi)源組件提供了限流的功能,比如Sentinel就是一個(gè)不錯(cuò)的選擇。Sentinel是阿里出品的開(kāi)源組件,并且包含在了Spring Cloud Alibaba組件庫(kù)中。Hystrix也具有限流的功能。
Guava的Ratelimiter設(shè)計(jì)實(shí)現(xiàn)相當(dāng)不錯(cuò),可惜只能支持單機(jī),網(wǎng)關(guān)層限流如果是單機(jī)則不太滿足高可用,并且分布式網(wǎng)關(guān)的話還是需要依賴中間件限流,而redis之類的網(wǎng)絡(luò)通信需要占用一小部分的網(wǎng)絡(luò)消耗。阿里的Sentinel也是同理,底層使用的是redis或者zookeeper,每次訪問(wèn)都需要調(diào)用一次redis或者zk的接口。那么在云原生場(chǎng)景下,我們有沒(méi)有什么更好的辦法呢?
對(duì)于極致追求高性能的服務(wù)不需要考慮熔斷、降級(jí)來(lái)說(shuō),是需要盡量減少網(wǎng)絡(luò)之間的IO,那么是否可以通過(guò)一個(gè)總限頻然后分配到具體的單機(jī)里面去,在單機(jī)中實(shí)現(xiàn)平均的限流,比如限制某個(gè)ip的qps為100,服務(wù)總共有10個(gè)節(jié)點(diǎn),那么平均到每個(gè)服務(wù)里就是10qps,此時(shí)就可以通過(guò)guava的ratelimiter來(lái)實(shí)現(xiàn)了,甚至說(shuō)如果服務(wù)的節(jié)點(diǎn)動(dòng)態(tài)調(diào)整,單個(gè)服務(wù)的qps也能動(dòng)態(tài)調(diào)整。
三、基于kubernetes的分布式限流
在Spring Boot應(yīng)用中,定義一個(gè)filter,獲取請(qǐng)求參數(shù)里的key(ip、userId等),然后根據(jù)key來(lái)獲取rateLimiter,其中,rateLimiter的創(chuàng)建由數(shù)據(jù)庫(kù)定義的限頻數(shù)和副本數(shù)來(lái)判斷,最后,再通過(guò)rateLimiter.tryAcquire來(lái)判斷是否可以通過(guò)。

3.1 kubernetes中的副本數(shù)
在實(shí)際的服務(wù)中,數(shù)據(jù)上報(bào)服務(wù)一般無(wú)法確定客戶端的上報(bào)時(shí)間、上報(bào)量,特別是對(duì)于這種要求高性能,服務(wù)一般都會(huì)用到HPA來(lái)實(shí)現(xiàn)動(dòng)態(tài)擴(kuò)縮容,所以,需要去間隔一段時(shí)間去獲取服務(wù)的副本數(shù)。
func CountDeploymentSize(namespace string, deploymentName string) *int32 {
deployment, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentName, metav1.GetOptions{})
if err != nil {
return nil
}
return deployment.Spec.Replicas
}用法:GET host/namespaces/test/deployments/k8s-rest-api直接即可。
3.2 rateLimiter的創(chuàng)建
在RateLimiterService中定義一個(gè)LoadingCache<String, RateLimiter>,其中,key可以為ip、userId等,并且,在多線程的情況下,使用refreshAfterWrite只阻塞加載數(shù)據(jù)的線程,其他線程則返回舊數(shù)據(jù),極致發(fā)揮緩存的作用。
private final LoadingCache<String, RateLimiter> loadingCache = Caffeine.newBuilder()
.maximumSize(10_000)
.refreshAfterWrite(20, TimeUnit.MINUTES)
.build(this::createRateLimit);
//定義一個(gè)默認(rèn)最小的QPS
private static final Integer minQpsLimit = 3000;之后是創(chuàng)建rateLimiter,獲取總限頻數(shù)totalLimit和副本數(shù)replicas,之后是自己所需的邏輯判斷,可以根據(jù)totalLimit和replicas的情況來(lái)進(jìn)行qps的限定。
public RateLimiter createRateLimit(String key) {
log.info("createRateLimit,key:{}", key);
int totalLimit = 獲取總限頻數(shù),可以在數(shù)據(jù)庫(kù)中定義
Integer replicas = kubernetesService.getDeploymentReplicas();
RateLimiter rateLimiter;
if (totalLimit > 0 && replicas == null) {
rateLimiter = RateLimiter.create(totalLimit);
} else if (totalLimit > 0) {
int nodeQpsLimit = totalLimit / replicas;
rateLimiter = RateLimiter.create(nodeQpsLimit > minQpsLimit ? nodeQpsLimit : minQpsLimit);
} else {
rateLimiter = RateLimiter.create(minQpsLimit);
}
log.info("create rateLimiter success,key:{},rateLimiter:{}", key, rateLimiter);
return rateLimiter;
}3.3 rateLimiter的獲取
根據(jù)key獲取RateLimiter,如果有特殊需求的話,需要判斷key不存在的嘗盡
public RateLimiter getRateLimiter(String key) {
return loadingCache.get(key);
}3.4 filter里的判斷
最后一步,就是使用rateLimiter來(lái)進(jìn)行限流,如果rateLimiter.tryAcquire()為true,則進(jìn)行filterChain.doFilter(request, response),如果為false,則返回HttpStatus.TOO_MANY_REQUESTS
public class RateLimiterFilter implements Filter {
@Resource
private RateLimiterService rateLimiterService;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String key = httpServletRequest.getHeader("key");
RateLimiter rateLimiter = rateLimiterService.getRateLimiter(key);
if (rateLimiter != null) {
if (rateLimiter.tryAcquire()) {
filterChain.doFilter(request, response);
} else {
httpServletResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
}
} else {
filterChain.doFilter(request, response);
}
}
}四、性能壓測(cè)
為了方便對(duì)比性能之間的差距,我們?cè)诒镜貑螜C(jī)做了下列測(cè)試,其中,總限頻都設(shè)置為3萬(wàn)。
無(wú)限流

使用redis限流
其中,ping redis大概6-7ms左右,對(duì)應(yīng)的,每次請(qǐng)求需要訪問(wèn)redis,時(shí)延都有大概6-7ms,性能下降明顯

自研限流
性能幾乎追平無(wú)限流的場(chǎng)景,guava的rateLimiter確實(shí)表現(xiàn)卓越

五、其他問(wèn)題
5.1 對(duì)于保證qps限頻準(zhǔn)確的時(shí)候,應(yīng)該怎么解決呢?
在k8s中,服務(wù)是動(dòng)態(tài)擴(kuò)縮容的,相應(yīng)的,每個(gè)節(jié)點(diǎn)應(yīng)該都要有所變化,如果對(duì)外宣稱限頻100qps,而且后續(xù)業(yè)務(wù)方真的要求百分百準(zhǔn)確,只能把LoadingCache<String, RateLimiter>的過(guò)期時(shí)間調(diào)小一點(diǎn),讓它能夠近實(shí)時(shí)的更新單節(jié)點(diǎn)的qps。這里還需要考慮一下k8s的壓力,因?yàn)槊看味家@取副本數(shù),這里也是需要做緩存的
5.2 服務(wù)從1個(gè)節(jié)點(diǎn)動(dòng)態(tài)擴(kuò)為4個(gè)節(jié)點(diǎn),這個(gè)時(shí)候新節(jié)點(diǎn)識(shí)別為4,但其實(shí)有些并沒(méi)有啟動(dòng)完,會(huì)不會(huì)造成某個(gè)節(jié)點(diǎn)承受了太大的壓力
理論上是存在這個(gè)可能的,這個(gè)時(shí)候需要考慮一下初始的副本數(shù)的,擴(kuò)縮容不能一蹴而就,一下子從1變?yōu)?變?yōu)閹资畟€(gè)這種。一般的話,生產(chǎn)環(huán)境肯定是不能只有一個(gè)節(jié)點(diǎn),并且要考慮擴(kuò)縮容的話,至于要有多個(gè)副本預(yù)備的
5.3 如果有多個(gè)副本,怎么保證請(qǐng)求是均勻的
這個(gè)是依賴于k8s的service負(fù)載均衡策略的,這個(gè)我們之前做過(guò)實(shí)驗(yàn),流量確實(shí)是能夠均勻的落到節(jié)點(diǎn)上的。還有就是,我們整個(gè)限流都是基于k8s的,如果k8s出現(xiàn)問(wèn)題,那就是整個(gè)集群所有服務(wù)都有可能出現(xiàn)問(wèn)題了。
到此這篇關(guān)于kubernetes實(shí)現(xiàn)分布式限流的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Kubernetes(K8S)基礎(chǔ)知識(shí)
- Kubernetes中Deployment的升級(jí)與回滾
- Kubernetes部署實(shí)例并配置Deployment、網(wǎng)絡(luò)映射、副本集
- Kubernetes關(guān)鍵組件與結(jié)構(gòu)組成介紹
- 配置Kubernetes外網(wǎng)訪問(wèn)集群
- 使用kubeadm命令行工具創(chuàng)建kubernetes集群
- Minikube搭建Kubernetes集群
- Kubernetes集群的組成介紹
- Kubernetes(K8S)入門基礎(chǔ)內(nèi)容介紹
- Kubernetes探針使用介紹
相關(guān)文章
Kubernetes kubectl中Pod創(chuàng)建流程源碼解析
這篇文章主要為大家介紹了Kubernetes kubectl中Pod創(chuàng)建流程源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Kubernetes ApiServer三大server權(quán)限與數(shù)據(jù)存儲(chǔ)解析
這篇文章主要為大家介紹了Kubernetes ApiServer三大server權(quán)限與數(shù)據(jù)存儲(chǔ)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Dashboard管理Kubernetes集群與API訪問(wèn)配置
這篇文章介紹了Dashboard管理Kubernetes集群與API訪問(wèn)配置的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
Docker與K8s關(guān)系介紹不會(huì)Docker也可以使用K8s
想學(xué)K8s,必須得先學(xué)會(huì)Docker嗎?這是很多網(wǎng)友在開(kāi)始有想法想要學(xué)?K8s的時(shí)候都會(huì)冒出來(lái)的想法,要回答這個(gè)問(wèn)題,我們需要先搞清楚?Docker?和?K8s?他們的角色是什么,相互之間是什么關(guān)系2022-06-06
CentOS 出現(xiàn)no space left on device錯(cuò)誤解決辦法
這篇文章主要介紹了CentOS 出現(xiàn)no space left on device錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-04-04
Kubernetes(K8S)容器集群管理環(huán)境完整部署詳細(xì)教程-上篇
本系列文章主要介紹了Kubernetes(K8S)容器集群管理環(huán)境完整部署的詳細(xì)教程,分為上中下三篇文章,此為上篇,需要的朋友可以參考下2022-01-01
Citrix Xenserver 7怎么安裝?Xenserver 7.0安裝詳細(xì)圖文教程(附下載地址)
XenServer 7.0正式版已近發(fā)布了,今天腳本之家 小編為大家?guī)?lái)了Xenserver 7安裝詳細(xì)圖文教程,希望對(duì)大家有所幫助2017-12-12

