Spring Cloud 与 Service Mesh,如何选择?

导读

Spring Cloud 基于Spring Boot开发,提供一套完整的微服务解决方案,具体包括服务注册与发现,配置中心,全链路监控,API网关,熔断器,远程调用框架,工具客户端等选项中立的开源组件,并且可以根据需求对部分组件进行扩展和替换。

Service Mesh,这里以Istio(目前Service Mesh具体落地实现的一种,且呼声最高)为例简要说明其功能。 Istio 有助于降低这些部署的复杂性,并减轻开发团队的压力。它是一个完全开源的服务网格,可以透明地分层到现有的分布式应用程序上。它也是一个平台,包括允许它集成到任何日志记录平台、遥测或策略系统的 API。Istio的多样化功能集使你能够成功高效地运行分布式微服务架构,并提供保护、连接和监控微服务的统一方法。

从上面的简单介绍中,我们可以看出 Service Mesh 比起 Spring Cloud 并不落下风,那这两种架构应该如何选择呢?

Service Mesh 的优势

2019 年,经过前几年的发展,Service Mesh 在国内开始大面积开花结果,互联网大厂纷纷开始走上实践道路(例如蚂蚁金服的SOFASTACK),也有大部分企业已经开始接触Service Mesh。

可以认为 Service Mesh 将是微服务的未来,是可以替换目前的事实标准 Spring Cloud 的存在,其原因,总结下来,有四个方面:

与 Spring Cloud 功能重叠

来简单看一下他们的功能对比:

功能列表 Spring Cloud Isito
服务注册与发现 支持,基于Eureka,consul等组件,提供server,和Client管理 支持,基于XDS接口获取服务信息,并依赖“虚拟服务路由表”实现服务发现
链路监控 支持,基于Zikpin或者Pinpoint或者Skywalking实现 支持,基于sideCar代理模型,记录网络请求信息实现
API网关 支持,基于zuul或者spring-cloud-gateway实现 支持,基于Ingress gateway以及egress实现
熔断器 支持,基于Hystrix实现 支持,基于声明配置文件,最终转化成路由规则实现
服务路由 支持,基于网关层实现路由转发 支持,基于iptables规则实现
安全策略 支持,基于spring-security组件实现,包括认证,鉴权等,支持通信加密 支持,基于RBAC的权限模型,依赖Kubernetes实现,同时支持通信加密
配置中心 支持,springcloud-config组件实现 不支持
性能监控 支持,基于Spring cloud提供的监控组件收集数据,对接第三方的监控数据存储 支持,基于SideCar代理,记录服务调用性能数据,并通过metrics adapter,导入第三方数据监控工具
日志收集 支持,提供client,对接第三方日志系统,例如ELK 支持,基于SideCar代理,记录日志信息,并通过log adapter,导入第三方日志系统
工具客户端集成 支持,提供消息,总线,部署管道,数据处理等多种工具客户端SDK 不支持
分布式事务 支持,支持不同的分布式事务模式:JTA,TCC,SAGA等,并且提供实现的SDK框架 不支持
其他 …… ……

从上面表格中可以看到,如果从功能层面考虑,Spring Cloud与Service Mesh在服务治理场景下,有相当大量的重叠功能,从这个层面而言,为Spring Cloud向Service Mesh迁移提供了一种潜在的可能性。

服务容器化

在行业当前环境下,还有一个趋势,或者说是现状。越来越多的应用走在了通往应用容器化的道路上,或者在未来,容器化会成为应用部署的标准形态。而且无论哪种容器化运行环境,都天然支撑服务注册发现这一基本要求,这就导致Spring Cloud体系应用上容器的过程中,存在一定的功能重叠,有可能为后期的应用运维带来一定的影响,而Service Mesh恰恰需要依赖容器运行环境,同时弥补了容器环境所欠缺的内容(后续会具体分析)。

术业有专攻

从软件设计角度出发,我们一直在追求松耦合的架构,也希望做到领域专攻。例如业务开发人员希望我只要关心业务逻辑即可,不需要关心链路跟踪,熔断,服务注册发现等支撑工具的服务;而平台支撑开发人员,则希望我的代码中不要包含任何业务相关的内容。而Service Mesh的出现,让这种情况成为可能。

语言壁垒

目前而言Spring Cloud虽然提供了对众多协议的支持,但是受限于Java技术体系。这就要求应用需要在同一种语言下进行开发(这不一定是坏事儿),在某种情况下,不一定适用于一些工作场景。而从微服务设计考虑,不应该受限于某种语言,各个服务应该能够相互独立,大家需要的是遵循通信规范即可。而Service Mesh恰好可以消除服务间的语言壁垒,同时实现服务治理的能力。

选择和迁移

从上文中我们得知 Service Mesh 的各种优势,那么,如何让 Service Mesh 在我们的项目中落地呢?接下来我们分几种情况讨论下

全新的项目

如果是小型项目,轻业务、重流量、需求快速变化,可以参考我之前写的文章轻量型互联网应用架构方式,语言层面选择 Node.js + Mongodb/Mysql.

如果项目属于业务复杂类型,语言层面可以选择 Java with Kotlin。

当然,全新项目,容器化是必须的。

而且如果你的工期紧张,可以考虑渐进式地推进, 先 k8s 后 Service Mesh。

先把项目服务容器话,在 k8s 上部署起来,此时已经能够享受 k8s 带来的一部分服务治理能力了。例如:

  • 服务发现
  • 负载均衡
  • API 网关
  • 服务路由

Istio 基于 k8s,项目有了 k8s 的基础,落地 Istio 也可以顺水渠成。

Spring Cloud 项目的迁移

现存的 Spring Cloud 项目的迁移方案,可以参考这篇文章 服务迁移之路 | Spring Cloud向Service Mesh转变

简单来说就是先把服务容器化,然后逐步用 Service Mesh 替换 Spring Cloud 的功能。

其他语言项目的迁移

和上面的方案大同小异,前提条件都是先把服务容器话。

参考文章

服务迁移之路 | Spring Cloud向Service Mesh转变

轻量型互联网应用架构方式

使用 Helm + Kubernetes 部署微服务

前言

在之前的文章中,我提出了轻量型互联网应用架构方式,其中核心思想就是依赖 Kubernetes 实现服务治理。

之后,我尝试在团队中进行落地改造,第一步,就是把目前的服务搬上 Kubernetes。

在这个实践过程中,有些经验可以分享一下。

落地经验

在 Kalengo ,开发隐约(并没有 title 上的区别,但实际工作内容有区分)可以分为两类,一类主要处理业务需求,另一类做基础架构。

