重點來了,本文全面闡述一下咱們的RPC是怎麼實現並如何使用的,跟Kubernetes和Openstack怎麼結合。
在選型一文中說到咱們選定的RPC框架是Apache Thrift,它的用法是在Main方法中重啓服務,在Client端鏈接服務去調用,java
而個人想法是要跟Dubblo、HSF的用法同樣,由於不少人都熟習這兩個框架的用法,特別是咱們好幾個項目都是基於EDAS開發的,並且世面上用Dubbo的公司也不少。docker
順便再說一下咱們對於RPC的幾點要求:數據庫
兼容Dubbo就必然要使用Spring框架,那咱們就直接上Spring Boot好了,號稱Spring Boot爲微服務開發而生,一步到位,將Thrift跟Spring Boot整合。apache
版本和服務分組能夠經過Kubernetes的Service的Label來實現,咱們客戶端在查找服務的時候經過這兩個標籤再加上接口類的Label來定位Service的Cluster IP,這裏不直接使用Service名稱來調用服務的緣由是經過Label查詢Servcie更加靈活一些,Service的名稱不受限制,隨時能夠啓動一個帶有相同Label的新Service來替換舊的Service.
項目隔離能夠用Kubernetes的namespace來實現,一個namespace是一個項目,固然項目之間也能夠互相調用,默認狀況下是整個Kubernetes集羣的服務都是能夠被調用到的若是在沒有指定namespace的狀況下。api
客戶端重試機制用代理Thrift鏈接的方式來實現,在鏈接或接口方法調用異常時發起從新鏈接,參考:https://liveramp.com/engineering/reconnecting-thrift-client/安全
客戶端鏈接池是因爲在WEB項目中每次用戶發起請求是在一個獨立的線程中,而Thrift的Client Socket鏈接不是線程安全的,所以要爲每一個用戶準備一個Socket鏈接,有點像數據庫的鏈接池,這個能夠用apache的commons pool2來實現,這個有不少網友的文章能夠參考,本文就不在贅述了。app
服務端平滑升級可使用Kubernetes的Kubectl rolling-update來實現,它的機制是先建立一個RC,而後新建一個新版本Pod,停掉一箇舊版本Pod,逐步來完成整個RC的更新,最後刪除舊的RC,把新的RC名稱改成舊的RC名稱,升級過程以下圖:
框架
這裏會有一個問題,由於有一個時間段會新舊RC共存,因爲Service是基於RC的Label創建的,那過來的請求是否是會獲得兩種結果?ide
若是是的話要防止這樣的狀況發生就要像上面說的,將整個Service替換,先啓動一個新的Service跟舊的Service有相同Label,而後刪除舊的Service以及RC,在發生服務請求的時候Thrift Client在找不到舊的服務的時候根據Label從新查找Service就會切換到新的Service上。微服務
下面展現一下大概的實現及使用方法,假設你熟習Kubernetes或者簡單瞭解,熟習Docker。
<bean class="io.masir.testcloud.thrift.HelloImpl" id="helloImpl"/> <bean class="io.masir.testcloud.thrift.ThriftSpringProviderBean" init-method="init" id="providerBean"> <property name="serviceInterface" value="io.masir.testcloud.thrift.HelloService"/> <property name="serviceVersion" value="1.0.0"/> <property name="serviceGroup" value="testServiceGroup"/> <property name="target" ref="helloImpl"/> </bean>
ThriftSpringProviderBean核心代碼 這是Thrift和Spring整合的核心代碼,能夠借鑑其它網友的Thrift Spring實例。
public class ThriftSpringProviderBean extends Thread { private int port = 10809; private String serviceInterface; private String serviceVersion; private String serviceGroup; private Object target; public void run() { try { TServerSocket serverTransport = new TServerSocket(getPort()); Class Processor = Class.forName(getServiceInterface() + "$Processor"); Class Iface = Class.forName(getServiceInterface() + "$Iface"); Constructor con = Processor.getConstructor(Iface); TProcessor processor = (TProcessor) con.newInstance(getTarget()); TBinaryProtocol.Factory protFactory = new TBinaryProtocol.Factory(true, true); TThreadPoolServer.Args args = new TThreadPoolServer.Args(serverTransport); args.protocolFactory(protFactory); args.processor(processor); TServer server = new TThreadPoolServer(args); logger.info("Starting server on port " + getPort() + " ..."); server.serve(); } catch (TTransportException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } public void init() { start(); } public String getServiceInterface() { if(serviceInterface.endsWith(".Iface")){ serviceInterface = serviceInterface.replace(".Iface",""); } return serviceInterface; } }
考慮到Kubernetes是有負載和服務發現的功能,那咱們如何跟Thrift整合在一塊兒使用是咱們要解決的問題
<bean class="io.masir.testcloud.thrift.ThriftClientBeanProxyFactory"> <property name="k8sAPIServer" value="http://100.0.1.5:8080/"/> <property name="interfaceName" value="io.masir.testcloud.thrift.HelloService"/> <property name="version" value="0.0.1"/> <property name="group" value="thrifttest"/> </bean>
k8sAPIServer 是Kubernetes的API地址,用來根據 group、version、interfaceName 三個參數查找服務的集羣地址
ThriftClientBeanProxyFactory 的實現請參考 http://blog.csdn.net/muyuxuebao/article/details/51556066 ,包括從新機制也有了。
另外推薦一個Kubernetes Api訪問的Java組件,很是好用和靈活
<dependency> <groupId>io.fabric8</groupId> <artifactId>kubernetes-client</artifactId> <version>1.4.14</version> </dependency>
服務的Dockerfile
FROM registry2.io/public/java:7 Copy jn-boot-0.0.1.jar /jn-boot.jar EXPOSE 10809 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo Asia/Shanghai > /etc/timezone ENV TZ Asia/Shanghai ENTRYPOINT java -jar /jn-boot.jar
消費者Dockerfile
FROM registry2.io/public/java:7 COPY jn-boot-client-0.0.2.jar /jn-boot-client.jar EXPOSE 10809 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo Asia/Shanghai > /etc/timezone ENV TZ Asia/Shanghai ENTRYPOINT java -jar /jn-boot-client.jar
須要有兩個地方注意一下,使用Copy,每次都要覆蓋拷貝到Image中,另外一個是日期應該放在基礎Image中,Build生成Image後Push到咱們Registry中。
程序開發或者說開發思路基本實現了,下面就是部署上線測試,Kubernetes的Pods基於Docker運行,那就會用到Registry,一個Pod會是一個Docker容器,因此Kubernetes的流程是從Registry中拿到Image而後啓動一個Dokcer容器,因爲咱們配置的Registry是有權限的,因此要先生成Kubernetes的Secrets,
kubectl create secret docker-registry registry2key --docker-server=registry2.io --docker-username=admin --docker-password=1 --docker-email=xxxx@163.com --namespace=thrift-demo
而後在yaml中配置:
apiVersion: v1 kind: ReplicationController metadata: name: thrift-c namespace: thrift-demo spec: replicas:1 selector: app: thrift-c template: metadata: name: thrift-c labels: app: thrift-c spec: containers: - name: thrift-c image: registry2.io/thrift/thrift-c:0.0.2 imagePullPolicy: Always ports: - containerPort: 9091 imagePullSecrets: - name: registry2key
注意裏面的 imagePullSecrets registry2key
{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "thrift-c-app", "namespace": "thrift-demo" }, "spec": { "selector": { "app": "thrift-c" }, "ports": [ { "protocol": "TCP", "port": 9091, "targetPort": 9091 } ] } }
Kubernetes的配置網上有不少,你們分頭去參考,這裏不過多說明,這是一個Thrift客戶端的Kubenetes RC和Service配置,在Kubernetes Master雲主機上經過Kubectl運行並啓動這個RC
另外還須要部署Thrift服務端的RC、Service,如圖:
(請注意服務端的Service的Label)
下面是Replication Controllers
調用測試,查看服務的訪問地址,咱們的客戶端服務使用的是Nodeport,查看Nodeport的方式,或者在Dashboard上查看
而後經過Kubernetes集羣中的任意一臺機器加上NodePort端口就能夠訪問咱們的Thrift客戶端服務了。
在本文中咱們能夠看到使用了大量的Kubernetes特性,服務發現、服務負載(基於Service)、滾動升級等等,其中服務發現是在咱們添加了Pods數量後會被Service自動發現,包括後面要說的自動擴容,而負載就是Service會在全部Pods中經過某種機制選擇某個Pod來調用,事實上還有不少Kubernetes的特性等待咱們去使用和發掘,Kubernetes真是一個得力的容器助手,但願咱們能把它用好,也但願Kubernetes愈來愈完善。
在下文中咱們將說一說服務的發佈,總不能都經過IP+NodePort的方式來訪問全部WEB服務吧,必定要有一個完美的合適的解決辦法,那會是什麼呢。。。