技术

如何使用RedisTemplate访问Redis数据结构 MySQL重要知识点 OAuth2认证授授权流程 分布式锁 服务调用 MQ的介绍 SpringCloud 使用链 Eureka 的点对点通信 介绍Eureka RabbitMQ与其它MQ的对比 Springboot 启动过程分析 Springboot 入门 Linux内存管理 自定义CNI IPAM 扩展Kubernetes 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 Kubernetes安全机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占cpu怎么办? 容器狂打日志怎么办? Kubernetes资源调度-scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects之编排对象 源码分析体会 自动化mock AIOps说的啥 从DevOps中挖掘docker的价值 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes整体结构 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 kubernetes实践 线程排队 jib源码分析之细节 从一个签名框架看待机制和策略 跨主机容器通信 jib源码分析及应用 docker环境下的持续构建 docker环境下的持续发布 一个容器多个进程 kubernetes yaml配置 marathon-client 源码分析 《持续交付36讲》笔记 程序猿应该知道的 mybatis学习 无锁数据结构和算法 《Container-Networking-Docker-Kubernetes》笔记 活用linux 命令 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 swagger PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 《深入剖析kubernetes》笔记 精简代码的利器——lombok 学习 java 语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 JVM4——《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 commons-pipeline 源码分析 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS3——论文学习 Unsafe Spark Stream 学习 linux 文件系统 mysql 批量操作优化 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 forkjoin 泛谈 hbase 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 calico 问题排查 bgp初识 mesos 的一些tips mesos 集成 calico calico AQS2——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 compensable-transaction 源码分析 硬件对软件设计的影响 elasticsearch 初步认识 mockito简介及源码分析 线上用docker要解决的问题 《Apache Kafka源码分析》——Producer与Consumer 停止容器 dns隐藏的一个坑 《mysql技术内幕》笔记2 《mysql技术内幕》笔记1 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 java系并发模型的发展 从一个marathon的问题开始的 docker 环境(主要运行java项目)常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 netty中的future和promise 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 一些tricky的code http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 JVM3——java内存模型 java concurrent 工具类 java exception java io涉及到的一些linux知识 network channel network byte buffer 测试环境docker化实践 通用transport层框架pigeon netty(七)netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 从Go并发编程模型想到的 mesos深入 Macvlan Linux网络源代码学习2 《docker源码分析》小结 对web系统的一些理解 docker中涉及到的一些linux知识 hystrix学习 Linux网络源代码学习 Docker网络五,docker网络的回顾 zookeeper三重奏 数据库的一些知识 Spark 泛谈 commons-chain netty(六)netty回顾 Thrift基本原理与实践(三) Thrift基本原理与实践(二) Thrift基本原理与实践(一) Future 回调 Docker0.1.0源码分析 基于spring boot和Docker搭建微服务 通过Docker Plugin来扩展Docker Engine java gc Docker网络四,基于Centos搭建Docker跨主机网络 google guava的一些理解 Jedis源码分析 Redis概述 Docker回顾 深度学习是个什么鬼 Docker网络三,基于OVS实现Docker跨主机网络 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 netty(四)netty对http协议的实现(废弃) netty(三)netty框架泛谈 向Hadoop学习NIO的使用 以新的角度看数据结构 AQS1——并发相关的硬件与内核支持 使用Ubuntu要做的一些环境准备 Docker网络二,libnetwork systemd 简介 那些有用的sql语句 异构数据库表在线同步 spring aop 实现原理简述——背景知识 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 我们编程的那些潜意识 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 一次代码调试的过程 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 docker volume 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM2——JVM和传统OS对比 git spring rmi和thrift maven/ant/gradle使用 再看tcp mesos简介 缓存系统——具体组件 缓存系统 java nio的多线程扩展 多线程设计模式/《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go 常用的一些库 Netty(一)初步了解 java mina Golang开发环境搭建(Windows下) java nio入门 ibatis自动生成类和文件 Python初学 Goroutine 调度模型猜想 一些编程相关的名词 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Hadoop安装与调试 Kubernetes持久化存储 Kubernetes 其它特性 访问Kubernetes上的服务 Kubernetes副本管理 Kubernetes pod 组件 使用etcd + confd + nginx做动态负载均衡 nginx安装与简单使用 在CoreOS集群上搭建Kubernetes 如何通过fleet unit files 来构建灵活的服务 CoreOS 安装 定制自己的boot2docker.iso CoreOS 使用 Go初学 JVM1——jvm小结 硬币和扑克牌问题 LRU实现 virtualbox 使用 os->c->java 多线程 容器类概述 zabbix 使用 zabbix 安装 Linux中的一些点 关于集群监控 ThreadLocal小结 我对Hadoop的认识 haproxy安装 docker快速入门

