规模化微服务——《微服务设计》读书笔记
系列文章目录:
改变思维的角度:故障无处不在
当微服务规模化后,故障是无可避免的,以往我们总是想尽力避免故障的发生,而当故障实际发生时,我们往往束手无策。我们花了很多时间在流程设计和应用设计的层面上来阻止故障的发生,但实际上很少花费时间思考如何第一时间从故障中恢复过来。
一些公司喜欢组织活动,活动当天系统会被关掉以模拟故障发生,然后不同团队演练如何应对这种情况。这些项目中最著名的是混乱猴子(Chaos Monkey),在一天的特定时间随机停掉服务器,这促使开发人员在构建系统时不得不为它做好准备。猴子军队可以在系统中注入网络延迟,也可以随机关闭整个可用区,这可以让你的系统得到终极验证。SimianArmy就是这样的一个项目。
通过让软件拥抱和引发故障,并构建系统来应对。
了解什么指标是我们必须关注的
企业Wiki一个月发生故障两天,我们认为是可以接受的,电商网站一天停一个小时也是不可接受的。我们需要正确地理解这些需求,以便采取合适的技术。
我们建议有一些默认的指标必须予以关注。
1.响应时间/延迟
鉴于网络的性质,你经常会遇到异常值,所以将监控的响应目标设置成一个百分比是合理的。
举例:我期望这个网站,当每秒处理200个并发连接时,90%的响应时间在2秒以内。
2.可用性
评价可用性,我们可以从历史报告来分析,以跟我们的期望值相对比。
3.数据持久性
用户的登录会话只需要保持一年,金融交易记录则需要保存很多年。
一.从体验上来解决:功能降级
当购物车功能不可用时,你可以隐藏掉这个图标,并提示“马上”,或者变成一个可下单的电话号码。
对于每个使用多个微服务的面向用户的界面,或每个依赖多个下游合作者的微服务来说,你都需要问自己“如果这个微服务宕掉会发生什么”,然后你就知道该如何做了。
二.从请求/响应上来解决
1.超时机制
给所有的跨进程调用设置超时,当发生超时时记录到日志中,并相应地调他们。
2.断路器
当对下游的请求发生一定数量的失败后,断路器会打开,接下来,所有的请求在断路器打开的情况下会快速地失败,一段时间后,客户端发送一些请求查看下游服务是否已经恢复,如果它得到了正常的响应,将重置断路器。
使用断路器,我们可以在请求失败后把这些请求存起来,然后稍后重试它们,也可以直接使用功能降级。Hystrix就是一个基于JVM的开源断路器。
3.舱壁
这是一种把自己从故障中隔离开的方式,如果我们使用下游多个服务,而其中一个服务占用了过多的连接池,由于是共用连接池,这势必会对其他的服务调用造成影响,舱壁的处理办法,即是为每个下游服务建立一个单独的连接池,我们当然也可以跟断路器联合起来使用。
在很多方面,舱壁是三个模式中最重要的,超时和断路器能够帮助你在资源受限时释放它们,但舱壁可以在第一时间确保它们不成为限制,它可以拒绝请求以避免资源达到饱和。
三.从服务依赖性上解决:隔离
一个服务依赖于另一个服务,另一个服务的健康将能影响其正常工作的能力,如果我们使用的集成技术允许下游服务器离线,上游服务便不太可能受到计划内或计划外宕机的影响。
Jekins的master/slave即是这样的解决思路。
四.从处理结果上解决:幂等
对幂等操作而言,多次执行所产生的影响均与一次执行的同,如果操作是幂等的,我们可以对其重复多闪调用,而不必担任会有不利影响。当我们不确定否被执行,想要重新处理消息,从而从错误中恢复时,幂等会非常有用。
五.从应用上解决:扩展应用
更强大的主机、一台主机一个微服务、关键业务弹性部署、多云服务数据商备份、异地灾备、负载均衡、重构系统、作业队列。
六.从数据库上解决:扩展数据库
方法1:主从复制
当主数据库出现故障,可以马上启动备份数据库,并提升至主数据库。
方法2:读写分离——扩展读操作
读操作使用的数据库和写操作使用的数据库分开,但这会造成数据暂时不一致的情况。
方法3:读写分离——扩展写操作
可以使用分片的方式来扩展写操作,当你有一块数据要写入时,对数据的关键字应用一个哈希函数,并基于这个函数的结果将数据发送到哪个分片。
但分片扩展会导致在查询上复杂起来,如果要查询所有年满18岁的顾客,那么要查询所有分片,然后在内存中拼起来;或者有一个替代的读数据训包含所有的数据集。
分片扩展的另一个问题是,如果想添加新的数据库节点,我们需要大量的宕机时间然后重新分配数据
方法4:CQRS——命令查询职责分离
在传统的系统中,数据的修改和查询使用的是同一个系统,使用CQRS后,系统的一部分负责获取修改状态的请求命令并处理它,而另一部分则负责处理查询。
七.从缓存上解决:缓存
1.客户端、代理服务器和服务器端缓存
使用客户端缓存,客户端会缓存存储的结果,由客户端决定何时获取最新副本。我们可以通过服务来告诉客户端应该更新缓存了。
代理服务器缓存,将一个代理服务放在客户端和服务端之间,反向代码或CDN就是这样的例子。
服务器缓存,由服务器端处理缓存,Redis、Memcache、内存缓存就是这样的系统。
2.HTTP缓存
标准的静态网站,像CSS或图片,很适合用HTTP中的cache-control和Expires指令,另外还可以使用HTTP的ETag来标示资源是否已改变,如果我更新了客户记录, 虽然访问资源的URI相同,但值已经不同,所以我会改变ETag。
3.使用写缓存
你可以先写入本地缓存,并在之后的某个时刻将缓存中的数据写入下游,当你有爆发式的写操作时,或同样的数据可能被写入多次时,或下游服务不可用时,这种方式都是很有用的。
4.为弹性使用缓存
我们可以缓存一个时间点的数据,在故障发生时提供这个时间点的数据,虽然可能不完全正确,最起码可以用。
5.隐藏源服务
有可能我们系统的80%请求都是通过缓存,那么缓存一旦失败,请求都将进入源服务,源服务从来没有处理过这么多请求,这将是灾难性的,我们可以阻塞请求,并在后台异步重建缓存。
6.避免过多地使用缓存
缓存越多,就越难评估数据的新鲜程度。
7.防止缓存中毒
你可能在系统中将HTTP缓存头的过期时间设置为Expires:Never,这样除非用户手工清除他们(也许CDN也会缓存这些内容),否则永远不会失效,这时我们唯一的选择就是:改变这些网页的URL,以便能够重新获取它们。
使用自动伸缩
现在的云服务提供商允许你制定自动伸缩服务的策略,通过响应式伸缩或者预测式伸缩帮助你在高峰(有可能是秒杀,有可能是突发式新闻,也有可能是因为周末)到来的时候提供服务。
CAP理论
Eric Brewer的CAP理论告诉我们,一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance),最多只能同时保证三个中的两个。
牺牲一致性:
我们只需要数据保持最终一致就可以了,因为我们需要分区来保证数据的安全性,同时保证系统的可用性。博客园我更新博客时,每个月份的博客统计数不会马上更新就是这样。
牺牲可用性:
像12306这样的网站每天晚上维护就是牺牲了可用性。
牺牲容忍性:
既然是分布式系统,这种情况是肯定不存在的。
服务发现、注册与编排
方法1:DNS
DNS让我们将一个名称与一个或多个机器的IP地址相关联,例如我们可决定在accounts.hello.com上发现账户服务,这个域名会关联到运行该服务的主机的IP上或负载均衡器上。
DNS的好处在于它是,但不好的地方在于更新它不是那么容易,而且对于一个新的节点,DNS不能“发现”这个节点。
方法2:Zookeeper
它支持配置管理、服务间的数据同步、leader选举、消息队列和命名服务。它的核心是提供了一个用于存储信息的分层命名空间,客户端可以在此层次结构中,插入新的节点,更改或查询它们。
方法3:Consul
它也支持配置管理和服务发现,但它比Zookeeper更进一步,为这些关键使用场景提供了更多支持,如它为服务发现提供了一个HTTP接口,另外它提供了现成的DNS服务器。
Consul还内置了一些功能,如对节点执行健康检查的能力。
Consul从注册服务、查询键/值存储到插入健康检查,都使用的是RESTful HTTP接口,这使集成不同技术栈变得简单。
方法4:Eureka
它提供了基本的负载均衡功能,它可以支持服务实例的基本轮询调度查找,它提供了一个基于REST的接口,因此你可以编写自己的客户端。
文档服务
微服务非常多,暴露的接口更多,我们希望知道关于这些接口的说明,理想情况下我们会确保文档总是和最新微服务的API文档同步,并当大家需要知道服务在哪里,能够很容易地看到这个文档。
1.Swagger
Swagger让你描述API,产生一个很友好的Web用户界面,使你可以查看文档并通过Web浏览器与API交互,还可以直接执行请求。
2.HAL和HAL浏览器
它是用媒体来生成我们的文档。
参考
《微服务设计》(Sam Newman 著 / 崔力强 张骏 译)