考虑到 Kubernetes 的复杂性,如果要做到开发不清楚 Kubernetes 原理和部署流程的情况下也能使用这一套工具,要做到以下这些点:

  1. 测试环境使用 Rancher管理 k8s 集群。
    Rancher 提供了 web 页面,方便开发直接做环境变量修改,镜像变更和部署等操作。

  2. 在 Rancher 中启用 Helm 模板
    把做好的目标发布到内部的 Helm 仓库,然后在配置到 Rancher 商店中,开发如果要新开一套测试环境,直接在 Rancher 商店里点击操作即可。只有模板制作者或运维才需要在本地安装 kubectl 和 helm 等工具

  3. 测试环境使用命名空间实现多租户
    不同开发的测试环境使用命名空间隔离,避免冲突

  4. 内部服务的 service name 要固定
    服务 A 访问服务 B,在没有 k8s 之前,我们是这样做的:
    给服务 B 挂在到负载均衡上:http://b.env.kalengo.cn, 然后在服务 A 通过这个地址访问,不同环境,这个地址不一样,所以就要把地址配置到环境变量中。

但是在 k8s 里,我们可以给服务 B 固定一个 service name:http://b-svc, 服务 A 通过这个地址访问,在任何环境都是这个地址。

总结

做好了 helm 模板之后,可以做到一键部署完整的服务集合。

无论是开发本地集群,测试环境还是阿里云,只需要修改对应环境参数即可。

同时借助于 Rancher 提供的优秀的 Web 管理能力,极大简化了微服务部署和管理难度。

轻量型互联网应用架构方式

轻量型 Web 架构模式

前言

说到互联网应用架构,就绕不开微服务,当下(2019)最热门的微服务架构体系应该还是 Spring Cloud 和 Dubbo,阿里也推出了自己的 Spring Cloud 实现 Spring Cloud Alibaba。这类框架围绕微服务体系提供了大而全的功能,包括服务发现、治理、流量监控、配置管理等,让人向往。

但是其缺点也是比较明显:

  • 限定于 Java 体系,其他编程语言无法享受这个体系
  • 侵入应用,几乎每个 Java 应用都挂载一堆 Spring 相关的 jar 包

所以目前社区发展的新方向是:云原生。

其中的翘楚便是 istio,基于 kubernetes 的 sidecar 模式,把 Sprint Cloud 做的大部分工作下沉到基础应用层面,让应用层无感。
有了 Istio 之后,微服务应用实现了大减负,所以我重新思考了对于小型互联网公司来说,当下最适合的架构模式应该是什么

Web 架构模式

适用场景:小型互联网公司,业务多而小,追求开发效率。
解决方案:把重复工作隐藏起来,让应用层轻量快跑

具体有两个方向
基础架构层面:使用 Kubernetes 和 Istio,提供微服务所需要的功能支持
应用层面:针对 Nodejs 后端编程领域,提供一个最佳实额

整体架构图:

基础架构实践

目前还没有足够的时间完成落地实践,只能说一下理论规划。
现在是云时代,所以直接用 Kubernetes 来做应用编排就可以了。
后期可以落地 Istio,就可以保证微服务们稳定运行。

容器编排

Kubernetes

日志收集

  • 在 k8s 集成 elk
  • 输入日志到阿里云 nas

应用观测

Istio 提供 Grafana 请求分析、 Jaeger链路追踪、告警等

流量控制

  • 权限校验
  • 加密
  • 熔断限流

Web 应用架构

在应用层,我最终选择是使用 Node.js 语言来做主要的业务开发。

使用 Koa + 自研框架。

语言的选择,为什么是 Node.js

主要原因是我目前所在团队技术栈在 Node 积累比较多。

其次原因是有了 typescript 之后,使用 node 编写后端应用变得更加可维护了,而且 node 小而快(开发效率快,当然IO密集型的应用运行效率也不差)非常适合微服务场景。

最后,Node 的上手难度真的很低。

当然要使用其他语言也是可以的,如果有着复杂业务逻辑,可以使用 Kotlin + Spring Boot 这组套件,但不包括 Spring Cloud

Kotlin 能完美复用 JVM 生态,也可以避免 Java 语言的一些繁琐的写法,甚至后期还可以用上 Kotlin 的协程,来替代 Java 目前的多线程并发模型。

当然,本文讨论的是 Web 应用架构,如果你的应用是数据处理方面,本文无法提供任何帮助。而在 Web 领域,Python 和 PHP 对比起 Node,其实并没有优势,所以就不考虑了。

Web 框架如何选

Node 的 Web 框架还挺多的,我比较熟悉的有:

  • Sails.js 大而全,甚至有自己的 orm, node 世界的 ruby on rails
  • Express 基础 web 框架
  • Koa 更基础的 web 框架,甚至两 body-parse 都不自带
  • Thinkjs Thinkphp 吧
  • Egg 阿里出品,挺受欢迎的
  • NestJS node 世界的 Spring MVC

我公司早期使用 sails,看中其集成的丰富功能,但是后期 sails 的成长速度跟不上社区,对一些新特性 Generator 等不能及时兼容,而且太笨重了。

所以团队在 17 年左右转型选择了 Koa,然后自己组装实践各个基础功能,这个做法的好处是高度自定义,非常契合公司发展需要。

社区优秀的框架们

在 17 年之后,相继涌现一些优秀框架,像 egg 和 nestjs,当时我们并没有采用他们。

Egg 本身是优秀的框架,但是它是为中台打造的,但我公司需要用 Node 完成本来是 Java 做的事情,我们会用 Node 做比较重的业务,Egg 在这方便并不适合,例如没有模块划分的概念。

比起 Egg ,NestJS 就更适合我们,当时看它的文档就能感受出来,NestJS 的作者们是真的拿这个框架来做后端业务的,文档里讲到了我们日常碰到的很多痛点:

  • 模块划分
  • 循环引用
  • OpenAPI
  • IOC 依赖注入
  • CRUD

当时感觉像是找到了真爱,但是实践过后,发现一些问题:

  • 编码规范:NestJS 有自己的一套规范,而且它封装得足够多,基本上无法修改这套规范,只能妥协。
  • 集成测试:我们在早期使用 Koa 的过程中,沉淀了一套集成测试方案,特别是对 mongodb,我们做了一些工具可以自动注入和清除测试数据,要在 NestJs 实现这个功能,需要有足够的时间对其改造

基于上面两个原因,NestJS 并没有在团队推广开来。

自研的方向

