我所理解的微服务

2017-06-27 15:18:11 +08:00
gouchaoer  gouchaoer

自从去年开始工作以后就被微服务刷屏了,各大厂争先进行微服务改造,放出的各种 PPT 文档也是眼花缭乱,由于概念太难了我一直没搞懂。最近终于搞懂了啥是微服务,其实很简单。

首先需要确认的是一般的 web 后端不适合微服务,什么是一般的 web 后端呢?就是一些比较简单的对数据库增删查改或者对缓存进行操作、用来生成网页 html 的、写成一个项目也没啥问题的后端,每一次 http 请求都很快很简单并且没有特别消耗 io 或者 cpu 的调用,比如没有去查 elastic search、没有去 rpc/http 访问别的接口。

而哪些任务适合写成微服务呢?就是那些可以从 web 后端中拆分出来的比较复杂、比较消耗 io、比较消耗 cpu 的一些 api,比如专门负责搜索的 api、比如专门返回用户订单列表的 api、比如需要在后端访问友商接口的 api,我们把这些 api 分离出来做成单独的 web 服务,而这些 web 服务有的可能只需要查询一下 es、数据库和缓存啥的,有的还可能需要在本次请求中再去 rpc/http 调用另一些内网外网的 api 服务。这么做好处就是开发+更新方便,服务之间不容易受到影响。那带来的坏处也有,首先就是前端服务器对 api 的调用需要走 rpc( http rest、thrift 等等),这种 io 消耗是需要避免的;此外还有就是微服务多了调用+上线更新变复杂了,需要自动化这个过程;因此微服务的很多工作就是来弥补这些坏处的。

我认为一个完整的微服务架构最需要满足的几点:

1、凡是涉及到 io 的部分必须有 tcp 池,包括 es、mysql、redis、rpc 甚至 http,我们知道 tcp 建立连接和断开对任何一方都是有消耗的,qps 大了这个消耗都必须避免

2、凡是涉及到 io 的部分要么用异步要么用协程,不允许阻塞,因为 io 部分意味着这部分消耗时间很长,如果阻塞在这里 qps 一大就会同时有长千上万的线程卡在 io 的地方,cpu 调度这些线程消耗很大。很多框架为了证明自己性能喜欢用 hello world 的 qps 来压测,其实这个只能反映一部分性能,因为 hello world 没有任何 io 开销。综合第 1 点和第 2 点,php-fpm 运行方式不适合微服务,应该尽量避免在 php-fpm 中开 httpclient 去访问别的 api。

3、所有内网的 api 服务应该实现自动注册发现( etcd、consul 和 redis 比较多),而且尽可能的使用 ip+端口号来访问,不要用内网域名也不要用 https

4、所有微服务 api 应该实现自动更新+自动上线,假设某个 api 后面后 AB 两台服务器,那么我一旦代码库里发布了一个正式版的代码,那么需要有一个机制保证首先 A 的流量被切掉并且所有 http 都返回后,A 应用 stop 然后更新最后 start,然后轮到 B。当然了这一套可以用 nginx 和一波 openresty 的扩展解决,也可以扩展成一个灰度更新机制(比如根据 cookie 来选择 upstream ),或者 AB test 机制。自动上线不一定非要弄个啥 docker 集群方案,因为我相信大多数业务都比较小可以越简单越好,比如写个 shell 脚本自动配置环境啥的,都行。

5、所有微服务 api 应该有 trace 机制和 log 收集机制,也就是说每次 api 调用链都打进 log 了并且被收集到 log 服务器(通常是 elk ),然后我可以看到本次调用时间或者哪个环境出了问题

6、关于异地部署和灾备,首先我反对异地部署同时提供服务(除非你是腾讯 google 那种大厂,因为太难了),异地可以做个灾备平时不用,一旦发生了严重的状况才把域名解析切到灾备平台(虽然灾备平台很难提供正常服务,不过聊胜于无)。为了避免发生灾难,我认为我们需要尽可能的把主要服务的依赖减到最低,比如我已经依赖了 redis,那么当我需要服务注册发现的时候如果可以用 redis 就最好别去搭建一个 etcd。

