微服务概览

miloyang
0 评论
/ /
355 阅读
/
7263 字
15 2025-01

背景

很多时候都这样,早期规模比较小,肯定是怎么简单怎么开发,那毫无争议,就是单体架构,比如下图: c7c33defaamag

一个传统的web应用,就是从浏览器到Apache,然后再到所谓巨石的一个war包,里面包含了全家桶,所有的应用都在里面,然后再简单的访问一下数据库。相对来说这比较容易开发、部署、扩展等等,因为你的业务本身体量不是很复杂的。但初具规模的时候,就会出现一个巨石问题题,比如明明只想修改A功能,但是不知道为什么会影响到了BCD功能其他的模块,这样也可能收到影响,这其实是非常痛苦的。再到后面功能越来越多,代码量也会越来越大,这对于IDE也是非常卡顿的,包括调试、部署会变得越来越复杂和混乱的。尽管我们在巨石架构里面有一个模块化的逻辑,但最终会打包成为一个整体的应用去部署的。
而且有一个热点的功能,需要扩容的情况下面需要扩所有的功能,这样单体架构带来的收益就不会那么的高。

尽快也是模块化逻辑,但是最终它还是会打包并部署为单体应用,其中主要问题就是这个应用太复杂,以至于任何单体开发者都不可能搞懂它,应用无法扩展,可靠性很低,最终,敏捷性开发和部署变得无法完成。

所以我们准备应对的思路就是化繁为简,分而治之

其实微服务也不是一挥而就立马就出来的,我们之前也经常谈论过一个叫做SOA(面相服务的架构模式)的,那它和微服务又是什么样的关系呢?其实你可以把微服务想成是SOA的一种最佳实践。比如下图,早期就是一个全家桶,一个单一的数据库,没有划分那么清楚,这样交给一个团队进行维护,大家要考虑非常清楚的模块划分。右侧其实就是从单体的一个模块逐渐引进到分布式的微服务的模型,可以看出它从一个拆成四个,每个业务都拥有自己的数据库,这也可以看出一个特点,就是每个业务都有自己独立性的一些东西。

dantijiagou17369107609881

所以可以看出有四个特征:

  • 小即是美:小的服务代码总量比较小,bug肯定也少,也比较容易测试,因为服务和服务是通过api或者其他进行交互的,如果有bug也当前服务里面排查,容易收敛bug,所以比较容易维护,也更容易不断迭代完善的精致,从而达到美妙。因为一个服务可以不断的平滑发版,只要向下兼容,架构就会不断的完善。如果你是一个整体的架构,如果高频发版,修改一个小功能就上线,其实对整个网站的可靠性是有风险的,因为对于运维来说是要控制变更,但研发来说是功能尽快上线,本质是存在冲突的。而且发版频繁带来的事故概率也会变高,所以单体架构一般有固定时间交付日,我们之前都是双周三等等。但这一次发版同时堆积了不同业务不同服务的变更,这一次发版的变更其实是非常大的,一旦上线出问题,那它的影响面巨大的,而且定位也非常困难,只能快速回滚。所以我们之前每次发版都搞到深夜,如履薄冰。但拆分微服务之后,每个服务都是独立的且向下兼容的,迭代这个服务的时候,我只要保证我的api服务是ok的,契约也是ok的,所以迭代当前业务服务的时候,就保证很快速的去交付。你代码小,交付速度也会快很多。
  • 单一职责:我们通常来说,或者看过《Clean Code》这本书,会提到一点,就是一个函数通常只做一件事情,甚至大一点来说,一个微服务只做一个功能,绕着这个功能专注做好。单一职责在我们的类设计、微服务设计甚至是架构设计里面,都是非常重要的一个点。
  • 尽可能早的去创建原型:之前我们的设计,拿到需求后从下至上都是定好数据库表、字段或者是实体、struct,然后再向上提供api,但微服务大部分是服务和服务之间的关系,把之前从类的依赖引用变成消息的交付,是基于API传递消息来达到交付。所以有了微服务之后,可以促使我们尽可能早的去提供服务API,建立服务契约,打成服务间沟通的一致性约定,因为有了api我就可以根据你的api进行一些数据的mock,对于主干流程开发的人,不会阻塞开发,对于api背后的实现和完善可以慢慢做。
  • 可移植性比效率更重要:单体架构下面你的语言都是统一的,比如都是go或者java,但随着微服务的理念进来,每个功能可能都有更适合他语言的交付方式,比如多方协同就可以使用nodejs,里面有现成的第三方库。所以服务间的轻量级交付协议在效率和可移植二者间,首要依然考虑兼容下和一致性。

微服务

那给个定义,到底什么是微服务:围绕业务功能去创建的,并且服务关注单一业务,服务间采用轻量级通信机制,可以全自动独立部署,可以使用不同的编程语言和数据库存储。微服务架构通过业务拆分实现服务组件化(面相业务功能、场景开发),通过组件组合快速开发系统,业务单一的服务组件又可以独立部署,使得整个系统变得清晰灵活。这里有一个重点,就是可以使用独立的数据库存储,比如之前的单体架构,无论业务之间的模块划分如何好,但数据库是一套,比如用户表,每个模块都读相同的用户表,这就会导致如果服务表底层的业务发生变化,比如横向分表了,这就导致所有的模块都会影响到,但是微服务不会,哪怕你把mysql变为mangodb都没关系,因为是通过api进行访问的,只要api接口没变就会不受影响。
比如下图就是一个非常典型的微服务模型: weifuwu_17369141718453