社区的框架既然不合适,那就博采众长,自己组装一套框架出来,所以我最近捣鼓出了 @akajs, 预期说这个东西是框架,更准确的说法是,Kalengo团队的Node后端开发最近实践集合。就是把后端常用的东西集合打包起来,包含:

  • IOC 依赖注入
  • 注解式路由
  • CRUD 我们很多项目非常简单,重复的 CRUD 工作必须简化
  • Mongoose + Typescript 封装
  • Redis + Redlock 封装
  • 集成测试支持
  • 常用 Util,如日期数字处理等
  • 请求参数校验和全局异常处理

此外还有些最佳实践

  • OpenAPI 文档自动生成
  • Docker 支持
  • UML 设计
  • 模块划分

这些我们则通过项目模板来提供。

有了这套工具,后端可以更加轻量和统一,开发可以短时间内搭建起一个完备的后端服务。

总结

这套架构方式,不论是基础架构层面,还是应用层面,都是通过抽离通用的功能,把服务治理的东西抽出下沉到 Istio,把后端通用的工具和实践抽出放在 @akajs 和项目模板中,以此来简化上层应用。

这样一线的后端开发,可以专注于业务逻辑的实现,不必在为基础设施烦恼。毕竟小公司要生存,“快”是很重要的。

关于编程基本功

熊节,《重构》中文版译者,他最近接受infoQ的采访,表达了“80% 的国内开发者缺乏基本功?” 这个观点,同时他认为,练习编程基本功的方式是:只看书是不够的,需要反复的练习,所以他创建了一个“测试驱动开发实战营”项目,为大量程序员创造一个刻意练习的环境,帮他们提升基本功。

我今天看了熊节发布的重构视频秀第二集,虽然说重构这本书我看过两遍,视频里提到的技巧也熟记于心,但还是能从视频中收获一些新东西。视频里展现出来的小步快跑的重构节奏,是我之前没有掌握好的,需要加强练习。

BTW:我是非常赞同熊节对于基本功的态度的,特别是见过一些程序员一顿瞎搞就是不出成果的时候。
他还有一个观点也让我茅塞顿开:

我有一个观察:越是基本功不怎么扎实、操作不怎么熟练的同学,越是在拿到问题时忍不住会想很多,一下子就扎到纷繁芜杂的泥潭里面去,怎么也理不清头绪,寸步难行。

原来人家不是想太多,而是基础差😂。

未来已来:云原生 Cloud Native

前言

自 2013 年容器(虚拟)技术(Docker)成熟后,后端的架构方式进入快速迭代的阶段,出现了很多新兴概念:

  • 微服务
  • k8s
  • Serverless
  • IaaS:基础设施服务,Infrastructure-as-a-service
  • PaaS:平台服务,Platform-as-a-service
  • SaaS:软件服务,Software-as-a-service
  • Cloud Native: 云原生
  • Service Mesh

后端架构的变迁和云计算的发展密切相关,架构其实在不断地适应云计算,特别是云原生,被誉为未来架构,在 2019 年,云原生落地方案 Service Mesh 在国内外全面开花,我认为,未来已来。

接下来,我们将:

  • 梳理后端架构演化史,回顾后端架构发展历程;
  • 回顾云服务发展历程,探讨云原生概念;
  • 梳理云原生实现方案 Service Mesh 的发展历程;
  • 介绍 Service Mesh 的代表 Istio 的亮眼功能;

后端架构演化史

集中式架构

集中式架构又叫单体式架构,在Web2.0模式并未大规模兴起时十分流行。后来,基于Web应用的B/S(Browser/Server)架构逐渐取代了基于桌面应用的C/S(Client/Server)架构。B/S架构的后端系统大都采用集中式架构,它当时以优雅的分层设计,统一了服务器后端的开发领域。

集中式应用分为标准的3层架构模型:数据访问层M、服务层V和逻辑控制层C。每个层之间既可以共享领域模型对象,也可以进行更加细致的拆分。

其缺点是

  • 编译时间过长;
  • 回归测试周期过长;
  • 开发效率降低等;
  • 不利于更新技术框架

分布式系统架构

对于互联网应用规模的迅速增长,集中式架构并无法做到无限制的提升系统的吞吐量,而分布式系统架构在理论上为吞吐量的上升提供了无限扩展的可能。因此,用于搭建互联网应用的服务器也渐渐地放弃了昂贵的小型机,转而采用大量的廉价PC服务器。

容器技术新纪元 Docker

分布式架构的概念很早就出现,阻碍其落地的最大问题是容器技术不成熟,应用程序在云平台运行,仍然需要为不同的开发语言安装相应的运行时环境。虽然自动化运维工具可以降低环境搭建的复杂度,但仍然不能从根本上解决环境的问题。

Docker的出现成为了软件开发行业新的分水岭;容器技术的成熟,也标志技术新纪元的开启。Docker让开发工程师可以将他们的应用和依赖封装到一个可移植的容器中。就像当年智能手机的出现改变了整个手机行业的游戏规则一样,Docker也大有席卷整个软件行业,并且进而改变行业游戏规则的趋势。通过集装箱式的封装,开发和运维都以标准化的方式发布的应用,异构语言不再是桎梏团队的枷锁。

在 Docker 之后,微服务得以流行开来

微服务架构

微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术。

微服务优势

  • 可扩展
  • 可升级
  • 易维护
  • 故障和资源的隔离

微服务的问题

但是,世界上没有完美无缺的事物,微服务也是一样。著名软件大师,被认为是十大软件架构师之一的 Chris Richardson 曾一针见血地指出:“微服务应用是分布式系统,由此会带来固有的复杂性。开发者需要在 RPC 或者消息传递之间选择并完成进程间通讯机制。此外,他们必须写代码来处理消息传递中速度过慢或者不可用等局部失效问题。”

在微服务架构中,一般要处理以下几类问题:

  • 服务治理:弹性伸缩,故障隔离
  • 流量控制:路由,熔断,限速
  • 应用观测:指标度量、链式追踪

解决方案 Spring Cloud(Netflix OSS)

这是一个典型的微服务架构图

Spring Cloud 体系提供了服务发现、负载均衡、失效转移、动态扩容、数据分片、调用链路监控等分布式系统的核心功能,一度成为微服务的最佳实践。

Spring Cloud 的问题

如果开始构建微服务的方法,肯定容易被 Netflix OSS/Java/Spring/SpringCloud 所吸引。但是要知道你不是Netflix,也不需要直接使用 AWS EC2,使得应用程序变得很复杂。如今使用 docker 和采用 memos/kubernetes 是明智之举,它们已经具备大量的分布式系统特性。在应用层进行分层,是因为 netflix 5年前面临的问题,而不得不这样做(可以说如果那时有了kubernetes,netflix OSS栈会大不相同)。