标签


SpringCloud 使用链

2019年07月05日

一 问题引入

面试的时候,面试官问:用户在电商网站中购买成功了,那么它在微服务中经历了什么?你该如何作答?

当我傻啊,用户在电商网站购买成功,还在微服务中,那肯定就是有一套微服务架构的电商系统。

设计一套电商系统还不简单?简单想象一下,既然是一个电商系统,有用户去购买,就肯定得有一个用户模块,购买什么东西总不是西北风吧,购买肯定是商品吧,省掉购物车,就得有商品模块吧。

商品总得有库存吧,库存就暂时跟商品放一起吧,什么仓储物流先别管,就当作是虚拟商品好了,反正题目也没说不能是虚拟商品。^_^

购买成功了,那就必须有订单吧,加个订单模块,下完单总得支付吧,不付钱人家凭什么把东西给你,那就得有个支付模块。

简单粗暴,四个模块,如上图:

用户模块 商品模块(库存) 订单模块 支付模块 好,几个模块搞定,外加下单流程图:

等等,貌似题目说是微服务,既然是微服务就涉及到拆分服务的问题。

二 DDD 领域驱动设计

刚刚确实是梳理了一下模块,既然是微服务,就得进行服务的拆分,服务怎么进行拆分呢?

貌似按照刚次梳理模块来划分也是可以的,不过这样好像显得我很是不专业,听说现在很多人都要使用 DDD(领域驱动设计)来指导微服务的拆分。

参考 DDD 的设计,DDD 官方的架构草图,总体架构分为四层:

  • Infrastructure(基础实施层)
  • Domain(领域层)
  • Application(应用层)
  • Interfaces(表示层,也叫用户界面层或是接口层)
  • 微服务结合 DDD

不过对于领域设计而言,代码层其实不是最重要,最重要的是如何去划分领域,划分好边界。

而对于微服务而言,非常适合从业务上去划分各个 Modules,划分好各个业务板块,微服务 + DDD,个人觉得首先从微服务的角度考虑去划分大的业务模块,每个微服务都应该是一个可以独立部署,各司其职的模块。

简单的说,在微服务实际的开发中,结合 DDD 的思想去划分所有属于自己的领域。

实施 DDD 的关键

第一点是使用通过的语言建立所有的聚合,实体,值对象。

第二点也就是最关键的“建模”:

  • 划分“战略建模”,从一种宏观的角度去审核整个项目,划分出“界限上下文”,形成具有上帝视角的“上下文映射图”。

  • 还有一个建模是“战术建模”,在我们的“战略建模”划分出来的“界限上下文”中进行“聚合”,“实体”,“值对象”,并按照模块分组。

构建电商系统的上下文映射图

先来确定我们的战略核心的领域是什么?我们的目的是什么?

作为一个电商系统,我们的核心肯定是卖出更多的商品,获取更多订单更多的利润,那么销售可以作为我们的一个核心的领域。

这个作为一个明确核心域确立下来:

确定完核心子域后,根据对这个领域的理解划分出各个上下文,然后根据上下文再确定其他的相关领域。

初步我们可以看出围绕销售核心域的包含的几大块内容,价格,销售方式,购买的方式,已经购买。

然后我们对支撑着核心域的子域也做了划分,支撑着核心域的有商品域,用户域,通用域有订单域,物流域,支付域。

回到我们的主题,我们这次没有购物车,也没有各个会员销售价格,把一些上下文拿掉,并建立映射。

领域驱动设计看似简单,其实很难实施,因为在各个环节中都需要对应的领域专家的参加或指导,这样才能设计出最符合实际的上下文映射图。

