【十】分布式微服务架构体系详解——架构设计理念

前言

前面几章的内容从分布式架构的一些常见场景出发,介绍了分布式的各种问题和解决方案以及目前一些比较成熟的技术实现。在实施微服务架构时,我们更多的是使用这些成熟的技术。作为一个架构师或者想在技术架构领域深耕的开发人员,对于分布式系统相关的技术不仅仅要会用。在做技术选型及方案时,更需要对这些技术的实现思路、算法以及优点和限制进行了解。本节内容作为整个系列的收尾,会把微服务架构结合Docker的一些技术选型进行介绍,包括服务的持续集成、容器编排以及日志采集和安全技术做个简单的引申,希望能给各位读者一些参考。微服务架构的概念本身不是什么新颖或万能的技术,还是要在实践中不断深入了解和摸索尝试。
如果读者们读到这一篇,应该还记得本系列文章还有示例的代码工程 microservice_coffeeshop,以及gateway_coffeeshop。示例工程代码会在8.10日更新完毕。

微服务架构的技术选型

05章 服务发现和服务通信 中介绍了基于Docker技术的一些服务发现方案。示例的工程是基于 Eureka 来做服务发现的。本篇主要围绕使用Docker容器实现微服务实例的构建、部署的相关技术选型介绍。

服务的持续集成部署

微服务本身和敏捷开发流程可以很好地融合。目前也有很多技术方案可以做微服务架构的 CI/CD (Continuous Integration/Continuous Delivery)。一个灵活高效的 CI/CD 也是符合DevOps 理念的。下面先简单介绍一些基础的技术设施,为需要搭建整个架构体系的DevOps提供一些方案和思路。

Docker镜像仓库

用 Docker 部署微服务,需要将运行着微服务实例的应用代码打包成Docker镜像,就如同部署在Web server的代码打包 war 文件一样。只不过Docker镜像运行在Docker容器中,并且Docker容器是相对隔离和独立的环境。对于企业服务来说,为了能统一管理打包以及分发(pull/push)镜像,一般需要搭建自己的镜像私库。或者使用一些云服务平台提供的CaaS(Containers as a Service)。
自己搭建私库只需依赖基础服务器,或者使用IaaS平台。一般负责镜像托管的一组服务器,需要先分别安装Docker环境,然后直接部署Dockerhub的镜像仓库 Registry2。目前最新的版本是V2。Dockerhub 的镜像资源,一般 pull 比较慢(服务分布在国外),在国内可以使用一些平台提供的镜像加速器。

代码仓库

代码托管也是项目持续集成的基础一环。企业的代码仓库的私库建立可以使用 SVN、Gitlab 等代码版本管理工具。

Gitlab 除了可以代码托管,还有轻量级的CI/CD 工具。通过[Git的Docker镜像](docker-hub gitlab)安装、部署操作也很便捷。具体安装步骤可以参考docker gitlab install。为了能快速构建、打包,也可将同一微服务的 Git 和 Registry 部署在同一台服务器,作为基础的SCM机器组。

如果代码托管基于Gitlab,那么Gitlab 的CI/CD 工具可以有效提升敏捷开发效率。Gitlab CI 工具的核心组件Pipelines包含了编译、测试、部署等步骤,只要在创建好 gitlab-ci.yml 文件,配置好Runner即可。并且Gitlab本身也有可视化的控制台可以管理集成的过程。除了Gitlab CI,Jenkins 也是一个被广泛使用的持续集成方案,Jenkins也有针对Docker的持续集成插件,但是还存在一些不完善。

项目构建

在Springboot项目中,构建工具可以用Maven,或者Gradle。Gradle相比Maven更加灵活。Springboot工程的整体风格也是去配置化的,用基于Groovy的Gradle会更加适合,DSL本身也比XML更加简洁高效。

因为Gradle支持自定义task。所以微服务的Dockerfile写好之后,就可以用Gradle的task脚本来进行构建、打包成Docker Image。本篇的示例工程代码是基于Sprigboot的,在checkout下来代码之后,可以通过以下一行简单的代码就可以打包好一个docker镜像,并push到指定的Registry镜像仓库。示例是上传到本地仓库,在企业应用中,一般需要 Tag、Push 到企业的私有仓库域名下。

$./gradlew buildDocker  

...
BUILD SUCCESSFUL

Total time: 2 mins 4.374 secs
...

$ docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
localhost:5000/user                1.0.0               42c4cb474806        1 second ago        223MB
localhost:5000/trade               1.0.0               234c73515f87        17 seconds ago      223MB
localhost:5000/item                1.0.0               977c63029e2e        41 seconds ago      223MB

而gradle的task代码也很简单:

task buildDocker(type: Docker, dependsOn: build) {

    tagVersion = "1.0.0"
    applicationName = "item"
    tag = "localhost:5000/${applicationName}"
    push = true
    dockerfile = ('../docker/Dockerfile')
    doFirst {
        copy {
            from jar
            rename { 'app.jar' }
            into stageDir
        }
    }
}


在创建工程时,需要引入classpath(‘se.transmode.gradle:gradle-docker:1.2’) 依赖。示例工程中使用的是开源的构建工具:Transmode-Gradlew插件。其还支持对子项目(单个微服务)进行独立构建Docker镜像。运行命令如下:

$  ./gradlew service-item:buildDocker 

在生产环境中,Gitlab的机器最好和Docker私有仓库,以及用来二方、三方lib管理的Nexus部署在同一内网环境,这样构建、打包、上传镜像的速度会很快。Nexus也可以用Docker进行部署,这里细节不再展开,目前 Nexus 的最新版本是Nexus3

容器编排技术

代码托管、镜像私库、持续集成的问题解决之后,还要处理集群的容器管理,包括Docker容器的部署、升级、回滚;集群的负载均衡、扩容缩容等。
Docker镜像构建好之后,因为每个容器运行着不同的微服务实例,容器之间也是隔离部署的。通过容器编排技术,可以使DevOps 高效地管理容器的部署、监控等。

目前一些通用的编排工具比如Ansible、Chef、Puppet,也可以做容器的编排。但他们都不是专门针对Docker容器的编排工具,所以使用时需要自己编写一些shell脚本,结合Docker命令。Ansible 针对自己研发的容器 Ansible Container 也提供了集成、部署以及容器管理方案。

围绕Docker容器的调度、编排,比较成熟的技术有Google的Kubernetes(下文会简写k8s) ; Apache Mesos + Marathon 管理Docker集群 ; Docker 1.12.0以上的官方版本内置的Docker Swarm (目前Swarm处于很尴尬的地位,之前写过一篇介绍swarm的博客 基于docker-swarm搭建持续集成集群服务,感兴趣的读者可以简单看看,不是很建议在企业集群中引入swarm)。编排技术是容器技术的重点之一。选择一个适合自己团队的容器编排技术也可以使运维更高效、自动化。

Kubernetes

Kubernetes是Google开源的容器集群管理系统,使用Go语言实现,其提供了Docker容器的部署、维护、 扩展等功能。目前可以在GCE、vShpere、CoreOS、OpenShift、Azure等平台使用k8s。国内目前Aliyun也提供了基于k8s的服务治理平台。如果企业自己基于物理机、虚拟机搭建 Docker 集群的话,也可以直接部署、运行k8s。在微服务的集群环境下,Kubernetes可以很方便管理跨机器的微服务容器实例。

目前k8s基本是公认的最强大开源服务治理技术之一。其主要提供以下功能:

  1. 自动化对基于Docker对服务实例进行部署和复制
  2. 以集群的方式运行,可以管理跨机器的容器,以及滚动升级、存储编排。
  3. 内置了基于Docker的服务发现和负载均衡模块
  4. K8s提供了强大的自我修复机制,会对崩溃的容器进行替换(对用户,甚至开发团队都无感知),并可随时扩容、缩容。让容器管理更加弹性化。

简单介绍下 k8s 的几个重要组件:

  1. Pod 是Kubernetes的最小的管理元素,一个或多个容器运行在pod中。pod的生命周期很短暂,会随着调度失败,节点崩溃,或者其他资源回收时消亡。
  2. Label 是key/value存储结构的,可以关联pod,主要用来标记pod,给服务分组。微服务之间通过label选择器(Selectors)来识别Pod。
  3. Replication Controller 是k8s Master节点的核心组件。用来确保任何时候Kubernetes集群中有指定数量的pod副本(replicas)运行。提供了自我修复机制的功能,并且对缩容扩容、滚动升级也很有用。
  4. Service 是对一组Pod的策略的抽象。也是k8s管理的基本元素之一。Service通过Label识别一组Pod。创建时也会创建一个本地集群的DNS(存储Service对应的Pod的服务地址)。所以客户端的请求,会先通过本地集群的 DNS,来获取一组当前可用的Pods的ip地址。之后通过每个Node中运行的kube-proxy将请求转发给其中一个Pod。这层负载均衡是透明的,但是目前的k8s的负载均衡策略还不是很完善,默认是随机策略的路由方式。

Swarm,k8s,Mesos各有各的特性,他们对于容器的持续部署、管理以及监控都提供了支持。Mesos还支持数据中心的管理。Docker swarm mode扩展了现有的Docker API,通过Docker Remote API的调用和扩展,可以调度容器运行到指定的节点。Kubernetes是目前市场规模最大的容器编排服务,目前很多大公司也都加入到了k8s家族,k8s应对集群应用的扩展、维护和管理更加灵活,但是负载均衡策略比较粗糙。而Mesos更专注于通用调度,提供了多种调度器。对于容器编排,还是要选择最适合自己团队的,如果初期机器数量很少,集群环境不复杂,可以用Ansible+Docker Compose,再加上Gitlab CI来做持续集成,节省成本、快速上线。

