今天给大家带来的是一个读书分享,这是一本关于微服务的书,书名是《MicroService From Design to Deployment》。
作者介绍
首先介绍一下书的作者,Chris Richardson 和 Floyd Smith。Chris Richardson更为著名一些,所以这里重点介绍一下。
Chris Richardson,是世界著名的软件大师。与 Martin Fowler(敏捷开发方法论,《Microservices》)、Sam Newman(《微服务设计》)、Adrian Cockcroft(科克罗夫特,前Netflix云架构团队的负责人,首席云架构师;目前在AWS任VP) 等并称为世界十大软件架构师。
作者的博客
https://www.nginx.com/people/chris-richardson/
翻译
http://blog.daocloud.io/microservices-1/
经典技术著作《POJOS IN ACTION》一书的作者
“Plain Old Java Object”“简单java对象”。POJO的内在含义是指那些没有从任何类继承、也没有实现任何接口,更没有被其它框架侵入的java对象。主要是Java的开发者被EJB的繁杂搞怕了,大家经过反思,又回归“纯洁老式”的JavaBean,即有无参构造函数,每个字段都有getter和setter的java类。可以转化为PO、DTO、VO;比如POJO在传输过程中就是DTO。读音:【POU JOU】
EJB (Enterprise Java Beans) 是基于分布式事务处理的企业级应用程序的组件。Sun公司发布的文档中对EJB的定义是:EJB是用于开发和部署多层结构的、分布式的、面向对象的Java应用系统的跨平台的构件体系结构。
对标API极其复杂的EJB(专用于分布式系统、分层系统),受到Hibernate框架(用于提供数据持久化和对象-关系映射)及Spring框架(用于封装业务逻辑)的影响。
理解DDD。
cloudfoundry.com 最初的创始人
Cloud Foundry是业界第一个开源PaaS云平台,诞生于2011年。最初由VMWare开发,开源的Cloud Foundry由Cloud Foundry基金会开发并支持,基金会包括Pivotal[ˈpɪvətl]、Dell EMC、IBM、VMWare以及其它许多公司。商业版本的Cloud Foundry,如 IBM Bluemix和Pivotal Cloud Foundry(简称PCF),是基于开源的Cloud Foundry 项目开发的。
成书背景
- 2011年,CloudFoundry创立,成为第一个开源PaaS云平台
- 2013年,docker问世
- 2013年,Cockcroft 在 Netflix 完成了一个壮举,把所有IT基础设施都转移到了亚马逊的AWS云端。此后,独乐乐不如众乐乐,Netflix公开了这套转型上云端的经验,甚至打包自己开发的工具和架构设计模板,开源推出了NetflixOSS平台。做了一系列的封装,就变成了Spring Cloud。
《云端教父辛酸讲述:匹敌阿里云的Netflixr如何成为云端巨头的》一点号 纵酒狂歌 - 2013年,进入PaaS云时代
- 2014年,K8S发布
- 2015年,作者的文章以博客的形式,在Nginx官网上发布
- 2016年,成书
- 2019年,Netflix 架构师 Ruslan Meshenberg(后被苹果挖走) 在 Goto 大会上介绍了 Netflix 的微服务架构,一天发布1000次。对DevOps的理解:“谁构建,谁运维”,这一理念旨在鼓励系统开发团队同时负责系统的运维与支持工作,从而真正将 DevOps 引入实践。
选择1-4章
framework的作用
当解决一个复杂而且庞大的问题的时候,往往将其分解为一个个更小、相对简单、易于理解的子问题,然后继续划分成更小的子问题,直到每个子问题都有成熟的方案备选,然后根据需要和约束选择选择方案。
粗略来看,微服务的framework如下:
重点是3个问题:系统外和系统内部如何通讯,系统内部微服务之间如何通讯、外界如何感知微服务以及微服务之间如何感知对方。
书的第一章是微服务的介绍,2-4章讲的就是这三个问题的原理和备选方案。所以这里选择1-4章进行介绍。
1. 微服务介绍
单体应用
如图所示,这是一个打车软件的单体应用:
这张图 是典型的六边形架构【1】(hexagonal-architecture[hekˈsæɡənl]), 将域模型(应用)和外部设备区分开来。分为三层:域模型、Port和Adapter。
当外界事件到达一个端口时,某个特定技术的适配器把它转换为可用的过程调用或者消息传递给应用。应用完全不知道输入设备的特征。当应用有东西需要发送出去时,它会通过一个端口,发送给某个适配器,由适配器来生成接受端技术(人或自动化的)所适合的信号。应用在其所有边界上都可以与适配器进行语义上的良性互动,而不用知道适配器另一端上东西的特征。这样当讨论域模型的时候,不需要关心技术,只需要考虑业务逻辑;同时方便开发、测试和部署。
关于六边形更多的细节,可以参考Ian Cooper的文章。《Exploring the Hexagonal Architecture》
简单介绍下途中的几个Adapter:
- twilio:拨打和接听网络电话(推流?读音参考twitter)
- Sendgrid:邮件平台
- Stripe:支付
- MySql:MariaDB兼容MySql
单体服务的问题
- 会变的复杂,很难理解
- 启动慢
- 伸缩困难,可靠性降低
- 部署困难
- 船大掉头难
- 不同模块对资源的需要不同
微服务
整张图,可以关注两点:
- 一个大的单体应用被拆成了多个微服务,每个微服务负责一部分相对独立的功能。
- 整个系统的通讯分为两类:服务间的通讯,术语是
东西向通讯
;系统外部和系统的通讯,术语是南北向通讯
。
伸缩的三个维度
上图是 Scale Cube 的 3D 模型,来自《The Art of Scalability》 一书。作者马丁 L. 阿伯特和迈克尔 T. 费舍尔分别是 eBay 和 PayPal 的前 CTO。
微服务架构范式对应 Y 轴,X 轴由负载均衡器后端运行的多个应用副本组成,Z 轴(数据分割)将需求路由到相关服务。
在运行时,可以看应用是如何从X维度进行伸缩。旅途管理服务由多个服务实例组成,每个服务实例是一个 Docker 容器。为了实现高可用,容器是在多个云虚拟机上运行的。服务实例的前方是一个负载均衡器(如 NGINX),用于跨实例分发请求。负载均衡器也可以处理其他问题,如缓存、访问控制、API 度量和监控。在这个例子里面,该维服务是使用 Docker 部署到 AWS EC2 上的。
微服务架构范式对应用和数据库的关系影响巨大。每个服务都有自身的数据库计划,而不与其它服务共享同一个数据库。
优势与不足
优势
- 通过分解巨大单体应用为多个服务方法解决了复杂性问题。
- 独立开发
- 独立部署
- 独立伸缩,比如,你可以在 EC2 Compute Optimized instances 上部署 CPU 敏感的服务,而在 EC2 memory-optimized instances 上部署内存数据库(Redis,Memcache,Berkeley DB)。
不足
- there are no silver bullets
- 固有的复杂性。包括进程间通讯机制、局部失效问题。如果是单体应用,处理模块间的通讯是语言级的,或者可以通过事务回滚解决。
- 分布式的数据库架构,带来的复杂性。
CAP:
Consistency:一致性,写操作后的读操作可以读取到最新的数据状态,当数据分布在多个节点时,从任意节点读取到的数据都是最新的状态。
Availability:可用性,指任何事务操作都可以得到相应结果,且不会出现响应超时或响应错误。
PartitionTolerance:通常分布式系统的各个节点部署在不同的子网,这就是网络分区,不可避免的会出现由于网络问题而导致节点之间通信失败,此时仍可对外提供服务,这叫分区容忍性。
在类似微服务的分布式系统语境中,通常情况下,P是默认的,但是需要考虑CA之间的矛盾。具体来说,如果要实现C则必须保证数据一致性,在数据同步的时候为防止向从数据库查询的不一致则需要从数据库锁定,待完成同步之后解锁,如果同步失败从数据库要返回错误信息或超时信息;然而,如果要实现A则必须保证数据可用性,不管任何时候都可以向从数据库进行查询数据,并且不能够返回错误信息或者超时信息。
CA组合
CA组合就是保证一致性和可用性,放弃分区容忍性,即不进行分区,不考虑由于网络不通或节点挂掉的问题。那么系统将不是一个标准的分布式系统,我们最常用的关系型数据库就满足了CA。CP组合
CP组合就是保证一致性和分区容忍性,放弃可用性。Zookerper就是追求强一致性,放弃了可用性,还有跨行转账,一次转账请求要等待双方银行系统都完成整个事务才能完成。AP组合
AP组合就是保证可用性和分区容忍性,放弃一致性。这是分布式系统设计时的选择。
Cassandra(阿里)、Dynamo(AWS)等,默认优先选择AP,弱化C;HBase、MongoDB、ZooKeeper 等,默认优先选择CP,弱化A。
BASE 理论
eBay 架构师Dan Pritchett(普里切特) 提出了BASE 理论,用于解决大规模分布式系统下的数据一致性问题。BASE 理论告诉我们:可以通过放弃系统在每个时刻的强一致性来换取系统的可扩展性。
BASE 理论核心思想:
- 基本可用(BasicallyAvailable):指分布式系统在出现故障时,允许损失部分的可用性来保证核心可用。
- 软状态(SoftState):指允许分布式系统存在中间状态,该中间状态不会影响到系统的整体可用性。
- 最终一致性(EventualConsistency):指分布式系统中的所有副本数据经过一定时间后,最终能够达到一致的状态。
在A和C进行权衡妥协。
一致性模型
数据的一致性模型可以分成以下 3 类:
- 强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。
- 弱一致性:数据更新成功后,系统不承诺立即可以读到最新写入的值,也不承诺具体多久之后可以读到。
- 最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺立即可以返回最新写入的值,但是保证最终会返回上一次更新操作的值。
分布式系统数据的强一致性、弱一致性和最终一致性可以通过Quorum NRW算法分析。
分布式事务的四种解决方案
-
两阶段提交(
强一致性
)
存在一个Coordinator,第一阶段是准备阶段,Coordinator询问参与者事务是否执行成功,参与者发回事务执行结果;第二阶段,如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
2PC的核心原理是通过提交分阶段和记日志的方式,记录下事务提交所处的阶段状态,在组件宕机重启后,可通过日志恢复事务提交的阶段状态,并在这个状态节点重试。
补偿事务 TCC(
最终一致性
)
其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
a)Try 阶段主要是对业务系统做检测及资源预留(例如,给数据加锁)
b)Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
c)如果b)执行成功,则执行成功;如果失败,进入Cancel阶段。Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
实现关键要素:服务调用链必须被记录下来;每个服务提供者都需要提供一组业务逻辑相反的操作,互为补偿,同时回滚操作要保证幂等;必须按失败原因执行不同的回滚策略。本地消息表(异步确保
最终一致性
)
在分布式事务操作的一方完成写业务数据的操作之后向本地消息表(在同一个数据库里)发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
一种非常经典的实现,避免了分布式事务,实现了最终一致性。eBay 的架构师Dan Pritchett,曾在一篇解释BASE 原理的论文《Base:An Acid Alternative》中提到一个eBay 分布式系统一致性问题的解决方案。它的核心思想是将需要分布式处理的任务通过消息或者日志的方式来异步执行,消息或日志可以存到本地文件、数据库或消息队列,再通过业务规则进行失败重试,它要求各服务的接口是幂等的。-
MQ 事务消息(
最终一致性
)
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
以阿里的 RocketMQ 中间件为例,其思路大致为:
第一阶段Prepared消息,会拿到消息的地址。 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
缓存数据最终一致性(
最终一致性
)
在我们的业务系统中,缓存(Redis 或者Memcached)通常被用在数据库前面,作为数据读取的缓冲,使得I/O 操作不至于直接落在数据库上。
要解决缓存和数据库数据不一致的问题我们有以下两种解决方案:
a)为缓存数据设置过期时间。当缓存中数据过期后,业务系统会从数据库中获取数据,并将新值放入缓存。这个过期时间就是系统可以达到最终一致的容忍时间。
b)更新数据库数据后同时清除缓存数据。数据库数据更新后,同步删除缓存中数据,使得下次对商品详情的获取直接从数据库中获取,并同步到缓存。
对购物转账等电商和金融业务,中间件层的2PC最大问题在于业务不可见,一旦出现不可抗力或意想不到的一致性破坏,如数据节点永久性宕机,业务难以根据2PC的日志进行补偿。金融场景下,数据一致性是命根,业务需要对数据有百分之百的掌控力,一般使用TCC这类分布式事务模型,或基于消息队列的柔性事务框架,这两种方案都在业务层实现,业务开发者具有足够掌控力,可以结合SOA框架来架构,包括Dubbo、Spring Cloud等。
API Gateway
定义客户端如何和应用交互,即所谓的南北向通讯
。还是以之前的应用为例,
如果是单体应用,移动客户端通过向应用程序发起一次 REST 调用(GET api.company.com/productdetails/)来获取这些数据。负载均衡器将请求路由给 N 个相同的应用程序实例中的其中之一。然后,应用程序会查询各种数据库表,并将响应返回给客户端。
如果是微服务,就需要考虑移动客户端如何访问这些服务。
显然直接访问不是好主意,每个微服务都有自己独特的架构和访问方式:
- 首先,这种方法还使得客户端代码非常复杂;
- 其次,RPC和MQ并不是浏览器友好、防火墙友好,防火墙之外,应该是HTTP或者WebSocket【1】;
- 最后,会使得微服务难以重构。
API Gateway的优点:
- 简单的接口。
- 对客户端可裁剪。例如:
- 对于基于HTML5/JavaScript的UI,可以通过服务器端的Web应用生成HTML
- 对于原生的Android和iPhone应用,可以通过REST API和客户端通讯
- 对于第三方的应用,可以提供REST接口
- 此外,API Gateway还可以提供认证、监控、计费、负载均衡,缓存,静态响应【2】,request shaping 和管理等功能【3】。
API 网关也有一些不足。它增加了一个我们必须开发、部署和维护的高可用组件。还有一个风险是,API 网关变成了开发瓶颈。为了暴露每个微服务的端点,开发人员必须更新 API 网关。API网关的更新过程要尽可能地简单,这很重要;否则,为了更新网关,开发人员将不得不排队等待。不过,虽然有这些不足,但对于大多数现实世界的应用程序而言,使用 API 网关是合理的。
API GateWay 的相关技术
有很多种类型的API GateWay,了解和选择的时候,有很多维度,考虑的东西很多,性能、可用性、可扩展性、需求的匹配度、是否开源、是否自主可控、公有云和私有云。这里仅从性能的角度展开讨论。
对于大多数应用程序而言,API 网关的性能都非常重要。因此,将 API 网关构建在一个支持异步、非阻塞 I/O 的平台上是合理的。有多种不同的技术可以实现一个可扩展的 API 网关。在 JVM 上,可以使用一种基于 NIO 的框架,比如 Netty、RxJava(NetFlix)中的一种。一个非常流行的非 JVM 选项是 Node.js。
Nginx-plus提供了动态重配置API,通过Lua或Perl进行动态更新,或者由Chef, Puppet, ZooKeeper, 或者DNS的更新来驱动。
API GateWay 主要的解决方案
私有云
- Kong:基于Nginx+Lua进行二次开发
- NetFlix Zuul:Spring Cloud的一个推荐组件,基于Servlet。
- Orange:国人当自强
公有云
- Amazon API Gateway
- 阿里云API网关
- 腾讯云API网关
自开发
- 基于Nginx+Lua+OpenResty,Nginx 是俄罗斯人发明的, Lua 是巴西几个教授发明的,中国人章亦春把 LuaJIT VM 嵌入到 Nginx 中,实现了 OpenResty 这个高性能服务端解决方案。
- 基于Netty,I/O 非阻塞
- 基于Node.js,Node.js天生非阻塞
- 基于Servlet
微服务API Gateway并不是必须的
Service Mesh(网格) 和 Dubbo
Inter-Process Communication
在单体应用程序中,组件可通过语言级方法或者函数相互调用。相比之下,基于微服务的应用程序是一个运行在多台机器上的分布式系统。通常,每个服务实例是一个进程。
因此,服务必须使用进程间通信(IPC)机制进行交互。
https://blog.csdn.net/g6U8W7p06dCO99fQ3/article/details/118833581
交互方式
1对多
1对1
同步,异步
处理局部故障
分布式系统普遍存在局部失败的问题。由于客户端和服务端是独立的进程,服务端可能无法及时响应客户端请求。服务端可能会因为故障或者维护而暂时不可用。服务端也可能会由于过载,导致对请求的响应极其缓慢。
假设推荐服务无法响应,客户端可能会由于无限期等待响应而阻塞。这不仅会导致很差的用户体验,并且在很多应用中还会占用之前的资源,比如线程;最终,如下图所示,运行时耗尽线程资源,无法响应。
为了预防这种问题,设计服务时候必须要考虑部分失败的问题。
解决方案
Netfilix 提供了一个比较好的解决方案,具体的应对措施包括:
- 网络超时:在等待响应时,不设置无限期阻塞,而是采用超时策略。使用超时策略可以确保资源不被无限期占用。
- 限制请求的次数:可以为客户端对某特定服务的请求设置一个访问上限。如果请求已达上限,就要立刻终止请求服务。备选方案包括线程池和semaphore(信号量,非阻塞)信号量代替线程,用于不进行网络调用时的依赖项执行(例如那些只执行内存缓存查找的线程),因为独立线程的开销对于这些类型的操作来说太高了。
- 断路器模式(Circuit Breaker Pattern):记录成功和失败请求的数量。如果失效率超过一个阈值,触发断路器使得后续的请求立刻失败。如果大量的请求失败,就可能是这个服务不可用,再发请求也无意义。
下图是一个断路器的简单实例,当timeout发生一定次数后,断路器被触发:
在一个失效期后,客户端可以再试,如果成功,关闭此断路器。
- 提供回滚:当一个请求失败后可以进行回滚逻辑。例如,返回缓存数据或者一个系统默认值。Netflix Hystrix 是一个实现相关模式的开源库。如果使用 JVM,推荐使用Hystrix。而如果使用非 JVM 环境,你可以使用类似功能的库。
这些容错方法各有优缺点,但是当它们结合在一起时,在用户请求和底层依赖项之间提供了一个全面的保护屏障。
当发生故障时,我们如何响应用户请求
- 缓存:如果实时依赖项不可用,则从本地或远程缓存检索数据,即使数据最终已过期
- 最终一致性:队列写入(如在SQS中),在依赖项再次可用时继续
- 存根数据:当无法检索个性化选项时,恢复到默认值
- 空响应(“Fail Silent”):返回null或空列表,ui可以忽略他们
以上内容来自:
1.《Fault Tolerance in a High Volume, Distributed System》Fault Tolerance in a High Volume, Distributed System
2.《博文精译-高容量分布式系统的容错》drjava_2019 csdn
主要技术
异步消息
由于异步通信,客户端不会因为等待而阻塞,相反会认为响应不会被立即收到。解耦,异步、削峰(秒杀或者团购时,对数据库的请求数有限制),
通过向发布/订阅渠道写入一条创建行程的消息,行程管理服务会通知调度服务有新的行程请求。调度服务发现可用的司机后会向发布/订阅渠道写入一条推荐司机的消息,并通知其它服务。
其实我们在双11的时候,当我们凌晨大量的秒杀和抢购商品,然后去结算的时候,就会发现,界面会提醒我们,让我们稍等,以及一些友好的图片文字提醒。而不是像前几年的时代,动不动就页面卡死,报错等来呈现给用户。
协议
AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
其中较为成熟的MQ产品有IBM WebSphere MQ、RabbitMQ 、ZeroMQ 、ActiveMQ(基于JMS的一个开源实现,JMS 是一个接口标准或者说是一个API消息服务的规范即JAVA Message Service,java消息服务)、Redis(当做一个轻量级的队列服务来使用)、Kafka(只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广,能处理的数据量大)、RocketMQ(是阿里出品,如果阿里放弃维护rocketmq,中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐)。
其他基于TCP/IP自定义的协议 有些特殊框架(如:redis、kafka、zeroMq等)根据自身需要未严格遵循MQ规范,而是基于TCP\IP自行封装了一套协议,通过网络socket接口进行传输,实现了MQ的功能。
RabbitMQ
基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富;支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。主从架构。在很多公司得到应用;有较多的文档;各种协议支持较好;中小型企业首选。
-
RabbitMQ 实现RPC的机制是(可靠性):
如图所示,从AMQP协议层面上来说,消息从生产者到消费者的过程分为四个阶段:
- 消息先从生产者Producer出发到达交换器Exchange(以RouteKey的形式);
- 交换器Exchange根据路由规则(RouteKey)将消息转发对应的队列Queue之上。交换器有三种模式:直连,广播和主题(支持“#”和“*”的通配符);
- 消息在队列Queue上进行存储;
- 消费者Consumer订阅队列Queue并进行消费。
从可靠性来说,没个阶段都有每个阶段的策略:
- 消息先从生产者Producer出发到达交换器Exchange
两种模式:
a. TRANSACTION模式:阻塞,性能低,严重浪费服务器资源。
b. CONFIRM模式:异步确认。类似TCP的处理,接收方返回一个Ack报文,里面携带seq号码,通知发送方报文已经收到。可以设置multiple参数,表示到这个序号之前的所有消息都已经得到了处理。这个就是“重复请求”(ARQ,Automatic Repeat Request),构成了很多通讯协议的基础,例如TCP。
-
交换器Exchange根据路由规则将消息转发对应的队列Queue之上
三种模式:
a. 丢弃
b. 添加ReturnListener监听器:生产者客户端通过ReturnListener监听到了这个事件,采取相应的行动,重新投递或者其它方案。
c. 备胎交换器:这样可以将未被路由的消息存储在RabbitMQ中,再在需要的时候去处理这些消息。
消息在队列Queue上进行存储
两种模式:
a. 持久化
b. 高可用消费者Consumer订阅队列Queue并进行消费
两种模式:
a. 丢弃
b. 消息确认机制(message acknowledgement):消费者在订阅队列时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息。
以上内容来自
《RABBITMQ 消息可靠性投递(实际项目改造)》 CSDN 朱小厮
- 消息重复
两种可能性:
- 消息消费成功,事务已经提交,ack时,机器宕机,导致消息没有确认,又需要重新发送;
- 消息消费失败,由于重试机制,自动又将消息发送出去。
解决办法:
- 消费者的业务消费接口应该设计为幂等性;
- 发送的每一个消息都有业务的唯一标识,处理过就不用处理,例如雪花算法。
- 消息堆积
消费者将消息批量取出,离线慢慢处理,最终一致性的解决方案。
Apache Kafka
使用Zookeeper。
Scala开发,分布式架构,可用性非常高。只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。
大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量,会选择kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。
-
高可用性
一个典型的Kafka体系架构包括若干Producer(可以是服务器日志,业务数据,页面前端产生的page view等等),若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),若干Consumer (Group),以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在consumer group发生变化时进行rebalance。Producer使用push(推)模式将消息发布到broker,Consumer使用pull(拉)模式从broker订阅并消费消息。
https://www.cnblogs.com/qingyunzong/p/9004509.html
https://zhuanlan.zhihu.com/p/56440807
同步的请求/响应 IPC
当使用基于同步、基于请求/响应的 IPC 机制时,客户端向服务器发送请求,该服务处理该请求并返回响应,与使用消息传递不同,客户端假定响应能及时到达。有许多协议可供选择。有两种流行协议分别是 REST 和 Thrift。
REST
REST这个词,是Roy Fielding在他2000年的博士论文中提出的。Fielding是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。
REST,即Representational [ˌreprɪzenˈteɪʃənl] State Transfer的缩写,可以翻译为"表现层状态转化"。如果一个架构符合REST原则,就称它为RESTful架构。这里,省略了一个主语:Resource。"表现层"其实指的是"资源"(Resources)的"表现层"。
- 资源:就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是应用程序对象、数据库记录、算法,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
- 表现层:"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
- 状态转化(State Transfer)。访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
乘客的智能手机通过向 Trip Management 服务的 /trips 资源发出一个 POST 请求来请求旅程。该服务通过向 Passenger Management 服务发送一个获取乘客信息的 GET 请求来处理该请求。在验证乘客被授权创建旅程后,Trip Management 服务将创建旅程,并向智能手机返回 201 响应。
RPC
RPC(Remote Procedure Call),即远程过程调用,它是一种通过网络从远程计算机程序上请求服务而不需要了解底层网络技术的思想。
三种比较流行的框架:
- gRPC:是 Google 公布的开源软件,基于 HTTP 2.0 协议,并支持常见的众多编程语言。底层使用到了 Netty 框架的支持(基于事件驱动)。
- Thrift:是 Facebook 的开源 RPC 框架,主要是一个跨语言的服务开发框架。
用户只要在其之上进行二次开发就行,应用对于底层的 RPC 通讯等都是透明的。不过这个对于用户来说需要学习特定领域语言这个特性,还是有一定成本的。 - Dubbo:是阿里集团开源的一个极为出名的 RPC 框架,在很多互联网公司和企业应用中广泛使用。协议和序列化框架都可以插拔是极其鲜明的特色。
服务发现
为什么要使用服务发现?
服务实例的网络位置都是动态分配的。由于扩展、失败和升级,服务实例会经常动态改变,因此,客户端代码需要使用较为复杂的服务发现机制。
发现模式
服务发现有两大模式:客户端发现模式和服务端发现模式。
客户端发现模式
使用客户端发现模式时,客户端决定相应服务实例的网络位置,并且对请求实现负载均衡。客户端查询服务注册表,后者是一个可用服务实例的数据库;然后使用负载均衡算法从中选择一个实例,并发出请求。
下图显示了这种模式的架构:
Netflix OSS 是客户端发现模式的绝佳范例。Netflix Eureka 是一个服务注册表,为服务实例注册管理和查询可用实例提供了 REST API 接口。Netflix Ribbon 是 IPC 客户端,与 Eureka 一起实现对请求的负载均衡。
国内阿里开源的Dubbo也是采用这种模式。
客户端发现模式优缺点兼有。这一模式相对直接,除了服务注册外,其它部分无需变动。此外,由于客户端知晓可用的服务实例,能针对特定应用实现智能负载均衡,比如使用哈希一致性。这种模式的一大缺点就是客户端与服务注册绑定,要针对服务端用到的每个编程语言和框架,实现客户端的服务发现逻辑。
服务端发现模式
另外一种服务发现的模式是服务端发现模式,下图展现了这种模式的架构:
这是最简单和传统的负载均衡做法,在服务消费者和生产者之间,代理作为独立一层集中部署,由独立团队(一般是运维或框架)负责治理和运维。常用的集中式代理有硬件负载均衡器(如F5),或者软件负载均衡器(如Nginx),F5(4层负载)+Nginx(7层负载)这种软硬结合两层代理也是业内常见做法,兼顾配置的灵活性(Nginx比F5易于配置)。
国外知名电商网站eBay,虽然体量巨大,但其内部的服务发现机制仍然是基于这种传统的集中代理模式,国内公司如携程,也是采用这种模式。
内容来自:《微服务之-ServiceMesh》HelloWide 简书
客户端通过负载均衡器向某个服务提出请求,负载均衡器查询服务注册表,并将请求转发到可用的服务实例。如同客户端发现,服务实例在服务注册表中注册或注销。
主要实现方式:
- AWS Elastic Load Balancer(ELB)是服务端发现路由的例子,ELB 通常均衡来自互联网的外部流量,也可用来负载均衡 VPC(Virtual private cloud)的内部流量。客户端使用 DNS 通过 ELB 发出请求(HTTP 或 TCP),ELB 在已注册的 EC2 实例或 ECS(Amazon Elastic Container Service)容器之间负载均衡。这里并没有单独的服务注册表,相反,EC2 实例和 ECS 容器注册在 ELB。
- Nginx+Consul:HTTP 服务器与类似 NGINX PLUS 和 NGINX 这样的负载均衡起也能用作服务端的发现均衡器。例如使用 Consul Template 来动态配置 NGINX 反向代理。Consul Template 定期从 Consul Template 注册表中的配置数据中生成 nginx.conf 文件,用于配置反向代理,然后运行命令,告诉 NGINX 重新加载配置文件。
- Kubernetes 这样的部署环境会在每个集群上运行一个代理(kube-proxy),将代理用作服务端发现的负载均衡器。客户端使用主机 IP 地址和分配的端口通过代理将请求路由出去,向服务发送请求。代理将请求透明地转发到集群中可用的服务实例。
服务注册表
服务注册表是服务发现的核心部分,是包含服务实例的网络地址的数据库。服务注册表需要高可用而且随时更新。客户端能够缓存从服务注册表中获取的网络地址,然而,这些信息最终会过时,客户端也就无法发现服务实例。因此,服务注册表会包含若干服务端,使用复制协议保持一致性。
主要实现:
- Netflix Eureka:为注册和请求服务实例提供了 REST API。服务实例使用 POST 请求来注册网络地址,每三十秒使用 PUT 请求来刷新注册信息。注册信息也能通过 HTTP DELETE 请求或者实例超时来被移除。以此类推,客户端能够使用 HTTP GET 请求来检索已注册的服务实例。
Netflix 通过在每个 AWS EC2 域运行一个或者多个 Eureka 服务实现高可用性。每个 Eureka 服务器都运行在拥有弹性 IP 地址的 EC2 实例上。DNS TEXT 记录被用来保存 Eureka 集群配置,后者包括可用域和 Eureka 服务器的网络地址列表。Eureka 服务在启动时会查询 DNS 去获取 Eureka 集群配置,确定同伴位置,以及给自己分配一个未被使用的弹性 IP 地址。
Eureka 客户端,包括服务和服务客户端,查询 DNS 去发现 Eureka 服务的网络地址。客户端首选同一AZ内的 Eureka 服务。然而,如果没有可用服务,客户端会使用其它AZ中的 Eureka 服务。 - etcd:高可用、分布式、一致性的键值存储,用于共享配置和服务发现。Kubernetes 和 Cloud Foundry 是两个使用 etcd 的著名项目。
- consul – 发现和配置的服务,提供 API 实现客户端注册和发现服务。Consul 通过健康检查来判断服务的可用性。
- Apache ZooKeeper – 被分布式应用广泛使用的高性能协调服务。Apache ZooKeeper 最初是 Hadoop 的子项目,现在已成为顶级项目。
- 此外,如前所强调,像 Kubernetes、Marathon 和 AWS 并没有明确的服务注册,相反,服务注册已经内置在基础设施中。
服务注册的方式
如前所述,服务实例必须在注册表中注册和注销。注册和注销有两种不同的方法。方法一是服务实例自己注册,也叫自注册模式(self-registration pattern);另一种是采用管理服务实例注册的其它系统组件,即第三方注册模式。
自注册方式
当使用自注册模式时,服务实例负责在服务注册表中注册和注销。另外,如果需要的话,一个服务实例也要发送心跳来保证注册信息不会过时。下图描述了这种架构:
- Netflix OSS Eureka 客户端是非常好的案例,它负责处理服务实例的注册和注销。Spring Cloud 能够执行包括服务发现在内的各种模式,使得利用 Eureka 自动注册服务实例更简单,
第三方注册模式
使用第三方注册模式,服务实例则不需要向服务注册表注册;相反,被称为服务注册器的另一个系统模块会处理。服务注册器会通过查询部署环境或订阅事件的方式来跟踪运行实例的更改。一旦侦测到有新的可用服务实例,会向注册表注册此服务。服务管理器也负责注销终止的服务实例。下面是这种模式的架构图。
- Registrator 是一个开源的服务注册项目,它能够自动注册和注销被部署为 Docker 容器的服务实例。Registrator 支持包括 etcd 和 Consul 在内的多种服务注册表。
- NetflixOSS Prana 是另一个服务注册器,主要面向非 JVM 语言开发的服务,是一款与服务实例一起运行的并行应用。Prana 使用 Netflix Eureka 来注册和注销服务实例。
- 服务注册器是部署环境的内置组件。由 Autoscaling Group 创建的 EC2 实例能够自动向 ELB 注册。Kubernetes 服务自动注册并能够被发现
微服务主要框架
Spring Cloud
内容来自《Spring Cloud 入门总结》 知乎 老刘
简介
Spring Cloud 就是微服务系统架构的一站式解决方案,在构建微服务的过程中需要做如服务发现注册 、配置中心 、消息总线 、负载均衡 、断路器 、数据监控 等操作,而 Spring Cloud 为我们提供了一套简易的全家桶式编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务项目的构建。
社区支持强大,更新非常快,所以开发效率高。
我们需要特别感谢Netflix ,这家很早就成功实践微服务的公司,几年前把自家几乎整个微服务框架栈贡献给了社区,Spring Cloud主要是对Netflix开源组件的进一步封装。还有Pivotal和Netflix是其强大的后盾与技术输出。其中Netflix开源的整套微服务架构套件是Spring Cloud的核心。
版本
Spring Cloud 的版本号并不是我们通常见的数字版本号,而是一些很奇怪的单词。这些单词均为英国伦敦地铁站的站名。同时根据字母表的顺序来对应版本时间顺序,比如:最早 的 Release 版本 Angel,第二个 Release 版本 Brixton(英国地名),然后是 Camden、 Dalston、Edgware、Finchley、Greenwich、Hoxton。
服务发现-Eureka
Eureka是基于REST的服务,主要在AWS云中用于定位服务。我们称此服务为Eureka服务器。Eureka还带有一个基于Java的客户端组件Eureka Client,Eureka Client查询一个注册中心,使与服务的交互变得更加容易。
Eureka Client通过心跳机制,每隔30秒(默认情况下)发送一次心跳来续约,如果 Eureka Server在90秒没有收到 Eureka Client 的续约,它会将实例从其注册表中删除。
大家可能觉得美国人起名都没什么历史文化积淀,比如Apple(水果)、IBM(International Business Machines Corporation 万国商业机器公司)、HP(创始人的名字开头字母)、Facebook(脸书)。。。而中国人起的名字都特别有文化:鸿蒙、天问、九章。。。Eureka
这个词其实还是挺有历史感的,因为它曾经出自阿基米德之口。阿基米德在浴缸里想到如何为国王鉴别王冠的含金量后,吐出的就是这个词,Eureka
!意思是找到了
!
Eureka的设计者认为对于服务发现而言,可用性比数据一致性更加重要,Eureka 设计则遵循AP原则。
负载均衡之 Ribbon
Ribbon 是 Netflix 公司的一个开源的负载均衡项目,是一个IPC内负载均衡器,运行在消费者端
。
Hystrix之熔断和降级
在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。
熔断:就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过断路器直接将此请求链路断开。
降级:是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。比如有一个热点新闻出现了,我们会推荐给用户查看详情,然后用户会通过id去查询新闻的详情,但是因为这条新闻太火了,大量用户同时访问可能会导致系统崩溃,那么我们就进行服务降级,一些请求会做一些降级处理比如当前人数太多请稍后查看等等。
微服务网关——Zuul
ZUUL 是从设备和 web 站点到 Netflix 流应用后端的所有请求的前门。作为边界服务应用,ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。它还具有根据情况将请求路由到多个 Amazon Auto Scaling Groups(亚马逊自动缩放组,亚马逊的一种云计算方式) 的能力
但是升级较慢,有人已经转到Spring Cloud Gateway
Spring Cloud配置管理——Config
当微服务系统开始慢慢地庞大起来,那么多 Consumer 、Provider 、Eureka Server、Zuul 系统都会持有自己的配置,这个时候我们在项目运行的时候可能需要更改某些应用的配置,Config能对配置文件统一地进行管理,又能在项目运行时动态修改配置文件。
Dubbo
Dubbo是阿里开发的RPC框架及服务治理框架. 捐献给了Apache。Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
或许很多人会说Spring Cloud和Dubbo的对比有点不公平,Dubbo只是实现了服务治理,而Spring Cloud下面有17个子项目(可能还会新增)分别覆盖了微服务架构下的方方面面,Dubbo仅仅关注服务治理(东西向通讯
),一定程度来说,Dubbo只是Spring Cloud Netflix中的一个子集。
服务自动注册和发现
远程方法调用
dubbo 底层就是 spring,Netty(适合小数据量、大并发的NIO),Zookeeper(Dubbo 原生支持的Redis 方案需要服务器时间同步,且性能消耗过大,Zookeeper 保证的是CP )。服务发布的时候 dubbo 依赖的协议,可以配置 dubbo(适合小数据量、大并发的NIO)、RMI、webservice、Thrift、Hessain、http等协议。
Dubbo 3.0重大革新
据了解,新的 Dubbo 内核与 Dubbo 2.0 完全不同,但它兼容 2.0。Dubbo 3.0 将以 Streaming 为内核,而不再是 2.0 时代的 RPC,但是 RPC 会在 3.0 中变成远程 Streaming 对接的一种可选形态。而 Streaming 通道与 gRPC 类似,支持 HTTP/2,同时 REST 接口也会受到一等公民支持,但是梁飞也表示此次在通讯上的改动并不大,重点是在服务治理和编程模型上。
说到编程模型的革新,梁飞透露,此次 Dubbo 3.0 能够开工,主要也是因为新特性将去掉一切阻塞,以“一切同步”为第一目标,在对 IO 密集业务的处理上,它能够提高机器利用率,使得一半机器的成本被节省下来。他还表示,其实 Dubbo 3.0 技术选型重大变更的驱动因素,也就是降低成本,因为在将系统服务化后,全业务线的机器都在等待返回数据,负载压不上去,机器浪费严重。
这个去阻塞化的模式,其实就是使用了“反应式编程”模式(Reactive Programming),梁飞介绍,在 Dubbo 3.0 中,reactive 将成为核心,会做到客户端、服务端、缓存和数据库,全程无阻塞。在数据库上,JDBC 驱动将进行更改,同时,为了性能,还会配合使用阿里毕玄对 JVM 协程的改造。更为重要的是,这个重大变更,不仅体现在 Dubbo 上,它也将影响到阿里 10 年来积累的中间件。
负载均衡
dubbo-cluster 集群模块,将多个服务提供方伪装为一个提供方,包括:负载均衡、容错、路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
ServiceMesh
该部分内容来自
- 《Pattern: Service Mesh》Aug 3, 2017,Phil Calçado
- 《什么是 Service Mesh》,Kenshin,知乎
Buoyant的CEO William Morgan,也就是Service Mesh这个词的发明人,对Service Mesh的定义:
服务网格是一个基础设施层,用于处理服务间通信。云原生应用有着复杂的服务拓扑,服务网格保证请求在这些拓扑中可靠地穿梭。在实际应用当中,服务网格通常是由一系列轻量级的网络代理组成的,它们与应用程序部署在一起,但对应用程序透明。
Service Mesh 是微服务时代的 TCP/IP 协议。分布式系统特有的通信语义又出现了,如熔断策略、负载均衡、服务发现、认证和授权、quota限制、trace和监控等等,于是服务根据业务需求来实现一部分所需的通信语义。
为了避免每个服务都需要自己实现一套分布式系统通信的语义功能,随着技术的发展,一些面向微服务架构的开发框架出现了,如Twitter的Finagle、Facebook的Proxygen以及Spring Cloud等等,这些框架实现了分布式系统通信需要的各种通用语义功能:如负载均衡和服务发现等,因此一定程度上屏蔽了这些通信细节,使得开发人员使用较少的框架代码就能开发出健壮的分布式系统。
也被形象称为边车(Sidecar)模式,如下图,早期有一些摩托车,除了主驾驶位,还带一个边车位,可以额外坐一个人。业务代码进程(相当于主驾驶)共享一个代理(相当于边车),代理除了负责服务发现和负载均衡,还负责动态路由、容错限流、监控度量和安全日志等功能,这些功能是具体业务无关的,属于跨横切面关注点(Cross-Cutting Concerns)范畴。
-
Service Mesh 一代
-
Service Mesh 二代
目前Service Mesh还处于演进的过程中,Istio 作为目前最流行的 Service Mesh 技术之一,前景值得关注。
一些大厂(Airbnb,华为,新浪微博,蚂蚁金服等)在试水。
拓展
CloudFoundry的没落
打包机制上的缺陷:
Cloud Foundry最核心的组件就是应用的打包和分发机制,也是开发者打交道最多的功能。Cloud Foundry为每一种主流的语言都定义了一套打包的方式,这些方式之间毫无章法。但就是这个打包功能,成了Cloud Foundry的软肋,一直为用户所诟病。开发者不得不为每一种语言,每一种框架,甚至是每个版本应用维护一个打好的包,还有可能出现本机运行成功,打了个包上传上去之后就无法运行的情况。情况最严重的时候,开发者在调试云平台系统上花的时间都已经比开发一个新软件的时间要长了。
Docker解决了这个痛点,秘诀就是镜像。不同于Cloud Foundry那种执行文件+启动脚本的打包结果,镜像提供给用户的是一套完整的运行环境,每一个镜像都可以指定操作系统版本,内部可以构建程序执行的文件结构,并且一份镜像可以完全共享在多处使用。
CloudFoundry提出来了一套兼容Docker格式的技术,但并不是Docker,并且对传统有状态的应用程序的处理技术不是很清晰。
CloudFoundry则趋于自成体系的专有软件。
IaaS,PaaS,SaaS市场
IaaS的利润率在5%到20%之间,而PaaS和SaaS的利润率要高出不少。
Docker 之“死”
- Swarm的没落
- K8S从1.20开始弃用Docker支持,替代品:Containerd,cri-o。
- docker自身的问题《How Docker broke in half》
路线不明确,一直纠结于商业化和社区的发展模式。之前有过Google和Docker的谈判,但是最终谈判破裂,docker没能成为K8S生态系统的一部分。其次,过早的图未穷而匕首现。商业化并不成功,Docker desktop开始收费,大家担心这是一个屠龙者变成恶龙的故事。 - K8S的强大,网络、存储都可以做,编排的更好。
新的领域:WebAssembly
部分内容参考:
【1】知乎 网易数帆
【2】微服务模式:https://microservices.io/patterns/microservices.html
【3】API Gateway BFF:https://microservices.io/patterns/apigateway.html
【4】博客中文版:http://blog.daocloud.io/microservices-1/
【5】《CAP 定理的含义》阮一峰的网络日志
【6】《Introducing the Microservices Reference Architecture from NGINX》https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/
【7】《RPC和Dubbo简介》简书 目睹了整个事件的索先生
【8】《Dubbo架构设计及原理详解》 cnblog BarryWang
推荐读物
【1】云原生应用架构实践
【2】《软件开发教父,Martin Fowler》知乎 老中医包治一针灵
【3】Netflix reactive:https://netflixtechblog.com/reactive-programming-in-the-netflix-api-with-rxjava-7811c3a1496a
【4】Netflex OSSC:https://netflix.github.io/
【5】浅析DDD(领域驱动设计) 简书 Pursue
【6】《领域驱动设计:软件核心复杂性应对之道:tackling complexity in the heart of software》EricEvans
注释
【1】WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
【2】静态响应
【3】这里有个问题,
request shaping
是不是改成traffic shaping
更好一些。