微服务实战(四):微服务化之无状态化与容器化

原文连接:微服务化之无状态化与容器化(来源:刘超的通俗云计算)

 

1、为何要作无状态化和容器化

不少应用拆分红微服务,是为了承载高并发,每每一个进程扛不住这么大的量,于是须要拆分红多组进程,每组进程承载特定的工做,根据并发的压力用多个副本公共承担流量。

将一个进程变成多组进程,每组进程多个副本,须要程序的修改支撑这种分布式的架构,若是架构不支持,仅仅在资源层建立多个副本是解决不了问题的。

不少人说,支撑双十一是靠堆机器,谁不会?真正经历过的会以为,可以靠堆机器堆出来的,都不是问题,怕的是机器堆上去了,由于架构的问题,并发量仍然上不去。

阻碍单体架构变为分布式架构的关键点就在于状态的处理。若是状态所有保存在本地,不管是本地的内存,仍是本地的硬盘,都会给架构的横向扩展带来瓶颈。

状态分为分发、处理、存储几个过程,若是对于一个用户的全部的信息都保存在一个进程中,则从分发阶段,就必须将这个用户分发到这个进程,不然没法对这个用户进行处理,然而当一个进程压力很大的时候,根本没法扩容,新启动的进程根本没法处理那些保存在原来进程的用户的数据,不能分担压力。

因此要讲整个架构分红两个部分,无状态部分和有状态部分,而业务逻辑的部分每每做为无状态的部分,而将状态保存在有状态的中间件中,如缓存、数据库、对象存储、大数据平台、消息队列等。

这样无状态的部分能够很容易的横向扩展,在用户分发的时候,能够很容易分发到新的进程进行处理,而状态保存到后端。然后端的中间件是有状态的,这些中间件设计之初,就考虑了扩容的时候,状态的迁移,复制,同步等机制,不用业务层关心。数据库

1.jpg


如图所示,将架构分为两层,无状态和有状态。

容器和微服务是双胞胎,由于微服务会将单体应用拆分红不少小的应用,于是运维和持续集成会工做量变大,而容器技术能很好的解决这个问题。然而在微服务化以前,建议先进行容器化,在容器化以前,建议先无状态化,当整个流程容器化了,之后的微服务拆分才会水到渠成。后端

2、无状态化的几个要点

前面说对于任何状态,须要考虑它的分发、处理、存储。缓存

2.jpg


对于数据的存储,主要包含几类数据:网络

  • 会话数据等,主要保存在内存中。
  • 结构化数据,主要是业务逻辑相关
  • 文件图片数据,比较大,每每经过CDN下发
  • 非结构化数据,例如文本、评论等


若是这些数据都保存在本地,和业务逻辑耦合在一块儿,就须要在数据分发的时候,将同一个用户分到同一个进程,这样就会影响架构的横向扩展。数据结构

3.jpg


对于保存在内存里的数据,例如Session,能够放在外部统一的缓存中。架构

4.jpg


对于业务相关的数据,则应该保存在统一的数据库中,若是性能扛不住,能够进行读写分离,如文章《微服务化的数据库设计与读写分离》。

若是性能仍是抗住不,则可使用分布式数据库。并发

5.jpg


对于文件,照片之类的数据,应该存放在统一的对象存储里面,经过CDN进行预加载,如文章《微服务的接入层设计与动静资源隔离》。

对于非结构化数据,能够存在在统一的搜索引擎里面,例如ElasticSearch。

若是全部的数据都放在外部的统一存储上,则应用就成了仅仅包含业务逻辑的无状态应用,能够进行平滑的横向扩展。

而全部的外部统一存储,不管是缓存、数据库、对象存储、搜索引擎、都有自身的分布式横向扩展机制。负载均衡

6.jpg


在实行了无状态化以后,就能够将有状态的集群集中到一块儿,进行跨机房的部署,实现跨机房的高可用性。而无状态的部分能够经过Dubbo自动发现,当进程挂掉的时候,自动重启,自动修复,也能够进行多机房的部署。less

