文档章节

基于Ambassador API网关实现Java速率限制服务

xiaomin0322
 xiaomin0322
发布于 07/12 18:00
字数 3268
阅读 7
收藏 0
点赞 0
评论 0

基于Kubernetes云原生的Ambassador API网关所提供的速率限制功能是完全可定制的,其允许任何实现gRPC服务端点的服务自行决定是否需要对请求进行限制。本文在先前第1部分和第2部分的基础上,阐述如何为Ambassador API网关创建和部署简单的基于Java的速率限制服务。

部署Docker Java Shop

在我之前的教程“使用Kubernetes和Ambassador API网关部署Java应用”中,我将开源的Ambassador API网关添加到现有的一个部署于Kubernetes的Java(Spring Boot和Dropwizard)服务中。 如果你之前不了解这个,建议你先阅读下此教程及其他相关内容来熟悉基础知识。 本文假定你熟悉如何构建基于Java的微服务并将其部署到Kubernetes,同时已经完成安装所有的必备组件(我在本文中使用Docker for Mac Edge,并启用其内置的Kubernetes支持。若使用minikube或远程群集应该也类似)。

先决条件

需要在本地安装:

  • Docker for Desktop:我使用edge community edition (18.04.0-ce),内置了对本地Kubernetes集群的支持。由于Java应用对内存有一定要求,我还将Docker可用内存增加到8G。

  • 编辑器选择:Atom 或者 VS code;当写Java代码时也可以使用IntelliJ。

你可以在这里获取最新版本的“Docker Java Shop”源代码:

https://github.com/danielbryantuk/oreilly-docker-java-shopping

你可以通过如下命令使用SSH克隆仓库:

 
  1. $ git clone git@github.com:danielbryantuk/oreilly-docker-java-shopping.git

第一阶段的服务和部署架构如下图所示:

从图中可以看到,Docker Java Shopping应用程序主要由三个服务组成。在先前的教程中,你已经添加Ambassador API网关作为系统的“front door”(大门)。需要注意的是,Ambassador API网关直接使用Web 80号端口,因此需要确保本地运行的其他应用没有占用该端口。

Ambassador API网关速率限制入门

我在本教程的仓库中增加了一个新文件夹 “kubernetes-ambassador-ratelimit”,用于包含Kubernetes相关配置。请通过命令行导航到此目录。此目录应包含如下文件:

 
  1. (master *) oreilly-docker-java-shopping $ cd kubernetes-ambassador-ratelimit/

  2. (master *) kubernetes-ambassador-ratelimit $ ll

  3. total 48

  4. 0 drwxr-xr-x 8 danielbryant staff 256 23 Apr 09:27 .

  5. 0 drwxr-xr-x 19 danielbryant staff 608 23 Apr 09:27 ..

  6. 8 -rw-r — r — 1 danielbryant staff 2033 23 Apr 09:27 ambassador-no-rbac.yaml

  7. 8 -rw-r — r — 1 danielbryant staff 698 23 Apr 10:30 ambassador-rate-limiter.yaml

  8. 8 -rw-r — r — 1 danielbryant staff 476 23 Apr 10:30 ambassador-service.yaml

  9. 8 -rw-r — r — 1 danielbryant staff 711 23 Apr 09:27 productcatalogue-service.yaml

  10. 8 -rw-r — r — 1 danielbryant staff 659 23 Apr 10:02 shopfront-service.yaml

  11. 8 -rw-r — r — 1 danielbryant staff 678 23 Apr 09:27 stockmanager-service.yaml

你可以使用以下命令来提交Kubernetes配置:

 
  1. $ kubectl apply -f .

通过以上命令部署,这与之前架构的区别在于添加了 ratelimiter服务。 这个服务是用Java编写的,且没有使用微服务框架。它发布了一个gRPC端点,可供Ambassador来使用以实现速率限制。这种方案允许灵活定制速率限制算法(关于这点的好处请查看我以前的文章)。

探索部署于Kubernetes的限速器服务

与任何其他服务一样,部署到Kubernetes的限速服务也可以根据需要进行水平扩展。 以下是Kubernetes配置文件 ambassador-rate-limiter.yaml的内容:

 
  1. ---

  2. apiVersion: v1

  3. kind: Service

  4. metadata:

  5.  name: ratelimiter

  6.  annotations:

  7.    getambassador.io/config: |

  8.      ---

  9.      apiVersion: ambassador/v0

  10.      kind: RateLimitService

  11.      name: ratelimiter_svc

  12.      service: "ratelimiter:50051"

  13.  labels:

  14.    app: ratelimiter

  15. spec:

  16.  type: ClusterIP

  17.  selector:

  18.    app: ratelimiter

  19.  ports:

  20.  - protocol: TCP

  21.    port: 50051

  22.    name: http

  23. ---

  24. apiVersion: v1

  25. kind: ReplicationController

  26. metadata:

  27.  name: ratelimiter

  28. spec:

  29.  replicas: 1

  30.  template:

  31.    metadata:

  32.      labels:

  33.        app: ratelimiter

  34.    spec:

  35.      containers:

  36.      - name: ratelimiter

  37.        image: danielbryantuk/ratelimiter:0.3

  38.        ports:

  39.        - containerPort: 50051

