微服务落地实践 - 经验分享

4,205 阅读12分钟

前言

随着架构设计的发展,微服务架构可以说是目前架构领域炙手可热的设计理念。在公司,笔者也一直在负责系统的服务化设计和开发工作。

今天就来谈谈微服务落地实践中的一些问题。希望对微服务设计无从下手的朋友,起到一些参考作用;另外也希望把自己的观点分享出来,期待与大家一起交流,能够认识到不足之处。

一、服务拆分

在落地微服务之前,我们遇到的第一个问题就是:应该如何拆分服务?

大家知道,关于如何拆分服务,并没有一个完全通用的法则。不过有以下几点要素,我们可以参考。

1、业务独立性

一般情况下,我们第一时间都会考虑到这点。将系统中的业务模块,按照职责标识出来,每个单独的模块拆分成一个独立的服务。

这样拆分之后,我们的服务要满足一个原则:高内聚,低耦合。

比如,我们把一个系统拆分为商品服务、订单服务、物流服务。一般情况下,我们修改物流服务的时候,并不会影响商品服务,这就是低耦合的体现;那么订单服务里面的功能和逻辑,都是围绕着订单这个核心业务流程的,就可以说它是高内聚的。

2、业务稳定性

我们可以把系统中的业务按照稳定性进行区分。比如,用户注册、登录部分,在我司的系统中,这一块的代码只要写完,基本就不再会发生变动,那么我就将他们拆为用户服务。

同理,还有日志服务、监控服务,这些模块基本上也都是很稳定的业务,可以考虑单独拆分。

3、业务可靠性

这里讲究的是,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。

避免由于非核心服务故障,而影响核心服务。

4、业务性能

基于业务性能拆分,考虑的是将性能压力大的模块拆分出来。对于这一点,笔者有两点想法:

  • 避免性能压力大的服务影响其他服务。
  • 将高流量的业务独立出来,扛不住的情况下,方便水平扩展。

比如,在笔者参与的一个系统中,曾通过 RocketMQ 对接来自多家厂商的大量数据。

当时,就独立出来一个消息服务,专门用来消费消息。然后有的是在本地处理,有的通过 RPC 接口转发到其他服务处理,有的直接通过 WebSocket 推送到前端展示。

这样的话,即便流量激增,考虑给消息服务增加机器,提高消费能力就好了。

当了解到上面这几种拆分方式之后,我们就可以根据自己的业务范围和技术团队规模,来考虑自己系统的服务拆分了。

5、过犹不及

不过在这里,尤为值得注意的是,微服务切忌拆分的过细,一定要结合业务规模和技术团队的规模。

笔者以前就遇到一个项目,在做服务化的过程中,当时的技术负责人按照业务独立性来拆分服务,结果拆出了10来个服务;然后更狠的是,每个业务服务又把Service层和Controller层拆分开来,每个业务方法都需要远程调用。据当事人描述,这样做是为了方便后期提升扩展能力。

不过说实话,有些系统业务量并没有那么大,一味的迎合微服务中的微字,无疑是给自己增加难度,破坏整体系统的稳定性。所以,在重新梳理了业务流程后,笔者对这个系统进行了重构,缩减服务数量。

在这里,笔者想说明一点。Service层和Controller层是可以拆分成多个模块的,这个没关系。不过,它只应该是模块的分离,而不是服务的拆分。比如我们可以在开发阶段把它们拆分成多个模块,然后通过 Maven modules 聚合到一块,在部署运行阶段,它们还都是一个服务。

二、技术选型

当完成了服务拆分之后,选用什么框架进行开发呢,应该选择 Dubbo 还是 SpringCloud ?

笔者不想单纯讨论它们的优劣之处,在这里可以就着这个问题分享下笔者的心路历程。

1、SpringCloud

最初进行选型时,笔者选择了 SpringCloud,毕竟它号称是微服务一站式解决方案。然后就搭建框架,集成各种组件,完成了一些 demo 的开发和测试。

不过,在完成这部分工作后,重新审视整个系统,看到SpringCloud中,涉及到的Eureka、Ribbon、Feign、Hystrix、Zuul、Config这些组件。

这时候,我会产生两个疑问:

  • 这些组件是不是非用不可 ? 有没有更轻便的系统方案 ?
  • 这么多东西,任一环节出了问题,我们是否能hold的住?

笔者对于一站式这个词有两层理解。第一,它简化了分布式系统基础设施的开发,易于开发;第二,简化的同时,它一定是屏蔽了复杂的配置和实现原理,不易于深入理解它的原理。

作为架构师或者团队技术负责人,我们必须对自己系统涉及到的技术点做到知根知底,或者说,最起码要懂得它们的原理。这样,即便遇到了问题,在 Baidu / Google 不出来结果时,也不会慌。

2、Dubbo

基于这样一个思路,笔者把目光又转向了Dubbo。对于笔者来说,对Dubbo就比较熟悉了,从它的框架本身来说,已经实现了负载均衡和集群容错,调用方式也是基于接口的。相较SpringCloud而言,不需要再额外引入像Ribbon/Feign这样的组件。

还有一点,Dubbo得益于强大的SPI机制,我们可以非常方便的扩展它。如果业务上有需要,在很多地方都可以对它进行扩展,比如 RPC 协议、集群、注册中心、序列化方式、线程池等。

不过话说回来,Dubbo只是一个高性能的 RPC 框架,拿它和SpringCloud相比,更多的还是比较REST和RPC。不过关于这一点,就一般的项目而已,这点性能差异还不足以一锤定音,最多只是锦上添花而已。