而且我们花费的精力可能相比以后的数据驱动开发模式更多,但在整体对项目的把控性能上说,领域比数据驱动更加抽象,更加的顶层设计,在对应互联网的多变情况看得更远。

我们将微服务拆分为 5 个领域,分别是:

  • 销售域
  • 商品域
  • 用户域
  • 订单域
  • 支付域

完美,接下来就可以开始开发了。^ _ ^

等等,兵马未动,粮草先行;代码未动,图先行,先把时序图画出来。

时序图

一个简单的下单流程,涵盖了几个领域:

完美,接下来就可以开发微服务了。^ _ ^

等等,微服务的技术栈还未选型。

三 微服务技术栈选型

服务拆分完了,时序图也画完了,可以开始我们的微服务之旅了,目前主流的微服务有阿里大名鼎鼎的 Dubbo 和 Spring Cloud 全家桶,还有新浪的 Motan。

比较熟悉的还是 Dubbo 和 Spring Cloud,也都使用过,究竟应该选用哪一个呢?

因为之前都使用过,做点简单,粗暴的总结。Dubbo 在很早之前就开始使用,当时的微服务还没有现在这么火,很多理论体系也未完善,Dubbo 更像是一套 RPC 整合框架,Spring Cloud 则更倾向微服务架构的生态。

相比 Dubbo,Spring Cloud 可以说是微服务一整套的解决方案,在功能上是 Dubbo 的一个超级。

Dubbo 和 Spring Cloud 比喻,Dubbo 架构的微服务就像组装电脑,各个环节自由度很高。Spring Cloud 更像品牌机。

基于不折腾,简单快捷,更倾向选择 Spring Cloud。OK,就定下来技术栈使用 Spring Cloud,愉快的决定。

等等,就这么草率就决定用 Spring Cloud 做为微服务,难道不需要把微服务的利弊先弄清楚吗?

微服务的利和弊

既然选择了微服务,就得知道微服务的利和弊,特别是弊,引入了微服务,就等于引入了一套复杂的体系,一套复杂的体系带来的各种挑战必须事先了解清楚。

  • 1 强模块化边界

我们知道做软件架构,软件设计,模块化是非常重要的一点,一开始我们写程序做软件,我们采用类的方式来做模块化,后面开始采用组件或类库的方式做模块化,可以做到工程上的重用和分享给其他团队来使用。

微服务在组件的层次上面又高了一层,以服务的方式来做模块化,每个团队独立开始和维护自己的服务,有明显的一个边界。

开发完一个服务,其他团队可以直接调用这个服务,不需要像组件通过 Jar 或源码的方式去进行分享,所以微服务的边界是比较清晰的。

  • 2 可独立部署

  • 3 技术多样性

弊(或者说挑战)

  • 1 分布式复杂性

在原来单块应用就是一个应用,一个对单块应用的架构比较熟悉的人可以对整个单块应用有一个很好的把控。

但是到了分布式系统,微服务化了以后可能涉及到的服务有好几十个,一些大公司可能涉及到的服务上百个,服务与服务之间是通过相互沟通来实现业务。

那么这个时候整个系统就变成非常复杂,一般的开发人员或一个团队都无法理解整个系统是如何工作的,这个就是分布式带来的复杂性。

  • 2 最终一致性

微服务的数据是分散式治理的,每个团队都有自己的数据源和数据拷贝,比方说团队 A 有订单数据,B 团队也有订单数据,团队 A 修改了订单数据是否应该同步给团队 B 的数据呢?

这里就涉及到数据一致性问题,如果没有很好的解决一致性问题,就可能造成数据的不一致,这个在业务上是不可以接受的。

  • 3 运维复杂性

以往的运维需要管理的是机器+单块的应用,分布式系统和单块应用不一样的是,分布式系统需要很多的服务,服务与服务之间相互协同。

那么对分布式系统的资源,容量规划,监控,对整个系统的可靠性稳定性都非常具备挑战的。

只有在清楚了解微服务带来的挑战,明知道山有虎偏向虎山行,才能够真正的胜任挑战,最重要的是,要清楚明了里面有什么坑,怎么避免踩坑。

