DockOne微信分享(一六四):扇贝网微服务编排和治理实践


【编者的话】随着流量的提升,团队的壮大,为了适应变化,2017年扇贝的服务端进行了比较大的改造,全面拥抱了微服务,容器编排等。从而很大程度上提高了开发效率,降低了维护成本。微服务的服务间通信与服务治理是微服务架构的实现层面的两大核心问题。希望通过这次分享能够给中小型的互联网公司实践微服务带来启发。

我们微服务从开始尝试到现在大规模使用,算来也有快 2 年了。回过来看,踩了无数的坑,有的是技术的,有的是管理的。算是有不少心得了。今天主要想跟大家分享下微服务的选择、DevOps 实践、服务治理和 Service Mesh。

要不要微服务

个人觉得选择微服务要慎重,要考虑清楚微服务可能带来的挑战。一定要结合自己的实际,慎重地决定是否需要微服务化。

网上关于微服务好处的介绍有很多,这里我想特别强调的是,从“人”的角度讲,微服务带来的优势和挑战。

微服务架构下,每个服务的复杂度大幅下降,从而维护成本也大幅降低。对于团队来说,新人也可以更快地上手项目,贡献代码。交接项目也变得更加容易。

单一服务架构下,随着时间的推移,项目的复杂度必然越来越高,相关人员也会越来越多,从而代码的合并频率会逐渐下降,权限越来越难以分配,需要越来越重的测试,部署也越来越复杂,频率越来越低。

然而微服务也会带来很多问题,例如如果设计不合理,整体的复杂度会提高。写代码的时候需要考虑调用失败的情况,需要考虑分布式的事务(当然很多时候是设计不合理导致的),从而对人提出了更高的要求。

总之,微服务带来了很多优势,同时也带来了很多挑战,我们需要认真的审视自己,这些优势对当下的我们是否是优势,这些挑战对当下的我们是否能hold住。

关键问题一:如何划分微服务

这里有几点心得:
  1. 区分边界服务和内部服务。所谓边界服务就是会接受公网流量的服务,比如处理 HTTP 请求的服务,反之就是内部服务。边界服务和内部服务在安全方面的考虑是不一样的(例如边界服务需要做针对User的鉴权等等)。
  2. 区分基础和业务服务。业务服务追求变化,feature,基础服务追求稳定,性能。
  3. 微服务的调用,一定要考虑调用会失败。微服务的提供方,一定要考虑调用可能会有bug(比如死循环)。
  4. 没有把握的做到合理拆分的时候可以选择先不拆,等到发现瓶颈了自然就知道怎么拆了。


关键问题二:微服务和 DevOps

微服务除了是个技术活,更是个管理活。一定得有和微服务相融的团队组织方式和工作流程。

首先就是要成立架构组

架构组要负责微服务的基础设施的建设,例如 CI/CD 系统,Kubernetes 集群, Service Mesh 集群,监控报警系统,日志收集系统,基础工具和库的构建维护。

总之架构组在团队中的意义,可以理解为是为内部构建 PaaS。

业务团队(无论是写基础业务的还是其他业务的)能从架构组那里得到一个稳定可靠的 PaaS 以及一揽子配套工具。

其次每个微服务要有固定的团队负责(实践中,可以是对微服务分组,每组微服务对应一组特定的人)

每个团队负责自己的微服务的从开发到上线,到维护,到性能调优,到Debug。

总之,每个团队要对自己的微服务全权负责

在我们的实践中,每个微服务团队由2-3人组成,有一个master,负责 DevOps 流程的建设,权限的分配等等一系列工作。

例如我们的 DevOps 是基于 GitLab CI 和 Kubernetes 的,所以master要负责 gitlab-ci.yml ,以及所有自己服务相关的 Kubernetes 的 deployment、job、service 等等的编写维护。

此外项目线上运行的状况也要自己负责,例如设计自己特异的监控指标,报警策略等等。这些反过来会指导自己的维护和调优

不同的微服务团队相互独立,通过gRPC 和 Celery 实现数据交换。

服务间通信

从通信类型的角度看,大概有三种类型:同步调用,异步调用,广播。

在微服务的设计之初要想清楚调用关系以及调用方式,哪些需要同步,哪些可以异步,哪些需要广播,最好团队内部要有统一的认识。