3、稳妥优先

至于其他的组件,比如Hystrix/Zuul/Config/zipkin等,笔者的观点,还是看业务规模。微服务只是一种设计思想,架构理念,而不是说用到了很多分布式框架才叫微服务。这些框架的产生,只是为了解决微服务系统遇到的问题的。

需知一口吃不成个胖子,在做技术选型时,切记直接对标像阿里、京东、美团这样的大厂经验,一来,也许我们遇不到那样的业务场景;再者,一般公司也没有人家那样的人才储备。毕竟如果线上出了问题,是没人跟你分享损失的。

总的来说,还是结合自己的实际情况,以最稳妥的技术方案,完成业务上的需求。

三、节外生枝

拆分了服务,也完成了技术方案的选型,那就万事大吉,开始撸代码了吗 ? 如果单纯作为开发人员,那确实开撸就行了。如果你是一个系统的负责人,只满足于高屋建瓴,不考虑细节问题,那必然会节外生枝。

1、超时和容错

服务化之后,不同服务之间的调用就是远程调用。远程调用有个最基本的设置,即超时时间。

比如,在Dubbo中,默认的超时时间是1秒。我们不能单纯的使用默认值或者统一设置成另外的值,为Dubbo设置超时时间最好是有针对性的。

比如,比较简单的业务可以设置的短一些;但对于复杂业务而言,则需要适当的加长这个时间。因为,这里还涉及到一个集群容错的问题。

Dubbo中,集群容错的默认策略是失败重试,次数为2。假如有的业务本身就需要耗费较长的时间来执行,因为超时时间太短就会触发容错机制,来重试。大量的并发重试请求,很可能会占满Dubbo的线程池,甚至影响后端数据库系统,导致连接被耗尽。

2、容错和幂等性

我们上面说,如果超时时间设置太短,有可能会导致大量请求会不断重试,而导致异常。

这里还隐瞒着另外一个细节,即读请求和写请求。如果是读请求,那么重试无所谓;如果是写请求,我们的接口是否也支持自动重试呢 ? 这就会涉及到接口幂等性问题。

如果写请求的接口,不支持幂等性,那么集群容错就得改为 failfast,即快速失败。

3、分布式事务

笔者感觉,分布式事务在业界是一个没有彻底解决的技术难题。 没有通用的解决方案,也没有既高效又简便的手段。

虽然如此,但我们也得事先考虑到这一点,不然数据肯定会变成脏乱差。

在考虑解决方案之前,我们需要先看看自己的系统是不是真的追求强一致性;按照BASE理论,在分布式系统中,允许不同节点在同步的过程存在延时,但是经过一段时间的修复后,能够达到数据的最终一致性。

基于这两个思路,我们才好制定自己的分布式事务方案。

对于要求强一致性的场景,或许可以考虑XA协议,通过二阶段提交或者三阶段提交来保证。

对于要求最终一致性的场景,可以考虑采用 TCC 模式,补偿模式,或者基于消息队列的模式。

比如基于消息队列模式,可以采用 RocketMQ。它支持事务消息,那么这时候整个流程大概是这样的:

  • 通过 RocketMQ 发送事务消息到消息队列;
  • 消息发送成功,则执行本地事务;
  • 如果本地事务执行成功,则提交RocketMQ事务消息,提交后对消费者可见;
  • 如果本地事务执行失败,则删除RocketMQ事务消息,消费者不会看到这条消息。

另外,在这里安利下阿里开源的Seata。目前最新版本是1.1.0,支持多种事务模式,比如 AT、TCC、SAGA 和 XA 事务模式。

笔者有篇文章是基于 Seata 0.7版本的写的,有兴趣的朋友可以了解下。

SpringBoot+Dubbo+Seata分布式事务实战

4、消息队列

在分布式系统架构中,为了系统间的解耦和异步处理,应对高并发和大流量,消息队列绝对是一大利器。

在使用这一利器前,我们也得考虑下有可能因为消息队列带来的烦恼。

首先需要考虑的就是可用性,如果消息队列不可用,会不会对系统本身造成大量的不可用;

然后,消息会不会丢失呢 ? 如何保证消息可靠性传输呢?比如要考虑消息队列本身的刷盘机制、同步机制;数据发送时的确认和消费后的提交;

然后就是重复消费,如果保证了消息不会丢失,多多少少都可能会有重复消息的问题,这时候就要考虑重复消费有没有问题,即消息幂等性;

还有,消息顺序性问题,你们的业务场景里,是否有消息顺序性问题,如果有这个问题,要么在设计时规避它,要么在消费时保证它的顺序。

5、统一日志

随着微服务的拆分,日志系统也可能会演变为独立的模块。为了查看日志,我们可能需要登录到不同的服务器去一个个查看。

因此,搭建统一的日志处理平台是必然的。我们可以采用 ELK 的解决方案进行日志聚合。

在这里,还需要链路追踪问题。在微服务复杂的链式调用中,会比单体应用更难以定位和追踪问题。

对于这个问题,我们考虑引入分布式调用链,生成一个全局唯一的 TraceID ,通过它把整个调用链串联起来。结合Dubbo框架的话,我们实现自己的Filter,用来透传这个TraceID

具体思路可以参考:SpringBoot+Dubbo集成ELK实战

当然,我们也可以选用一些成熟的开源框架来解决。

总结

本文简单总结了微服务设计和开发过程中,可能会涉及到的一些问题。

以上观点只是一家之言,仅仅是笔者在过去时间里的经验总结。

如果对您有帮助,请点赞鼓励~ 如果您有不同观点,请积极发言,共同交流~