因此,建议谨慎选择,按需选择,避免给应用程序带来不必要的复杂度。

的确 SpringCloud 方案看起来很美好,但是它具有非常强的侵入性,应用代码中会包含大量的 SpringCloud 模块,而且对其他编程语言也不友好。

Kubernetes

Kubernetes 出现就是为了解决 SpringCloud 的问题,不侵入应用层,在容器层解决问题。

Kubernetes 起源

Kubernetes最初源于谷歌内部的Borg,提供了面向应用的容器集群部署和管理系统。

Kubernetes的目标旨在消除编排物理/虚拟计算,网络和存储基础设施的负担,并使应用程序运营商和开发人员完全将重点放在以容器为中心的原语上进行自助运营。

Kubernetes 也提供稳定、兼容的基础(平台),用于构建定制化的 workflows 和更高级的自动化任务。 Kubernetes 具备完善的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建负载均衡器、故障发现和自我修复能力、服务滚动升级和在线扩容、可扩展的资源自动调度机制、多粒度的资源配额管理能力

Kubernetes 还提供完善的管理工具,涵盖开发、部署测试、运维监控等各个环节。

Service Mesh

Service Mesh 是对 Kubernetes 的增强,提供了更多的能力。

2018年9月1日,Bilgin Ibryam 在 InfoQ 发表了一篇文章 Microservices in a Post-Kubernetes Era,中文版见后 Kubernetes 时代的微服务(译文有些错误,仅供参考)。

文中作者的观点是:在后 Kubernetes 时代,服务网格(Service Mesh)技术已完全取代了使用软件库实现网络运维(例如 Hystrix 断路器)的方式。

如果说 Kubernetes 对 Spring Cloud 开了第一枪,那么 Service Mesh 就是 Spring Cloud 的终结者。

总结

最后我们用一个流程图来描述后端架构的发展历程

每个关键节点的大概时间表

架构/技术 时间/年份
集中式架构 ~
分布式架构 ~
Docker 2013
微服务 2014
Spring Cloud 2014
Kubernetes 成熟 2017
Service Mesh 2017

可以看出,微服务生态这里,Spring Cloud 为代表的这条路已经后继无人了,未来属于 Service Mesh 。
Service Mesh 经过2年的发展,目前 Service Mesh 已经足够成熟,已经有生产落地的案例,我们接下来就看看 Service Mesh,在此之前,我们要先理解一个概念,云原生。

云原生 Cloud Native

如何理解“云原生”?之所以将这个话题放在前面,是因为,这是对云原生概念的最基本的理解,而这会直接影响到后续的所有认知。

注意:以下云原生的内容将全部引用敖小剑的 畅谈云原生(上):云原生应用应该是什么样子? 这篇文章,图画得太好了。

云原生的定义一直在发展,每个人对云原生的理解都可能不同,就如莎士比亚所说:一千个人眼中有一千个哈姆雷特。

2018 年 CNCF (Cloud Native Computing Foundation)更新了云原生的定义。

这是新定义中描述的代表技术,其中容器和微服务两项在不同时期的不同定义中都有出现,而服务网格这个在 2017 年才开始被社区接纳的新热点技术被非常醒目的列出来,和微服务并列,而不是我们通常认为的服务网格只是微服务在实施时的一种新的方式。

那我们该如何理解云原生呢?我们尝试一下,将 Cloud Native 这个词汇拆开来理解,先看看什么是 Cloud。

什么是云 Cloud

快速回顾一下云计算的历史,来帮助我们对云有个更感性的认识。

云计算的出现和虚拟化技术的发展和成熟密切相关,2000 年前后 x86 的虚拟机技术成熟后,云计算逐渐发展起来。

基于虚拟机技术,陆续出现了 IaaS/PaaS/FaaS 等形态,以及他们的开源版本。

2013 年 docker 出现,容器技术成熟,然后围绕容器编排一场大战,最后在 2017 年底,kubernetes 胜出。2015 年 CNCF 成立,并在近年形成了 cloud native 生态。

在这个过程中,云的形态一直变化,可以看到:供应商提供的功能越来越多,而客户或者说应用需要自己管理的功能越来越少。


架构也在一直适应云计算的变化

什么是原生 Native

在回顾完云计算的历史之后,我们对 Cloud 有更深的认识,接着继续看一下:什么是 Native?
字典的解释是:与生俱来的。
那 Cloud 和 native 和在一起,又该如何理解?

这里我们抛出一个我们自己的理解:云原生代表着原生为云设计。详细的解释是:应用原生被设计为在云上以最佳方式运行,充分发挥云的优势。

这个理解有点空泛,但是考虑到云原生的定义和特征在这些年间不停的变化,以及完全可以预料到的在未来的必然变化,我觉得,对云原生的理解似乎也只能回到云原生的出发点,而不是如何具体实现。

Cloud Native 是道,Service Mesh 是术

那在这么一个云原生理解的背景下,我再来介绍一下我对云原生应用的设想,也就是我觉得云原生应用应该是什么样子。

在云原生之前,底层平台负责向上提供基本运行资源。而应用需要满足业务需求和非业务需求,为了更好的代码复用,通用型好的非业务需求的实现往往会以类库和开发框架的方式提供,另外在 SOA/ 微服务时代部分功能会以后端服务的方式存在,这样在应用中就被简化为对其客户端的调用代码。

然后应用将这些功能,连同自身的业务实现代码,一起打包。

而云的出现,可以在提供各种资源之外,还提供各种能力,从而帮助应用,使得应用可以专注于业务需求的实现。
非业务需求相关的功能都被移到云,或者说基础设施中去了,以及下沉到基础设施的中间件。

以服务间通讯为例:需要实现上面列举的各种功能。

SDK 的思路:在应用层添加一个胖客户端,在这个客户端中实现各种功能。

Service Mesh 的思路,体现在将 SDK 客户端的功能剥离出来,放到 Sidecar 中。就是把更多的事情下沉,下沉到基础设施中。

在用户看来,应用长这样:

云原生是我们的目标,Service Mesh 交出了自己的答卷,接下来我们可以回到 Service Mesh 这里了。

Service Mesh

其中文译名是服务网格,这个词最早使用由开发Linkerd的Buoyant公司提出,并在内部使用。

定义

服务网格的基本构成

纷争 2017