这里不需要关注最后Docker Image处的 danielbryantuk/ratelimiter:0.3 ,而需要注意的是:此服务在集群使用50051 TCP端口。

ambassador-service.yaml配置文件中,还更新了Ambassador Kubernetes annotations配置,以确保能通过包含 rate_limits属性来限制对shopfront服务的请求。 我还添加了一些额外的元数据 -descriptor:Exampledescriptor,这将在下一篇文章中更详细地解释。这里我们需要注意的是,如果要将元数据传递到速率限制服务,这种方法不错。

 
  1. ---

  2. apiVersion: v1

  3. kind: Service

  4. metadata:

  5.  labels:

  6.    service: ambassador

  7.  name: ambassador

  8.  annotations:

  9.    getambassador.io/config: |

  10.      ---

  11.      apiVersion: ambassador/v0

  12.      kind:  Mapping

  13.      name:  shopfront_stable

  14.      prefix: /shopfront/

  15.      service: shopfront:8010

  16.      rate_limits:

  17.        - descriptor: Example descriptor

你可以使用kubectl命令来检查部署是否成功:

 
  1. (master *) kubernetes-ambassador-ratelimit $ kubectl get svc

  2. NAME               TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE

  3. ambassador         LoadBalancer   10.105.253.3     localhost     80:30051/TCP     1d

  4. ambassador-admin   NodePort       10.107.15.225    <none>        8877:30637/TCP   1d

  5. kubernetes         ClusterIP      10.96.0.1        <none>        443/TCP          16d

  6. productcatalogue   ClusterIP      10.109.48.26     <none>        8020/TCP         1d

  7. ratelimiter        ClusterIP      10.97.122.140    <none>        50051/TCP        1d

  8. shopfront          ClusterIP      10.98.207.100    <none>        8010/TCP         1d

  9. stockmanager       ClusterIP      10.107.208.180   <none>        8030/TCP         1d

6个业务服务看起来都不错(去除Kubernetes服务):包含3个Java服务,2个Ambassador服务和1个ratelimiter服务。

你可以通过curl命令对shopfront的服务端点进行测试,其应绑定在外部IP localhost的80端口上(如上文所示):

 
  1. (master *) kubernetes-ambassador-ratelimit $ curl localhost/shopfront/

  2. <!DOCTYPE html>

  3. <html lang="en" xmlns="http://www.w3.org/1999/xhtml">

  4. <head>

  5.    <meta charset="utf-8" />

  6. ...

  7. </div>

  8. </div>

  9. <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->

  10. <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

  11. <!-- Include all compiled plugins (below), or include individual files as needed -->

  12. <script src="js/bootstrap.min.js"></script>

  13. </body>

  14. </html>(master *) kubernetes-ambassador-ratelimit $

你会注意到这里显示了一些HTML,这只是Docker Java Shop的首页。虽然可以通过浏览器在http://localhost/shopfront/访问,对于我们的速率限制实验,最好还是使用curl命令。

速率限制测试

对于这种演示性质的速率限制服务,这里仅对服务本身进行限制。比如当速率限制服务需要计算是否需要限制请求时,唯一需要考虑的指标是在一段时间内针对特定后端的请求数量。在代码实现中使用令牌桶算法。假设桶中令牌容量为20,并且每秒钟的补充10个令牌。由于速率限制与请求相关联,这意味着你可以每秒发出10次API请求,这没有任何问题,同时由于存储桶最初包含20个令牌,你可以暂时超过此并发数量。但是,一旦最初额外的令牌使用完,并且你仍在尝试每秒发出10个以上请求,那么你将收到HTTP 429 “Too Many Requests” 状态码。这时,Ambassador API网关不会再将请求转发到后端服务。