完美,已经了解微服务带来的好处和挑战,接下来就可以开始开发了。^ _ ^

等等,微服务还没有做逻辑分层。

微服务怎么做逻辑分层

目前我们的微服务里面有几个服务,分别是订单,商品,用户。

如果客户端向查看 “我的订单” 这么一个接口;如果客户端假定是 PC 端,就需要请求三次接口,分别对接订单,商品,用户三个服务,分别拿完三次调用数据,再将三次调用数据进行整合输出展示。

要知道 PC 调用后端服务是走外网,这无疑大大增加了网络的开销,而且让 PC 端变成更为复杂。

假定在中间加多一个层为聚合服务层,即对网络开销进行减少,因为微服务内部是通过内网进行数据传输,也让 PC 端的业务变得比较简单。

图中的 “PC 聚合服务” 也是一个微服务,只不过它是属于聚合服务中间层,我们将为微服务进行逻辑划分,分为 2 个层:

微服务基础服务层

基础服务一般属于互联网平台基础性的支撑服务,比方说,电商网站的基础服务有订单服务,商品服务,用户服务等。

这些都属于比较基础和原子性,下沉一个公司的基础设施的低层,向下承接存储,向上提供业务能力,有些公司叫基础服务,中间层服务,公共服务,Netflix 成为中间层服务。我们暂且统称为基础服务。

微服务聚合服务层

已经有了基础服务能提供业务能力,为什么还需要聚合服务,因为我们有不同的接入端,如 App 和 H5,PC 等等,它们看似调用大致相同的数据,但其实存在很多差异。

例如 PC 需要展示更多信息,App 需要做信息裁剪等等。一般低层服务都是比较通用的,基础服务应该对外输出相对统一的服务,在抽象上做得比较好。

但是对不同的外界 App 和 PC 的接入,我们需要作出不同的适配,这个时候需要有一个层去做出聚合裁剪的工作。

例如一个商品详情在 PC 端展示和 App 端的展示,PC 可能会展示更多的信息,而 App 则需要对信息作出一些裁剪。

如果基础服务直接开放接口给到 PC 和 App,那么基础服务也需要去做成各种设配,这个很不利于基础服务的抽象。

所以我们在基础层之上加入聚合服务层,这个层可以针对 PC 和 App 做成适当的设配进行相应的裁剪。

那么我们的微服务中,又增加了一个服务,属于聚合服务。

好了,接下来可以愉快的 Coding……

等等,貌似不对,如果是单块应用加上事务应该没问题,这里是分布式,恐怕得考虑加分布式事务。

分布式事务

我们来理一理创建订单和扣件库存模块之间的关系:

可以发现,因为微服务的原因,我们把服务进行了分布式,随着各个数据库也随着变成分布式每个数据库不一定存在相同的物理机中。

那么这个时候单个数据库的 ACID 已经不能适应这种情况,而在这种集群中想去保证集群的 ACID 几乎很难达到,或者即使能达到那么效率和性能会大幅下降,最为关键的是再很难扩展新的分区了。

这个时候如果再追求集群的 ACID 会导致我们的系统变得很差,这时我们就需要引入一个新的理论原则来适应这种集群的情况,就是 CAP。

CAP 定理

CAP 必须满足以下的 3 个属性:

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
  • 分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。

简单的来说,在一个分布式系统中,最多能支持上面的两种属性。但显然既然是分布式注定我们是必然要进行分区,既然分区,我们就无法百分百避免分区的错误。因此,我们只能在一致性和可用性去作出选择。

在分布式系统中,我们往往追求的是可用性,它的重要性比一致性要高,那么如何实现高可用,这里又有一个理论,就是 BASE 理论,它给 CAP 理论做了进一步的扩充。

BASE 理论

BASE 理论指出:

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)

BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

好了,说了一大顿理论,程序员们都等急了,赶快来看看分布式事务的解决方案有哪些,可以进行接下去的 Coding……

来吧,讨论技术方案:

几个方案拿出来了,因为我们不是专门来讲解分布式事务的机制和原理,主要还是来做分布式事务的技术选型。

先排除掉我们应该不会选择的方案,一个是 XA 两阶段提交,这个在很多传统型公司会被使用,但不适合互联网微服务的分布式系统,锁定资源时间长,性能影响大,排除。