日志采集

基于容器技术的微服务的监控体系面临着更复杂的网络、服务环境。日志采集、监控如何能对微服务减少侵入性、对开发者更透明,也是很多微服务的 DevOps 需要不断思考和实践的。

1. 微服务日志的采集

微服务的API层的监控,需要从API-Gateway 的请求开始,一直到每个微服务的调用路径的跟踪、采集以及分析。使用Rest API的话,为了对所有请求进行采集,可以使用Spring Web的OncePerRequestFilter对所有请求进行拦截,在采集日志的时候,也最好对请求的RT(response time)进行记录。

服务日志除了要记录access,request等信息,还需要对API调用链路进行跟踪。如果单纯记录每个服务以及Gateway的日志,那么当Gateway Log出现异常的时候,就不知道其具体是微服务的哪个容器实例出现了问题。如果容器达到一定数量,也不可能排查所有容器以及服务实例的日志。比较简单的解决方式就是对日志信息都append一段含有容器信息(Container Id)的、唯一可标识的Trace串。

日志采集之后,还需要对其进行分析。目前比较成熟的在线实时日志分析,可以使用E.L.K技术体系,ELK中的 E 是指Elasticsearch,基于Lucene实现的高性能可扩展的搜索引擎,使用Docker也可以搭建 Elasticsearch 集群,详细的搭建步骤可以阅读下这篇文章:用Docker搭建Elasticsearch集群 。如果应用架构本身使用了Elasticsearch作为搜索集群,搭建 ELK 会更加容易。Logstash 的作用是进行日志收集、分析,并将数据同步到Elasticsearch。Kibana 可以为ElasticSearch提供可视化平台,增强日志数据的可视化管理。

对于数据量大的日志的采集,为了提升采集性能,一般还需要在ELK的基础上增加一个高性能的消息队列。优化后的日志采集架构如下(Kafka作为消息队列为例,也可以使用其他消息队列):

ELK + Kafka 日志分析采集

2. 底层服务的日志采集

除了要对 Gateway 以及微服务实例的所有Rest API请求的日志进行采集。也有必要对于底层依赖的存储服务、中间件(包括Redis、Mysql、Elasticsearch、MQ等)的调用日志进行采集和分析。

对于中间件服务的日志采集,一般需要埋点在应用代码中。更好的方式是通过动态代理实现。对于服务调用的如cache层、repository层(包括搜索和DB)的基础方法,进行拦截及回调日志记录。具体的实现方式可以采用字节码生成框架ASM,关于方法的逻辑注入,可以参考之前写的一篇ASM(四) 利用Method 组件动态注入方法逻辑,如果觉得ASM代码不太好维护,也可以使用相对API友好的Cglib。性能稍差一些但是实现更快的方式还可以用AspectJ框架。

安全技术

安全性是架构的基础。互联网的环境复杂,保护好服务的安全,也是对用户的基本承诺。安全技术涉及到的范围比较广,本文选几个常用方式做简单介绍。

服务实例安全

分布式集群本身就是对于服务实例安全的一种保障。一台服务器或者某一个服务实例出现问题的时候,负载均衡可以将请求转发到其他可用的服务实例。但很多企业是自建机房,而且是单机房的,这种布局其实比较危险。因为服务器的备份容灾也得不到完整的保障。对于底层依赖的存储服务,尤其是数据库,尽量保证可以有多数据中心,做好网络分区。所有的机器需要注意配置防火墙安全策略。
如果可以,尽量使用一些高可用、高可伸缩的稳定性 IaaS、PaaS 平台。

网络安全

1. 预防网络攻击

目前主要的网络攻击有一下几种:

  • SQL注入:根据不同的持久层框架,应对策略不同。如果使用JPA,则只要遵循JPA的规范,基本不用担心。
  • XSS攻击:做好参数的转义处理和校验。具体参考XSS预防
  • CSRF攻击:做好Http的Header信息的Token、Refer验证。具体参考CSRF预防
  • DDOS攻击:大流量的DDoS攻击,一般是采用高防IP。也可以接入一些云计算平台的高防IP。

以上只是列举了几种常见的攻击,想要深入了解的可以多看看REST安全防范表。安全领域,一般很容易被初创企业忽视,如果没有专门的运维团队,需要开发工程师们有能力处理好 RESTful API的安全。

2. 使用安全协议