2017 年年底,当非侵入式的 Service Mesh 技术终于从萌芽到走向了成熟,当 Istio/Conduit 横空出世,人们才惊觉:微服务并非只有侵入式一种玩法,更不是 Spring Cloud 的独角戏!

解读 2017 之 Service Mesh:群雄逐鹿烽烟起

文章总结一下:
创业公司 Buoyant 的产品 Linkerd 开局拿下一血;
Envoy 默默耕耘;
从 Google 和 IBM 联手推出 Istio,Linkerd 急转直下;
2017 年底 Buoyant 推出 Conduit 背水一战;
Nginmesh 与 Kong 低调参与;

百家争鸣 2018

2018 年,Service Mesh 又多了哪些内容呢?在 2018 年,Service Mesh 在国内大热,有多家公司推出自己的 Service Mesh 产品和方案,Service Mesh 更加热闹了。
详细见这篇文章 下一代微服务!Service Mesh 2018 年度总结

文章总结一下:
Service Mesh 在国内大热,有多家公司加入战场;
Istio 发布1.0,成为最受欢迎的 Service Mesh 项目,获得多方支持;
Envoy 继续稳扎稳打,Envoy 被 Istio 直接采用为数据平面,有望成为数据平面标准;
Linkerd1.x 陷入困境,Conduit 小步快跑,但响应平平,Buoyant 公司决定合并产品线,Linkerd1.x + Conduit = Linkerd2.0;
更多的公司参与 Service Mesh,国外有 Nginx、Consul、Kong、AWS等,国内有蚂蚁金服、新浪微博、华为,阿里 Dubbo,腾讯等;

持续发展 2019

2019 将会听到更多 Service Mesh 的声音,请关注Service Mesh 中文社区

Istio

前文讲到 Istio 是当前最受欢迎的 Service Mesh 框架,一句话定义 Istio:一个用来连接、管理和保护微服务的开放平台。
它能给我们的微服务提供哪些功能呢?

连接

  • 动态路由
  • 超时重试
  • 熔断
  • 故障注入

详细见官网介绍

保护

安全问题一开始就要做好,在 Istio 实现安全通讯是非常方便的。

Istio 支持双向 TLS 加密

见官方文档

控制

速率限制
黑白名单

见官方文档

观测

  • 指标度量:每秒请求数,Prometheus 与 Grafana
    使用 Grafana 观测流量情况

  • 分布式追踪:Jaeger 或 Zipkin
    快速观测调用链路

  • 日志:非应用日志

  • 网格可视化
    快速理清服务的关系

总结

虚拟化技术推动这云计算技术的变革,顺带也影响了后端架构的演进,目前我们身处云时代,将会有更多的元原生应用出现,Istio 作为其中的佼佼者,值得你投入一份精力了解一下。

学习资料/指引

Service Mesh 中文社区 上面提供了丰富的学习资料。

搭建 Kubernetes 集群会比较麻烦,推荐几种方式。主要原因是很多镜像需要翻墙才能下载。

  1. Docker Desktop 自带的 Kubernetes 集群
  2. 使用 Rancher2.0 搭建 Kubernetes 集群
  3. 在 Google Cloud 上直接开集群,可以领 300 美金的体验金,需要翻墙

不推荐 MiniKube,翻墙和代理问题非常难搞。
再附上 Docker 设置代理的方式

在线体验 Istio

参考资料

kubernetes-handbook

istio-handbook

微服务学习笔记

畅谈云原生(上):云原生应用应该是什么样子?

Service Mesh:下一代微服务?

从架构到组件,深挖istio如何连接、管理和保护微服务2.0?

分布式事务的 N 种实现

目录

需求缘起

在微服务架构中,随着服务的逐步拆分,数据库私有已经成为共识,这也导致所面临的分布式事务问题成为微服务落地过程中一个非常难以逾越的障碍,但是目前尚没有一个完整通用的解决方案。

其实不仅仅是在微服务架构中,随着用户访问量的逐渐上涨,数据库甚至是服务的分片、分区、水平拆分、垂直拆分已经逐渐成为较为常用的提升瓶颈的解决方案,因此越来越多的原子操作变成了跨库甚至是跨服务的事务操作。最终结果是在对高性能、高扩展性,高可用性的追求的道路上,我们开始逐渐放松对一致性的追求,但是在很多场景下,尤其是账务,电商等业务中,不可避免的存在着一致性问题,使得我们不得不去探寻一种机制,用以在分布式环境中保证事务的一致性

引用自 https://www.infoq.cn/article/2018/08/rocketmq-4.3-release

理论基石

ACID 和 BASE

https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
https://www.txlcn.org/zh-cn/docs/preface.html

2PC

谈到分布式事务,首先要说的就是 2PC(two phase commit)方案,如下图所示:

2PC 把事务的执行分为两个阶段,第一个阶段即 prepare 阶段,这个阶段实际上就是投票阶段,协调者向参与者确认是否可以共同提交,再得到全部参与者的所有回答后,协调者向所有的参与者发布共同提交或者共同回滚的指令,用以保证事务达到一致性。
2PC 是几乎所有分布式事务算法的基础,后续的分布式事务算法几乎都由此改进而来。

需求样例

这里我们定义一个充值需求,后续我们在各个实现中看看如何为该需求实现分布式事务。

Order 和 Account 分别是独立的一个服务,充值完成后,要分别将订单Order 设置为成功以及增加用户余额。

实现1 Seata

介绍 & 框架

Seata(Fescar) is a distributed transaction solution with high performance and ease of use for microservices architecture.
阿里开源,其特点是用一个事务管理器,来管理每个服务的事务,本质上是 2PC(后文会解释) 的一种实现。
Seata 提供了全局的事务管理器

原理

Fescar官方介绍

Fescar全局锁的理解

代理 SQL 查询,实现事务管理,类似中间件

实现充值需求

用该方案实现需求的话,就是这样的:

Order 和 Account 都接入 Seata 来代理事务

代码示例

比起自己去实现 2PC,Seata 提供了简化方案,代码实例见 :

Seata Samples

实现2 TCC

介绍

TCC(Try-Confirm-Concel) 模型是一种补偿性事务,主要分为 Try:检查、保留资源,Confirm:执行事务,Concel:释放资源三个阶段,如下图所示:

其中,活动管理器记录了全局事务的推进状态以及各子事务的执行状态,负责推进各个子事务共同进行提交或者回滚。同时负责在子事务处理超时后不停重试,重试不成功后转手工处理,用以保证事务的最终一致性。

原理