3、幂等的接口设计

可是还有一个遗留的问题,就是已经分发,正在处理,可是还没有存储的数据,确定会在内存中有一些,在进程重启的时候,数据仍是会丢一些的,那这部分数据怎么办呢?

这部分就须要经过重试进行解决,当本次调用过程当中失败以后,前序的进程会进行重试,例如Dubbo就有重试机制。既然重试,就须要接口是幂等的,也即同一次交易,调用两次转帐1元,不能最终转走2元。

接口分为查询、插入、更新、删除等操做。

对于查询接口来说,自己就是幂等的,不用作特殊的判断。

对于插入接口来说,若是每个数据都有惟一的主键,也能保证插入的惟一性,一旦不惟一,则会报错。

对于更新操做来说,则比较复杂,分几种状况。

一种状况是同一个接口,先后调用屡次的幂等性。另外一种状况是同一个接口,并发环境下调用屡次的正确性。

为了保持幂等性,每每要有一个幂等表,经过传入幂等参数匹配幂等表中ID的方式,保证每一个操做只被执行一次,并且在实行最终一致性的时候,能够经过不断重试,保证最终接口调用的成功。

对于并发条件下,谁先调用,谁后调用,须要经过分布式锁如Redis,ZooKeeper等来实现同一个时刻只有一个请求被执行,如何保证屡次执行结果仍然一致呢?则每每须要经过状态机,每一个状态只流转一次。还有就是乐观锁,也即分布式的CAS操做,将状态的判断、更新整合在一条语句中,能够保证状态流转的原子性。乐观锁并不保证更新必定成功,须要有对应的机制来应对更新失败。运维

4、容器的技术原理

7.jpg


无状态化以后,实行容器化就十分顺畅了,容器的不可改变基础设施,以及容器基于容器平台的挂掉自动重启,自动修复,都由于无状态顺畅无比。

关键技术一:Dockerfile

例以下面的Dockerfile。

8.png


为何必定要用Dockerfile,而不建议经过保存镜像的方式来生成镜像呢?

这样才能实现环境配置和环境部署代码化 ,将Dockerfile维护在Git里面,有版本控制,而且经过自动化的build的过程来生成镜像,而镜像中就是环境的配置和环境的部署,要修改环境应先经过Git上面修改Dockerfile的方式进行,这就是IaC。

关键技术二:容器镜像

经过Dockerfile能够生成容器镜像,容器的镜像是分层保存,对于Dockerfile中的每个语句,生成一层容器镜像,如此叠加,每一层都有UUID。

容器镜像能够打一个版本号,放入统一的镜像仓库。

9.jpg

 

关键技术三:容器运行时

10.jpg


容器运行时,是将容器镜像之上加一层可写入层,为容器运行时所看到的文件系统。

容器运行时使用了两种隔离的技术。

一种是看起来是隔离的技术,称为namespace,也即每一个namespace中的应用看到的是不一样的IP地址、用户空间、程号等。

11.jpg


另外一种是用起来是隔离的技术,称为CGroup,也即明明整台机器有不少的CPU、内存,而一个应用只能用其中的一部分。

CGroup

12.jpg

 

5、容器化的本质和容器化最佳实践

不少人会将容器当成虚拟机来用,这是很是不正确的,并且容器所作的事情虚拟机都能作到。

若是部署的是一个传统的应用,这个应用启动速度慢,进程数量少,基本不更新,那么虚拟机彻底可以知足需求。

  • 应用启动慢:应用启动15分钟,容器自己秒级,虚拟机不少平台能优化到十几秒,二者几乎看不出差异。
  • 内存占用大:动不动32G,64G内存,一台机器跑不了几个。
  • 基本不更新:半年更新一次,虚拟机镜像照样可以升级和回滚。
  • 应用有状态:停机会丢数据,若是不知道丢了啥,就算秒级启动有啥用,照样恢复不了,并且还有可能由于丢数据,在没有修复的状况下,盲目重启带来数据混乱。
  • 进程数量少:两三个进程相互配置一下,不用服务发现,配置不麻烦。


