歡迎光臨
每天分享高質量文章

如何使用訊息佇列、Spring Boot和Kubernetes擴充套件微服務?

當你設計和構建大規模應用時,你將面臨兩個重大挑戰:可伸縮性和健壯性
你應該這樣設計你的服務,即使它受到間歇性的重負載,它仍能可靠地執行。
以Apple Store為例。
每年都有數百萬的Apple客戶預先註冊購買新的iPhone。
這是數百萬人同時購買物品。
如果你要將Apple商店的流量描述為每秒的請求數量,那麼它可能是下圖的樣子:
現在想象一下,你的任務是構建這樣的應用程式。
 
你正在建立一個商店,使用者可以在那裡購買自己喜歡的商品。
你構建一個微服務來呈現網頁並提供靜態資產。你還構建了一個後端REST API來處理傳入的請求。
你希望將兩個元件分開,因為這樣可以使用相同的REST API,為網站和移動應用程式提供服務。
今天是重要的一天,你的商店上線了。
你決定將應用程式擴充套件為前端四個實體和後端四個實體,因為你預測網站比平常更繁忙。
你開始接收越來越多的流量。
前端服務正在很好得處理流量。但是你註意到連線到資料庫的後端正在努力跟上事務的數量。
不用擔心,你可以將後端的副本數量擴充套件到8。
你收到的流量更多,後端無法應對。
一些服務開始丟棄連線。憤怒的客戶與你的客服取得聯絡。而現在你被淹沒在大量流量中。
你的後端無法應付它,它會失去很多連線。
你剛丟了一大筆錢,你的顧客也不高興。
你的應用程式並沒有設計得健壯且高可用:
  • 前端和後端緊密耦合——實際上它不能在沒有後端的情況下處理應用

  • 前端和後端必須一致擴充套件——如果沒有足夠的後端,你可能會淹沒在流量中

  • 如果後端不可用,則無法處理傳入的事務。

 

失去事務意味著收入損失。
你可以重新設計架構,以便將前端和後端用佇列分離。
前端將訊息釋出到佇列,而後端則一次處理一個待處理訊息。
新架構有一些明顯的好處:
  • 如果後端不可用,則佇列充當緩衝區

  • 如果前端產生的訊息多於後端可以處理的訊息,則這些訊息將緩衝在佇列中

  • 你可以獨立於前端擴充套件後端——即你可以擁有數百個前端服務和後端的單個實體

太好了,但是你如何構建這樣的應用程式?
你如何設計可處理數十萬個請求的服務?你如何部署動態擴充套件的應用程式?
在深入瞭解部署和擴充套件的細節之前,讓我們關註應用程式。
編寫Spring應用程式

 

該服務有三個元件:前端,後端和訊息代理。
前端是一個簡單的Spring Boot Web應用程式,帶有Thymeleaf模板引擎。
後端是一個消耗佇列訊息的工作者。
由於Spring Boot與JSM能出色得整合,因此你可以使用它來傳送和接收非同步訊息。
你可以在learnk8s / spring-boot-k8s-hpa[1]中找到一個連線到JSM的前端和後端應用程式的示例專案。
請註意,該應用程式是用Java 10編寫的,以利用改進的Docker容器整合能力。
只有一個程式碼庫,你可以將專案配置為作為前端或後端執行。
你應該知道該應用程式具有:
  • 一個購買物品的主頁

  • 管理面板,你可以在其中檢查佇列中的訊息數

  • 一個 /health 端點,用於在應用程式準備好接收流量時發出訊號

  • 一個 /submit 端點,從表單接收提交併在佇列中建立訊息

  • 一個 /metrics 端點,用於公開佇列中待處理訊息的數量(稍後將詳細介紹)

 

該應用程式可以在兩種樣式下執行:
 
作為前端,應用程式呈現人們可以購買物品的網頁。
 
作為工作者,應用程式等待佇列中的訊息並處理它們。
請註意,在示例專案中,使用Thread.sleep(5000)等待五秒鐘來模擬處理。
你可以透過更改application.yaml中的值來在任一樣式下配置應用程式。
模擬應用程式的執行

 

