中小企业的微服务之路

概要

本文主要是说一下中小企业的微服务化的过程和实践。具体内容会在具体部分进行展开。每个部分都展开说的话都是洋洋洒洒的。

什么是微服务

在nginx blog里有一篇文章整体概要了这个。https://www.nginx.com/blog/introduction-to-microservices/

简单来说将单体式服务拆分成各个单元。比如将一个jira的应用拆分成,用户管理,issue管理,附件管理,登录认证这些模块。而微服务一直鼓吹的优势就是如下了:

  • 方便升级,每个模块都可以独立升级。而不会影响到其他模块。(但是有前提哦,这个下面说)
  • 研发接手快。不用看完整的代码,只要单一小模块的。(可这样是真的好吗?并不觉得只看很小的一块是一个合格的开发。)而且很多本来就是模块化的。
  • 不限语言。你只要符合对外暴露的API规范,不需要管你内部是什么语言开发的。

但是凡事有优点就必有缺点,下面列下对应的缺点:

  • 服务太多,不方便维护。
  • 如果没有很好的API规范,会导致这些依赖后期无法维护。
  • 基础组件过多,每个应用都是独立的,都会使用一些自己享用的基础组件。
  • 资源浪费。因为每个微服务以及对应的基础组件都要给予一些系统余量,虽然单个很少,但是加起来就多了。

我们看看单体服务和微服务的架构图:

下面这个还是简化了的,因为这些所有的微服务可能每个都要连这些基础服务和consul, 而这些微服务之间也是有互相调用的。

使用微服务都需要有哪些前置条件

API规范

这个是太重要了。

  • 使用RESTful API还是rpc。
  • 如果是RESTful,那遵循github的标准还是别的。
    • 这里主要涉及到API的版本如何安放。
    • api的错误返回状态码和错误信息如何定义。见过太多开发所有返回都是200,错误信息都放body体里。
  • 还是每个服务自己独立的API前缀是什么?
    见过一堆服务都是用同样的前缀,有时候GET和POST还是指向不同的服务,要这种设计还是迟早断了微服务的念头吧。
  • API变更向下兼容

认证规范

内部服务互相调用的认证是怎么样的?是有单独的认证服务来进行?cookie? HMAC? 还是别的。
还是内部服务有单独的认证系统,而不是每个服务独立去开发。
还是内部直接没有认证。

基础组件规范

为了保证整个基础组件的可用性,我们不可能不加限制的使用任意的基础组件。这只能是一个有限集。但是每种类型的中间件你都得给人列一个,不然就没法用了。

同时在有限集内的基础组件我们也需要做好规范限制。

  • 组件名。
  • 组件版本范围。
  • 组件里哪些函数是禁用的。
  • 组件里哪些特性是禁用的。
  • 组件使用的最大数据量,包括条数和磁盘容量。
  • 组件保证的QPS是多少。
  • 在QPS内的响应时间保证是多少。
  • 组件的可用性是多少。

以上根据自己部门的人员水平,可以按实际来进行。但是必须要有规范,不然后果难以想象。

运维挑战

ci cd流程

因为服务多了很多,要是没有一个制度和流程来保证这个,那以后发布上线累不死你。
同时由于存在多种语言的问题,所以我们使用了简单的方法,就是使用商业版本,由于贵司所有代码都是在github上,所以可选余地还是非常多的。
所以所有编译完成的包都进入一个release仓库中,这个是无法进行覆盖的。然后会分发到各个国内镜像站中。
同时提交对应的配置和db-change到github上。
最后在发布平台中进行整个发布流程,测试流程,上线流程。

服务注册

要是单体服务的话,就没有服务注册这一说了,也没有后面的服务发现了。
我们直接用的就是consul,但是需要注意下自己的spring cloud的版本,有些还是会产生不兼容的问题。
consul是server和client模式。服务注册注销都是client上进行的,并最终同步到所有的server端。内部使用gossip协议进行通讯。
从部署方案来说,client是跟应用服务部署在一起,client会转发查询,注册,注销服务的请求,同时需要进行健康检查。
而server需要单独进行部署,因为会存储集群状态和查询请求。
由于需要支持后面的灰度发布,因此各个服务在注册的时候需要把自己的版本号注册进去。而不仅仅是服务名,监听地址和端口这些。
具体的可以参考:http://ljchen.net/2019/01/04/consul%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/

服务发现

对于spring cloud的程序来说就是feign client了。对于其他的就各自找对应的。由于consul是支持dns协议的,所以最不济的就是通过dns来进行发现。
因此我们有时候都可以直接用shell来进行服务发现。当然python是带了consul模块的。
然后nginx负载均衡这一层也是要进行服务发现的。这个贵司直接用的dyups的方案,而不是原生的consul-template的方案,最大的好处就是不用重启nginx。
当然也有upsync这样的方案,但是在实际应用中问题多多,虽然可以直接编译进nginx中,但是架不住问题多。本来在沙箱中都测试完成了,但是一上量就崩了。

灰度发布

关于灰度,这个有的公司是直接研发在代码里进行判断的,然后在外部加个开关,有些是通过入口来进行控制的。
而这些所有的前提是需要给用户一个唯一的id,不然在多个服务需要进行灰度的时候会出现问题。
而贵司的产品都是有用户ID的,所以直接通过用户id来进行灰度。而灰度的方式是在nginx中进行的,通过lua+redis进行控制。
由于需要灰度发布,所以各个微服务在服务注册的时候需要把版本号注册进去。

其他

由于有那么多的服务了,所以当出现问题的时候就不那么好查了,因此日志收集系统也要上了。贵司用了EFK的,只是把logstash替换成了fluentd和flume。原因是fluentd的第三方插件很多。主要是我们各种日志格式。所以在研发规范里也要定义好这个。
现在研发查问题都是把事件ID放kibana里一搜就看跟踪到哪里了。

肯定有人会问为什么不用trace系统呢。当然有用啊,pinpoint和jaeger。一个是针对java程序的,真正做到了零侵入,jaeger就是针对其他语言的了。想想也是醉了。
infoq上有一篇文章就是讲的trace系统的比较,可以看下 https://www.infoq.cn/article/apm-Pinpoint-practice
但是所有的trace系统都是抽样检查的,因此除非出现大规模的性能问题,其他问题都必须通过日志系统来进行。比如上周出现的内部调用报了个“Caused by: feign.RetryableException: Unrecognized SSL message, plaintext connection? executing POST http://xxxxx”
结果放狗搜了下居然是spring cloud的bug。https://bishion.github.io/2018/03/10/spring-feign-https-bug/

服务启动平台,现在大家都在说k8s,但是放3年前就未必是这样的。当时是有mesos,K8s,docker swarm
这些。综合比较我们选择了mesos+marathon的方案。一直运行到现在。当时的k8s简直就是渣渣的存在。
当然后我们后续也会逐步演进,面向简历运维是比较简单的,但是真正把它用好就是另外一回事了。

总结

我们看了下,这个要是从单体服务进化到微服务,这中间要干的事情还是非常多的。具体看各自对自己应用,成本,性能的一个综合考量。
正所谓天下大势,合久必分,分久必合,这世界总是多元化的,没有银弹。