让我看下如何通过curl发出大量请求来模拟这个操作。避免显示的HTML页面(通过 -output/dev/null参数)及curl请求(通过 --silent参数),但需要显示符合预期的HTTP响应状态(通过 -- show-error  --fail参数)。下文通过一个bash循环脚本,并记录时间输出(以显示发出请求的时间),以此来创建一个非常粗颗粒度的负载发生器(可以通过 CTRL-C来终止循环):

 
  1. $ while true; do curl --silent --output /dev/null --show-error --fail http://localhost/shopfront/; echo -e $(date);done

  2. (master *) kubernetes-ambassador-ratelimit $ while true; do curl --silent --output /dev/null --show-error --fail http://localhost/shopfront/; echo -e $(date);done

  3. Tue 24 Apr 2018 14:16:31 BST

  4. Tue 24 Apr 2018 14:16:31 BST

  5. Tue 24 Apr 2018 14:16:31 BST

  6. Tue 24 Apr 2018 14:16:31 BST

  7. ...

  8. Tue 24 Apr 2018 14:16:35 BST

  9. curl: (22) The requested URL returned error: 429 Too Many Requests

  10. Tue 24 Apr 2018 14:16:35 BST

  11. curl: (22) The requested URL returned error: 429 Too Many Requests

  12. Tue 24 Apr 2018 14:16:35 BST

  13. Tue 24 Apr 2018 14:16:35 BST

  14. curl: (22) The requested URL returned error: 429 Too Many Requests

  15. Tue 24 Apr 2018 14:16:35 BST

  16. curl: (22) The requested URL returned error: 429 Too Many Requests

  17. Tue 24 Apr 2018 14:16:35 BST

  18. ^C

如你所见,从输出日志来看,前几个请求显示日期且没有错误,一切正常。过不了多久,当在我测试的Mac上的请求循环超过每秒10次,HTTP 429错误便开始出现。

顺便说一下,我通常使用 Apache Benchmarking “ab” 负载生成工具来进行这种简单实验,但这工具在调用本地localhost会有问题(同时Docker配置也给我带来了额外问题)。

检验速率限制器服务

Ambassador Java限速服务的源代码在我GitHub帐户的ambassador-java-rate-limiter仓库中。其中也包含用于构建我推送到DockerHub中容器镜像的Dockerfile。你可以以此Dockerfile作为模板进行修改,然后构建和推送自己的镜像至DockerHub。你也可以修改在Docker Java Shopping仓库中的ambassador-rate-limiter.yaml文件来扩展使用你自己的速率限制服务。

研究Java代码

如果你深入研究Java代码,最需要关注的类应该是RateLimiterServer,它实现了在Ambassador API中使用的Envoy代理所定义的速率限制gRPC接口。我创建了一个ratelimit.proto接口的副本,其通过Maven pom.xml中定义的gRPC Java构建工具来构建使用。代码主要涉及三点:实现gRPC接口,运行gRPC服务器,并实现速率限制。下面让我们来进一步分析。

实现速率限制gRPC接口

查看 RateLimitServer中的内部类 RateLimiterImpl,其对 RateLimitServiceGrpc.RateLimitServiceImplBase进行扩展,你可以看到此抽象类中的下列方法被重写:

 
  1. public void shouldRateLimit(Ratelimit.RateLimitRequest rateLimitRequest, StreamObserver<Ratelimit.RateLimitResponse> responseStreamObserver)

这里使用的很多命名规约来自于Java gRPC库,进一步信息请参阅gRPC Java文档。 尽管这样,如果查看ratelimit.proto文件,你可以清楚看到很多命名根,这些命名根定义了在Ambassador中使用的Envoy代理所需要的速率限制接口。例如,你可以看到此文件中定义的核心服务名为 RateLimitService(第9行),并且在服务 rpcShouldRateLimit(RateLimitRequest)returns(RateLimitResponse){}(第11行)中定义了一个RPC方法, 它在Java中实现通过上面所定义的 shouldRateLimit方法。

如果有兴趣,可以看看那些由 protobuf-maven-plugin(pom.xml的第99行)生成的Java gRPC代码。

运行gRPC服务器

一旦你实现了用 ratelimit.proto定义的gRPC接口,下一件事情就是创建一个gRPC服务器用来监听和回复请求。可以根据 main方法调用链来查看RateLimitServer的内容。简而言之, main方法创建一个 RateLimitServer类的实例,调用 start()方法,再调用 blockUntilShutdown()方法。 这将启动一个应用实例,并在指定的服务端点上发布gRPC接口,同时侦听请求。

实现Java速率限制