預設情況下,應用程式作為前端和工作程式啟動。
你可以執行該應用程式,只要你在本地執行ActiveMQ實體,你就應該能夠購買物品並讓系統處理這些物品。
如果檢查日誌,則應該看到工作程式處理專案。
它確實工作了!編寫Spring Boot應用程式很容易。
一個更有趣的主題是學習如何將Spring Boot連線到訊息代理。
使用JMS傳送和接收訊息

 

Spring JMS(Java訊息服務)是一種使用標準協議傳送和接收訊息的強大機制。
如果你以前使用過JDBC API,那麼你應該熟悉JMS API,因為它的工作方式很類似。
你可以按JMS方式來使用的最流行的訊息代理是ActiveMQ——一個開源訊息伺服器。
使用這兩個元件,你可以使用熟悉的介面(JMS)將訊息釋出到佇列(ActiveMQ),並使用相同的介面來接收訊息。
更妙的是,Spring Boot與JMS的整合非常好,因此你可以立即加快速度。
實際上,以下短類封裝了用於與佇列互動的邏輯:
  1. @Component
  2. public class QueueService implements MessageListener {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(QueueService.class);
  4. @Autowired
  5.  private JmsTemplate jmsTemplate;
  6.  public void send(String destination, String message) {
  7.    LOGGER.info("sending message='{}' to destination='{}'", message, destination);
  8.    jmsTemplate.convertAndSend(destination, message);
  9.  }
  10. @Override
  11.  public void onMessage(Message message) {
  12.    if (message instanceof ActiveMQTextMessage) {
  13.      ActiveMQTextMessage textMessage = (ActiveMQTextMessage) message;
  14.      try {
  15.        LOGGER.info("Processing task "   textMessage.getText());
  16.        Thread.sleep(5000);
  17.        LOGGER.info("Completed task "   textMessage.getText());
  18.      } catch (InterruptedException e) {
  19.        e.printStackTrace();
  20.      } catch (JMSException e) {
  21.        e.printStackTrace();
  22.      }
  23.    } else {
  24.      LOGGER.error("Message is not a text message "   message.toString());
  25.    }
  26.  }
  27. }
你可以使用send方法將訊息釋出到命名佇列。
此外,Spring Boot將為每個傳入訊息執行onMessage方法。
最後一個難題是指示Spring Boot使用該類。
你可以透過在Spring Boot應用程式中註冊偵聽器來在後臺處理訊息,如下所示:
  1. @SpringBootApplication
  2. @EnableJms
  3. public class SpringBootApplication implements JmsListenerConfigurer {
  4.  @Autowired
  5.  private QueueService queueService;
  6. public static void main(String[] args) {
  7.    SpringApplication.run(SpringBootApplication.class, args);
  8.  }
  9. @Override
  10.  public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
  11.    SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
  12.    endpoint.setId("myId");
  13.    endpoint.setDestination("queueName");
  14.    endpoint.setMessageListener(queueService);
  15.    registrar.registerEndpoint(endpoint);
  16.  }
  17. }
其中id是使用者的唯一識別符號,destination是佇列的名稱。
你可以從GitHub[2]上的專案中完整地讀取Spring佇列服務的原始碼。
回顧一下你是如何在少於40行程式碼中編寫可靠佇列的。
 
你一定很喜歡Spring Boot。
你在部署時節省的所有時間都可以專註於編碼

 

