本文来自做者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践}
基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,做为一名 Docker 的 DevOps,也见证了 Docker 的技术体系的快速发展。本文主要是结合在公司搭建的微服务架构的实践过程,作一个简单的总结。但愿给在创业初期探索如何布局服务架构体系的 DevOps,或者想初步了解企业级架构的同窗们一些参考。前端
对于创业公司的技术布局,不少声音基本上是,创业公司就是要快速上线快速试错。用单应用或者先后台应用分离的方式快速集成,快速开发,快速发布。但其实这种结果形成的隐性成本会更高。当业务发展起来,开发人员多了以后,就会面临庞大系统的部署效率,开发协同效率问题。而后经过服务的拆分,数据的读写分离、分库分表等方式从新架构,并且这种方式若是要作的完全,须要花费大量人力物力。node
我的建议,DevOps 结合本身对于业务目前以及长期的发展判断,可以在项目初期使用微服务架构,多为后人谋福。
随着 Docker 周围开源社区的发展,让微服务架构的概念能有更好的一个落地实施的方案。而且在每个微服务应用内部,均可以使用 DDD(Domain-Drive Design)的六边形架构来进行服务内的设计。关于 DDD 的一些概念也能够参考以前写的几篇文章:领域驱动设计整理——概念&架构、领域驱动设计整理——实体和值对象设计、领域服务、领域事件。mysql
清晰的微服务的领域划分,服务内部有架构层次的优雅的实现,服务间经过 RPC 或者事件驱动完成必要的 IPC,使用 API gateway 进行全部微服务的请求转发,非阻塞的请求结果合并。本文下面会具体介绍,如何在分布式环境下,也能够快速搭建起来具备以上几点特征的,微服务架构 with Docker。git
若是使用 Docker 技术来架构微服务体系,服务发现就是一个必然的课题。目前主流的服务发现模式有两种:客户端发现模式,以及服务端发现模式。
客户端发现模式
客户端发现模式的架构图以下:面试
客户端发现模式的典型实现是Netflix体系技术。客户端从一个服务注册服务中心查询全部可用服务实例。客户端使用负载均衡算法从多个可用的服务实例中选择出一个,而后发出请求。比较典型的一个开源实现就是 Netflix 的 Eureka。redis
Netflix-Eureka
Eureka 的客户端是采用自注册的模式,客户端须要负责处理服务实例的注册和注销,发送心跳。
在使用 SpringBoot 集成一个微服务时,结合 SpringCloud 项目能够很方便得实现自动注册。在服务启动类上添加@EnableEurekaClient便可在服务实例启动时,向配置好的 Eureka 服务端注册服务,而且定时发送以心跳。客户端的负载均衡由 Netflix Ribbon 实现。服务网关使用 Netflix Zuul,熔断器使用 Netflix Hystrix。算法
除了服务发现的配套框架,SpringCloud 的 Netflix-Feign,提供了声明式的接口来处理服务的 Rest 请求。固然,除了使用 FeignClient,也可使用 Spring RestTemplate。项目中若是使用@FeignClient可使代码的可阅读性更好,Rest API 也一目了然。spring
服务实例的注册管理、查询,都是经过应用内调用 Eureka 提供的 REST API 接口(固然使用 SpringCloud-Eureka 不须要编写这部分代码)。因为服务注册、注销是经过客户端自身发出请求的,因此这种模式的一个主要问题是对于不一样的编程语言会注册不一样服务,须要为每种开发语言单独开发服务发现逻辑。另外,使用 Eureka 时须要显式配置健康检查支持。sql
服务端发现模式
服务端发现模式的架构图以下:docker
客户端向负载均衡器发出请求,负载均衡器向服务注册表发出请求,将请求转发到注册表中可用的服务实例。服务实例也是在注册表中注册,注销的。负载均衡可使用可使用 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 的一些内部原理能够看下etcd v3原理分析
Zookeeper
ZK 最先应用于 Hadoop,其体系已经很是成熟,常被用于大公司。若是已经有本身的 ZK 集群,那么能够考虑用 ZK 来作本身的服务注册中心。
Zookeeper 同 Etcd 同样,强一致性,高可用性。一致性算法是基于 Paxos 的。对于微服务架构的初始阶段,没有必要用比较繁重的 ZK 来作服务发现。
服务注册表是服务发现中的一个重要组件。除了 Kubernetes、Marathon 其服务发现是内置的模块以外。服务都是须要注册到注册表上。上文介绍的 Eureka、consul、etcd 以及 ZK 都是服务注册表的例子。
微服务如何注册到注册表也是有两种比较典型的注册方式:自注册模式,第三方注册模式。
自注册模式 Self-registration pattern
上文中的 Netflix-Eureka 客户端就是一个典型的自注册模式的例子。也即每一个微服务的实例自己,须要负责注册以及注销服务。Eureka 还提供了心跳机制,来保证注册信息的准确,具体的心跳的发送间隔时间能够在微服务的 SpringBoot 中进行配置。
以下,就是使用 Eureka 作注册表时,在微服务(SpringBoot 应用)启动时会有一条服务注册的信息:
一样,在应用停用时,服务实例须要主动注销本实例信息:
自注册方式是比较简单的服务注册方式,不须要额外的设施或代理,由微服务实例自己来管理服务注册。可是缺点也很明显,好比 Eureka 目前只提供了 Java 客户端,因此不方便多语言的微服务扩展。由于须要微服务本身去管理服务注册逻辑,因此微服务实现也耦合了服务注册和心跳机制。跨语言性比较差。
在此我向你们推荐一个架构学习交流群。交流学习群号:478030634 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
第三方注册,也即服务注册的管理(注册、注销服务)经过一个专门的服务管理器(Registar)来负责。Registrator 就是一个开源的服务管理器的实现。Registrator 提供了对于 Etcd 以及 Consul 的注册表服务支持。 Registrator 做为一个代理服务,须要部署、运行在微服务所在的服务器或者虚拟机中。比较简单的安装方式就是经过 Docker,以容器的方式来运行。
三方注册模式的架构图以下:
经过添加一个服务管理器,微服务实例再也不直接向注册中心注册,注销。由服务管理器(Registar)经过订阅服务,跟踪心跳,来发现可用的服务实例,并向注册中心(consul、etcd 等)注册,注销实例,以及发送心跳。这样就能够实现服务发现组件和微服务架构的解耦。
Registrator 配合 Consul,以及 Consul Template 搭建服务发现中心,能够参考: Scalable Architecture DR CoN: Docker, Registrator, Consul, Consul Template and Nginx 。此文示例了 Nginx 来作负载均衡,在具体的实施过程当中也能够用 Haproxy 或其余方案进行替代。
除了以上几种作服务发现的技术,Kubernetes 自带了服务发现模块,负责处理服务实例的注册和注销。Kubernetes 也在每一个集群节点上运行代理,来实现服务端发现路由器的功能。若是编排技术使用的 k8n,能够用 k8n 的一整套 Docker 微服务方案,对 k8n 感兴趣的能够阅读下Kubernetes 架构设计与核心原理。
在实际的技术选型中,最主要仍是要结合业务、系统的将来发展的特征进行合理判断。
按照微服务的架构体系,解决了服务发现的问题以后。就须要选择合适的服务间通讯的机制。若是是在 SpringBoot 应用中,使用基于 Http 协议的 REST API 是一种同步的解决方案。并且 Restful 风格的 API 可使每一个微服务应用更加趋于资源化,使用轻量级的协议也是微服务一直提倡的。
若是每一个微服务是使用 DDD(Domain-Driven Design)思想的话,那么须要每一个微服务尽可能不使用同步的 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 的成熟度进行了四个层次的分析: Richardson Maturity Model steps toward the glory of REST。
若是使用Springboot的话,不管使用什么服务发现机制,均可以经过 Spring 的RestTemplate来作基础的Http请求封装。
若是使用的前文提到的Netflix-Eureka的话,可使用Netflix-Feign。Feign是一个声明式 Web Service 客户端。客户端的负载均衡使用 Netflix-Ribbon。
异步
在微服务架构中,排除纯粹的“事件驱动架构”,使用消息队列的场景通常是为了进行微服务之间的解耦。服务之间不须要了解是由哪一个服务实例来消费或者发布消息。只要处理好本身领域范围的逻辑,而后经过消息通道来发布,或者订阅本身关注的消息就能够。目前开源的消息队列技术也不少。好比 Apache Kafka,RabbitMQ,Apache ActiveMQ 以及阿里巴巴的 RocketMQ 目前已经成为 Apache 项目之一。消息队列的模型中,主要的三个组成就是:
Producer:生产消息,将消息写入 channel。
Message Broker:消息代理,将写入 channel 的消息按队列的结构进行管理。负责存储/转发消息。Broker 通常是须要单独搭建、配置的集群,并且必须是高可用的。
Consumer:消息的消费者。目前大多数的消息队列都是保证消息至少被消费一次。因此根据使用的消息队列设施不一样,消费者要作好幂等。
不一样的消息队列的实现,消息模型不一样。各个框架的特性也不一样:
RabbitMQ
RabbitMQ 是基于 AMQP 协议的开源实现,由以高性能、可伸缩性出名的 Erlang 写成。目前客户端支持 Java、.Net/C# 和 Erlang。在 AMQP(Advanced Message Queuing Protocol)的组件中,Broker 中能够包含多个Exchange(交换机)组件。Exchange 能够绑定多个 Queue 以及其余 Exchange。消息会按照 Exchange 中设置的 Routing 规则,发送到相应的 Message Queue。在 Consumer 消费了这个消息以后,会跟 Broker 创建链接。发送消费消息的通知。则 Message Queue 才会将这个消息移除。
Kafka
Kafka 是一个高性能的基于发布/订阅的跨语言分布式消息系统。Kafka 的开发语言为 Scala。其比较重要的特性是:
以时间复杂度为O(1)的方式快速消息持久化;
高吞吐率;
支持服务间的消息分区,及分布式消费,同时保证消息顺序传输;
支持在线水平扩展,自带负载均衡;
支持只消费且仅消费一次(Exactly Once)模式等等。
说个缺点: 管理界面是个比较鸡肋了点,可使用开源的kafka-manager
其高吞吐的特性,除了能够做为微服务之间的消息队列,也能够用于日志收集, 离线分析, 实时分析等。
Kafka 官方提供了 Java 版本的客户端 API,Kafka 社区目前也支持多种语言,包括 PHP、Python、Go、C/C++、Ruby、NodeJS 等。
ActiveMQ
ActiveMQ 是基于 JMS(Java Messaging Service)实现的 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 支持全局的顺序消息,以及有友好的管理页面,能够很好的监控消息队列的消费状况,而且支持手动触发消息屡次重发。
经过上篇的微服务的服务发现机制,加上 Restful API,能够解决微服务间的同步方式的进程间通讯。固然,既然使用了微服务,就但愿全部的微服务能有合理的限界上下文(系统边界)。微服务之间的同步通讯应尽可能避免,以防止服务间的领域模型互相侵入。为了不这种状况,就能够在微服务的架构中使用一层API gateway(会在下文介绍)。全部的微服务经过API gateway进行统一的请求的转发,合并。而且API gateway也须要支持同步请求,以及NIO的异步的请求(能够提升请求合并的效率以及性能)。
消息队列能够用于微服务间的解耦。在基于Docker的微服务的服务集群环境下,网络环境会比通常的分布式集群复杂。选择一种高可用的分布式消息队列实现便可。若是本身搭建诸如Kafka、RabbitMQ集群环境的话,那对于Broker设施的高可用性会要求很高。基于Springboot的微服务的话,比较推荐使用Kafka 或者ONS。虽然ONS是商用的,可是易于管理以及稳定性高,尤为对于必要场景才依赖于消息队列进行通讯的微服务架构来讲,会更适合。若是考虑到会存在日志收集,实时分析等场景,也能够搭建Kafka集群。目前阿里云也有了基于Kafka的商用集群设施。
使用 API Gateway 处理微服务请求转发、合并
前面主要介绍了如何解决微服务的服务发现和通讯问题。在微服务的架构体系中,使用DDD思想划分服务间的限界上下文的时候,会尽可能减小微服务之间的调用。为了解耦微服务,便有了基于API Gateway方式的优化方案。
解耦微服务的调用
好比,下面一个常见的需求场景——“用户订单列表”的一个聚合页面。须要请求”用户服务“获取基础用户信息,以及”订单服务“获取订单信息,再经过请求“商品服务”获取订单列表中的商品图片、标题等信息。以下图所示的场景 :
若是让客户端(好比H五、Android、iOS)发出多个请求来解决多个信息聚合,则会增长客户端的复杂度。比较合理的方式就是增长API Gateway层。API Gateway跟微服务同样,也能够部署、运行在Docker容器中,也是一个Springboot应用。以下,经过Gateway API进行转发后:
全部的请求的信息,由Gateway进行聚合,Gateway也是进入系统的惟一节点。而且Gateway和全部微服务,以及提供给客户端的也是Restful风格API。Gateway层的引入能够很好的解决信息的聚合问题。并且能够更好得适配不一样的客户端的请求,好比H5的页面不须要展现用户信息,而iOS客户端须要展现用户信息,则只须要添加一个Gateway API请求资源便可,微服务层的资源不须要进行变动。
API gateway除了能够进行请求的合并、转发。还须要有其余的特色,才能成为一个完整的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以及Spring Security(Spring Security也能够集成在Spring MVC项目中)。
Spring Security主要使用AOP,对资源请求进行拦截,内部维护了一个角色的Filter Chain。由于微服务都是经过Gateway请求的,因此微服务的@Secured能够根据Gateway中不一样的资源的角色级别进行设置。
Spring Security提供了基础的角色的校验接口规范。但客户端请求的Token信息的加密、存储以及验证,须要应用本身完成。对于Token加密信息的存储可使用Redis。这里再多提一点,为了保证一些加密信息的可变性,最好在一开始设计Token模块的时候就考虑到支持多个版本密钥,以防止万一内部密钥被泄露(以前听一个朋友说其公司的Token加密代码被员工公布出去)。至于加密算法,以及具体的实如今此就再也不展开。 在Gateway鉴权经过以后,解析后的token信息能够直接传递给须要继续请求的微服务层。
若是应用须要受权(对资源请求须要管理不一样的角色、权限),也只要在Gateway的Rest API基础上基于AOP思想来作便可。统一管理鉴权和受权,这也是使用相似Facade模式的Gateway API的好处之一。
负载均衡
API Gateway跟Microservice同样,做为Springboot应用,提供Rest api。因此一样运行在Docker容器中。Gateway和微服务之间的服务发现仍是能够采用前文所述的客户端发现模式,或者服务端发现模式。
在集群环境下,API Gateway 能够暴露统一的端口,其实例会运行在不一样IP的服务器上。由于咱们是使用阿里云的ECS做为容器的基础设施,因此在集群环境的负载均衡也是使用阿里云的负载均衡SLB,域名解析也使用AliyunDNS。下图是一个简单的网络请求的示意:
在实践中,为了避免暴露服务的端口和资源地址,也能够在服务集群中再部署Nginx服务做为反向代理,外部的负载均衡设施好比SLB能够将请求转发到Nginx服务器,请求经过Nginx再转发给Gateway端口。若是是自建机房的集群,就须要搭建高可用的负载均衡中心。为了应对跨机器请求,最好使用Consul,Consul(Consul Template)+Registor+Haproxy来作服务发现和负载均衡中心。
缓存
对于一些高QPS的请求,能够在API Gateway作多级缓存。分布式的缓存可使用Redis,Memcached等。若是是一些对实时性要求不高的,变化频率不高可是高QPS的页面级请求,也能够在Gateway层作本地缓存。并且Gateway可让缓存方案更灵活和通用。
在Gateway的具体实现过程当中,错误处理也是一个很重要的事情。对于Gateway的错误处理,可使用Hystrix来处理请求的熔断。而且RxJava自带的onErrorReturn回调也能够方便得处理错误信息的返回。对于熔断机制,须要处理如下几个方面:
做为一个合理的Gateway,其应该只负责处理数据流、事件流,而不该该处理业务逻辑。在处理多个微服务的请求时,会出现微服务请求的超时、不可用的状况。在一些特定的场景下, 须要可以合理得处理部分失败。好比上例中的“用户订单列表”,当“User”微服务出现错误时,不该该影响“Order”数据的请求。最好的处理方式就是给当时错误的用户信息请求返回一个默认的数据,好比显示一个默认头像,默认用户昵称。而后对于请求正常的订单,以及商品信息给与正确的数据返回。若是是一个关键的微服务请求异常,好比当“Order”领域的微服务异常时,则应该给客户端一个错误码,以及合理的错误提示信息。这样的处理能够尽可能在部分系统不可用时提高用户体验。使用RxJava时,具体的实现方式就是针对不一样的客户端请求的状况,写好onErrorReturn,作好错误数据兼容便可。
Gateway主要是作请求的转发、合并。为了能清楚得排查问题,定位到具体哪一个服务、甚至是哪一个Docker容器的问题,须要Gateway能对不一样类型的异常、业务错误进行捕捉和记录。若是使用FeignClient来请求微服务资源,能够经过对ErrorDecoder接口的实现,来针对Response结果进行进一步的过滤处理,以及在日志中记录下全部请求信息。若是是使用Spring Rest Template,则能够经过定义一个定制化的RestTempate,并对返回的ResponseEntity进行解析。在返回序列化以后的结果对象以前,对错误信息进行日志记录。
Gateway线程中大可能是IO线程,为了防止由于某一微服务请求阻塞,致使Gateway过多的等待线程,耗尽线程池、队列等系统资源。须要Gateway中提供超时机制,对超时接口能进行优雅的服务降级。
在SpringCloud的Feign项目中集成了Hystrix。Hystrix提供了比较全面的超时处理的熔断机制。默认状况下,超时机制是开启的。除了能够配置超时相关的参数,Netflix还提供了基于Hytrix的实时监控Netflix -Dashboard,而且集群服务只需再附加部署Netflix-Turbine。通用的Hytrix的配置项能够参考Hystrix-Configuration。
若是是使用RxJava的Observable的响应式编程,想对不一样的请求设置不一样的超时时间,能够直接在Observable的timeout()方法的参数进行设置回调的方法以及超时时间等。
对于一些关键的业务,在请求超时时,为了保证正确的数据返回,须要Gateway能提供重试机制。若是使用SpringCloudFeign,则其内置的Ribbon,会提供的默认的重试配置,能够经过设置spring.cloud.loadbalancer.retry.enabled=false将其关闭。Ribbon提供的重试机制会在请求超时或者socket read timeout触发,除了设置重试,也能够定制重试的时间阀值以及重试次数等。
对于除了使用Feign,也使用Spring RestTemplate的应用,能够经过自定义的RestTemplate,对于返回的ResponseEntity对象进行结果解析,若是请求须要重试(好比某个固定格式的error-code的方式识别重试策略),则经过Interceptor进行请求拦截,以及回调的方式invoke屡次请求。
对于微服务的架构,经过一个独立的API Gateway,能够进行统一的请求转发、合并以及协议转换。能够更灵活得适配不一样客户端的请求数据。并且对于不一样客户端(好比H5和iOS的展现数据不一样)、不一样版本兼容的请求,能够很好地在Gateway进行屏蔽,让微服务更加纯粹。微服务只要关注内部的领域服务的设计,事件的处理。
API gateway还能够对微服务的请求进行必定的容错、服务降级。使用响应式编程来实现API gateway可使线程同步、并发的代码更简洁,更易于维护。在对于微服务的请求能够统一经过FeignClint。代码也会颇有层次。以下图,是一个示例的请求的类层次。
前文主要介绍了微服务的服务发现、服务通讯以及API Gateway。总体的微服务架构的模型初见。在实际的开发、测试以及生产环境中。使用Docker实现微服务,集群的网络环境会更加复杂。微服务架构自己就意味着须要对若干个容器服务进行治理,每一个微服务都应能够独立部署、扩容、监控。下面会继续介绍如何进行Docker微服务的持续集成部署(CI/CD)。
镜像仓库
用Docker来部署微服务,须要将微服务打包成Docker镜像,就如同部署在Web server打包成war文件同样。只不过Docker镜像运行在Docker容器中。
若是是Springboot服务,则会直接将包含Apache Tomcat server的Springboot,以及包含Java运行库的编译后的Java应用打包成Docker镜像。
为了能统一管理打包以及分发(pull/push)镜像。企业通常须要创建本身的镜像私库。实现方式也很简单。能够在服务器上直接部署Docker hub的镜像仓库的容器版Registry2。目前最新的版本是V2。
代码仓库
代码的提交、回滚等管理,也是项目持续集成的一环。通常也是须要创建企业的代码仓库的私库。可使用SVN,GIT等代码版本管理工具。
目前公司使用的是Gitlab,经过Git的Docker镜像安装、部署操做也很便捷。具体步骤能够参考docker gitlab install。为了能快速构建、打包,也可将Git和Registry部署在同一台服务器上。
项目构建
在Springboot项目中,构建工具能够用Maven,或者Gradle。Gradle相比Maven更加灵活,并且Springboot应用自己去配置化的特色,用基于Groovy的Gradle会更加适合,DSL自己也比XML更加简洁高效。
由于Gradle支持自定义task。因此微服务的Dockerfile写好以后,就能够用Gradle的task脚原本进行构建打包成Docker Image。
目前也有一些开源的Gradle构建Docker镜像的工具,好比Transmode-Gradlew插件。其除了能够对子项目(单个微服务)进行构建Docker镜像,也能够支持同时上传镜像到远程镜像仓库。在生产环境中的build机器上,能够经过一个命令直接执行项目的build,Docker Image的打包,以及镜像的push。
容器编排技术
Docker镜像构建以后,由于每一个容器运行着不一样的微服务实例,容器之间也是隔离部署服务的。经过编排技术,可使DevOps轻量化管理容器的部署以及监控,以提升容器管理的效率。
目前一些通用的编排工具好比Ansible、Chef、Puppet,也能够作容器的编排。但他们都不是专门针对容器的编排工具,因此使用时须要本身编写一些脚本,结合Docker的命令。好比Ansible,确实能够实现很便利的集群的容器的部署和管理。目前Ansible针对其团队本身研发的容器技术提供了集成方案:Ansible Container。
集群管理系统将主机做为资源池,根据每一个容器对资源的需求,决定将容器调度到哪一个主机上。
目前,围绕Docker容器的调度、编排,比较成熟的技术有Google的Kubernetes(下文会简写k8s),Mesos结合Marathon管理Docker集群,以及在Docker 1.12.0版本以上官方提供的Docker Swarm。编排技术是容器技术的重点之一。选择一个适合本身团队的容器编排技术也可使运维更高效、更自动化。
Docker Compose
Docker Compose是一个简单的Docker容器的编排工具,经过YAML文件配置须要运行的应用,而后经过compose up命令启动多个服务对应的容器实例。Docker中没有集成Compose,须要另外安装。
Compose能够用于微服务项目的持续集成,但其不适合大型集群的容器管理,大集群中,能够Compose结合Ansible作集群资源管理,以及服务治理。
对于集群中服务器很少的状况,可使用Compose,其使用步骤主要是:
Docker Swarm
在16年,Docker的1.12版本出来以后,使用新版本的Docker,就自带Docker swarm mode了。不须要额外安装任何插件工具。能够看出去年开始Docker团队也开始重视服务编排技术,经过内置Swarm mode,也要抢占一部分服务编排市场。
若是团队开始使用新版本的Docker,能够选择Docker swarm mode来进行集群化的容器调度和管理。Swarm还支持滚动更新、节点间传输层安全加密、负载均衡等。
DockerSwarm的使用示例能够参考以前写的一篇:使用docker-swarm搭建持续集成集群服务。
Kubernetes
Kubernetes是Google开源的容器集群管理系统,使用Go语言实现,其提供应用部署、维护、 扩展机制等功能。目前能够在GCE、vShpere、CoreOS、OpenShift、Azure等平台使用k8s。国内目前Aliyun也提供了基于k8s的服务治理平台。若是是基于物理机、虚拟机搭建的Docker集群的话,也能够直接部署、运行k8s。在微服务的集群环境下,Kubernetes能够很方便管理跨机器的微服务容器实例。
目前k8s基本是公认的最强大开源服务治理技术之一。其主要提供如下功能:
k8s主要经过如下几个重要的组件完成弹性容器集群的管理的:
微服务架构体系中,一个合适的持续集成的工具,能够很好得提高团队的运维、开发效率。目前相似Jenkins也有针对Docker的持续集成的插件,可是仍是存在不少不完善。因此建议仍是选择专门应对Docker容器编排技术的Swarm,k8s,Mesos。或者多个技术结合起来,好比Jenkins作CI+k8s作CD。
Swarm,k8s,Mesos各有各的特性,他们对于容器的持续部署、管理以及监控都提供了支持。Mesos还支持数据中心的管理。Docker swarm mode扩展了现有的Docker API,经过Docker Remote API的调用和扩展,能够调度容器运行到指定的节点。Kubernetes则是目前市场规模最大的编排技术,目前不少大公司也都加入到了k8s家族,k8s应对集群应用的扩展、维护和管理更加灵活,可是负载均衡策略比较粗糙。而Mesos更专一于通用调度,提供了多种调度器。
对于服务编排,仍是要选择最适合本身团队的,若是初期机器数量不多,集群环境不复杂也能够用Ansible+Docker Compose,再加上Gitlab CI来作持续集成。
服务集群的解决方案
企业在实践使用Docker部署、运行微服务应用的时候,不管是一开始就布局微服务架构,或者从传统的单应用架构进行微服务化迁移。都须要可以处理复杂的集群中的服务调度、编排、监控等问题。下面主要为介绍在分布式的服务集群下,如何更安全、高效得使用Docker,以及在架构设计上,须要考虑的方方面面。
负载均衡
这里说的是集群中的负载均衡,若是是纯服务端API的话就是指Gateway API的负载均衡,若是使用了Nginx的话,则是指Nginx的负载均衡。咱们目前使用的是阿里云的负载均衡服务SLB。其中一个主要缘由是能够跟DNS域名服务进行绑定。对于刚开始进行创业的公司来讲,能够经过Web界面来设置负载均衡的权重,比较便于部分发布、测试验证,以及健康检查监控等等。从效率和节约运维成本上来讲都是个比较适合的选择。
若是本身搭建七层负载均衡如使用Nginx或Haproxy的话,也须要保证负责负载均衡的集群也是高可用的,以及提供便捷的集群监控,蓝绿部署等功能。
关系型数据库(RDBMS)
对于微服务来讲,使用的存储技术主要是根据企业的须要。为了节约成本的话,通常都是选用Mysql,在Mysql的引擎选择的话建议选择InnoDB引擎(5.5版本以前默认MyISAM)。InnoDB在处理并发时更高效,其查询性能的差距也能够经过缓存、搜索等方案进行弥补。InnoDB处理数据拷贝、备份的免费方案有binlog,mysqldump。不过要作到自动化的备份恢复、可监控的数据中心仍是须要DBA或者运维团队。相对花费的成本也较高。若是初创企业,也能够考虑依托一些国内外比较大型的云计算平台提供的PaaS服务。
微服务通常按照业务领域进行边界划分,因此微服务最好是一开始就进行分库设计。是否须要进行分表须要根据每一个微服务具体的业务领域的发展以及数据规模进行具体分析。但建议对于比较核心的领域的模型,好比“订单”提早作好分表字段的设计和预留。
KV模型数据库(Key-Value-stores)
Redis是开源的Key-Value结构的数据库。其基于内存,具备高效缓存的性能,同时也支持持久化。Redis主要有两种持久化方式。一种是RDB,经过指定时间间隔生成数据集的时间点快照,从内存写入磁盘进行持久化。RDB方式会引发必定程度的数据丢失,但性能好。另一种是AOF,其写入机制,有点相似InnoDB的binlog,AOF的文件的命令都是以Redis协议格式保存。这两种持久化是能够同时存在的,在Redis重启时,AOF文件会被优先用于恢复数据。由于持久化是可选项,因此也能够禁用Redis持久化。
在实际的场景中,建议保留持久化。好比目前比较流行的解决短信验证码的验证,就可以使用Redis。在微服务架构体系中,也能够用Redis处理一些KV数据结构的场景。轻量级的数据存储方案,也很适合自己强调轻量级方案的微服务思想。
咱们在实践中,是对Redis进行了缓存、持久化,两个功能特征进行分库的。
在集成Springboot项目中会使用到spring-boot-starter-data-redis来进行Redis的数据库链接以及基础配置、以及spring-data-redis提供的丰富的数据APIOperations。
另外,若是是要求高吞吐量的应用,能够考虑用Memcached来专门作简单的KV数据结构的缓存。其比较适合大数据量的读取,但支持的数据结构类型比较单一。
图形数据库(Graph Database)
涉及到社交相关的模型数据的存储,图形数据库是一种相交关系型数据库更高效、更灵活的选择。图形数据库也是Nosql的一种。其和KV不一样,存储的数据主要是数据节点(node),具备指向性的关系(Relationship)以及节点和关系上的属性(Property)。
若是用Java做为微服务的主开发语言,最好选择Neo4j。Neo4j是一种基于Java实现的支持ACID的图形数据库。其提供了丰富的JavaAPI。在性能方面,图形数据库的局部性使遍历的速度很是快,尤为是大规模深度遍历。这个是关系型数据库的多表关联没法企及的。
下图是使用Neo4j的WebUI工具展现的一个官方Getting started数据模型示例。示例中的语句MATCH p=()-[r:DIRECTED]->() RETURN p LIMIT 25是Neo4j提供的查询语言——Cypher。
在项目使用时能够集成SpringData的项目Spring Data Neo4j。以及SpringBootStartersspring-boot-starter-data-neo4j
文档数据库(Document database)
目前应用的比较普遍的开源的面向文档的数据库能够用Mongodb。Mongo具备高可用、高可伸缩性以及灵活的数据结构存储,尤为是对于Json数据结构的存储。比较适合博客、评论等模型的存储。
搜索技术
在开发的过程当中,有时候常常会看到有人写了很长很绕、很难维护的多表查询SQL,或者是各类多表关联的子查询语句。对于某一领域模型,当这种场景多的时候,就该考虑接入一套搜索方案了。不要什么都用SQL去解决,尤为是查询的场景。慢查询语句的问题有时候甚至会拖垮DB,若是DB的监控体系作的不到位,可能问题也很难排查。
Elasticsearch是一个基于Apache Lucene实现的开源的实时分布式搜索和分析引擎。Springboot的项目也提供了集成方式: spring-boot-starter-data-elasticsearch以及spring-data-elasticsearch。
对于搜索集群的搭建,可使用Docker。具体搭建方法能够参考用Docker搭建Elasticsearch集群,对于Springboot项目的集成能够参考在Springboot微服务中集成搜索服务。至今,最新版本的SpringDataElasticsearch已经支持到了5.x版本的ES,能够解决不少2.x版本的痛点了。
若是是小规模的搜索集群,能够用三台低配置的机器,而后用ES的Docker进项进行搭建。也可使用一些商业版的PaaS服务。如何选择仍是要根据团队和业务的规模、场景来看
。
目前除了ES,使用比较普遍的开源搜索引擎还有Solr,Solr也基于Lucene,且专一在文本搜索。而ES的文本搜索确实不如Solr,ES主要专一于对分布式的支持,而且内置了服务发现组件Zen来维护集群状态,相对Solr(须要借助相似Zookeeper实现分布式)部署也更加轻量级。ES除了分析查询,还能够集成日志收集以及作分析处理。
消息队列
消息队列如前篇所述,能够做为很好的微服务解耦通讯方式。在分布式集群的场景下,对于分布式下的最终一致性也能够提供技术基础保障。而且消息队列也能够用来处理流量削锋。
消息队列的对比在此再也不赘述。目前公司使用的是阿里云的ONS。由于使用消息队列仍是考虑用在对高可用以及易于管理、监控上的要求,因此选择了安全可靠的消息队列平台。
安全技术
安全性是作架构须要考虑的基础。互联网的环境复杂,保护好服务的安全,也是对用户的基本承诺。安全技术涉及到的范围比较广,本文选几个常见问题以及经常使用方式来简单介绍下。
服务实例安全
分布式集群自己就是对于服务实例安全的一种保障。一台服务器或者某一个服务实例出现问题的时候,负载均衡能够将请求转发到其余可用的服务实例。但不少企业是自建机房,并且是单机房的,这种布局其实比较危险。由于服务器的备份容灾也得不到完整的保障。最怕的就是数据库也是在同一机房,主备全都在一块儿。不单是安全性得不到很高的保障,日常的运维花销也会比较大。并且须要注意配置防火墙安全策略。
若是能够,尽可能使用一些高可用、高可伸缩的稳定性IaaS平台。
1. 预防网络攻击
目前主要的网络攻击有一下几种:
SQL注入:根据不一样的持久层框架,应对策略不一样。若是使用JPA,则只要遵循JPA的规范,基本不用担忧。
XSS攻击:作好参数的转义处理和校验。具体参考XSS预防
CSRF攻击:作好Http的Header信息的Token、Refer验证。具体参考CSRF预防
DDOS攻击:大流量的DDoS攻击,通常是采用高防IP。也能够接入一些云计算平台的高防IP。
以上只是列举了几种常见的攻击,想要深刻了解的能够多看看REST安全防范表。在网络安全领域,通常很容易被初创企业忽视,若是没有一个运维安全团队,最好使用相似阿里云-云盾之类的产品。省心省成本。
2. 使用安全协议
这个不用多说,不管是对于使用Restful API的微服务通讯,仍是使用的CDN或者使用的DNS服务。涉及到Http协议的,建议都统一使用Https。不管是什么规模的应用,都要防范流量劫持,不然将会给用户带来很很差的使用体验。
3. 鉴权
关于微服务的鉴权前面API Gateway已经有介绍。除了微服务自己以外,咱们使用的一些如Mysql,Redis,Elasticsearch,Eureka等服务,也须要设置好鉴权,而且尽可能经过内网访问。不要对外暴露过多的端口。对于微服务的API Gateway,除了鉴权,最好前端经过Nginx反向代理来请求API层。
基于容器技术的微服务的监控体系面临着更复杂的网络、服务环境。日志采集、监控如何能对微服务减小侵入性、对开发者更透明,一直是不少微服务的DevOps在不断思考和实践的。
1. 微服务日志的采集
微服务的API层的监控,须要从API Gateway到每一个微服务的调用路径的跟踪,采集以及分析。使用Rest API的话,为了对全部请求进行采集,可使用Spring Web的OncePerRequestFilter对全部请求进行拦截,在采集日志的时候,也最好对请求的rt进行记录。
除了记录access,request等信息,还须要对API调用进行请求跟踪。若是单纯记录每一个服务以及Gateway的日志,那么当Gateway Log出现异常的时候,就不知道其具体是微服务的哪一个容器实例出现了问题。若是容器达到必定数量,也不可能排查全部容器以及服务实例的日志。比较简单的解决方式就是对log信息都append一段含有容器信息的、惟一可标识的Trace串。
日志采集以后,还须要对其进行分析。若是使用E.L.K的技术体系,就能够灵活运用Elasticsearch的实时分布式特性。Logstash能够进行日志进行收集、分析,并将数据同步到Elasticsearch。Kibana结合Logstash和ElasticSearch,提供良好的便于日志分析的WebUI,加强日志数据的可视化管理。
对于数据量大的日志的采集,为了提高采集性能,须要使用上文提到的消息队列。优化后的架构以下:
2. 基础服务的调用日志采集
经过对微服务的全部Rest API的日志采集、分析能够监控请求信息。
在服务内部,对于中间件、基础设施(包括Redis,Mysql,Elasticsearch等)调用的性能的日志采集和分析也是必要的。
对于中间件服务的日志采集,咱们目前能够经过动态代理的方式,对于服务调用的如cache、repository(包括搜索和DB)的基础方法,进行拦截及回调日志记录方法。具体的实现方式能够采用字节码生成框架ASM,关于方法的逻辑注入,能够参考以前写的一篇ASM(四) 利用Method 组件动态注入方法逻辑,若是以为ASM代码不太好维护,也可使用相对API友好的Cglib。
架构五要素:
最后,结合架构核心的五要素来回顾下咱们在搭建Docker微服务架构使用的技术体系:
对于服务集群的解决方案,其实不管是微服务架构或者SOA架构,都是比较通用的。只是对于一些中间件集群的搭建,可使用Docker。一句Docker ps就能够很方便查询运行的服务信息,而且升级基础服务也很方便。
对于优秀的集群架构设计的追求是永无止境的。在跟不少创业公司的技术朋友们接触下来,你们都是比较偏向于快速搭建以及开发、发布服务。然而一方面也顾虑微服务的架构会比较复杂,杀鸡用牛刀。可是微服务自己就是一种敏捷模式的优秀实践。这些朋友每每会在业务飞速发展的时候面临一个困扰,就是服务拆分,数据库的分库分表、经过消息去解耦像面条同样的同步代码,想要优化性能可是无从下手的尴尬。
本文主要是对于Docker的微服务实践进行技术方案选型以及介绍。不一样的业务、团队可能会适合不经过的架构体系和技术方案。
做为架构师应该结合公司近期、长期的战略规划,进行长远的布局。最起码基础的架构也是须要能支撑3年发展,期间能够不断引入新的技术并进行服务升级和持续的代码层重构。
也许一个架构师从0开始搭建一整套体系并不须要花费多久时间,最须要其进行的就是不断在团队推行Domain-Driven Design。而且使团队一块儿遵循Clean Code,进行敏捷开发OvO。
在此我向你们推荐一个架构学习交流群。交流学习群号:478030634 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
文章有点长,你们以为做者总结的还能够,你们能够点击下方二维码进行关注。《Java烂猪皮》公众号聊的不只仅是Java技术知识,还有面试等干货,后期还有大量架构干货。你们一块儿关注吧!关注烂猪皮,你会了解的更多..............