重点来了,本文全面阐述一下咱们的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服务吧,必定要有一个完美的合适的解决办法,那会是什么呢。。。