7、关于报警和熔断,实际上对机器的性能监控以及对 api 错误的监控大家都在做没啥好说的,然而我认为监控需要能看到哪些 api 请求异常(所以不同的 api 最好独立部署在不同机器上),此时咱们首先肯定加机器(这个加机器能自动自然最好),实在不行肯定就只有把异常的 api 调用掐掉。而异常的 api 调用来自 cc 攻击、ddos 攻击以及爬虫等乱七八糟的,所以说有个很重要的一点是我们可以在 load balancer 的地方清洗一下流量,比如我专门有个服务去读 log 服务器中的异常调用 ip/用户,甚至我的后台逻辑里就主动上报有问题的 ip/用户,然后在 load balancer 的地方主动拦截掉。

以上几点就是我认为一个微服务需要具备的特点。

另外我想谈谈一种长时间 io 服务的情况,比如我有个 api 把一个关键词 http 传给友商,友商需要花上几十秒甚至几分钟才能完成任务,这种情况如果友商完成任务后直接回调通知我还好,如果友商可能没法回调我又或者不太稳定的话,我估计就得每隔几秒去轮询一次直到超时,那这样一个请求占了一个线程几分钟 qps 岂不是狂降?这个时候就用带有 tcp 池的协程就方便很好了,当然了你说可以用延时队列,然而我们到底应该开多少线程去盯着延时队列呢(一个线程同时只能处理一个请求)?

6955 次点击
所在节点   程序员  程序员
38 条回复
timbotetsu
timbotetsu
2017-06-27 15:37:13 +08:00
只说我懂的部分,好吧沟槽聚聚

1 和 2 实际上业界更通用的做法是使用 MQ 做解耦
3. 如果应用的配置正好能与 SCM 管理工具合作的话也基本不会用到服务发现
4. 和我的理解上还是有点偏差,觉得过于复杂了
5. 对,但是也不一定是必须,因为能从 upstream、传参上也能大致定位到问题在哪
6. 异地部署和灾备的作用理解好像有点问题,涉及到 DNS 的切换的话,不是一下子就能迅速切过去的
7. 有 API 监控的方案,但是报警方案觉得设计上会比较难,相当于一个规则系统了,做好智能包茎是挺难的
gouchaoer
gouchaoer
2017-06-27 15:46:27 +08:00
@timbotetsu 1 和 2 是我对微服务最本质的理解,是我总结的关于微服务的“两个凡是”方针,你否定了我的“两个凡是”就等于否定了我
lovedebug
lovedebug
2017-06-27 15:51:08 +08:00
我理解的微服务是系统由一堆的服务以及服务管理器组成,可以随意的增删。这要求系统必须尽可能解耦。
heihei20088
heihei20088
2017-06-27 15:51:58 +08:00
我所理解的微服务是基于业务的拆解,而不是 io,多线程的方面。
timbotetsu
timbotetsu
2017-06-27 15:55:19 +08:00
@gouchaoer 那我还是不谈了:)
misaka19000
misaka19000
2017-06-27 15:55:23 +08:00
@heihei20088 #4 我觉得也是,所以感觉微服务和 DDD 能够非常好的结合起来使用
Morriaty
Morriaty
2017-06-27 16:07:17 +08:00
@timbotetsu 你们热更新怎么做的?
timbotetsu
timbotetsu
2017-06-27 16:12:58 +08:00
@Morriaty blue-green deployment
monsoon
monsoon
2017-06-27 16:24:17 +08:00
我的一些看法,当然我不是微服务的专家!

> 首先需要确认的是一般的 web 后端不适合微服务,什么是一般的 web 后端呢?就是一些比较简单的对数据库增删查改或者对缓存进行操作、用来生成网页 html 的、写成一个项目也没啥问题的后端,每一次 http 请求都很快很简单并且没有特别消耗 io 或者 cpu 的调用,比如没有去查 elastic search、没有去 rpc/http 访问别的接口。

我觉得这句话其实是不正确的。一般的 Web 后端不是不适合微服务,而是没必要微服务。但是实际上一些非常简单的对数据库的删查改都非常适合做成微服务,如果这部分提供的 API 服务是你业务非常核心的业务,而且非常需要 Scale (乃至 reactive ),这时候做成微服务是非常有价值的,而且也利于迭代这部分核心的业务。

> 1、凡是涉及到 io 的部分必须有 tcp 池,包括 es、mysql、redis、rpc 甚至 http,我们知道 tcp 建立连接和断开对任何一方都是有消耗的,qps 大了这个消耗都必须避免