你驗證了應用程式的工作原理,現在是時候部署它了。
此時,你可以啟動VPS,安裝Tomcat,並花些時間製作自定義指令碼來測試,構建,打包和部署應用程式。
或者你可以編寫你希望擁有的描述:一個訊息代理和兩個使用負載均衡器部署的應用程式。
諸如Kubernetes之類的編排器可以閱讀你的願望清單並提供正確的基礎設施。
由於花在基礎架構上的時間減少意味著更多的時間編碼,這次你將把應用程式部署到Kubernetes。但在開始之前,你需要一個Kubernetes叢集。
你可以註冊Google雲平臺或Azure,並使用Kubernetes提供的雲提供商服務。或者,你可以在將應用程式移動到雲上之前在本地嘗試Kubernetes。
minikube是一個打包為虛擬機器的本地Kubernetes叢集。如果你使用的是Windows,Linux和Mac,那就太好了,因為建立群集需要五分鐘。
你還應該安裝kubectl,即連線到你的群集的客戶端。
你可以從官方檔案[3]中找到有關如何安裝minikube和kubectl的說明。
如果你在Windows上執行,則應檢視有關如何安裝Kubernetes和Docker的詳細指南[4]。
你應該啟動一個具有8GB RAM和一些額外配置的叢集:
  1. minikube start \
  2.  --memory 8096 \
  3.  --extra-config=controller-manager.horizontal-pod-autoscaler-upscale-delay=1m \
  4.  --extra-config=controller-manager.horizontal-pod-autoscaler-downscale-delay=2m \
  5.  --extra-config=controller-manager.horizontal-pod-autoscaler-sync-period=10s
請註意,如果你使用的是預先存在的minikube實體,則可以透過銷毀VM來重新調整VM的大小。只需新增–memory8096就不會有任何影響。
驗證安裝是否成功。你應該看到以串列形式展示的一些資源。叢集已經準備就緒,也許你應該立即開始部署?
還不行。
你必須先裝好你的東西。
什麼比uber-jar更好?容器

 

部署到Kubernetes的應用程式必須打包為容器。畢竟,Kubernetes是一個容器編排器,所以它本身無法執行你的jar。
容器類似於fat jar:它們包含執行應用程式所需的所有依賴項。甚至JVM也是容器的一部分。所以他們在技術上是一個更胖的fat-jar。
將應用程式打包為容器的流行技術是Docker。
雖然Docker是最受歡迎的,但它並不是唯一能夠執行容器的技術。其他受歡迎的選項包括rkt和lxd。
如果你沒有安裝Docker,可以按照Docker官方網站上的說明[5]進行操作。
通常,你構建容器並將它們推送到倉庫。它類似於向Artifactory或Nexus推送jar包。但在這種特殊情況下,你將在本地工作並跳過倉庫部分。實際上,你將直接在minikube中建立容器映象。
首先,按照此命令列印的說明將Docker客戶端連線到minikube:
  1. minikube docker-env
請註意,如果切換終端,則需要重新連線minikube內的Docker守護程式。每次使用不同的終端時都應遵循相同的說明。
並從專案的根目錄構建容器映象:
  1. docker build -t spring-k8s-hp0a .
你可以驗證映象是否已構建並準備好執行:
  1. docker images |grep spring
很好。
群集已準備好,你打包應用程式,也許你已準備好立即部署?
是的,你最終可以要求Kubernetes部署應用程式。
將你的應用程式部署到Kubernetes

 

你的應用程式有三個元件:
  • 呈現前端的Spring Boot應用程式

  • ActiveMQ作為訊息代理

  • 處理事務的Spring Boot後端

 

你應該分別部署這三個元件。
對於每個元件你都應該建立:
 
  • Deployment物件,描述部署的容器及其配置

  • 一個Service物件,充當Deployment部署建立的應用程式的所有實體的負載均衡器

部署中的每個應用程式實體都稱為Pod
部署ActiveMQ

 

讓我們從ActiveMQ開始吧。
你應該建立一個activemq-deployment.yaml檔案,其中包含以下內容:
  1. apiVersion: extensions/v1beta1
  2. kind: Deployment
  3. metadata:
  4.  name: queue
  5. spec:
  6.  replicas: 1
  7.  template:
  8.    metadata:
  9.      labels:
  10.        app: queue
  11.    spec:
  12.      containers:
  13.      - name: web
  14.        image: webcenter/activemq:5.14.3
  15.        imagePullPolicy: IfNotPresent
  16.        ports:
  17.          - containerPort: 61616
  18.        resources:
  19.          limits:
  20.            memory: 512Mi