另一个是阿里的 GTS,并没有开源,目前已经开源了 Fescar,不过目前尚缺少调研,可能在下个阶段研究后会使用,目前先排除。

剩下的是 TCC 和 MQ 消息事务两种。

MQ 消息事务:RocketMQ

先说说 MQ 的分布式事务,RocketMQ 在 4.3 版本已经正式宣布支持分布式事务,在选择 RokcetMQ 做分布式事务请务必选择 4.3 以上的版本。

事务消息作为一种异步确保型事务, 将两个事务分支通过 MQ 进行异步解耦,RocketMQ 事务消息的设计流程同样借鉴了两阶段提交理论,整体交互流程如下图所示:

这个时候我们基本可以认为,只有 MQ 发送方自己的本地事务执行完毕,那么 MQ 的订阅方必定百分百能够接收到消息,我们再对下单减库存的步骤进行改造。

这里涉及到一个异步化的改造,我们理一下,如果是同步流程中的各个步骤:

  • 查看商品详情(或购物车)
  • 计算商品价格和目前商品存在库存(生成订单详情)
  • 商品扣库存(调用商品库存服务)
  • 订单确认(生成有效订单)

订单创建完成后,发布一个事件“orderCreate” 到消息队列中,然后由 MQ 转发给订阅该消息的服务,因为是基于消息事务,我们可以认为订阅该消息的商品模块是百分百能收到这个消息的。

商品服务接受到 orderCreate 消息后就执行扣减库存的操作,注意⚠️,这里可能会有一些不可抗的因素导致扣减库存失败。

无论成功或失败,商品服务都将发送一个扣减库存结果的消息“stroeReduce”到消息队列中,订单服务会订阅扣减库存的结果。

订单服务收到消息后有两种可能:

  • 如果扣减库存成功,将订单状态改为 “确认订单” ,下单成功。
  • 如果扣减库存失败,将订单状态改为 “失效订单” ,下单失败。

这种模式将确认订单的流程变成异步化,非常适合在高并发的使用,但是,切记了,这个需要前端用户体验的一些改变,要配合产品来涉及流程。

完美,使用 MQ 分布式事务就可以解决调一致性问题。

等等,MQ 消息事务方案的风险了解一下。

上面使用 MQ 的方式确实是可以完成 A 和 B 操作,但是 A 和 B 并不是严格一致性,而是最终一致性。

我们牺牲掉严格一致性,换来性能的提升,这种很适合在大促高并发场景使用。

但是如果 B 一直执行不成功,那么一致性也会被破坏,后续应该考虑到更多的兜底方案,方案越细系统就将越复杂。

TCC 方案

TCC 是服务化的二阶段变成模型,每个业务服务都必须实现 Try,Confirm,Calcel 三个方法。

这三个方式可以对应到 SQL 事务中 Lock,Commit,Rollback:

  • Try 阶段:Try 只是一个初步的操作,进行初步的确认,它的主要职责是完成所有业务的检查,预留业务资源。
  • Confirm 阶段:Confirm 是在 Try 阶段检查执行完毕后,继续执行的确认操作,必须满足幂等性操作,如果 Confirm 中执行失败,会有事务协调器触发不断的执行,直到满足为止。
  • Cancel:是取消执行,在 Try 没通过并释放掉 Try 阶段预留的资源,也必须满足幂等性,跟 Confirm 一样有可能被不断执行。

接下来看看,我们的下单扣减库存的流程怎么加入 TCC:

在 Try 的时候,会让库存服务预留 n 个库存给这个订单使用,让订单服务产生一个“未确认”订单,同时产生这两个预留的资源。

在 Confirm 的时候,会使用在 Try 预留的资源,在 TCC 事务机制中认为,如果在 Try 阶段能正常预留的资源,那么在 Confirm 一定能完整的提交:

在 Try 的时候,有任务一方为执行失败,则会执行 Cancel 的接口操作,将在 Try 阶段预留的资源进行释放。

完美,可以把我们的系统引入 TCC。^ _ ^

等等,有同学提问:

有同学可能会问了,如果在 Confirm 或 Cancel 中,有一方的操作失败了,可能出现异常等情况该怎么解决。

  • 这个就涉及 TCC 的事务协调器了,事务协调器就 Confirm 或 Cancel 没有得到返回的时候,会启用定时器不断的进行 Confirm 或 Cancel 的重试。 这个也就是我们强调,Confirm,Cancel 接口必须是幂等性的一个原因了。

  • 还有同学会问了,为什么事务协调器知道 Confirm,或 Cancel 没有完成。 这个就涉及到了 TCC 也做了一张本地消息表,会记录一次事务,包括主事务,子事务,事务的完成情况都会记录在这种表中(当然未必是表,可能是 ZK,Redis 等等介质),然后启用一个定时器去检查这种表。

  • 还有同学会问,事务怎么传递,这个就涉及使用的 TCC 的框架了,一般来说用的都是隐式传参的方式。 在主事务创建的时候用隐式传参调用子事务,子事务包含 Try,Confirm,Cancel 都会记录到事务表里面。

这里推荐 TCC 的开源框架使用 mengyun 的 TCC,然后也可以其他的,无所谓。

完美,下单的流程开发完毕了,可以让 QA 接入。^ _ ^

等等,微服务的保护措施做了吗?

熔断限流隔离降级

微服务分布式依赖关系错综复杂,比方说前端的一个请求,这来到后端会被转为为很多个请求。

这个时候后台的服务出现不稳定或者延迟,如果没有好的限流熔断措施,可能会造成用户体验的下降,严重的时候会出现雪崩效应,把整个网站给搞垮。

如果像阿里巴巴在双 11 等活动中,如果没有一套好的限流熔断措施,这是不可想象的,可能是根本无法支撑那么大的并发容量。

Netflix 在 2012 年前也没有设计好的限流容错,当时也是饱受着系统稳定性的困扰,好几次网站因为没有好的熔断措施把网站搞垮。

在 2012 年 Netflix 启动了弹性工程项目,其中有一个产品叫 Hystrix,这个产品主要用来解决微服务的可靠性。

有了这个系统之后,Netflix 在系统稳定性上上了一个大的台阶,在此之后就没有出现过大规模的雪崩事故。

下面使用 Hystrix 例子来讲解一下限流熔断。

几个概念:熔断,隔离,限流,降级,这几个概念是分布式容错最重要的概念和模式。

①熔断:如果说房子里面安装了电路熔断器,当你使用超大功率的电路时,有熔断设备帮你保护不至于出问题的时候把问题扩大化。

②隔离:我们知道计算资源都是有限的,CPU,内存,队列,线程池都是资源。

他们都是限定的资源数,如果不进行隔离,一个服务的调用可能要消耗很多的线程资源,把其他服务的资源都给占用了,那么可能出现因为一个服务的问题连带效应造成其他服务不能进行访问。

③限流:让大流量的访问冲进去我们的服务时,我们需要一定的限流措施,比方说我们规则一定时间内只允许一定的访问数从我们的资源过,如果再大的话系统会出现问题,那么就需要限流保护。

④降级:如果说系统后台无法提供足够的支撑能力,那么需要一个降级能力,保护系统不会被进一步恶化,而且可以对用户提供比较友好的柔性方案,例如告知用户暂时无法访问,请在一段时候后重试等等。

Hystrix

Hystrix 就把上面说的熔断,隔离,限流,降级封装在这么一个组件里面,下图是 Hystrix 内部设计和调用流程:

大致的工作流如下:

  • 构建一个 HystrixCommand 对象,用于封装请求,并在构造方法配置请求被执行需要的参数。
  • 执行命令,Hystrix 提供了几种执行命令的方法,比较常用到的是 Synchrous 和 Asynchrous。
  • 判断电路是否被打开,如果被打开,直接进入 Fallback 方法。
  • 判断线程池/队列/信号量是否已经满,如果满了,直接进入 Fallback 方法。
  • 执行 Run 方法,一般是 HystrixCommand.run(),进入实际的业务调用,执行超时或者执行失败抛出未提前预计的异常时,直接进入 Fallback 方法。
  • 无论中间走到哪一步都会进行上报 Metrics,统计出熔断器的监控指标。
  • Fallback 方法也分实现和备用的环节。
  • 最后是返回请求响应。