他有手机端和web端,可以通过内部的网关把请求转发到内部的账号服务或者是仓库服务、物流服务等等,每个独立的服务都会独占DB,这还有另一层好处就是分库分表了,分别部署在不同的物理节点上,他的系统吞吐能力会变得更高。我之前的腾讯的时候,都还没拆分,很多都是巨石架构,比如缓存、db、mq都是一套,都在整个大模型里面去玩,如果一个组件达到瓶颈,比如db,就只能去整个扩充整个DB业务。如果每个业务都有独占的DB,那么就可以扩这个热点DB即可。

所以微服务有几个特别的点:

  • 原子服务:意味着他只关注单一的业务和场景,他的api肯定是围绕着这一个业务单元来的。
  • 独立进程:也就是他肯定是独立部署的。
  • 隔离部署:比如K8S下,肯定每一个微服务都是一个PODS,或者docker下面都是一个单独的容器,也就是单独的一个镜像。
  • 去中心化服务治理:比如之前要调用一个微服务,可能要经过四层负载均衡LVS,或者七层负载均衡Nginx,然后再转发到我的业务服,那经过这些,如果是热服务,请求密集的话,也是会产生流量的,流量一大,那么上游依赖的中间流量也会变得非常热,也会需要跟着去扩容。所以我们尽量需要让服务和服务之间的通信变得简单,也就是直连,如A和B服务结点是RPC通信的。当然咯,我们的缓存比如redis,队列等等,都是去中心化的一种方式。当然咯,有一些比如服务发现,这种就避免不了中心化。

当然,微服务也是有不少缺点的,比如一个最大的就是基础设施的建设,复杂度高,因为要拆分很多个微服务,对基础设施无限依赖。还有大的系统可能有上千个微服务,那管理的人是谁、日志、监控等等都是非常庞大的问题。如果是单体架构出现问题再怎么样都好查,但分布式下面很难排查具体是哪个服务出现的问题,而且对于数据一致性的要求也会变高。比如之前单体架构测试,测试小仙女们只会让你交付一个分支,构建、发布测试,但是现在完成一个复杂功能,比如动到会员服务这块,就会联合很多个服务同时进行测试,必须保证每个服务都交付完成测试,这些复杂度都会变高。

微服务不足

大佬说:there are no silver bullets,没有银弹,但凡有利就有弊,微服务也不是万能的。
这个可能和公司的体量、业务的发展规模都是有关系的,比如下面这张图。 guimo_17369252213765
横轴是业务复杂度,纵轴是生成力,也就是效率。随着你的产品迭代,业务的增长,巨石架构他的效率是在逐渐的下降,微服务肯定也会下降,但是整个衰减的力度没那么大。

  • 1.微服务应用是分布式系统,肯定会带来一定的复杂性,开发者不得不使用RPC或者消息传递来实现进程间通信,之前可能只是需要调用一个class。进程间通信就会有网络问题或者RPC不可达局部不可用问题,都会对全局造成影响,此外,必须要写代码来处理传递中速度过慢或者服务不可用等局部失效问题。

  • 2.分区的数据库架构,之前都很习惯使用事务去操作很多张表,这些表都是不同的业务单元,去更新多个业务主体的事务都是很普遍的,这种事务对于单体架构很容易,因为只有一个数据库,在微服务中,因为每个服务都是一个独占的数据库,所以需要更新不同服务所使用的不同数据库,从而对开发者提出更高的要求和挑战。比如要依赖MQ或者2CP、3CP等等分布式事务的手段。

  • 3.测试一个基于微服务架构的应用也是很复杂的任务,比如一个微服务不正常,就会影响全局,还存在多个微服务之间,不同的分支发布要构建环境来测试,也是非常难的一个点。

  • 4.服务模块间的依赖,应用的升级有可能会波及多个服务模块的修改。我一个大的功能模块修改,肯定会有多个微服务进行协同修改,需要多个人力同时修改,从管理方面也是很复杂。

  • 5.对运维基础设施的挑战比较大。对应用的管理难度增加,需要记录各个应用的id、名称等等,要做到快速的部署、测试、链路监控、指标、日志等等,都会提出挑战,而且微服务和微服务之间还需要编排框架、治理框架等等。还有服务和服务之间并不一定使用RPC,还有消息队列等等,这些对中间件都会提出要求。

不过我倒是觉得这些都是好事,因为随着业务变得复杂,规模变大,这些推动我们技术的发展和引进。

组件服务化