然后就是要确定好调用协议了,例如常见的选择有:
  • 同步调用:HTTP REST、gRPC、thrift,etc。
  • 异步调用:Sidekiq、Celery,etc。对应的backend有 Redis,各类 AMQP 实现,Kafka 等等。
  • 广播:各类 AMQP 实现,Kafka,etc。


对于如何选择,我们需要从很多角度去思考。网上有大量的 “X vs Y”的文章或问答。其实无非就是从几个角度:性能,成熟度,易用性,生态,技术团队。我们的建议是:优先选择社区活跃,以及和自己团队技术栈相融的。社区活跃的技术往往代表了趋势和生态,而团队技术栈的相容性则是能否落地成功的保证。

比如当时扇贝选择gRPC作为同步调用的接口协议。主要考虑的就是两点:1. 社区活跃,发展非常迅速;2. 基于 HTTP/2,与我们的技术栈相容。

除了选择协议,更加重要的应该就是接口文档的管理了。最好接口文档和代码是强相关的,就像 gRPC 的 proto 和生成出来的代码那样。否则人往往是有惰性的,很有可能代码改了文档没改。

总之,关于服务间通信,我们需要做好:
  • 确定接口规范,什么用同步调用,什么用异步,什么用广播;同步调用用什么协议,异步用什么
  • 确定接口协议,以及思考接口文档的管理,接口文档与代码之间如何建立强联系


服务治理

服务治理是个非常大的话题,真的要铺开来讲,分享的时间肯定讲不完。

这里我们想先简单地看一下服务治理要解决的问题,以及现在的一些解决方案,发展趋势。最后以扇贝为例,简单介绍下在一个真实的百万日活的生产环境中,服务治理是如何做的。

什么是服务治理?

微服务化带来了很多好处,例如:通过将复杂系统切分为若干个微服务来分解和降低复杂度,使得这些微服务易于被小型的开发团队所理解和维护。然而也带来了很多挑战,例如:微服务的连接、服务注册、服务发现、负载均衡、监控、AB测试,金丝雀发布、限流、访问控制,等等。

这些挑战即是服务治理的内容。

除了新星 Service Mesh,现在还有诸如 Spring Cloud 等方案。

这些方案的问题是:1. 对代码有侵入,也就意味着,如果想换框架,得改很多东西。2. 语言特异性(Java),如果我们用的是 Go/Python,或者我们的微服务不全是 Java,就搞不定了。

之后便是 Service Mesh 方案了,其实 DockOne 上关于 Service Mesh 的介绍太多了,我这里不想浪费大家时间,就不赘述了,我就直接介绍下我们是怎么用 Service Mesh 的。

扇贝的 Service Mesh

扇贝的 Service Mesh 是基于 Envoy 配合 Kubernetes 实现的。

首先介绍一些前提:扇贝的微服务全部容器化,编排系统基于 Kubernetes,同步调用基于 gRPC,异步基于 celery[rabbitmq]。开发语言以 Python 3、Node.js、Go 为主,Service Mesh 基于 Envoy。

总体的方案是:Envoy 以 DaemonSet 的形式部署到 kubernetes 的每个 Node 上,采用 Host 网络模式。 所有的微服务的 Pod 都将 gRPC 的请求发送到所在 Node 的 Envoy,由 Envoy 来做负载均衡。如下图所示:
1.jpeg

这里做一些详细的解释:
  1. Envoy 中的 Route 类似于 Nginx 的 Location,Cluster 类似于 Nginx 的 upstream,Endpoint 对应于 Nginx 的 upstream 中的条目。
  2. 之所以选择 Envoy 而没有用 Linkerd,是因为当时 Envoy 是对 HTTP/2 支持最好的。且资源消耗更少。
  3. 之所以选择 Host 网络模式是为了最大化提高性能。
  4. 对于 Enovy 而言,服务发现就是告诉 Envoy,每个 Cluster 背后提供服务的实例的IP(对应于 Kubernetes 也就是 Pod 的IP)是什么。
  5. 最开始服务发现是利用 Kubernetes 的 DNS,因此,在创建Service的时候,要使用 ClusterIP: None。
  6. 后来的服务发现是基于 Kubernetes 的 Endpoint API 实现了 Enovy 的 EDS(这个项目我们日后会开源到 GitHub 上)。
  7. 对于 Envoy 而言,实现熔断,只要实现 rate limit service 就行了。我们基于Lyft/ratelimit 实现的 rate limit service。
  8. 微服务之间的调用情况都可以通过 Envoy 的 statistic API反映出来,所以我们针对 statistic API做服务调用的监控报警。
  9. 同理,调用日志也可以利用 Envoy 的 Access Log 来实现。
  10. 之所以不用istio,是因为生产环境真不能用。性能问题,稳定问题太过突出。


