十几年前就有一些公司开始践行服务拆分以及 SOA, 六年前有了微服务的概念, 于是大家开始思考 SOA 和微服务的关系和区别. 最近三年 Spring Cloud 的大火把微服务的实践推到了高潮, 而近两年 K8S 在容器编排的地位确定之后大家又开始实践起以 K8S 为核心的云原生思想和微服务的结合如何去落地, 2018 年又多出一个 ServiceMesh 服务网格的概念, 大家又在思考如何引入落地 ServiceMesh,ServiceMesh 和 K8S 以及 Spring Cloud 的关系如何等等.
确实有点乱了, 这一波又一波的热潮, 几乎每两年都会来一波有关微服务架构理念和平台, 许多公司还没完成微服务的改造就又提出了服务 + 容器道路, 又有一些公司打算从微服务直接升级成 ServiceMesh. 本文尝试总结一下我见过的或实践过的一些微服务落地方式, 并且提出一些自己的观点, 希望抛砖引玉, 大家可以畅谈一下自己公司的微服务落地方式.
1, 微服务 v0.1-- 古典玩法
(图中灰色部分代表元数据存储区域, 也就是 Service 和 Endpoint 关系所保存的地方, 之后所有的图都是这样)
其实在 2006 年在使用. NET Remoting 做服务拆分的时候 (其实当时我们没有意识到这叫服务拆分, 这是打算把一些逻辑使用独立的进程来承载, 以 Windows 服务形式安装在不同服务器上分散压力), 我们使用了 F5 来做服务的负载均衡. 没有所谓的服务发现, 针对每一个服务, 我们直接在程序配置文件中写死 F5 的 IP 地址和端口, 使用 Excel 来记录所有服务在 F5 的端口地址以及服务实际部署的 IP: 端口, 然后在 F5 进行配置. F5 在这里做了负载均衡, 简单的路由策略(相同的客户端总是优先路由到相同的后端) 以及简单的白名单策略等等.
2, 微服务 v0.2-- 改进版古典玩法
之后尝试过这种改进版的古典玩法. 相比 v0.1 的区别是, 不再使用硬件 F5 了, 而是使用几组软件反向代理服务器, 比如 Nginx 来做服务负载均衡(如果是 TCP 的负载均衡的话可以选择 HaProxy),Nginx 的配置会比 F5 更方便而且还不花钱. 由于生产环境 Nginx 可能是多组, 客户端不在配置文件中写死 Nginx 地址而是把地址放到了配置中心去, 而 Nginx 的配置由源码仓库统一管理, 运维通过文件同步方式或其它方式从源码仓库拉取配置文件下发到不同的 Nginx 集群做后端服务的配置(Nginx 的配置也不一定需要是一个大文件放所有的配置, 可以每一组服务做一个配置文件更清晰).
虽然我的标题说这是古典玩法, 但是可以说很多公司如果没有上 RPC, 没有上 Spring Cloud, 也没有上 K8S 的话很可能就是这样的玩法. 无论是 v0.2 还是 v0.1, 本质上服务是固定在虚拟机或实体机部署的, 如果需要扩容, 需要迁移, 那么肯定需要修改反向代理或负载均衡器的配置. 少数情况下, 如果调整了反向代理或负载均衡器的 IP 地址, 那么还可能会需要修改客户端的配置.
3, 微服务 v0.5--SOA ESB 玩法
SOA 的一个特点是使用了服务总线, 服务总线承担了服务的发现, 路由, 协议转换, 安全控制, 限流等等. 2012 年我参与了一个大型 MMO 游戏《激战 2》项目的技术整合工作, 这个游戏整个服务端就是这种架构. 它有一个叫做 Portal 的服务总线, 所有游戏的十几个子服务都会把自己注册到服务总线, 不管是什么服务需要调用什么接口, 都是在调用服务总线, 由服务总线来进行服务的寻址路由和协议转换, 服务总线也会做服务的精细化限流, 每一个用户都有自己的服务请求队列. 这种架构的好处是简单, 服务总线承担了所有工作, 但是服务总线的压力很大, 承担了所有的服务转发工作. 同时需要考虑服务总线本身如何进行扩容, 如果服务总线是有状态的, 显然要进行扩容不是这么简单. 对于游戏服务器来说, 扩容可能不是一个强需求, 因为游戏服务天然会按照大区进行分流, 一个大区的最大人数上限是固定的.
貌似互联网公司这样玩的不多, 传统企业或是游戏服务端是比较适合服务总线这种架构的, 如果服务和服务之间的协议不统一的话, 要在客户端做协议转换的工作比较痛苦, 如果可以由统一的中间层接入所有协议统一进行转换的话, 客户端会比较轻量, 但是这种架构的很大问题在于服务总线的扩容和可靠性.
4, 微服务 v1.0-- 传统服务框架玩法
上图是大多数 RPC 框架的架构图. 大多数早期的微服务实践都是 RPC 的方式, 最近几年 Spring Cloud 盛行后其实 Spring Cloud 的玩法也差不多, 只是 Spring Cloud 推崇的是 JSON over HTTP 的 RESTful 接口, 而大多数 RPC 框架是二进制序列化 over TCP 的玩法(也有 JSON over HTTP 的 RPC).
其实 RPC 框架我个人喜欢 JSON over HTTP, 虽然我们知道 HTTP 和 JSON 序列化性能肯定不如一些精简的二进制序列化 + TCP, 但是优点是良好的可读性, 测试方便, 客户端开发方便, 而且我不认为 15000 的 QPS 和 20000 的 QPS 对于一般应用有什么区别.
总的来说, 我们会有一个集群化的分布式配置中心来充当服务注册的存储, 比如 ZK,Consul,Eureka 或 etcd. 我们的服务框架会有客户端和服务端部分, 客户端部分会提供服务的发现, 软负载, 路由, 安全, 策略控制等功能(可能也会通过插件形式包含 Metrics,Logging,Tracing,Resilience 等功能), 服务端部分对于 RPC 框架会做服务的调用也会辅助做一些安全, 策略控制, 对于 RESTful 的话就服务端一般除了监控没有额外的功能.
比如使用 Spring Cloud 来玩, 那么:
- Service Discovery:Eureka,Open Feign
- Load Balancing:Ribbon,Spring Cloud LoadBalancer
- Metrics:Micrometer,Spring Boot Actuator
- Resilience:Hystrix,Resilience4j
- Tracing:Sleuth,Zipkin
在之前《朱晔和你聊 Spring 系列 S1E8: 凑活着用的 Spring Cloud(含一个实际业务贯穿所有组件的完整例子)》一文中, 我有一个完整的例子介绍过 Spring Cloud 的这套玩法, 可以说的确 Spring Cloud 给了我们构建一套微服务体系最基本的东西, 我们只需要进行一些简单的扩展和补充, 比如灰度功能, 比如更好的配置服务, 就完全可以用于生产.
这种模式和之前 0.x 的很大区别是, 服务的注册有一个独立的组件, 注册中心完成, 通过配合客户端类库的服务发现, 至少服务的扩容很轻松, 扩容后也不需要手动维护负载均衡器的配置, 相当于服务端从死到活的一个重大转变. 而且在 1.0 的时代, 我们更多看到了服务治理的部分, 开始意识到成百上千的服务, 如果没有 Metrics,Logging,Tracing,Resilience 等功能来辅助的话, 微服务就是一个灾难.
Spring Cloud 已经出了 G 版了, 表示 Netflix 那套已经进入了维护模式, 许多程序员表示表示扶我起来还能学. 我认为 Spring Cloud 这个方向其实是挺对的, 先有开源的东西来填补空白, 慢慢再用自己的东西来替换, 但是开发比较苦, 特别是一些公司基于 Spring Cloud 辛苦二次开发的框架围绕了 Netflix 那套东西来做的会比较痛苦. 总的来说, 虽然 Spring Cloud 给人的感觉很乱, 变化很大, 大到 E 到 G 版的升级不亚于在换框架, 而且组件质量层次不齐, 但是它确实是一无所有的创业公司能够起步微服务的不多的选择之一. 如果没有现成的框架(不是说 RPC 框架, RPC 框架虽是微服务功能的 80% 重点, 但却是代码量 20% 的部分, 工作量最大的是治理和整合那套), 基于 Spring Cloud 起步微服务, 至少你可以当天起步, 1 个月完成适合自己公司的二次开发改造.
5, 微服务 v2.0-- 容器 + K8S 容器调度玩法
K8S 或者说容器调度平台的引入是比较革命性的, 容器使得我们的微服务对环境的依赖可以打包整合进行随意分发, 这是微服务节点可以任意调度的基础, 调度平台通过服务的分类和抽象, 使得微服务本身的部署和维护实现自动化, 以及实现更上一层楼的自动伸缩. 在 1.x 时代, 服务可以进行扩缩容, 但是一切都需要人工介入, 在 2.x 时代, 服务本身在哪里存在甚至有多少实例存在并不重要, 重要的只是我们有多少资源, 希望服务的 SLA 是怎么样的, 其余留给调度平台来调度.
如果说 1.0 时代大家纠结过 Dubbo 还是 Spring Cloud,2.0 时代我相信也有一些公司上过 Mesos 的 "贼船", 我们不是先知很难预测什么框架什么技术会在最后存活下来, 但是这却是也给技术带来了不少痛苦, 相信还是有不少公司在干 Mesos 转 K8S 的事情.
如果引入了 K8S, 那么服务发现可以由 K8S 来做, 不一定需要 Eureka. 我们可以为 Pod 创建 Service, 通过 Cluster 虚拟 IP 的方式 (如上图所示, 通过 IP tables) 路由到 Pod IP 来做服务的路由 (除了 Cluster IP 方式也有的人对于内部连接会采用 Ingress 方式去做, 路由方面会更强大, 不过这是不是又类似 v0.2 了呢?). 当然, 我们还可以更进一步引入内部 DNS, 使用内部域名解析成 Cluster IP, 客户端在调用服务的时候直接使用域名(域名可以通过配置服务来配置, 也可以直接读取环境变量) 即可. 如果这么干的话其实就没有 Eureka 啥事了, 有的公司没有选择这种纯 K8S 服务路由的方式还是使用了注册中心, 如果这样的话其实服务注册到注册中心的就是 Pod IP, 还是由微服务客户端做服务发现的工作. 我更喜欢这种方式, 我觉得 K8S 的服务发现还是弱了一点, 而且 IP tables 的方式让人没有安全感(IPVS 应该是更好的选择), 与其说是服务发现, 我更愿意让 K8S 只做容器调度的工作以及 Pod 发现的工作.
虽然 K8S 可以做一部分服务发现的工作, 我们还是需要在客户端中去实现更多的一些弹力方面的功能, 因此我认为 2.0 时代只是说是微服务框架结合容器, 容器调度, 而不能是脱离微服务框架本身完全依靠 K8S 实现微服务. 2.0 和 1.0 的本质区别或者说增强还是很明显, 那就是我们可以全局来统筹解决我们的微服务部署和可靠性问题, 在没有容器和容器调度这层抽象之前, 有的公司通过实现自动化虚拟机分配拉起, 加上自动化初始脚本来实现自动的微服务调度扩容, 有类似的意思, 但是非常花时间而且速度慢. K8S 真正让 OPS 成为了 DEV 而不是执行者, 让 OPS 站在总体架构的层面通过 DEV(咱不能说开发 DSL 文件不算开发吧)资源和资源之间的关系来统筹整个集群. 在只有十几个微服务若干台服务器的小公司可能无法发挥 2.0 容器云的威力, 但是服务器和服务一多, 纯手工的命令式配置容易出错且难以管理, K8S 真的释放了几十个运维人力.
6, 微服务 v3.0--ServiceMesh 服务网格玩法
在之前提到过几个问题:
SOA 的模式虽然简单, 但是集中的 Proxy 在高并发下性能和扩容会是问题
传统的 RPC 方式, 客户端很重, 做了很多工作, 甚至协议转换都在客户端做, 而且如果涉及到跨语言, 那么 RPC 框架需要好几套客户端和服务端
K8S 虽然是一个重要的变革, 但是在服务调度方面还是太弱了, 它的专项在于资源调度
于是 ServiceMesh 服务网格的概念腾空而出, 巧妙解决了这几个问题:
采用边车模式的 Proxy 随服务本身部署, 一服务一边车与服务共生死 (当然, 有的公司会使用类似 ServiceBus 的 Global Proxy 作为 Sidecar Proxy 的后备, 防止服务活着 Sidecar 死了的情况) 可以解决性能问题
Sidecar 里面做了路由, 弹力等工作, 客户端里可以啥都不干, 如上图所示, 上图是 Istio 的架构, Istio 的理念是把 ServiceMesh 分成了数据面和控制面, 数据面主要是负责数据传输, 由智能代理负责 (典型的组件是 Envoy), 控制面由三大组件构成, Pilot 负责流量管理和配置(路由策略, 授权策略) 下发, Mixer 负责策略和数据上报(遥测),Citadel 用于密钥和证书管理
由于我们双边都走 Sidecar Proxy, 我们对于流量的进出都可以做很细粒度的控制, 这个控制力度是之前任何一种模式都无法比拟的, 这种架构的方式就像把服务放到了网格之中, 服务连接外部的通讯都由网格进行, 服务本身轻量且只需要关注业务逻辑, 网格功能强大而灵活
对于 Proxy 的流量劫持可以使用 IP table 进行拦截, 对于服务本身无感知, 而且 Sidecar 可以自动注入 Pod, 和 K8S 进行自动整合, 无需特殊配置, 做到透明部署透明使用
Pilot 是平台无关的, 采用适配器形式可以和多个平台做整合, 如果和 K8S 整合的话, 它会和 API Server 进行通讯, 订阅服务, 端点的信息, 然后把信息转变成 Istio 自己的格式作为路由的元数据
Mixer 期望的是抽象底层的基础设施, 不管是 Logging 还是 Metrics,Tracing, 在之前 RPC 时代的做法是客户端和服务端都会直接上报信息到 InfluxDb,Tracing Server 等, 这让客户端变得很臃肿, Istio 的理念是这部分对接后端的工作应该由统一的组件进行, 不但使得 Proxy 可以更轻而且可以通过 Plugin 机制对接各种后端基础设施
说了这么多 ServiceMesh 的优势, 我们来看一下这种模式的性能问题. 想一下各种模式下客户端要请求服务端整个 HTTP 请求 (跳) 次数:
古典模式: 2 跳, 代理转发一次
SOA 模式: 2 跳, 总线转发一次
传统模式: 1 跳, 客户端直连服务端
K8S Service 模式: 1 跳(路由表会有一定损耗)
ServiceMesh 模式: 3 跳(其中 2 跳是 localhost 回环)
总的来说, 3 跳并不是 ServiceMesh 的瓶颈所在, 而更多的可能性是 Istio 的倔强的架构理念. Istio 认为策略和遥测不应该耦合在 Sidecar Proxy 应该放到 Mixer, 那么相当于在调用服务的时候还需要额外增加 Mixer 的同步请求(来获得策略方面的放行).Istio 也在一直优化这方面, 比如为 Mixer 的策略在 Proxy 做本地缓存, 为遥测数据做批量上报等等. 虽然经过层层优化, 但是 Istio 目前的 TPS 不足 2000, 还是和一般的 RPC 能达到的 20000 + 有着十倍的差距, 说不定将来 Istio 会有架构上的妥协, 把 Mixer 变为非直接依赖, 策略方面还是采用类似 Pilot 统一管理配置下发的方式, 遥测方面还是由 Sidecar 直接上报数据到 Mixer.
我个人认为, ServiceMesh 是一个非常正确的道路, 而且 ServiceMesh 和 K8S 结合会更好, 理由在于:
K8S 让资源调度变得自由, 但微服务调度不是其所长也不应该由它深入实现
以 Istio 为代表的 ServiceMesh 做了 K8S 少的, 但是微服务又必须的那块工作
Istio 的设计方面和 K8S 极其相似, 低耦合, 抽象的很好, 两者结合的也很好, 我非常喜欢和赞同 Agent + 统一的资源管理配置下发的方式(K8S 的 Agent 就是 KubeProxy 和 Kubelet,Istio 的 Agent 就是 Sidecar Proxy), 这是松耦合和高性能的平衡
在复杂的异构环境下, 多协议的内部通讯, 跨平台跨语言的内部通讯很常见, 如果采用传统方式, 框架太胖太重, 把这部分工作从内部剥离出来好处多多
但是, 可以看到目前 ServiceMesh 还不算非常成熟, Istio 在不断优化中, Linkerd 2.x 也想再和 Istio 拼一下, 到底谁会胜出还难以知晓, 经过之前 Dubbo vs Spring Cloud 的折腾, Mesos vs K8S 的折腾, VM vs Docker 的折腾, 是否还能经得起折腾 Istio vs Linkerd 2 呢? 我建议还是再看一看, 再等一等.
7, 畅想 Everything Mesh 模式?
之前看到过 ShardingSphere 受到 ServiceMesh 的理念影响提出了 DB Mesh 的架构. 其实 DB Proxy 的中间件已经存在很多年了(集中化的 Proxy 类似服务总线的方式),DB Mesh 把 Proxy 也变为轻量的 Sidecar 方式, DB 的访问也都走本地代理. 那么这里我也在想, 是不是有可能所有东西都有本地的代理呢?
作为应用服务本身而言, 只需要和本地代理做通讯调用外部服务, 缓存, 数据库, 消息队列, 不需要关心服务和资源所在何地, 以及背后的实际服务的组件形态. 当然, 这只是一个畅想了, 对于有状态的资源, Mesh 的难度很大, 对于类似 DB 这样的资源因为调用层次并不复杂, 也不太会存在异构场景, Mesh 的意义不大, 综合起来看 Everything Mesh 的投入产出比相比 Service Mesh 还是小很多.
8,Spring Cloud,K8S 和 ServiceMesh 的关系
如果搞 Java 微服务的话, Spring Boot 是离不开的, 但是是否要用 Spring Cloud 呢? 我的观点是, 在目前阶段如果没有什么更好的选择, 还是应该先用. Spring Cloud 和 K8S 首先并不是矛盾的东西, K8S 是偏运维的, 主要做资源整合和管理, 如果彻底没有服务治理框架纯靠 K8S 的话会很累, 而且功能不完整. 开发和架构可以在 Spring Cloud 方面深耕, 运维可以在容器和 K8S 方面发力, 两套体系可以协作形成目前来说比较好的微服务基石. 至于 K8S 的推行, 这一定是一个正确的方向, 而且和软件架构方面的改进工作一点不矛盾, 毕竟 K8S 是脱离于具体语言和平台的.
至于 Service Mesh, 它做的事情和 Spring Cloud 是有很多重复的, 在将来 Istio 如果发展的更好的情况下, 应该可以替代 Spring Cloud, 开发人员只需要用 Spring Boot 开发微服务即可, 客户端方面也可以很瘦, 不需要过多关心服务如何通讯和路由, 服务的安全, 通讯, 治理, 控制都由 Service Mesh 进行(但是, 是否有了 Sidecar, 客户端真的完全不需要 SDK 了呢? 我认为可能还是需要的, 对于 Tracing, 如果没有客户端部分显然是不完整的, 虽然 Sidecar 是 localhost 但是还是跨进程了).
Spring Cloud 目前虽然针对 K8S 和 Istio 做了一些整合, 但是并没看到一套针对 ServiceMesh 的最佳实践出来, 是否将来 Spring Cloud 会在微服务这方面做退化给 ServiceMesh 让步还不得而知. 总的来说, 长期我看好 Spring Boot + K8S + Istio 的组合, 短期我认为还是 Spring Boot + K8S + Spring Cloud 这么用着.
9, 总结
本文总结了各种微服务落地的形态, 由于技术多样, 各种理念层出不穷, 造成了微服务的落地方式真的很难找到两家相同的公司, 本文中我们介绍了:
客户端写死地址 + F5 代理的方式
客户端把地址配置在配置服务 + Nginx 代理的方式
SOA + 集中式 ESB 的方式
传统的具有注册中心的服务框架 SDK 形式
服务框架 + K8S 方式
K8S Service Iptables 路由方式
ServiceMesh 代理 3 跳转发方式
当然, 可能还会有更多的方式:
内部 DNS 方式(直接 DNS 轮询)
K8S 内部服务走 Ingress 方式(内部服务也走 Ingress, 类似所有服务 Nginx 代理的方式)
ServiceMesh 代理 2 跳转发方式(可以根据需要跳过远端的 Sidecar 来提高性能等等)
瘦服务框架 SDK+ServiceMesh 方式(也就是还是有一个小的 SDK 来对接 ServiceMesh 的 Sidecar, 而不是让应用程序自己发挥 Http Client, 这个方式的好处在于更灵活, 这个 SDK 可以在这一层再做一次路由, 甚至在 Sidecar 出问题的时候直接把流量切换出去, 切换为直连远端或统一的 Global Proxy)
也可能很多公司在混用各种方式, 具有 N 套服务注册中心, 正在做容器化迁移, 想想就头痛, 微服务的理念层出不穷伴随着巨头之间的技术战役, 苦的还是架构和开发, 当然, 运维可能也苦, 2019 新年快乐, Enjoy 微服务!
来源: https://www.cnblogs.com/lovecindywang/p/10358064.html