我觉得这也是错误的,微服务并不需要一定有 TCP 池,一个微服务可以通过各种的形式给外接提供接口,并不需要 TCP 池,就算你通过 Atom ( https://en.wikipedia.org/wiki/Atom_(standard)) 让别人订阅数据也没关系。

> 2、凡是涉及到 io 的部分要么用异步要么用协程,不允许阻塞,因为 io 部分意味着这部分消耗时间很长,如果阻塞在这里 qps 一大就会同时有长千上万的线程卡在 io 的地方,cpu 调度这些线程消耗很大。

我觉得这也是错误的,在一个现实世界里,一个微服务用到一些东西是阻塞的是再说难免。比如说你用的数据库并不是在驱动级别支持异步的。但这从来不是问题,你只要讲这部分阻塞的东西放到一个很好的围栏里(放到一个线程池里并采取相应的措施)。当然你的微服务的很多阻塞的部分可能是你当前的微服务的一块比较短的短板,但是这永远也不会因为你的一个后端有阻塞的部分,而使你的后端无法成为一个微服务。
jiangzhuo
2017-06-27 16:26:12 +08:00
所有业务迁移到 lambda 上了一个月,然后我又都换回 EC2 了,那稳定性那酸爽
cevincheung
2017-06-27 16:26:33 +08:00
底层服务:
SLB & POOL: SocketManager、MQ、SearchServices、Payment

上层基础服务(应用下层):
商品搜索:Input & Output -> SearchServices
订单推送、短信下发:MQ & Worker
系统订单支付、管理、对账: 订单发生->Payment,对账监控&支付结果处理:MQ

应用层:
商城系统,用户系统,订单系统

我们这么设计的
gouchaoer
2017-06-27 16:33:00 +08:00
@monsoon 我也不懂,因为我没做过微服务产品呢
monsoon
2017-06-27 16:45:39 +08:00
@gouchaoer 感觉我刚刚好像批评的太多了(我的错),不要太往心里去,权当学习交流好了。
不过这个东西真的很复杂,其实有段时间我也想学习下微服务,后来发现如果我工作里完全没用到的话,不就一点价值都没有了,然后我就没学了……
gouchaoer
2017-06-27 16:48:58 +08:00
@monsoon 有理太敏感了,我根本就是字面上意思,外加一点欢乐的调侃而已。。。
yanzixuan
2017-06-27 16:51:36 +08:00
@gouchaoer 微服务还有一个短路器你提到,专门解决调用响应时间长拖慢整体系统的问题。
timbotetsu
2017-06-27 17:21:50 +08:00
@monsoon 你看他凶我那样,真是
yoke123
2017-06-27 17:36:37 +08:00
太高端 看不懂 老实搬砖去了.....
sumuu
2017-06-27 17:43:11 +08:00
微服务是属于架构层的概念,用最简单的一句话来概括就是"把一个整体项目通过适应的模式(业务),分解成多个小项目"

举个很简单的例子:
一个电商平台,有用户,产品,支付等系统,通过业务分开后为: 用户,产品,支付,各自解耦,分开部署,互不干涉.
然后通过制定的通信方式,REST ? RPC? 达到各个系统的通信.

我对微服务的抽象理解,具体用什么技术,用什么软件,这个是更细的,和微服务没有关系.
codein
2017-06-27 17:44:09 +08:00
有不少互联网公司现在都跟着 netflix,用 spring cloud 那一整套
gouchaoer
2017-06-27 17:53:34 +08:00
@monsoon scale 和微服务并没有关系,就算是一个单体应用我部署到多台机器上也可以 scale 啊,核心业务我封装一下复用修改也很方便,搞成微服务还有一层 rpc 消耗呢;对数据库的增删查改恰恰不适合做成微服务,而且我认为每次请 api 请求所涉及到的内部微服务调用应该尽量少。。。

至于为什么 io 的地方一定要用协程或者异步,因为一个 api 在某次请求去 rpc 调用别的微服务的时候阻塞的时间会更长(必须等待别的微服务完成处理并返回),所以必须异步或协程,mysql 驱动必须使用异步 /协程版本的,redis 也是,httpclient 也是,rpc 也是。。。所有用同步阻塞的,qps 必然有本质降低

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/371405

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX