- 什么是微服务
- 微服务怎么拆分?
- 服务拆分需要解决的问题
- 服务发布和引用
- 服务注册和发现
随着业务规模的不断扩大,团队开发人员的不断扩张,单体应用架构就会开始出现:部署效率低下、团队协作开发成本高、系统高可用性差、线上发布变慢等问题、
想要解决上面这些问题,服务化的思想也就应运而生。
那么微服务相比于服务化又有什么不同呢?在我看来,可以总结为以下四点:
- 服务拆分粒度更细。
微服务可以说是更细维度的服务化,小到一个子模块,只要该模块依赖的资源与其他模块都没有关系,那么就可以拆分为一个微服务。 - 服务独立部署。
每个微服务都严格遵循独立打包部署的准则,互不影响。比如一台物理机上可以部署多个 Docker 实例,每个 Docker 实例可以部署一个微服务的代码。 - 服务独立维护。
每个微服务都可以交由一个小团队甚至个人来开发、测试、发布和运维,并对整个生命周期负责。 - 服务治理能力要求高。
因为拆分为微服务之后,服务的数量变多,因此需要有统一的服务治理平台,来对各个服务进行管理。
总结:
服务化就是把传统的单机应用中通过 JAR 包依赖产生的本地方法调用,改造成通过 RPC 接口产生的远程方法调用,也就是说:微服务架构是将复杂臃肿的单体应用进行细粒度的服务化拆分,每个拆分出来的服务各自独立打包部署,并交由小团队进行开发和运维,从而极大地提高了应用交付的效率
纵向拆分
一个最有效的手段就是将不同的功能模块服务化,独立部署和运维。以社交 App 为例,你可以认为首页信息流是一个服务,评论是一个服务,消息通知是一个服务,个人主页也是一个服务。
这种服务化拆分方式是纵向拆分,是从业务维度进行拆分。标准是按照业务的关联程度来决定,关联比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合单独拆分为一个微服务。
横向拆分
还有一种服务化拆分方式是横向拆分,是从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不与其他业务耦合。
继续以社交 App 举例,无论是首页信息流、评论、消息箱还是个人主页,都需要显示用户的昵称。假如用户的昵称功能有产品需求的变更,你需要上线几乎所有的服务,这个成本就有点高了。显而易见,如果我把用户的昵称功能单独部署成一个独立的服务,那么有什么变更我只需要上线这个服务即可,其他服务不受影响,开发和上线成本就大大降低了。
怎么选?
实际业务中这两种拆分方式一般是相互结合使用的,如果业务比较多分散,适合做纵向拆分。如果多个业务之间有公共模块耦合,适合把公共模块拆分出来,适合做横向拆分。
1、服务如何定义
对于单体应用来说,不同功能模块之前相互交互时,通常是以类库的方式来提供各个模块的功能。
对于微服务来说,每个服务都运行在各自的进程之中,应该以何种形式向外界传达自己的信息呢?答案就是接口,无论采用哪种通讯协议,是 HTTP 还是 RPC,服务之间的调用都通过接口描述来约定,约定内容包括接口名、接口参数以及接口返回值。
2、服务如何发布和订阅
单体应用的接口之间的调用属于进程内的调用。
而拆分为微服务独立部署后,不在一个进程内了。服务提供者该如何对外暴露自己的地址,服务调用者该如何查询所需要调用的服务的地址呢?
这个时候你就需要一个类似登记处的地方,能够记录每个服务提供者的地址以供服务调用者查询,在微服务架构里,这个地方就是注册中心。
3、服务如何监控
通常对于一个服务,我们最关心的是 QPS(调用量)、AvgTime(平均耗时)以及 P999(99.9% 的请求性能在多少毫秒以内)这些指标。这时候你就需要一种通用的监控方案,能够覆盖业务埋点、数据收集、数据处理,最后到数据展示的全链路功能。
4、故障如何定位
在单体应用拆分为微服务之后,一次用户调用可能依赖多个服务,每个服务又部署在不同的节点上,如果用户调用出现问题,你需要有一种解决方案能够将一次用户请求进行标记,并在多个依赖的服务系统中继续传递,以便串联所有路径,从而进行故障定位。
5、该不该分库?
服务化最难的是在数据库层,因为数据库中很多数据都是相互关联的,比如用户跟订单,订单和商品等等这些数据之间都是有关联的,服务拆分之后会面临以下问题:
当需要读取关联数据时,如果采用表连接的方式查询数据会出现跨数据库查询的可能,
如果是通过RPC的方式多次调用(比如要查订单,就需要查询商品及订单详细信息),也会出现多次调用导致频繁的数据库连接,
而如果使用缓存,也会面临数据库与缓存之间的数据同步问题,特别是数据库与缓存服务器都存在集群的情况下,会更难处理,这也是在做微服务之前必须要考虑的问题。
所以服务化拆分最好不要涉及跨数据库表交互
6、DAO层是单独抽出来作为微服务还是随着业务进行拆分,即每个业务微服务内部管理自己会用到的db数据?
建议后者,每个小应用都应该有自己独立的数据库,如果dao抽出来,意味着其他服务也可以调用这个dao,那就会造成数据库数据的混乱。最好是在dao层上面封装service层,提供给其他应用使用,这样能保证数据的读写可控
服务发布和引用主要有三种方式:
-
RESTful API:用作 HTTP 或者 HTTPS 协议的接口定义
-
XML 配置
-
IDL 文件:用作跨语言平台的服务之间的调用,有两种最常用的 IDL:一个是 Facebook 开源的Thrift 协议,另一个是 Google 开源的gRPC 协议。
在微服务架构下,A服务要调用B服务,怎么知道B服务的地址呢?这是就需要服务注册中心了。
服务提供者(RPC Server)、服务消费者(RPC Client)和服务注册中心(Registry),三者的交互关系:
注册中心需要解决以下几个问题:
提供哪些接口, 该如何部署; 如何存储服务信息; 如何监控服务提供者节点的存活; 如果服务提供者节点有变化,如何通知服务消费者 以及如何控制注册中心的访问权限
1、提供哪些接口?
服务注册接口:服务提供者通过调用服务注册接口来完成服务注册。
服务反注册接口:服务提供者通过调用服务反注册接口来完成服务注销。
心跳汇报接口:服务提供者通过调用心跳汇报接口完成节点存活状态上报。
服务订阅接口:服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。
服务变更查询接口:服务消费者通过调用服务变更查询接口,获取最新的可用服务节点列表。
2、该如何部署?——集群部署
注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。
以zk为例:
3、如何存储服务信息?——目录存储
还是以 ZooKeeper 为例,注册中心存储服务信息一般采用层次化的目录结构:
每个目录在 ZooKeeper 中叫作 znode,并且其有一个唯一的路径标识。
znode 可以包含数据和子 znode。
znode 中的数据可以有多个版本,比如某一个 znode 下存有多个数据版本,那么查询这个路径下的数据需带上版本信息。
4、如何监控服务提供者节点的存活?——服务健康状态检测
注册中心除了要支持最基本的服务注册和服务订阅功能以外,还必须具备对服务提供者节点的健康状态检测功能,这样才能保证注册中心里保存的服务节点都是可用的。
还是以 ZooKeeper 为例,zk是通过客户端和服务端的长连接和会话超时控制机制来实现服务健康状态检测的。
在 ZooKeeper 中,客户端和服务端建立连接后,会话也随之建立,并生成一个全局唯一的 Session ID。服务端和客户端维持的是一个长连接,在 SESSION_TIMEOUT 周期内,服务端会检测与客户端的链路是否正常,具体方式是通过客户端定时向服务端发送心跳消息(ping 消息),服务器重置下次 SESSION_TIMEOUT 时间。如果超过 SESSION_TIMEOUT 后服务端都没有收到客户端的心跳消息,则服务端认为这个 Session 就已经结束了,ZooKeeper 就会认为这个服务节点已经不可用,将会从注册中心中删除其信息。
5、如果服务提供者节点有变化,如何通知服务消费者?
一旦注册中心探测到有服务提供者节点新加入或者被剔除,就必须立刻通知所有订阅该服务的服务消费者,刷新本地缓存的服务节点信息,确保服务调用不会请求不可用的服务提供者节点。
继续以 ZooKeeper 为例,基于 ZooKeeper 的 Watcher 机制,来实现服务状态变更通知给服务消费者的。服务消费者在调用 ZooKeeper 的 getData 方法订阅服务时,还可以通过监听器 Watcher 的 process 方法获取服务的变更,然后调用 getData 方法来获取变更后的数据,刷新本地缓存的服务节点信息。
6、如何控制注册中心的访问权限?——白名单机制
注册中心需要提供一个保护机制,你可以把注册中心想象成一个带有门禁的房间,只有拥有门禁卡的 RPC Server 才能进入。在实际应用中,注册中心可以提供一个白名单机制,只有添加到注册中心白名单内的 RPC Server,才能够调用注册中心的注册接口,这样的话可以避免测试环境中的节点意外跑到线上环境中去。