这个不用多说,无论是对于使用RESTful API的微服务通信,还是使用的CDN服务、DNS服务。涉及到Http协议的,建议都统一使用Https。无论是什么规模的应用,都要防范流量劫持,否则将会给用户带来很不好的使用体验。

3. 鉴权

微服务架构中,API-Gateway 作为系统的统一入口,基于微服务的所有鉴权,都可以围绕Gateway容器去做。在Springboot应用中,基础的授权可以使用spring-boot-starter-security以及Spring Security(Spring Security也可以集成在Spring MVC项目中)。
Spring Security主要通过 AOP 对资源请求进行拦截,其内部维护了一个不同角色(Roles)的Filter Chain。因为微服务都是通过Gateway请求的,所以微服务的@Secured可以根据Gateway中不同的资源的角色级别进行设置。

Spring Security提供了基础的角色的校验接口规范。但客户端请求的Token信息的加密、存储以及验证,需要应用自己完成。对于Token加密信息的存储可以使用Redis。这里再多提一点,为了保证一些加密信息的可变性,最好在一开始设计Token模块的时候就考虑到支持多个版本密钥,以防止万一内部密钥被泄露可以及时更换。至于加密算法,以及具体的实现在此就不再展开。

除了微服务本身之外,我们使用的一些如 Mysql、Redis、Elasticsearch、服务发现等基础服务,也需要设置好鉴权,并且尽量通过内网访问。不要对外暴露过多的端口。前端请求最好通过Nginx反向代理再请求到 API-Gateway 服务。

架构要素分析

无论我们采用微服务或者SOA架构,也无论使用什么服务来实现集群的管理,在分布式系统中,没有一蹴而就的完美的架构。并且每位在分布式环境从事开发的工程师们也需要了解到,时刻存在着不可靠的网络和随时可能挂掉的节点。在做技术选型或者拟定技术方案时,可以多想想架构的几个要素,在考量时也不能过于追求完美,只有适合的才是最好的。

最后,结合架构核心的五要素来列举一些搭建Docker微服务集群时可以使用的技术体系:

  1. 高性能
    • 尽量使用异步的方式,比如完成某业务逻辑后,上传资源或者发送短信,可以通过消息队列如Kafka、RocketMQ、ActiveMQ等替代同步调用。
    • 在Gateway中使用类似RxJava框架,可异步并发请求多个(数据无关联和依赖的)微服务的API。
    • 对于读高频并且实时性依赖不高的请求可以使用分布式缓存、本地缓存、Http的Etag缓存。
    • 对于查询条件复杂,排序场景多的情况,可以引进分布式搜索引擎技术,如Elasticsearch、Solr。
    • 图片、样式等静态资源可以使用CDN加速。
  2. 可用性
    • 使用高可用的服务发现技术,如 Consul、Eureka、Etcd。
    • 在Gateway层做好熔断处理,可以使用Hytrix熔断器或者通过RxJava的onErrorReturn自定义ErrorHandler。
    • 适当针对特殊场景做服务降级。
    • 在Gateway以及微服务之间做好超时机制、重试机制、并对POST、PUT等提交数据请求做好幂等处理。
    • 降低对线性一致性的追求,更多使用最终一致性。
  3. 伸缩性
    • 服务器集群的伸缩,主要是基于硬件和服务器层面可以使用一些弹性云平台,方便进行水平和垂直扩展。
    • 容器层面,可以使用容器编排技术Kubernetes。
    • 服务实例本身,针对不同微服务的特性,在打包docker镜像时,对服务依赖资源做好分配(比如JVM层面的堆、栈空间,GC策略等等)。
    • 存储方面,可以做数据库的数据复制、数据分区(第3,4章内容有介绍)。
    • 使用一些支持线性伸缩的 NoSQL技术如Redis,MongoDB等。
  4. 扩展性
    • 微服务本身就是一种扩展性架构
  5. 安全性
    • 鉴权框架的引入,如:SpringSecurity、OAuth等。
    • 日志采集、监控
    • Https协议、HTTP/2.0
    • 高防IP 、防火墙策略等
    • 使用Nginx做反向代理

小结

本系列文章的前面内容重点介绍了分布式中的一些常见场景的问题以及解决方案。通过对一些原理和算法的介绍,帮助读者可以更好地理解分布式架构相关的技术原理。本篇文章主要介绍了微服务架构的技术选型。微服务的开源社区目前也越来越成熟,实施微服务的过程中也需要做更多的调研。对企业的服务架构来说,技术实施以及架构要素的权衡是一方面,更重要的是结合业务和团队风格。适合的才是最好的。
对于文章内容,或者示例代码工程如有疑问,都可留言探讨,也可以在 Github 提 issues .

猜你喜欢

转载自blog.csdn.net/lijingyao8206/article/details/107630523