完美,把 Hystrix 加入我们系统吧,这样突然有洪峰流量也不至于我们的系统一下就冲垮。^ _ ^

等等,Hystrix 的限流数值,错误数熔断,超时熔断,尝试恢复比率这些需要我们配置的数值应该怎么定呢?

这个就取决你的系统压测的指标和你部署的规模了,这里还涉及到一个容量设计的问题,一会我们将系统部署上线的时候再来详细说道。

刚刚提到一个问题,就是这些限流数值,错误数熔断这些数字,我们现在都写在配置文件里面。

例如说写在 Properties,YML 里面,当有一天突然需要把限流数下调(可能是系统遭受到什么压力打击),那我们只能把代码拉下来,巴拉巴拉改了。

然后重新上传打包,发布重启,一个流程下来,不说个把小时吧,十来分钟总少不了吧。

想办法我们把这些配置项放到一个集中式配置中心。

##四 集中式配置中心 ##

自己写配置中心还挺麻烦的,去菜市场逛逛吧,菜市场里面有,Spring Cloud Config,百度的 Disconf,阿里的 Diamond,还有携程的 Apollo。

基本上他们的原理都差不多,配置中心可以简单的理解为一个服务模块,开发人员或运维人员可以通过界面对配置中心进行配置。

下面相关的微服务连接到配置中心上面就可以实时连接获取到配置中心上面修改的参数。

更新的方式一般有两种:

  • Pull 模式,服务定时去拉取配置中心的数据。
  • Push 模式,服务一直连接到配置中心上,一旦配置有变成,配置中心将把变更的参数推送到对应的微服务上。

Pull 和 Push 两种模式各有优缺点:

  • Pull 一般使用定时器拉取,就算某一个网络抖动没有 Pull 成功,在下一次定时器的时候,终将能保证获取最新的配置。
  • Push 可以避免 Pull 定时器存在的延时,基本可以做到实时获取数据,但也有问题就是网络抖动的时候可能会丢失更新。

携程的 Apollo

携程的 Apollo 比较有特色的是融合了 Pull 和 Push 两种模式,把两者的优点进行了结合,开发或运维人员在配置中心进行修改,配置中心服务将实时将修改推送 Push 到 Apollo 的客户端。

但考虑到可能由于某些网络抖动没有推送成功,客户端还具备了定时向 Apollo 服务端拉取 Pull 数据的功能。

就算推送没成功,但是只要一定时间周期,客户端还是会主动去拉取同步数据,保证能把最终配置同步到服务中。这个也是 Apollo 在高可用方面上非常有特色的设计。

Apollo 在高可用上也做了保证,客户端获取到数据会把数据缓存在内存,还会 Sync 到本地磁盘。

就算 Apollo 服务器挂掉了,就算客户端服务重启了,也可以从本地磁盘中拉取回来数据,继续提供对外服务,从这点来看 Apollo 的配置中心在高可用上考虑还是比较周到的。

把配置中心配置上去后,我们就可以把 Hystrix 还有 MySQL 的用户密码,还有一些业务开关等等的配置参数放上去了。

完美,开发基本完工了,其实就几个模块,一个简单的下单购物流程,当我们把系统交付给运维,运维喊道,日志呢,做微服务怎么可以没有调用链日志呢?

调用链监控&日志

确实,微服务是一个分布式非常复杂的系统,如果没有一套调用链监控,如果服务之间依赖出现问题就很难进行定位。

下图是阿里在鹰眼系统给出的微服务之“熵”:

目前各大主流互联网公司中,阿里有非常出色的鹰眼系统,点评也有一套很出名的调用链监控系统 CAT。

调用链监控其实最早是 Google 提出来的,2010 年 Google 发表了一篇调用链的论文,论文以它内部的调用链系统 Dapper 命名。

这个论文中讲解调用链在 Google 使用的经验和原理,大致的原理如下图:

这里可以采用 ELK 的方式去记录和展示调用链监控日志,当我们一条调用为一行记录存储下来。

通过 TraceId 和 ParentSpanId 就可以串联起来为一个整体的链路,并可以从这个链路去分析错误或者调用延时和调用次数等等。