负责速率限制过程的实际Java代码包含在 RateLimiterImpl内部类的 shouldRateLimit()方法(第75行)中。我没有自己实现算法,而是使用基于令牌桶算法的Java速度限制开源库bucket4j。由于我限制了对每个服务的请求,因此每个存储桶与服务名称所绑定。对每个服务的请求都会从其所关联的存储桶中删除一个令牌。在本案例中,桶没有存储在外部数据库,而是存储在内存中的 ConcurrentHashMap中。如果在生产环境中,通常会使用类似Redis的外部持久化存储方案来实现横向扩展。这里必须注意,如果在不更改每个服务桶限制的前提下水平扩展速率限制服务,那么将直接导致(非速率限制)请求数量的增加,但实际服务可支持的请求数量没有增加。

创建bucket4j存储桶的 RateLimiterImpl大致代码如下:

 
  1. private Bucket createNewBucket() {

  2.    long overdraft = 20;

  3.    Refill refill = Refill.smooth(10, Duration.ofSeconds(1));

  4.    Bandwidth limit = Bandwidth.classic(overdraft, refill);

  5.    return Bucket4j.builder().addLimit(limit).build();

  6. }

在下面可以看到 shouldRateLimit方法的代码,它只是简单地尝试执行 tryConsume(1)使用桶中一个令牌,并返回适当的HTTP响应。

 
  1. @Override

  2. public void shouldRateLimit(Ratelimit.RateLimitRequest rateLimitRequest, StreamObserver<Ratelimit.RateLimitResponse> responseStreamObserver) {

  3.    logDebug(rateLimitRequest);

  4.    String destServiceName = extractDestServiceNameFrom(rateLimitRequest);

  5.    Bucket bucket = getServiceBucketFor(destServiceName);

  6. Ratelimit.RateLimitResponse.Code code;

  7.    if (bucket.tryConsume(1)) {

  8.        code = Ratelimit.RateLimitResponse.Code.OK;

  9.    } else {

  10.        code = Ratelimit.RateLimitResponse.Code.OVER_LIMIT;

  11.    }

  12. Ratelimit.RateLimitResponse rateLimitResponse = generateRateLimitResponse(code);

  13.    responseStreamObserver.onNext(rateLimitResponse);

  14.    responseStreamObserver.onCompleted();

  15. }

代码比较容易解释。如果当前请求不需要进行速率限制,则此方法返回 Ratelimit.RateLimitResponse.Code.OK;如果当前请求由于速度限制而被拒绝,则此方法返回 Ratelimit.RateLimitResponse.Code.OVER_LIMIT。根据此gRPC服务的响应,Ambassador API网关将请求传递给后端服务,或者中断请求并返回HTTP状态码429 “Too Many Requests” 而不再调用后端服务。

这个简单案例只可以防止一个服务的访问过载,但也希望这能够阐明速率限制的核心概念,进而可以相对容易实现基于请求元数据(例如用户ID等)的速率限制。

下一阶段

本文演示了如何在Java中创建速率限制服务,并轻易与Ambassador网关所集成。如果需要,你也可以基于任何自定义的速率限制算法实现。 在本系列的最后一篇文章中,您将更深入地了解Envoy速率限制API,以便进一步学习如何设计速率限制服务。

本文转载自:https://mp.weixin.qq.com/s/djt72Px1gyWKJOLZAcc_Vg

共有 人打赏支持
xiaomin0322
粉丝 81
博文 3453
码字总数 140043
作品 0
上海
架构师
Nginx反向代理,负载均衡+Tomcat实现Session共享

Nginx反向代理,负载均衡+Tomcat实现Session共享 防伪码:学而不思则罔,思而不学则殆。 作者:何小帅 博客URL:http://hexiaoshuai.blog.51cto.com 一、如何保持session会话 目前,为了使w...

何小帅
06/26
0
0
个推首席架构师Qcon分享 |微服务架构的那些事儿

微服务架构需要注意哪些问题? 微服务架构,首先考虑客户端与服务端之间的通信问题。有两种解决办法,一是客户端与多个服务端直接进行通信,但存在对外暴露接口细节、众多接口协议无法统一、...

个推
07/02
0
0
深入了解java运行时的内存区域

对于java程序员来说,并不必显示地对内存进行管理,一切都交给java虚拟机去做吧,而且,你也不一定做得比java虚拟机来得专业。好像所有内存管理都交给虚拟机去做就万事大吉了,但是,事实有时...

依然范特西
2012/09/26
0
0
基于OpenResty和Node.js的微服务架构实践

什么是微服务? 传统的单体服务架构是单独服务包,共享代码与数据,开发成本较高,可维护性、伸缩性较差,技术转型、跨语言配合相对困难。而微服务架构强调一个服务负责一项业务,服务可以单...

个推
07/02
0
0
NGN学习笔记6——NGN的业务提供技术