若是是一个传统应用,根本没有必要花费精去容器化,由于白花了力气,享受不到好处。

13.png


什么状况下,才应该考虑作一些改变呢?

传统业务忽然被互联网业务冲击了,应用总是变,三天两头要更新,并且流量增大了,原来支付系统是取钱刷卡的,如今要互联网支付了,流量扩大了N倍。

没办法,一个字:拆!

拆开了,每一个子模块独自变化,少相互影响。

拆开了,原来一个进程扛流量,如今多个进程一块儿扛。

因此称为微服务。

微服务场景下,进程多,更新快,因而出现100个进程,天天一个镜像。

容器乐了,每一个容器镜像小,没啥问题,虚拟机哭了,由于虚拟机每一个镜像太大了。

因此微服务场景下,能够开始考虑用容器了。

14.jpg


虚拟机怒了,老子不用容器了,微服务拆分以后,用Ansible自动部署是同样的。

这样说从技术角度来说没有任何问题。

然而问题是从组织角度出现的。

通常的公司,开发会比运维多的多,开发写完代码就不用管了,环境的部署彻底是运维负责,运维为了自动化,写Ansible脚原本解决问题。

然而这么多进程,又拆又合并的,更新这么快,配置老是变,Ansible脚本也要常改,天天都上线,不得累死运维。

因此这如此大的工做量状况下,运维很容易出错,哪怕经过自动化脚本。

这个时候,容器就能够做为一个很是好的工具运用起来。

除了容器从技术角度,可以使得大部分的内部配置能够放在镜像里面以外,更重要的是从流程角度,将环境配置这件事情,往前推了,推到了开发这里,要求开发完毕以后,就须要考虑环境部署的问题,而不能当甩手掌柜。

这样作的好处就是,虽然进程多,配置变化多,更新频繁,可是对于某个模块的开发团队来说,这个量是很小的,由于5-10我的专门维护这个模块的配置和更新,不容易出错。

若是这些工做量全交给少数的运维团队,不但信息传递会使得环境配置不一致,部署量会大很是多。

容器是一个很是好的工具,就是让每一个开发仅仅多作5%的工做,就可以节约运维200%的工做,而且不容易出错。

然而原本原来运维该作的事情开发作了,开发的老大愿意么?开发的老大会投诉运维的老大么?

这就不是技术问题了,其实这就是DevOps,DevOps不是不区分开发和运维,而是公司从组织到流程,可以打通,看如何合做,边界如何划分,对系统的稳定性更有好处。

因此微服务,DevOps,容器是相辅相成,不可分割的。

不是微服务,根本不须要容器,虚拟机就能搞定,不须要DevOps,一年部署一次,开发和运维沟通再慢都能搞定。

因此,容器的本质是基于镜像的跨环境迁移。

镜像是容器的根本性发明,是封装和运行的标准,其余什么Namespace、CGroup,早就有了。这是技术方面。

在流程方面,镜像是DevOps的良好工具。

容器是为了跨环境迁移的,第一种迁移的场景是开发,测试,生产环境之间的迁移。若是不须要迁移,或者迁移不频繁,虚拟机镜像也行,可是老是要迁移,带着几百G的虚拟机镜像,太大了。

第二种迁移的场景是跨云迁移,跨公有云,跨Region,跨两个OpenStack的虚拟机迁移都是很是麻烦,甚至不可能的,由于公有云不提供虚拟机镜像的下载和上传功能,并且虚拟机镜像太大了,一传传一天。

15.jpg


因此如图为将容器融入持续集成的过程当中,造成DevOps的流程。

经过这一章,再加上第一章《微服务化的基石——持续集成》就构成了微服务,DevOps,容器化三位一体的统一。

16.jpg


对于容器镜像,咱们应该充分利用容器镜像分层的优点,将容器镜像分层构建,在最里面的OS和系统工具层,由运维来构建,中间层的JDK和运行环境,由核心开发人员构建,而最外层的Dockerfile就会很是简单,只要将jar或者war放到指定位置就能够了。