每个子节点,要实现 TCC 接口,才能被管理。
优点:不依赖 local transaction,可以管理非关系数据库库的服务
缺点:TCC 模式多增加了一个状态,导致在业务开发过程中,复杂度上升,而且协调器与子事务的通信过程增加,状态轮转处理也更为复杂。而且,很多业务是无法补偿的,例如银行卡充值。

实现框架

tx-lcn LCN distributed transaction framework, compatible with dubbo, spring cloud and Motan framework, supports various relational databases https://www.txlcn.org

或者 Seata MT 模式

代码示例

txlcn-demo

实现充值需求

需要把 Oder.done 和 Account 的余额+ 操作都实现 tcc 接口
可以看出,这样真的很麻烦,能用本地事务的还是尽量用本地事务吧

实现3 事务消息

介绍

以购物场景为例,张三购买物品,账户扣款 100 元的同时,需要保证在下游的会员服务中给该账户增加 100 积分。由于数据库私有,所以导致在实际的操作过程中会出现很多问题,比如先发送消息,可能会因为扣款失败导致账户积分无故增加,如果先执行扣款,则有可能因服务宕机,导致积分不能增加,无论是先发消息还是先执行本地事务,都有可能导致出现数据不一致的结果。

事务消息的本质就是为了解决此类问题,解决本地事务执行与消息发送的原子性问题。

实现框架

Apache RocketMQ™ is an open source distributed messaging and streaming data platform.

原理

  1. 事务发起方首先发送 prepare 消息到 MQ。
  2. 在发送 prepare 消息成功后执行本地事务。
  3. 根据本地事务执行结果返回 commit 或者是 rollback。
  4. 如果消息是 rollback,MQ 将删除该 prepare 消息不进行下发,如果是 commit 消息,MQ 将会把这个消息发送给 consumer 端。
  5. 如果执行本地事务过程中,执行端挂掉,或者超时,MQ 将会不停的询问其同组的其它 producer 来获取状态。
  6. Consumer 端的消费成功机制有 MQ 保证。

优点:对异步操作支持友好
缺点:Producer 端要为 RMQ 实现事务查询接口,导致在业务开发过程中,复杂度上升。

代码示例

// TODO

实现充值需求

通过 MQ,来保障 Order 和 Acount 的两个操作要么一起成功,要么一起失败。
注意一个点,假设 Account 的余额+失败了,这里是无法回滚 Order 的操作的,Account 要保证自己能正确处理消息。

实现4 本地消息表

介绍 & 原理

分布式事务=A系统本地事务 + B系统本地事务 + 消息通知;
准备:
A系统维护一张消息表log1,状态为未执行,
B系统维护2张表,
未完成表log2,
已完成表log3,
消息中间件用两个topic,
topic1是A系统通知B要执行任务了,
topic2是B系统通知A已经完成任务了,

  1. 用户在A系统里领取优惠券,并往log1插入一条记录
  2. 由定时任务轮询log1,发消息给B系统
  3. B系统收到消息后,先检查是否在log3中执行过这条消息,没有的话插入log2表,并进行发短信,发送成功后删除log2的记录,插入log3
  4. B系统发消息给A系统
  5. A系统根据id删除这个消息

假设出现网络中断和系统 Crash 等问题时,为了继续执行事务,需要进行重试。重试方式有:

  1. 定时任务恢复事务的执行,
  2. 使用 MQ 来传递消息,MQ可以保证消息被正确消费。

优点:简单
缺点:程序会出现执行到一半的状态,重试则要求每个操作需要实现幂等性

注意:分布式系统实现幂等性的时候,记得使用分布式锁,分布式锁详细介绍见文末参考文章

实现充值需求

通过消息表,把断开的事务继续执行下去。

实现5 考拉的方案

介绍 & 原理

考拉的方案,就是使用本地消息表,但是少了两个重要组件(MQ 和 关系型数据库),写起来还是比较辛苦的。

考拉方案有如下特点:

  1. Order 表承担了消息表功能
  2. 服务之间使用 http 通信,所以碰到问题要依赖定时任务发布补单重试
  3. 没有使用关系型数据库,幂等性的实现比较困难。

实现充值需求

难点:

  • 实现幂等性的要求太高,基本要求所有操作都需要实现幂等性,例如更新余额操作,要高效更新,简单的办法是使用乐观锁,但是要同时兼顾幂等性的话,乐观锁就不够用了。

  • 程序在任一一步断开,都需要重新运行起来,补单程序会很难写(简单的业务还好,复杂业务就会混乱了)

改进建议:

  • 服务直接使用 mq 通信,服务异常需要重试消费。
  • 使用关系型数据库,通过本地事务,可以只程序开始处判断重复,简化幂等性的实现逻辑

实际上就是往上一个实现4上走

总结

我们先对这些实现方案进行一个总结:

基础原理 实现 优势 必要前提
2PC Seata 简单 关系型数据库
2PC TCC 不依赖关系系数据库 实现 TCC 接口
2PC 事务消息 高性能 实现事务检查接口
最终一致性 本地消息表 去中心化 侵入业务,接口需要幂等性

各个方案有自己的优劣,实际使用过程中,我们还是需要根据情况来选择不同事务方案来灵活组合。

例如存在服务模块A 、B、 C。A模块是mysql作为数据源的服务,B模块是基于redis作为数据源的服务,C模块是基于mongo作为数据源的服务。若需要解决他们的事务一致性就需要针对不同的节点采用不同的方案,并且统一协调完成分布式事务的处理。

方案:将A模块采用 Seata 模式、B/C采用TCC模式就能完美解决。

参考文章

RocketMQ 4.3 正式发布,支持分布式事务

Seata

txlcn

分布式事务 CAP 理解论证 解决方案

再有人问你分布式锁,这篇文章扔给他

如何 parse 一个 typescript interface

blog 编程相关 Node posts 编程相关 typescript ast

目标

有如下一个接口定义,我们想把它的结构 parse 出来,知道每个字段的定义和注释,方便我们生成文档

import {IBanner} from './base/IBanner'
import {IProductGroup} from './base/IProductGroup'
/**
* 投资列表页面
*/
export interface IProductList {
/**
* 产品分组
*/
groupList: Array<IProductGroup>
/**
* 产品列表
*/
list: Array<{
a: string,
b: number
}>
/**
* 投资列表页面的banner
*/
banners: Array<IBanner>
}

typescript 提供的 ast

import * as ts from 'typescript'
const {options} = ts.convertCompilerOptionsFromJson({}, '.', 'tsconfig.json')
options.skipLibCheck = true
options.skipDefaultLibCheck = true
let program = ts.createProgram([
'./src/dashboard/interface/IProductService'
], options)
for (let sourceFile of program.getSourceFiles()) {
if (sourceFile.fileName.includes('IProductService.ts')) {
console.log('fileName', sourceFile.fileName)
console.log('statements', sourceFile.statements[1].members[])
}
}