fuwuhua_17369274414178.png
要去实现一个微服务,第一个思路是什么,就是组件服务化,我们传统的SOA,就是提供各种各样的库或者SDK,然后库和应用是一起运行在进程中的,库的局部变化意味着整个应用需要重新交付部署,那么这个就是一个巨石架构完整的一个应用。如果通过服务来实现组件,就意味着将应用拆散为一系列的服务运行在不同的进程中,那么单一服务的局部变化只需要重新部署对应的服务进程。举个例子,比如我们使用go语言实施一个微服务,那么只需要做到以下的事情:

  • Kit,一个微服务的基础库,就是一个框架,比如腾讯开源的tars,会包含注册中心、日志的抽象、应用声明周期的管理等等。
  • service:服务的业务代码,以及一些kit依赖还有第三方依赖注册的业务微服务代码。
  • 轻量级通讯机制:一些跨API、跨进程的通信机制,比如RPC或者是消息队列。

本质上等同于,多个微服务组合(compose)完成一个完整的用户场景(usercase)。这就是组件服务化。

去中心化

每个服务面临的业务场景不同,就会针对性的选择合适的语言来进行解决,那就会涉及到多语言。我们可能就是PHP和GO多一点。但也是需要避免过度多样化,因为多语言下不太容易构建基础库,也需要开发很多套SDK,虽然鼓励多样化,但还是要避免太多,因为成本太高,需要结合团队实际情况来选择取舍。如果每个服务都是使用不同的技术栈来实现,维护成本就太高了。
当然,去中心化还有:

  • 数据去中心化:微服务的演进实例,每个服务都独占数据库,独占缓存等等,去一个超大数据库。
  • 去治理中心化:比如刚刚讲到的7层负载均衡或者是一些依赖的基础设置,所有流量热点密集的地方,避免一个集中式的东西做处理,比如做分发等等。
  • 技术去中心化:也就是我们没必要把所有的东西放到一个语言里面,比如go语言,后续可能架构的东西可以用C++,或者多端在线协同可以使用NodeJS等等。

每个服务独享自身的数据存储设施(缓存、数据库等),不像传统应用共享一个缓存核数据库,这样有利于服务的独立性,隔离相关干扰,因为所有中性化的东西都容易出连锁故障。

基础设施自动化

无自动化就不微服务,自动化肯定是包括测试和部署。单一进程的传统应用被拆分为一系列的多进程服务后,意味着开发、调试、测试、监控和部署的复杂度都会相应增大,必须要有合适的自动化基础设施来支持微服务架构模式,不然开发、运维成本将会大大增加。

  • CICD:可以使用gitlab+gitlab hooks +k8s ,就可以很方便的进行构建、测试、单元测试等等。比如我收到代码提交的hooks,就可以进行构建,然后做一个测试的任务,这是非常方便的,而不是像类似于使用jenkins需要手动的去构建。
  • Testing:一定要把测试环境,特别是多测试环境进行部署好,还有单元测试以及API自动化测试,比如YAPI等等。
  • 在线运行时:k8s,以及一系列后续的Prometheus、ELK、日志采集等等还有一些链路追踪等等都需要有储备,这样微服务落地才顺利。 devops_17369331134490.png

可用性&兼容下设计

有个著名的 Design For Failure思想(即当你的系统将错误当作正常流时,系统便已经对错误免疫了。),具体可以移步Design for failure常见的12种设计思想

微服务架构采用粗粒度的进程间通信,引入了额外的复杂性和需要处理的新问题。粗粒度指的是什么,比如我们要获取50个用户的信息,如果只是提供了一个细粒度的接口,就是传入用户id返回用户信息,那不就是需要for循环50次查询接口?然后跨50次网络请求再返回,这样就高频的调用api了,给性能会带来压力。如果提供一个批量查询用户的接口,比如batchUser或者是multipleUser等等,那么就只需要获取一次网络请求拿到大量数据。之前单体架构可能都是获取人家的方法,多个也无所谓,但是现在就要考虑了,比如一些容错、负载均衡、跨网络和容错等等,忽略其中任何一点都属于对于“分布式计算的误解”,都会出问题。
当然,微服务设计是一个非常大的话题,比如他的隔离、超时控制、负载保护、限流、降级、重试、负载均衡等等,每一个都是非常大的话题。

一旦采用了微服务的架构模式,那么兼容性是非常小心的,特别是服务需要变更的时候,服务提供者的变更可能引发服务消费者的兼容下破坏,时刻谨记保持服务契约(接口)的兼容下行。比如接口从V1升级到V2如何做兼容下设计呢?比如状态码的处理,error的处理。

API设计里面有一句话非常的经典,就是Be conservative in what you send,be liberal in what you accept. 发送时要保守,接收时要开发 这是伯斯塔尔法则的思想来设计和实现服务时,就是发送数据要保守,意味着最小化的传送必要的信息,不必要的数据就不要提供了,微服务提供api的时候就是要提供粗粒度的接口,按需提供,接收时更开放意味着要最大限度的容忍冗余数据,因为后续会有很多V1V2的数据过来,甚至参数上面的容错都需要考虑,来保证数据的兼容性。

人未眠
工作数十年
脚步未曾歇,学习未曾停
乍回首
路程虽丰富,知识未记录
   借此博客,与之共进步