該模板冗長但直接易讀:
  • 你從名為webcenter / activemq的官方倉庫中請求了一個activemq容器

  • 容器在埠61616上公開訊息代理

  • 為容器分配了512MB的記憶體

  • 你要求提供單個副本 – 你的應用程式的單個實體

使用以下內容建立activemq-service.yaml檔案:
  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4.  name: queue
  5. spec:
  6.  ports:
  7.  - port: 61616
  8.    targetPort: 61616
  9.  selector:
  10.    app: queue
幸運的是,這個模板更短!
這個yaml表示:
  • 你建立了一個公開埠61616的負載均衡器

  • 傳入流量分發到所有具有app:queue型別標簽的Pod(請參閱上面的部署)

  • targetPort是Pod暴露的埠

 

你可以使用以下命令建立資源:
  1. kubectl create -f activemq-deployment.yaml
  2. kubectl create -f activemq-service.yaml
你可以使用以下命令驗證資料庫的一個實體是否正在執行:
  1. kubectl get pods -l=app=queue
部署前端

 

使用以下內容建立fe-deployment.yaml檔案:
  1. apiVersion: extensions/v1beta1
  2. kind: Deployment
  3. metadata:
  4.  name: frontend
  5. spec:
  6.  replicas: 1
  7.  template:
  8.    metadata:
  9.      labels:
  10.        app: frontend
  11.    spec:
  12.      containers:
  13.      - name: frontend
  14.        image: spring-boot-hpa
  15.        imagePullPolicy: IfNotPresent
  16.        env:
  17.        - name: ACTIVEMQ_BROKER_URL
  18.          value: "tcp://queue:61616"
  19.        - name: STORE_ENABLED
  20.          value: "true"
  21.        - name: WORKER_ENABLED
  22.          value: "false"
  23.        ports:
  24.        - containerPort: 8080
  25.        livenessProbe:
  26.          initialDelaySeconds: 5
  27.          periodSeconds: 5
  28.          httpGet:
  29.            path: /health
  30.            port: 8080
  31.        resources:
  32.          limits:
  33.            memory: 512Mi
 
Deployment看起來很像前一個。
但是有一些新的欄位:
  • 有一個section可以註入環境變數

  • 還有Liveness探針,可以告訴你應用程式何時可以接受流量

使用以下內容建立fe-service.yaml檔案:
  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4.  name: frontend
  5. spec:
  6.  ports:
  7.  - nodePort: 32000
  8.    port: 80
  9.    targetPort: 8080
  10.  selector:
  11.    app: frontend
  12.  type: NodePort
你可以使用以下命令建立資源:
  1. kubectl create -f fe-deployment.yaml
  2. kubectl create -f fe-service.yaml
你可以使用以下命令驗證前端應用程式的一個實體是否正在執行:
  1. kubectl get pods -l=app=frontend
部署後端

 

使用以下內容建立backend-deployment.yaml檔案:
  1. apiVersion: extensions/v1beta1
  2. kind: Deployment
  3. metadata:
  4.  name: backend
  5. spec:
  6.  replicas: 1
  7.  template:
  8.    metadata:
  9.      labels:
  10.        app: backend
  11.      annotations:
  12.        prometheus.io/scrape: 'true'
  13.    spec:
  14.      containers:
  15.      - name: backend
  16.        image: spring-boot-hpa
  17.        imagePullPolicy: IfNotPresent
  18.        env:
  19.        - name: ACTIVEMQ_BROKER_URL
  20.          value: "tcp://queue:61616"
  21.        - name: STORE_ENABLED
  22.          value: "false"
  23.        - name: WORKER_ENABLED
  24.          value: "true"
  25.        ports:
  26.        - containerPort: 8080
  27.        livenessProbe:
  28.          initialDelaySeconds: 5
  29.          periodSeconds: 5
  30.          httpGet:
  31.            path: /health
  32.            port: 8080
  33.        resources:
  34.          limits:
  35.            memory: 512Mi
使用以下內容建立backend-service.yaml檔案:
  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4.  name: backend
  5.  spec:
  6.    ports:
  7.    - nodePort: 31000
  8.      port: 80
  9.      targetPort: 8080
  10.    selector:
  11.      app: backend
  12.    type: NodePort
