自从去年开始工作以后就被微服务刷屏了,各大厂争先进行微服务改造,放出的各种 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 池的协程就方便很好了,当然了你说可以用延时队列,然而我们到底应该开多少线程去盯着延时队列呢(一个线程同时只能处理一个请求)?
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.