基于Docker的微服务架构实践前言基于Docker的容器技术是在.docx
《基于Docker的微服务架构实践前言基于Docker的容器技术是在.docx》由会员分享,可在线阅读,更多相关《基于Docker的微服务架构实践前言基于Docker的容器技术是在.docx(30页珍藏版)》请在冰豆网上搜索。
基于Docker的微服务架构实践前言基于Docker的容器技术是在
基于Docker的微服务架构实践
前言
基于Docker的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名Docker的DevOps,也见证了Docker的技术体系的快速发展。
本文主要是结合在公司搭建的微服务架构的实践过程,做一个简单的总结。
希望给在创业初期探索如何布局服务架构体系的DevOps,或者想初步了解企业级架构的同学们一些参考。
Microservice和Docker
对于创业公司的技术布局,很多声音基本上是,创业公司就是要快速上线快速试错。
用单应用或者前后台应用分离的方式快速集成,快速开发,快速发布。
但其实这种结果造成的隐性成本会更高。
当业务发展起来,开发人员多了之后,就会面临庞大系统的部署效率,开发协同效率问题。
然后通过服务的拆分,数据的读写分离、分库分表等方式重新架构,而且这种方式如果要做的彻底,需要花费大量人力物力。
个人建议,DevOps结合自己对于业务目前以及长期的发展判断,能够在项目初期使用微服务架构,多为后人谋福。
随着Docker周围开源社区的发展,让微服务架构的概念能有更好的一个落地实施的方案。
并且在每一个微服务应用内部,都可以使用DDD(Domain-DriveDesign)的六边形架构来进行服务内的设计。
关于DDD的一些概念也可以参考之前写的几篇文章:
领域驱动设计整理——概念&架构、领域驱动设计整理——实体和值对象设计、领域服务、领域事件。
清晰的微服务的领域划分,服务内部有架构层次的优雅的实现,服务间通过RPC或者事件驱动完成必要的IPC,使用APIgateway进行所有微服务的请求转发,非阻塞的请求结果合并。
本文下面会具体介绍,如何在分布式环境下,也可以快速搭建起来具有以上几点特征的,微服务架构withDocker。
服务发现模式
如果使用Docker技术来架构微服务体系,服务发现就是一个必然的课题。
目前主流的服务发现模式有两种:
客户端发现模式,以及服务端发现模式。
客户端发现模式
客户端发现模式的架构图如下:
客户端发现模式的典型实现是Netflix体系技术。
客户端从一个服务注册服务中心查询所有可用服务实例。
客户端使用负载均衡算法从多个可用的服务实例中选择出一个,然后发出请求。
比较典型的一个开源实现就是Netflix的Eureka。
Netflix-Eureka
Eureka的客户端是采用自注册的模式,客户端需要负责处理服务实例的注册和注销,发送心跳。
在使用SpringBoot集成一个微服务时,结合SpringCloud项目可以很方便得实现自动注册。
在服务启动类上添加@EnableEurekaClient即可在服务实例启动时,向配置好的Eureka服务端注册服务,并且定时发送以心跳。
客户端的负载均衡由NetflixRibbon实现。
服务网关使用NetflixZuul,熔断器使用NetflixHystrix。
除了服务发现的配套框架,SpringCloud的Netflix-Feign,提供了声明式的接口来处理服务的Rest请求。
当然,除了使用FeignClient,也可以使用SpringRestTemplate。
项目中如果使用@FeignClient可以使代码的可阅读性更好,RestAPI也一目了然。
服务实例的注册管理、查询,都是通过应用内调用Eureka提供的RESTAPI接口(当然使用SpringCloud-Eureka不需要编写这部分代码)。
由于服务注册、注销是通过客户端自身发出请求的,所以这种模式的一个主要问题是对于不同的编程语言会注册不同服务,需要为每种开发语言单独开发服务发现逻辑。
另外,使用Eureka时需要显式配置健康检查支持。
服务端发现模式
服务端发现模式的架构图如下:
客户端向负载均衡器发出请求,负载均衡器向服务注册表发出请求,将请求转发到注册表中可用的服务实例。
服务实例也是在注册表中注册,注销的。
负载均衡可以使用可以使用Haproxy或者Nginx。
服务端发现模式目前基于Docker的主流方案主要是Consul、Etcd以及Zookeeper。
Consul
Consul提供了一个API允许客户端注册和发现服务。
其一致性上基于RAFT算法。
通过WAN的Gossip协议,管理成员和广播消息,以完成跨数据中心的同步,且支持ACL访问控制。
Consul还提供了健康检查机制,支持kv存储服务(Eureka不支持)。
Consul的一些更详细的介绍可以参考之前写的一篇:
Docker容器部署Consul集群。
Etcd
Etcd都是强一致的(满足CAP的CP),高可用的。
Etcd也是基于RAFT算法实现强一致性的KV数据同步。
Kubernetes中使用Etcd的KV结构存储所有对象的生命周期。
关于Etcd的一些内部原理可以看下etcdv3原理分析
Zookeeper
ZK最早应用于Hadoop,其体系已经非常成熟,常被用于大公司。
如果已经有自己的ZK集群,那么可以考虑用ZK来做自己的服务注册中心。
Zookeeper同Etcd一样,强一致性,高可用性。
一致性算法是基于Paxos的。
对于微服务架构的初始阶段,没有必要用比较繁重的ZK来做服务发现。
服务注册
服务注册表是服务发现中的一个重要组件。
除了Kubernetes、Marathon其服务发现是内置的模块之外。
服务都是需要注册到注册表上。
上文介绍的Eureka、consul、etcd以及ZK都是服务注册表的例子。
微服务如何注册到注册表也是有两种比较典型的注册方式:
自注册模式,第三方注册模式。
自注册模式Self-registrationpattern
上文中的Netflix-Eureka客户端就是一个典型的自注册模式的例子。
也即每个微服务的实例本身,需要负责注册以及注销服务。
Eureka还提供了心跳机制,来保证注册信息的准确,具体的心跳的发送间隔时间可以在微服务的SpringBoot中进行配置。
如下,就是使用Eureka做注册表时,在微服务(SpringBoot应用)启动时会有一条服务注册的信息:
flix.discovery.DiscoveryClient:
DiscoveryClient_SERVICE-USER/{your_ip}:
service-user:
{port}:
cc9f93c54a0820c7a845422f9ecc73fb:
registeringservice...
同样,在应用停用时,服务实例需要主动注销本实例信息:
2018-01-0420:
41:
37.290INFO49244---[Thread-8]c.n.e.EurekaDiscoveryClientConfiguration:
Unregisteringapplicationservice-userwitheurekawithstatusDOWN
2018-01-0420:
41:
37.340INFO49244---[Thread-8]flix.discovery.DiscoveryClient:
ShuttingdownDiscoveryClient...
2018-01-0420:
41:
37.381INFO49244---[Thread-8]flix.discovery.DiscoveryClient:
Unregistering...
2018-01-0420:
41:
37.559INFO49244---[Thread-8]flix.discovery.DiscoveryClient:
DiscoveryClient_SERVICE-USER/{your_ip}:
service-user:
{port}:
cc9f93c54a0820c7a845422f9ecc73fb-deregisterstatus:
200
自注册方式是比较简单的服务注册方式,不需要额外的设施或代理,由微服务实例本身来管理服务注册。
但是缺点也很明显,比如Eureka目前只提供了Java客户端,所以不方便多语言的微服务扩展。
因为需要微服务自己去管理服务注册逻辑,所以微服务实现也耦合了服务注册和心跳机制。
跨语言性比较差。
第三方注册模式Thirdpartyregistrationpattern
第三方注册,也即服务注册的管理(注册、注销服务)通过一个专门的服务管理器(Registar)来负责。
Registrator就是一个开源的服务管理器的实现。
Registrator提供了对于Etcd以及Consul的注册表服务支持。
Registrator作为一个代理服务,需要部署、运行在微服务所在的服务器或者虚拟机中。
比较简单的安装方式就是通过Docker,以容器的方式来运行。
三方注册模式的架构图如下:
通过添加一个服务管理器,微服务实例不再直接向注册中心注册,注销。
由服务管理器(Registar)通过订阅服务,跟踪心跳,来发现可用的服务实例,并向注册中心(consul、etcd等)注册,注销实例,以及发送心跳。
这样就可以实现服务发现组件和微服务架构的解耦。
Registrator配合Consul,以及ConsulTemplate搭建服务发现中心,可以参考:
ScalableArchitectureDRCoN:
Docker,Registrator,Consul,ConsulTemplateandNginx。
此文示例了Nginx来做负载均衡,在具体的实施过程中也可以用Haproxy或其他方案进行替代。
小结
除了以上几种做服务发现的技术,Kubernetes自带了服务发现模块,负责处理服务实例的注册和注销。
Kubernetes也在每个集群节点上运行代理,来实现服务端发现路由器的功能。
如果编排技术使用的k8n,可以用k8n的一整套Docker微服务方案,对k8n感兴趣的可以阅读下Kubernetes架构设计与核心原理。
在实际的技术选型中,最主要还是要结合业务、系统的未来发展的特征进行合理判断。
在CAP理论中。
Eureka满足了AP,Consul是CA,ZK和Etcd是CP。
在分布式场景下Eureka和Consul都能保证可用性。
而搭建Eureka服务会相对更快速,因为不需要搭建额外的高可用服务注册中心,在小规模服务器实例时,使用Eureka可以节省一定成本。
Eureka、Consul都提供了可以查看服务注册数据的WebUI组件。
Consul还提供了KV存储,支持支持http和dns接口。
对于创业公司最开始搭建微服务,比较推荐这两者。
在多数据中心方面,Consul自带数据中心的WAN方案。
ZK和Etcd均不提供多数据中心功能的支持,需要额外的开发。
跨语言性上,Zookeeper需要使用其提供的客户端api,跨语言支持较弱。
Etcd、Eureka都支持http,Etcd还支持grpc。
Consul除了http之外还提供了DNS的支持。
安全方面,Consul,Zookeeper支持ACL,另外Consul、Etcd支持安全通道Https。
SpringCloud目前对于Eureka、Consul、Etcd、ZK都有相应的支持。
Consul和Docker一样,都是用Go语言实现,基于Go语言的微服务应用可以优先考虑用Consul。
服务间的IPC机制
按照微服务的架构体系,解决了服务发现的问题之后。
就需要选择合适的服务间通信的机制。
如果是在SpringBoot应用中,使用基于Http协议的RESTAPI是一种同步的解决方案。
而且Restful风格的API可以使每个微服务应用更加趋于资源化,使用轻量级的协议也是微服务一直提倡的。
如果每个微服务是使用DDD(Domain-DrivenDesign)思想的话,那么需要每个微服务尽量不使用同步的RPC机制。
异步的基于消息的方式比如AMQP或者STOMP,来松耦合微服务间的依赖会是很好的选择。
目前基于消息的点对点的pub/sub的框架选择也比较多。
下面具体介绍下两种IPC的一些方案。
同步
对于同步的请求/响应模式的通信方式。
可以选择基于Restful风格的Http协议进行服务间通信,或者跨语言性很好的Thrift协议。
如果是使用纯Java语言的微服务,也可以使用Dubbo。
如果是SpringBoot集成的微服务架构体系,建议选择跨语言性好、Spring社区支持比较好的RPC。
Dubbo
Dubbo是由阿里巴巴开发的开源的Java客户端的RPC框架。
Dubbo基于TCP协议的长连接进行数据传输。
传输格式是使用Hessian二进制序列化。
服务注册中心可以通过Zookeeper实现。
ApacheThrift
ApacheThrift是由Facebook开发的RPC框架。
其代码生成引擎可以在多种语言中,如C++、Java、Python、PHP、Ruby、Erlang、Perl等创建高效的服务。
传输数据采用二进制格式,其数据包要比使用Json或者XML格式的HTTP协议小。
高并发,大数据场景下更有优势。
Rest
Rest基于HTTP协议,HTTP协议本身具有语义的丰富性。
随着Springboot被广泛使用,越来越多的基于Restful风格的API流行起来。
REST是基于HTTP协议的,并且大多数开发者也是熟知HTTP的。
这里另外提一点,很多公司或者团队也是使用Springboot的,也在说自己是基于Restful风格的。
但是事实其实往往是实施得并不到位。
对于你的Restful是否是真的Restful,可以参考这篇文章,对于Restful风格API的成熟度进行了四个层次的分析:
RichardsonMaturityModelstepstowardthegloryofREST。
如果使用Springboot的话,无论使用什么服务发现机制,都可以通过Spring的RestTemplate来做基础的Http请求封装。
如果使用的前文提到的Netflix-Eureka的话,可以使用Netflix-Feign。
Feign是一个声明式WebService客户端。
客户端的负载均衡使用Netflix-Ribbon。
异步
在微服务架构中,排除纯粹的“事件驱动架构”,使用消息队列的场景一般是为了进行微服务之间的解耦。
服务之间不需要了解是由哪个服务实例来消费或者发布消息。
只要处理好自己领域范围的逻辑,然后通过消息通道来发布,或者订阅自己关注的消息就可以。
目前开源的消息队列技术也很多。
比如ApacheKafka,RabbitMQ,ApacheActiveMQ以及阿里巴巴的RocketMQ目前已经成为Apache项目之一。
消息队列的模型中,主要的三个组成就是:
Producer:
生产消息,将消息写入channel。
MessageBroker:
消息代理,将写入channel的消息按队列的结构进行管理。
负责存储/转发消息。
Broker一般是需要单独搭建、配置的集群,而且必须是高可用的。
Consumer:
消息的消费者。
目前大多数的消息队列都是保证消息至少被消费一次。
所以根据使用的消息队列设施不同,消费者要做好幂等。
不同的消息队列的实现,消息模型不同。
各个框架的特性也不同:
RabbitMQ
RabbitMQ是基于AMQP协议的开源实现,由以高性能、可伸缩性出名的Erlang写成。
目前客户端支持Java、.Net/C#和Erlang。
在AMQP(AdvancedMessageQueuingProtocol)的组件中,Broker中可以包含多个Exchange(交换机)组件。
Exchange可以绑定多个Queue以及其他Exchange。
消息会按照Exchange中设置的Routing规则,发送到相应的MessageQueue。
在Consumer消费了这个消息之后,会跟Broker建立连接。
发送消费消息的通知。
则MessageQueue才会将这个消息移除。
Kafka
Kafka是一个高性能的基于发布/订阅的跨语言分布式消息系统。
Kafka的开发语言为Scala。
其比较重要的特性是:
以时间复杂度为O
(1)的方式快速消息持久化;
高吞吐率;
支持服务间的消息分区,及分布式消费,同时保证消息顺序传输;
支持在线水平扩展,自带负载均衡;
支持只消费且仅消费一次(ExactlyOnce)模式等等。
说个缺点:
管理界面是个比较鸡肋了点,可以使用开源的kafka-manager
其高吞吐的特性,除了可以作为微服务之间的消息队列,也可以用于日志收集,离线分析,实时分析等。
Kafka官方提供了Java版本的客户端API,Kafka社区目前也支持多种语言,包括PHP、Python、Go、C/C++、Ruby、NodeJS等。
ActiveMQ
ActiveMQ是基于JMS(JavaMessagingService)实现的JMSProvider。
JMS主要提供了两种类型的消息:
点对点(Point-to-Point)以及发布/订阅(Publish/Subscribe)。
目前客户端支持Java、C、C++、C#、Ruby、Perl、Python、PHP。
而且ActiveMQ支持多种协议:
Stomp、AMQP、MQTT以及OpenWire。
RocketMQ/ONS
RocketMQ是由阿里巴巴研发开源的高可用分布式消息队列。
ONS是提供商业版的高可用集群。
ONS支持pull/push。
可支持主动推送,百亿级别消息堆积。
ONS支持全局的顺序消息,以及有友好的管理页面,可以很好的监控消息队列的消费情况,并且支持手动触发消息多次重发。
小结
通过上篇的微服务的服务发现机制,加上RestfulAPI,可以解决微服务间的同步方式的进程间通信。
当然,既然使用了微服务,就希望所有的微服务能有合理的限界上下文(系统边界)。
微服务之间的同步通信应尽量避免,以防止服务间的领域模型互相侵入。
为了避免这种情况,就可以在微服务的架构中使用一层APIgateway(会在下文介绍)。
所有的微服务通过APIgateway进行统一的请求的转发,合并。
并且APIgateway也需要支持同步请求,以及NIO的异步的请求(可以提高请求合并的效率以及性能)。
消息队列可以用于微服务间的解耦。
在基于Docker的微服务的服务集群环境下,网络环境会比一般的分布式集群复杂。
选择一种高可用的分布式消息队列实现即可。
如果自己搭建诸如Kafka、RabbitMQ集群环境的话,那对于Broker设施的高可用性会要求很高。
基于Springboot的微服务的话,比较推荐使用Kafka或者ONS。
虽然ONS是商用的,但是易于管理以及稳定性高,尤其对于必要场景才依赖于消息队列进行通信的微服务架构来说,会更适合。
如果考虑到会存在日志收集,实时分析等场景,也可以搭建Kafka集群。
目前阿里云也有了基于Kafka的商用集群设施。
使用APIGateway处理微服务请求转发、合并
前面主要介绍了如何解决微服务的服务发现和通信问题。
在微服务的架构体系中,使用DDD思想划分服务间的限界上下文的时候,会尽量减少微服务之间的调用。
为了解耦微服务,便有了基于APIGateway方式的优化方案。
解耦微服务的调用
比如,下面一个常见的需求场景——“用户订单列表”的一个聚合页面。
需要请求”用户服务“获取基础用户信息,以及”订单服务“获取订单信息,再通过请求“商品服务”获取订单列表中的商品图片、标题等信息。
如下图所示的场景:
如果让客户端(比如H5、Android、iOS)发出多个请求来解决多个信息聚合,则会增加客户端的复杂度。
比较合理的方式就是增加APIGateway层。
APIGateway跟微服务一样,也可以部署、运行在Docker容器中,也是一个Springboot应用。
如下,通过GatewayAPI进行转发后:
所有的请求的信息,由Gateway进行聚合,Gateway也是进入系统的唯一节点。
并且Gateway和所有微服务,以及提供给客户端的也是Restful风格API。
Gateway层的引入可以很好的解决信息的聚合问题。
而且可以更好得适配不同的客户端的请求,比如H5的页面不需要展示用户信息,而iOS客户端需要展示用户信息,则只需要添加一个GatewayAPI请求资源即可,微服务层的资源不需要进行变更。
APIGateway的特点
APIgateway除了可以进行请求的合并、转发。
还需要有其他的特点,才能成为一个完整的Gateway。
响应式编程
Gateway是所有客户端请求的入口。
类似Facade模式。
为了提高请求的性能,最好选择一套非阻塞I/O的框架。
在一些需要请求多个微服务的场景下,对于每个微服务的请求不一定需要同步。
前文举例的“用户订单列表”的例子中,获取用户信息,以及获取订单列表,就是两个独立请求。
只有获取订单的商品信息,需要等订单信息返回之后,根据订单的商品id列表再去请求商品微服务。
为了减少整个请求的响应时间,需要Gateway能够并发处理相互独立的请求。
一种解决方案就是采用响应式编程。
目前使用Java技术栈的响应式编程方式有,Java8的CompletableFuture,以及ReactiveX提供的基于JVM的实现-RxJava。
ReactiveX是一个使用可观察数据流进行异步编程的编程接口,ReactiveX结合了观察者模式、迭代器模式和函数式编程的精华。
除了RxJava还有RxJS,RX.NET等多语言的实现。
对于Gateway来说,RxJava提供的Observable可以很好的解决并行的独立I/O请求,并且如果微服务项目中使用Java8,团队成员会对RxJava的函数学习吸收会更快。
同样基于Lambda风格的响应式编程,可以使代码更加简洁。
关于RxJava的详细介绍可以可以阅读RxJava文档和教程。
通过响应式编程的Observable模式,可以很简洁、方便得创建事件流、数据流,以及用简洁的函数进行数据的组合和转换,同时可以订阅任何可观察的数据流并执行操作。
通过使用RxJava,“用户订单列表”的资源请求时序图:
响应式编程可以更好的处理各种线程同步、并发请求,通过Observables和Schedulers提供了透明的数据流、事件流的线程处理。
在敏捷开发模式下,响应式编程使代码更加简洁,更好维护。
鉴权
Gateway作为系统的唯一入口,基于微服务的所有鉴权,都可以围绕Gateway去做。
在Springboot工程中,基础的授权可以使用spring-boot-starter-security以及SpringSec