节选一段输出:

{
pos: 38,
end: 276,
flags: 0,
transformFlags: undefined,
parent: undefined,
kind: 235,
jsDoc:
[ NodeObject {
.....
tags: undefined,
comment: '投资列表页面' } ],
decorators: undefined,
modifiers:
[ TokenObject { pos: 38, end: 64, flags: 0, parent: undefined, kind: 84 },
pos: 38,
end: 64 ],
name:
IdentifierObject {
.....
escapedText: 'IProductList' },
typeParameters: undefined,
heritageClauses: undefined,
members:
[ NodeObject {
.....
jsDoc: [Array],
modifiers: undefined,
name: [Object],
questionToken: undefined,
type: [Object] },
....

原始的 ast ,需要自己写程序遍历完整结构。

ts-simple-ast

ts-simple-ast 提供了一些元素获取的方法,例如
getInterface
getProperties

可以快速拿到你要的对象。
不足是,无法识别 import 进来的内容,例如

interfaceFile.getInterface('IBanner')

是无法获取的,因为 IBanner 是引用其他文件的

import {Project} from 'ts-simple-ast'
// initialize
const project = new Project()
project.addSourceFilesFromTsConfig('./tsconfig.json')
const interfaceFile = project.getSourceFile('src/dashboard/interface/IProductService.ts')
console.log('getProperties', interfaceFile.getInterface('IProductList').getProperties())

readts

readts 这个开源包虽然很冷门,但它却更符合我们的需求,它可以 parse 出项目里所有的 class 和 interface 等,而且连 import 进来的对象也帮你 ref 好了。

遗憾的是,匿名的对象定义还是无法 parse。

{
a: string,
b: number
}

用法:

const readts = require('readts');
const lodash = require('lodash');
var parser = new readts.Parser();
// Read configuration used in the project we want to analyze.
var config = parser.parseConfig('tsconfig.json');
// Modify configuration as needed, for example to avoid writing compiler output to disk.
config.options.noEmit = true;
// Parse the project.
var tree = parser.parse(config);
var interfaceList = lodash(tree)
.filter(item => item.interfaceList.length > 0)
.concat()
.map('interfaceList')
.map(item => item[0])
// .filter(item => item.name && item.name.match(/^I.*/))
// .value()
.find(item => item.name === 'IProductList');
console.log('interfaceList', interfaceList);

输出示例:

ClassSpec {
name: 'IProductList',
pos:
{ sourcePath: '/Users/nick/nodePro/klg-app/src/dashboard/interface/IProductService.ts',
firstLine: 6,
lastLine: 22 },
symbol:
SymbolObject {
flags: 64,
escapedName: 'IProductList',
declarations: [ [Object] ],
members:
Map {
'groupList' => [Object],
'list' => [Object],
'banners' => [Object] },
parent:
SymbolObject {
flags: 512,
escapedName: '"/Users/nick/nodePro/klg-app/src/dashboard/interface/IProductService"',
declarations: [Array],
exports: [Object],
valueDeclaration: [Object],
id: 8410 },
documentationComment: [ [Object] ],
id: 8359 },
doc: '投资列表页面',
propertyList:
[ IdentifierSpec {
name: 'groupList',
type: [Object],
optional: false,
pos: [Object],
doc: '产品分组' },
IdentifierSpec {
name: 'list',
type: [Object],
optional: false,
pos: [Object],
doc: '产品列表' },
IdentifierSpec {
name: 'banners',
type: [Object],
optional: false,
pos: [Object],
doc: '投资列表页面的banner' } ] }

AI 考拉 2018 开源汇总(Node 基础架构)

blog 编程相关 Node posts 编程相关

前言

2018 年,考拉开始对现有项目的常用的工具库进行整理,包含日期处理,数字处理,logger 等常用工具,并打包成 npm module,方便各个项目使用。

代码风格

在开发工具库之前,我们统一了编码标准

JavaScript 代码规范

前端 browser 通用
统一使用 eslint-config-klg ,基于 eslint-config-standard 封装
安装说明见文档

Typescript 代码规范

统一使用 tslint-config-klg ,基于 tslint-config-standard 封装

安装说明见文档

脚手架

我们也提供了脚手架 klg-init,来方便大家启动一个新项目。
安装好这个工具后,一键生成项目模板

$ klg-init dest
[klg-init] fetching npm info of klg-init-config
? Please select a boilerplate type (Use arrow keys)
──────────────
❯ module - npm 库项目模板
model - mongoose model 模板 todo
project - JavaScript 后端项目模板 todo
project-ts - Typescript 后端项目模板 todo
admin - 管理后台项目模板 todo

目前支持的模板有:

工具库列表

目前已经发布的工具库有:

  • klg-logger : logger 工具,基于 tracer
  • klg-number : 数字处理,主要解决 node 小数精度问题
  • klg-request-log :http 请求 log 记录,方便排查问题
  • klg-mq : rabbitmq 连接工具
  • klg-mq-koa : 将 mq 和 koa router 无缝结合
  • klg-redlock : 基于 redis 的分布式锁
  • klg-date : 日期处理,基于 moment
  • klg-request : 后端使用的 http 请求工具,基于 superagent
  • klg-retry : 重试工具
  • klg-tracer : 链路追踪工具,该项目实际不能使用,原因见项目内文档

上述项目都可以在我们公司的开源账号找到 https://github.com/kaolalicai?utf8=%E2%9C%93&q=&type=&language=typescript

API 接口与 Typescript Interface

blog 编程相关 Node posts 编程相关

前言

Interface 就是接口,在 typescript 用于类型限定。
在 Node 项目中,我们常用 apidoc 来定义接口文档。
那么,他们俩是否能结合起来呢?

apidoc 的痛点

apidoc Inline Documentation for RESTful web APIs,使用起来非常方便,直接在 api 上添加注释,就可以生成可视化的文档。

但是,在实际业务中,我们有些接口比较复杂,定义返回值是一件比较痛苦的事情,如图:

而且,过了一段时间之后,出于惰性,开发人员会放弃更新 API 文档,导致文档落后于实际。

API Interface

API 未定义的问题

在 js 项目中,api 返回结构一般是无法清晰地看出来的,你得跑一下接口,才知道会返回什么,或者仔细看一遍代码。这种情况下,开发人员维护 API 会面临以下问题:

  • 文档缺失:接口文档缺少对某个字段的定义,不知道该字段作何用处
  • 字段混乱:明明是一个字段,可能在 接口 A 是叫 amount, 在接口 B 却叫 number
  • 重复定义:多个接口中都用到一个叫 Banner 的东西,需要在每个接口文档中定义一遍,稍有改动,需要更改多处

使用 interface 的好处

所以可以尝试使用 typescript 的 interface 来定义 API 的返回值结构,一个接口对应一个 interface。

假设一个 api 是 product info ,定义结构如下:

/**
* 产品明细
*/
export interface IProductInfo {
/**
* 基础利息
*/
baseRate: number
/**
* 加息
*/
bonusRate: number
/**
* 加息提示
*/
bonusRateStr: string
}

另一个 api 是 product list ,可以很方便地进行复用:

/**
* 产品列表
*/
export interface IProductList {
list: Array<IProductInfo>
}

这些接口定义也可以共享给前端(前端也用 typescript 的话)

apidoc + interface

很多时候,你的痛点其实也是其他人的痛点,所以就有了开源项目,通过搜寻,我们可以找到一个 apidoc 的插件 apidoc-plugin-ts,这个插件可以根据 interface 的定义帮你生成 apidoc 需要的 @apiSuccess 内容。
定义一个接口:

filename: ./employers.ts
export interface Employer {
/**
* Employer job title
*/
jobTitle: string;
/**
* Employer personal details
*/
personalDetails: {
name: string;
age: number;
}
}

引入 interface:

@apiInterface (./employers.ts) {Person}

翻译的效果:

@apiSuccess {String} jobTitle Job title
@apiSuccess {Object} personalDetails Empoyer personal details
@apiSuccess {String} personalDetails.name
@apiSuccess {Number} personalDetails.age

还支持数组结构定义呢~
有了这个插件,就可以通过维护 interface 来维护 apidoc 了。

注意:

该插件不支持匿名接口定义数组结构, 如下:

export interface IProductList {
list: Array<{
a: string,
b: string
}>
}

AST 解析代码的时候会拿不到某些属性,会报错

Node.js APM 软件调研报告

blog 编程相关 Node posts 编程相关

前言

最近做 Node 服务的性能优化工作,在 2016年3月,我写过 Node 性能优化 这篇文章, 讲解了常见的 Node 服务缓慢原因,还介绍了 内存泄露 和 cpu profile 的知识。

这一次,是侧重中应用运行信息收集这一块,根据同事 Leo 的介绍,我对一些支持 Node.js 的 APM 软件做了一次调研。

在分别介绍每个软件之前,我要先阐述我使用这些软件的目的:它可以发现慢的 http 请求,并且可以查看当时的 Node 服务状态(包括内存 CPU 占用状态),database 状态(这里是 mongodb),帮助 dev 准确分析出请求处理缓慢的原因。

下文的体验报告将以此目标为标准进行评价。

软件列表

本次体验的 APM 软件有:

名称 介绍 开源/收费情况
atatus 支持多种语言,包括前端 不开源,收费
newrelic 支持多种语言,包括前端 开源,收费
keymetrics pm2, Node 应用管理器 开源,限额免费
Pandora.js 阿里出品,Node 应用管理器 开源,免费(自建服务)
alinode Node 底层的监控 开源,免费(阿里云上配置实例)
statsd + graphite + grafana 监控三件套,开发自由度高 开源,自建服务

atatus

atatus 提供了 npm 工具包 atatus-node 用于监控数据收集,具体配置见官方文档

使用报告:不是 100 % 的采样率,没有收集到官网宣传的各种维度的信息,使用 Koa 框架,基本上只能看到总的 http response time。

newrelic

网址 https://newrelic.com/ ,和 atatus 基本相同,也提供 npm 工具包 newrelic。

keymetrics

偏向应用生命周期管理,可以看到 Node 服务的 cpu 内存等占用情况,有错误(crash)收集功能,国内访问很卡。

Pandora.js

Pandora.js 是一个 Node.js 应用监控管理器。它集成了多种类型的能力诸如:监控、链路追踪、调试、进程管理等等。

它是个很有趣的东西,总结来说是它很强大但是还不够成熟,包括 Pandora 自身和整个 Node 生态都不成熟。

应用管理

可以对标 PM2,不赘述。

应用度量

有操作系统指标,包含 Load、CPU、内存、磁盘、网络、TCP 等各种指标;
有 Node.js 指标(内存占用);
还可以自定义指标,类似 statsd 做的事情,但是后续的存储和展示嘛,非常简单,没有 statsd + graphite + grafana 好用。

链路追踪

这个是 Pandora 提供的比较“先进”的功能,链路追踪理论上可以分析出一个 http 哪里耗时多,例如一个 get 请求,做的事情有,查询 db ,调用 第三方服务等。链路追踪会记录每一个环节的耗时:

链路追踪的实现依赖了 Node 的一个实验性特性 async_hooks,因为是新特性,目前还有很多问题没有解决,例如 mongoose 和 superagent 两个工具包的 Promise 实现方式会导致 async_hooks,具体可以见我在 Pandora 提的 issue ,也就是说,如果你刚好用了这两个框架,Pandora 链路追踪就失效了。

alinode

Node.js 性能平台 https://cn.aliyun.com/product/nodejs ,alinode 是在 node runtime 层面做的应用信息收集,而不是上述各个框架在应用层进行信息收集,所以 alinode 可以监控到 进程数据,堆快照、堆时间线、CPU Profile、GC Trace 等非常底层的信息,如果你的应用性能瓶颈在 Node 服务本身,使用该工具会有很大的帮助,如果你的应用性能瓶颈在 DB,那应该是用 DB 监控工具。

statsd + graphite + grafana

这个三件套有意思的是,监控指标是开发自己定义的,考拉用这三件套监控请求处理时间,记录每个请求的处理时间
这样在 grafana 上可以快速看出当前应用的请求量与趋势,已经快速分析哪些接口缓慢。

接口访问量的趋势变化:

处理最慢的接口:

这套工具的优势在于 grafana, 提供了非常直观的图表。
这套工具的搭建方法我之前也写过文章,可以看看 快速搭建一个监控服务

总结

回到我们的目标“帮助 dev 准确分析出请求处理缓慢的原因”,
理论上最贴合这个目标的是 Pandora.js ,但是我们还需要再等等,等它成熟。
最简单而强大的是 statsd + graphite + grafana 三件套,不用考虑 atatus newrelic 等。
如果你使用 Node 做高并发服务,alinode 将会很有用。