你可以使用以下命令建立資源:
  1. kubectl create -f backend-deployment.yaml
  2. kubectl create -f backend-service.yaml
你可以驗證後端的一個實體是否正在執行:
  1. kubectl get pods -l=app=backend
部署完成。
 
它真的有效嗎?
你可以使用以下命令在瀏覽器中訪問該應用程式:
  1. minikube service backend
  1. minikube service frontend
如果它有效,你應該嘗試購買一些物品!
工作者在處理交易嗎?
是的,如果有足夠的時間,工作人員將處理所有待處理的訊息。
恭喜!
你剛剛將應用程式部署到Kubernetes!
手動擴充套件以滿足不斷增長的需求
單個工作程式可能無法處理大量訊息。實際上,它當時只能處理一條訊息。
如果你決定購買數千件物品,則需要數小時才能清除佇列。
此時你有兩個選擇:
  • 你可以手動放大和縮小

  • 你可以建立自動縮放規則以自動向上或向下擴充套件

讓我們先從基礎知識開始。
你可以使用以下方法將後端擴充套件為三個實體:
  1. kubectl scale --replicas=5 deployment/backend
你可以驗證Kubernetes是否建立了另外五個實體:
  1. kubectl get pods
並且應用程式可以處理五倍以上的訊息。
一旦工人排空佇列,你可以縮小:
  1. kubectl scale --replicas=1 deployment/backend
如果你知道最多的流量何時達到你的服務,手動擴大和縮小都很棒。
如果不這樣做,設定自動縮放器允許應用程式自動縮放而無需手動幹預。
你只需要定義一些規則。
公開應用程式指標

 

Kubernetes如何知道何時擴充套件你的申請?
很簡單,你必須告訴它。
自動調節器透過監控指標來工作。只有這樣,它才能增加或減少應用程式的實體。
因此,你可以將佇列長度公開為度量標準,並要求autoscaler觀察該值。佇列中的待處理訊息越多,Kubernetes將建立的應用程式實體就越多。
 
那麼你如何公開這些指標呢?
應用程式具有/metrics端點以顯示佇列中的訊息數。如果你嘗試訪問該頁面,你會註意到以下內容:
  1. # HELP messages Number of messages in the queue
  2. # TYPE messages gauge
  3. messages 0
應用程式不會將指標公開為JSON格式。格式為純文字,是公開Prometheus指標的標準。不要擔心記憶格式。大多數情況下,你將使用其中一個Prometheus客戶端庫。
在Kubernetes中使用應用程式指標

 

你幾乎已準備好進行自動縮放——但你應首先安裝度量伺服器。實際上,預設情況下,Kubernetes不會從你的應用程式中提取指標。如果你願意,可以啟用Custom Metrics API。
要安裝Custom Metrics API,你還需要Prometheus – 時間序列資料庫。安裝Custom Metrics API所需的所有檔案都可以方便地打包在learnk8s / spring-boot-k8s-hpa中[1]。
你應下載該儲存庫的內容,並將當前目錄更改為該專案的monitoring檔案夾。
  1. cd spring-boot-k8s-hpa/monitoring
從那裡,你可以建立自定義指標API:
  1. kubectl create -f ./metrics-server
  2. kubectl create -f ./namespaces.yaml
  3. kubectl create -f ./prometheus
  4. kubectl create -f ./custom-metrics-api
你應該等到以下命令傳回自定義指標串列:
  1. kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
任務完成!
 
你已準備好使用指標。
實際上,你應該已經找到了佇列中訊息數量的自定義指標:
  1. kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/messages" | jq .
恭喜,你有一個公開指標的應用程式和使用它們的指標伺服器。
你最終可以啟用自動縮放器!
在Kubernetes中進行自動擴充套件部署

 