这样能够下降Dockerfile和容器化的门槛,促进DevOps的进度。

6、容器平台的最佳实践

容器化好了,应该交给容器平台进行管理,从而实现对于容器的自动化管理和编排。

17.png


例如一个应用包含四个服务A、B、C、D,她们相互引用,相互依赖,若是使用了容器平台,则服务之间的服务发现就能够经过服务名进行了。例如A服务调用B服务,不须要知道B服务的IP地址,只须要在配置文件里面写入B服务服务名就能够了。若是中间的节点宕机了,容器平台会自动将上面的服务在另外的机器上启动起来。容器启动以后,容器的IP地址就变了,可是不用担忧,容器平台会自动将服务名B和新的IP地址映射好,A服务并没有感知。这个过程叫作自修复和自发现。若是服务B遭遇了性能瓶颈,三个B服务才能支撑一个A服务,也不须要特殊配置,只须要将服务B的数量设置为3,A仍是只须要访问服务B,容器平台会自动选择其中一个进行访问,这个过程称为弹性扩展和负载均衡。

当容器平台规模不是很大的时候,Docker Swarm Mode仍是比较好用的:

  • 集群的维护不须要ZooKeeper,不须要Etcd,本身内置
  • 命令行和Docker同样的,用起来顺手
  • 服务发现和DNS是内置的
  • Docker Overlay网络是内置的


总之Docker帮你料理好了一切,你不用太关心细节,很容易就可以将集群运行起来。

并且能够经过Docker命令,像在一台机器上使用容器同样使用集群上的容器,能够随时将容器当虚拟机来使用,这样对于中等规模集群,以及运维人员仍是比较友好的。

固然内置的太多了也有缺点,就是很差定制化,很差Debug,很差干预。当你发现有一部分性能不行的时候,你须要改整个代码,所有从新编译,当社区更新了,合并分支是很头疼的事情。当出现了问题的时候,因为Manager大包大揽干了不少活,不知道哪一步出错了,反正就是没有返回,停在那里,若是重启整个Manager,影响面又很大。

18.jpg


当规模比较大,应用比较复杂的时候,则推荐Kubernetes。

Kubernetes模块划分得更细,模块比较多,并且模块之间彻底的松耦合,能够很是方便地进行定制化。

19.jpg

并且Kubernetes的数据结构的设计层次比较细,很是符合微服务的设计思想。例如从容器->Pods->Deployment->Service,原本简单运行一个容器,被封装为这么多的层次,每次层有本身的做用,每一层均可以拆分和组合,这样带来一个很大的缺点,就是学习门槛高,为了简单运行一个容器,须要先学习一大堆的概念和编排规则。可是当须要部署的业务愈来愈复杂时,场景愈来愈多时,你会发现Kubernetes这种细粒度设计的优雅,使得你可以根据本身的须要灵活的组合,而不会由于某个组件被封装好了,从而致使很难定制。例如对于Service来说,除了提供内部服务之间的发现和相互访问外,还灵活设计了headless service,这使得不少游戏须要有状态的保持长链接有了很好的方式,另外访问外部服务时,例如数据库、缓存、headless service至关于一个DNS,使得配置外部服务简单不少。不少配置复杂的大型应用,更复杂的不在于服务之间的相互配置,能够有Spring Cloud或者Dubbo去解决,复杂的反而是外部服务的配置,不一样的环境依赖不一样的外部应用,External Name这个提供和很好的机制。包括统一的监控cAdvisor,统一的配置ConfigMap,都是构建一个微服务所必须的。然而Kubernetes当前也有一个瓶颈——集群规模还不是多么大,官方说法是几千个节点,因此超大规模的集群,仍是须要有很强的IT能力进行定制化。可是对于中等规模的集群也足够了。并且Kubernetes社区的热度,可使得使用开源Kubernetes的公司可以很快地找到帮助,等待到新功能的开发和Bug的解决。

相关文章
相关标签/搜索