监控报警和日志收集

我们的监控报警方案是基于 Prometheus 做的,分了三个级别:cluster级别,包括Node的状况,集群核心组件的状况,等等。
  • 集群级别是架构组关心的, 基础服务级别是所有人都要关心的,业务自定义级别就是微服务团队关心的
  • 基础服务级别:包括 Service Mesh 的各种指标(例如请求量,请求时间等等),Redis、MySQL、RabbitMQ 等
  • 业务自定义级别:包括各个微服务团队自己设计的监控指标


报警直接报到同一个钉钉群,相互监督。
2.png

上面这个例子就是我们的“单词大师”团队自定义的监控指标,包括他们特有的 “Match API Received”(也就是对战匹配API调用次数)等等。

至于日志的话,基本还是沿用:Container -> stdout -> Filebeat -> Kafka -> Logstash -> ES(定期 archive)。所有容器的日志都是直接打到stdout/stderr 的。

多说一句,我们的日志系统出了方便给程序员Debug,同时还有承担打点数据的收集的职责。所有的打点数据,也是通过日志系统,在 Kafka -> Logstash 那一步分拣出来的。
3.jpeg

例如上图就是我们分拣的部分 gRPC 的调用日志。

Q&A

Q:Prometheus 只采集 Kubernetes 集群的指标数据,那非 Kubernetes 的指标数据用什么采集?

A:都是 Prometheus,应用是可以装 Prometheus 的client,暴露自己的metrics的,然后 Redis 什么的也都有exporter。
Q:请问下日志如果都落到 stdout 的话,会不会造成格式不一致,从而导致收集和归档困难?比如说业务日志和中间件日志都打到 stdout,在 Filebeat 或者 Logstash 内是怎么处理的?另外容器日志定期删除的策略是什么?

A:这是个好问题,需要分拣的日志会在 message 的开头做特定的标记。例如 [DATABEAT] 表示打点数据。ES 有很多工具可以做定期 archive 的,策略比如保留多少天,多少月,根据不同的数据的策略不同。
Q:Envoy 对性能的损耗有多大,自身的性能消耗有多大?

A:Envoy 是有性能损耗的,因为 API 的平均响应时间差不多在 100-150 ms,同时相比其带来的好处,我们认为这些损耗是值得的,所以我们具体并没有测算。
Q:gRPC 做了服务注册吗,gRPC 新增减少字段兼容性如何处理的?
A:服务注册/发现是基于 Kubernetes 和我们写的 Kubernetes Endpoint 到 Envoy eds 的转化服务做的。

Q:Istio 和 Envoy 什么关系?

A:Istio 包括一个控制面 + 数据面,Envoy 可以作为 Istio 的数据面的一个实现。
以上内容根据2018年4月10日晚微信群分享内容整理。 分享人丁彦,扇贝英语技术总监。主导了扇贝DevOps、容器化、微服务化的生产实践。曾任暴走漫画技术总监,负责数据和内容推荐相关系统。翻译过《Git版本控制系统》,东南大学生物信息专业毕业。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesa,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

1 个评论

丁老师,我们也在调研istio/envoy的可行性,您提供的实战资料对我们很有帮助,有些问题想请教一下:
1. envoy的配置(路由策略、熔断限流等) 您是怎么动态更新的?
2. 同一pod内的容器如果对外暴露端口相同,节点上的envoy怎么路由到制定容器呢?
3. 您提到的istio性能问题,可能是因为跟mixer的频繁交互引起的,您有没有试试禁用mixer?另外您提到的稳定问题能说具体一些吗?

Thanks in advance!
Sean

要回复文章请先登录注册