Kubernetes有一個名為Horizontal Pod Autoscaler的物件,用於監視部署並上下調整Pod的數量。
你將需要其中一個來自動擴充套件實體。
你應該建立一個包含以下內容的hpa.yaml檔案:
  1. apiVersion: autoscaling/v2beta1
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4.  name: spring-boot-hpa
  5. spec:
  6.  scaleTargetRef:
  7.    apiVersion: extensions/v1beta1
  8.    kind: Deployment
  9.    name: backend
  10.  minReplicas: 1
  11.  maxReplicas: 10
  12.  metrics:
  13.  - type: Pods
  14.    pods:
  15.      metricName: messages
  16.      targetAverageValue: 10
這個檔案很神秘,所以讓我為你翻譯一下:
  • Kubernetes監視scaleTargetRef中指定的部署。在這種情況下,它是工人。

  • 你正在使用messages指標來擴充套件你的Pod。當佇列中有超過十條訊息時,Kubernetes將觸發自動擴充套件。

  • 至少,部署應該有兩個Pod。10個Pod是上限。

 

你可以使用以下命令建立資源:
  1. kubectl create -f hpa.yaml
提交自動縮放器後,你應該註意到後端的副本數量是兩個。這是有道理的,因為你要求自動縮放器始終至少執行兩個副本。
你可以檢查觸發自動縮放器的條件以及由此產生的事件:
  1. kubectl describe hpa
自動定標器表示它能夠將Pod擴充套件到2,並且它已準備好監視部署。
 
令人興奮的東西,但它有效嗎?
負載測試

 

只有一種方法可以知道它是否有效:在佇列中建立大量訊息。
轉到前端應用程式並開始新增大量訊息。在新增訊息時,使用以下方法監視Horizontal Pod Autoscaler的狀態:
  1. kubectl describe hpa
Pod的數量從2上升到4,然後是8,最後是10。
 
該應用程式隨訊息數量而變化!歡呼!
你剛剛部署了一個完全可伸縮的應用程式,可根據佇列中的待處理訊息數進行擴充套件。
另外,縮放演演算法如下:
  1. MAX(CURRENT_REPLICAS_LENGTH * 2, 4)
在解釋演演算法時,檔案沒有多大幫助。你可以在程式碼中找到詳細資訊。此外,每分鐘都會重新評估每個放大,而每兩分鐘縮小一次。
以上所有都是可以調整的設定。
但是你還沒有完成。
什麼比自動縮放實體更好?自動縮放群集

 

跨節點縮放Pod非常有效。但是,如果群集中沒有足夠的容量來擴充套件Pod,該怎麼辦?
如果達到峰值容量,Kubernetes將使Pod處於暫掛狀態並等待更多資源可用。
如果你可以使用類似於Horizontal Pod Autoscaler的自動縮放器,但對於節點則會很棒。
好訊息!
你可以擁有一個群集自動縮放器,可以在你需要更多資源時為Kubernetes群集新增更多節點。
群集自動縮放器具有不同的形狀和大小。它也是特定於雲提供商的。
請註意,你將無法使用minikube測試自動縮放器,因為它根據定義是單節點。
你可以在GitHub[6]上找到有關叢集自動調節器和雲提供程式實現的更多資訊。
概覽

 

設計大規模應用程式需要仔細規劃和測試。
基於佇列的體系結構是一種出色的設計樣式,可以解耦你的微服務並確保它們可以獨立擴充套件和部署。
雖然你可以用指令碼來擴充套件你的應用,但可以更輕鬆地利用容器編排器(如Kubernetes)自動部署和擴充套件應用程式。
相關連結:
  1. https://github.com/learnk8s/spring-boot-k8s-hpa

  2. https://github.com/learnk8s/spring-boot-k8s-hpa/blob/master/src/main/java/com/learnk8s/app/queue/QueueService.java

  3. https://kubernetes.io/docs/tasks/tools/

  4. https://learnk8s.io/blog/installing-docker-and-kubernetes-on-windows

  5. https://docs.docker.com/install/

  6. https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler#deployment

原文連結:https://medium.freecodecamp.org/how-to-scale-microservices-with-message-queues-spring-boot-and-kubernetes-f691b7ba3acf

 

贊(0)

分享創造快樂