目前市面主流的调用链选型有 Zipkin,Pinpoint,Cat,Skywalking,他们之间各有一些偏重点。

值得一说的是 Skywalking 是国人出品的一款新的调用链工具,采用开源的基于字节码注入的调用链分析,接入段无代码入侵。

而且开源支持多种插件,UI 在几款工具来说比较功能比较强大,而且 UI 也比较赏心悦目,目前已经加入了 Apache 孵化器。

###采用 Skywalking 作为调用链工具###

为何会采用 Skywaling,在低层原理的实现,这几款产品都差不多,但在实现和使用的细节相别还是很大:

  • 首先在实现方式上,Skywalking 基本对于代码做到了无入侵,采用 Java 探针和字节码增强的方式,而在 Cat 还采用了代码埋点,而 Zipkin 采用了拦截请求,Pinpoint 也是使用 Java 探针和字节码增强。
  • 其次在分析的颗粒度上,Skywaling 是方法级,而 Zipkin 是接口级,其他两款也是方法级。
  • 在数据存储上,Skywalking 可以采用日志体系中比较出名的 ES,其他几款,Zipkin 也可以使用 ES,Pinpoint 使用 Hbase,Cat 使用 MySQL 或 HDFS,相对复杂。由于目前公司对 ES 熟悉的人才比较有保证,选择熟悉存储方案也是考虑技术选型的重点。 还有就是性能影响,根据网上的一些性能报告,虽然未必百分百准备,但也具备参考价值,Skywalking 的探针对吞吐量的影响在 4 者中间是最效的,经过对 Skywalking 的一些压测也大致证明。

完美,把微服务的包打好,上传到服务器就可以运行了。^ _ ^

等等,微服务包都打好了,剩下就是 Jar 包或 War 包一个一个上传到服务器上,然后用个脚本 Start,在以前单块应用还好,现在微服务几十几百个应用,请问,运营人员怕不怕?

听说,Docker + Kubernetes 和微服务更配喔。

Docker + Kubernetes

就几个服务,先不用容器化部署了……乍一看,没完没了,还有 CICD,灰度发布……容易编排……

下次再讲吧,先把服务部署上去吧。

部署到生产,预估容量

该把服务部署上线了,一个服务上线肯定得评估下或者预估下访问量有多少用户,有多少访问,这个涉及到该配置多少的机器资源,这应该怎么去估算呢,反正程序员在家里怎么算都算不出来。

评估访问量

①问运营,如果是一个已经上线的产品,肯定存在已有的用户数和访问数据,就算存在偏差,也是可控的范围。

②问产品,确定一个什么样形态的产品,例如是拼团,例如是秒杀,各种处理方式都不同。

评估平均访问量 QPS

一天 86400 秒,一般认为请求大部分发生在白天,就按照 40000 计算,日平均访问量=日总访问量/40000。

评估高峰 QPS

可以把之前每日的访问曲线图拉出来看看,峰值是根据业务不同而定的,例如,有些业务是白天早上 10 点的流量偏多,有些业务是晚上人家休闲类的流量偏多。

总之,根据业务去估算出日均的峰值,类似于电商类的服务,一般峰值是日均流量的 5 倍左右。

还有例如一些大促活动可能会更高,这个都要跟运营人员提前沟通好的,还有一些活动例如,秒杀,这个就不是靠预估出来,秒杀是另一种的考虑情况,采取的应对策略跟普通订单是完全不同。

评估系统,单机极限 QPS

在上线之前需要跟测试人员一起做压力测试,针对每个服务每台机器去做,一般来说,会把一个服务一台机器压到极限,在逐步的进行优化。

思考一个问题,假定单台机器最大的 QPS 是 1000,我们峰值是 5000,那需要用多少台机器去抗?答案是大于等于 6 台,最少的容错不得少于 1 台。

貌似一个非常简单的微服务就差不多了,不过貌似还是差了很多,数一下:

  • 监控系统哪去了(基础设施监控,系统监控,应用监控,业务监控)
  • 网关哪里去了
  • 统一的异常处理哪里去了
  • API 文档哪里去了
  • 容器化哪里去了
  • 服务编排哪里去了
  • ……