1.NGN业务的基本概念 1)定义: 下一代网络的根本目标是提供业务,业务可以被定义为一种软件应用,能够向用户提供有用的、完善的功能。它通过分布在网络中的多个计算元素上的软件组件的交互来...

AlphaJay
2010/05/31
0
0
hadoop2.5.1安装到虚拟机

1.虚拟机安装 系统版本 RHEL6.3 2.虚拟机ip配置 采用共享方式(nat) : 默认: 使用vmnet8 将虚拟机设置成使用dhcp方式上网,windows下选择"自动获取ip",linux下开启dhcp服务即可。 手动设置: i...

暗夜孤灯
2014/12/01
0
0
加强Docker容器与Java 10集成

很多运行在Java虚拟机(JVM)中的应用,包括数据服务如Apache Spark和Kafka以及传统企业应用,都运行在容器中。最近,运行在容器里的JVM出现了由于内存和CPU资源限制和使用率导致性能损失问题...

java高级架构牛人
06/04
0
0
【死磕Sharding-jdbc】—–分布式ID

原文作者:阿飞Javaer 原文链接:https://www.jianshu.com/p/7f0661ddd6dd 实现动机 传统数据库软件开发中,主键自动生成技术是基本需求。而各大数据库对于该需求也提供了相应的支持,比如M...

飞哥-Javaer
05/05
0
0
Android的虚拟机Dalvik 介绍

随着上周Google的Android SDK的发布,关于它的API以及在移动电话领域所带来的预期影响这些方面的讨论不胜枚举。不过,其中的一个话题在Java社区是一石激起千层浪,这就是 Android平台的基础—...

Kevin_Gan
2010/08/19
0
0
Android的虚拟机Dalvik 介绍

随着上周Google的AndroidSDK的发布,关于它的API以及在移动电话领域所带来的预期影响这些方面的讨论不胜枚举。不过,其中的一个话题在Java社区是一石激起千层浪,这就是Android平台的基础——...

Kevin_Gan
2010/07/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Git 基础 - 远程仓库的使用

远程仓库的使用 要参与任何一个 Git 项目的协作,必须要了解该如何管理远程仓库。远程仓库是指托管在网络上的项目仓库,可能会有好多个,其中有些你只能读,另外有些可以写。同他人协作开发某...

谢思华
5分钟前
0
0
面试宝典-悲观锁和乐观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。 乐观锁(Optimistic...

suyain
7分钟前
0
0
崛起于Springboot2.X之集成MongoDb使用mongoTemplate CRUD(27)

1、pom依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.7</version></dependency><dependency> <groupId>log4j</......

木九天
17分钟前
0
0
切分log日志

新建logback.xml放到resource里面 <?xml version="1.0" encoding="utf-8"?><configuration> <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender"> <......

talen
22分钟前
0
0
spring @Resource 和 @Autowired 的使用区别

这两个 注解 @Resource 和 @Autowired , 常识都知道 @Resource 是 JAVAEE 自带的,@Autowired 是 spring 的自定义注解。 一般情况下, 使用 bean的时候, 这两个注解 随便使用一个即可。 但...

之渊
27分钟前
0
0
springboot集成elasticsearch客户端问题记录

1背景说明 服务端ES版本为5.5.2,springboot版本为1.5.6。 工程中添加如下依赖 2问题记录 2.1 NetworkPlugin类找不到 报错java.lang.ClassNotFoundException: org.elasticsearch.plugins.Net...

zjg23
29分钟前
1
0
快速构建ceph可视化监控系统

前言 ceph的可视化方案很多,本篇介绍的是比较简单的一种方式,并且对包都进行了二次封装,所以能够在极短的时间内构建出一个可视化的监控系统 本系统组件如下: ceph-jewel版本 ceph_expor...

万建宁
29分钟前
0
0
Java构造器使用注意

public class 父类A {int age = 10;protected void say() {System.out.println("父类A");}public 父类A() {override();}public void override() {Syst...

咸鱼AI
30分钟前
0
0
TensorFlow 线性分类

构造直线 z = 2 * x - 3 * y + 4 x0*w0+x1*w1+b=0 x1=-x0* w0/w1-b/w1 斜率 k= -w0/w1 截距 -b/w1 随机生成数据,加入一定的偏差,用直线将二维平面分为两部分 使用线性模型拟合参数 损失函数...

阿豪boy
33分钟前
0
0
翻译冒泡排序测试

翻译一个冒泡排序: var a = [1,3,2,4,6,5];var f = 0;var n = a.length ;for( var i =1; i<= n; i++) { for( var j = n-1 ; j >= i; j --) { if(a[j] < a[j+1]) { ......

钟元OSS
34分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部