zz《可伸缩服务架构 框架与中间件》综合

    =======开篇吐槽:最近一段时间刚好碰上中秋国庆双节,而且工作任务繁重,基本很难保证有时间来写文章了=======

      《可伸缩服务架构 框架与中间件》与《分布式服务架构 原理、设计与实战》是要配套捆绑着看,这营销手段,服。

       这书主要介绍了在分布式系统中常规用到的一些框架组件,比如分布式ID、消息队列、缓存、RPC框架、ES等。书中大部分内容的作用更多的是整体介绍、知识点扩展、初步入门,书中贴的源代码其中很难让人认真一行一行去阅读学习。想要更深入的学习,需要在平时工作多积累丰富的项目经验,或者多看看开源项目,从而去总结和提取。

       每一章介绍一个组件,摘抄一些自己觉得有用的内容,归纳整理,然后加以理解。(主要还是强迫自己形成总结成文的习惯,看的书很多,都总是很容易忘记,效果甚微)

第1章 如何设计一款永不重复的高性能分布式发号器

1. 为什么不直接采用UUID?

虽然UUID能够保证唯一性,但无法满足业务系统需要的很多其他特性,比如时间粗略有序性、可反解和可制造性(说人话,就是分布式ID需要体现根据时间递增的特点,并且从ID串中能解析出一定的业务含义),同时UUID比较长,占空间大,性能较差。

扫描二维码关注公众号,回复: 8144685 查看本文章

2. 那基于数据库来实现呢?

即通过调整自增字段或者数据库sequence的步长来确保跨数据库的ID的唯一性,但这种方案强依赖于数据库。

实现方案,可见我:重构 - 分布式ID设计方案

3. 分布式ID的基本需求

(1)全局唯一

分布式系统保证唯一的一个悲观策略是使用锁或者分布式锁,但是这样会大大降低性能。因此利用时间的有序性,并且在时间的某个单元下采用自增序列,来达到全局唯一。

(2)粗略有序

UUID最大的问题是无序。

(3)可反解

即能从ID串能看出一定的业务含义,比如什么时候产生的,跟哪些业务功能模块相关的。

(4)可制造

不依赖发号器(如果系统崩了),也能通过一定的规律来手工处理数据。

(5)高性能

产生一个新业务,就要生成一个新ID,所以对性能要求非常高。ID的生成取决于网络I/O和CPU的性能,网络I/O一般不是瓶颈。

(6)高可用

发号器应该是满足HA的集群,同时拥有重试机制。在极端情况下,还要有本地的容错方案。

4. 如何保证性能需求?

在项目初期提出性能需求,在项目进行中做性能测试来验证。

5. 获取分布式ID的几种方法

(1)REST方法

提供一个HTTP接口来获取

(2)RPC服务化方法

服务化模式通过Dubbo导出RPC服务

(3)嵌入方法

将发号器服务嵌入到业务项目中,并且提供JVM进程内的本地服务

第2章 可灵活扩展的消息队列框架的设计与实现

1. 背景介绍

消息队列多应用于异步处理、模块之间的解耦和高并发系统的削峰等场景中。

2. 线程模型

(1)同步线程模型

客户端为每个消费者流使用一个线程,每个线程负责从Kafka队列里消费消息,并且在同一个线程里处理业务。

(2)异步线程模型

客户端为每个消费者流使用一个线程,每个线程负责从Kafka队列里消费消息,并且传递消费得到的消息到后端的异步线程池中,在异步线程池中处理业务。

而后端的异步业务线程池又可细分为:

1> 所有消费者流共享线程池

此种模式可以创建更少的线程池对象,节省些许内存

2> 每个流独享线程池

使用不同的异步业务线程池来处理不同的流里面的消息,互相隔离、互相独立、不互相影响。比如,区分普通用户的消息,和VIP用户的消息,这样可以在不同业务线程池中来处理。

3. 异常处理

对于在消息处理过程中产生的业务异常,当前在业务处理的上层捕捉了Throwable,在专用的错误恢复日志中记录了出错的信息,后续可根据错误恢复日志人工处理错误消息,也可重做或者清洗数据。也可考虑采用Listener体系,对异常处理采用监听者模式来实现异常处理器的可插拔等。

4. 优雅关机

通过注册JVM退出钩子进行优雅关机。

=========================继续看下篇==========================

=================================请看上篇======================================

第3章 轻量级的数据库分库分表架构与框架

1. 分库分表的定义

(1)垂直拆分

根据业务的维度,将原来的一个库(表)拆分为多个库(表),每个库(表)与原有的结构不同。

这种方式除了用于分解单库单表的压力,也用于实现冷热分离。

1》在Mysql中,冷数据查询较多,更新较少,适合用MyISAM引擎,而热数据更新比较频繁,适合使用InnoDB存储引擎。

2》对读多写少的冷数据可配置更多的从库来化解大量查询请求的压力;对于热数据,可以使用多个主库构建分库分表的结构。

3》对于一些特殊的活跃数据或者热点数据,也可以考虑使用Memcache、Redis之类的缓存,等累计到一定的量后再更新数据库。例如,在记录微博点赞数量的业务中,点赞数量被存储在缓存中,每增加1000个点赞,才写一次数据。

(2)水平拆分

根据分片(sharding)算法,将一个库(表)拆分为多个库(表),每个库(表)依旧保留原有的结构。

切分的维度有:

1》按哈希切片

好处是数据切片比较均匀,对数据压力分散的效果较好,缺点是数据分散后,对于查询需求需要进行聚合处理。

2》按时间切片

适用于有明显时间特点的数据。

2. 分库分表的三种实现方案

(1)客户端分片

使用分库分表的数据库的应用层直接操作分片逻辑,分片规则需要在同一个应用的多个节点间进行同步,每个应用层都嵌入一个操作切片的逻辑实现(分片规则)。一般是Jar包的形式。

(2)代理分片

在应用层和数据库层增加一个代理层,把分片的路由规则配置在代理层,代理层对外提供与JDBC兼容的接口给应用层。我们公司正在用这样的框架:Mycat。

(3)支持事务的分布式数据库

将可伸缩的特点和分布式事务的实现包装到分布式数据库内部实现,对使用者透明。例如:OceanBase、TiDB等产品。

3. 分布式事务

解决方案有三种:两阶段提交协议、最大努力保证模式、事务补偿机制。

第4章 缓存的本质和缓存使用的优秀实践

通过缓存和消息队列来化解海量的读请求和写请求对后端数据库服务造成的压力。

1. 伪共享

是指在多个线程同时读写同一个缓存行的不同变量时,尽管这些变量之间没有任何关系,但是在多个线程之间仍然需要同步,从而导致性能下降的情况。

2. JVM对象的内存模型

1》所有的Java对象都有8字节的对象头,前4个字节用来保存对象的哈希码和锁的状态,前3个字节用来存储哈希码,最后一个字节用来存储锁的状态,一旦对象上锁,这4个字节都会被拿出对象外,并用指针进行链接;剩下的4个字节用来存储对象所属类的引用。每个对象的大小都会对齐到8字节的倍数,不够8字节的部分需要填充。

2》为了保证效率,Java编译器在编译Java对象时,会通过字段类型对Java对象的字段进行排序。在任意字段之间通过填充长整型的变量,把热点变量隔离在不同的缓存行中,通过减少伪同步,在多核心CPU中能够极大地提高效率。

3》著名的无锁队列Disruptor通过解决伪共享的问题来提高效率,它通过在RingBuffer的游标和BatchEventProcessor的序列变量之后填充变量,并使之与64字节大小的缓存行对齐,来解决伪竞争的问题。

3. 常用的分布式缓存:Redis、Memcached、Tair

4. 缓存穿透、缓存并发和缓存雪崩

(1)缓存穿透

指的是使用不存在的key进行大量的高并发查询,这导致缓存无法命中,每次请求都要穿透到后端数据库系统进行查询,使数据库压力过大,甚至使数据库服务被压死。

通常将空值缓存起来,再次接收到同样的查询请求时,若命中缓存并且值为空,就会直接返回,不会透传到数据库,避免缓存穿透。

但有时恶意攻击者会猜到这种方案,每次都会使用不同的参数来查询,这是需要我们对输入的参数进行过滤。

(2)缓存并发

通常发生在高并发的场景下,当一个缓存key过期时,因为访问这个缓存key的请求量过大,多个请求同时发现缓存过期,因此多个请求会同时访问数据库来查询最新数据,并且会写缓存,这样会造成应用和数据库的负载增加,性能降低。

有3种方式解决:

1》分布式锁

2》本地锁

3》软过期

就是不使用缓存服务提供的过期时间,而是业务层在数据中存储过期时间信息,由业务程序判断是否过期并更新。

(3)缓存雪崩

指缓存服务器重启或者大量缓存集中在某一个时间段内失效,给后端数据库造成瞬时的负载升高的压力。

通常是对不同的数据使用不同的失效时间。

5. 缓存设计的优秀实践(好好看看)

(1)核心业务和非核心业务使用不同的缓存实例,从物理上进行隔离。

(2)缓存的超时时间的设置是很重要的。给个数字,希望是100毫秒以内。

(3)所有的缓存实例都需要添加监控,需要对慢查询、大对象、内存使用情况做可靠的监控。

(4)通过规范来限制各个应用使用的key有唯一的前缀。

(5)任何缓存的key都必须设定缓存失效时间,且失效时间不能集中在某一点。

(6)通常情况下,读的顺序是先缓存,后数据库;写的顺序是先数据库,后缓存。

(7)在使用缓存时,一定要有降级处理,尤其是对关键的业务环节。常见的方案是在数据库层次预留足够的容量。

(8)如果有大量的键值对要保存,则请使用字符串键的数据库类型,并对每个键都设置过期时间,请不要在哈希键内部存储一个没有边界的集合数据。

====================================继续看下篇===============================================

==================================请看上篇========================================

第5章 大数据利器之Elasticsearch

1. Lucene

底层是基于Lucene实现的,其基于倒排表的设计原理,采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。

2. 分段存储

(1)在删除文件时,Lucene在索引文件下新增了一个.del的文件,用于专门存储被删除的数据id。

(2)为了提升写的性能,Lucene并没有每新增一条数据就增加一个段,而是采用延迟写的策略,每当有新增的数据时,就将其先写入内存中,然后批量写入磁盘中。

所以Lucene或Elasticsearch并不能称为实时的搜索引擎,只能被称为准实时的搜索引擎。

3. 段合并策略

当索引中段的数量太多时,不仅会严重消耗服务器的资源,还会影响检索的性能。所以必须定期进行段合并操作。

Lucene段合并的思路:根据段的大小先将段进行分组,再将属于同一组的段进行合并。Lucene会在段的大小达到一定的规模,或者段里面的数据量达到一定条数时,不会再进行合并。

4. Lucene相似度打分

最经典的两个文本相似度算法:基于向量空间模型的算法和基于概率的算法(BM25)

5. 3C和脑裂

(1)共识性(Consensus)

在分布式系统中的所有节点必须对给定的数据或者节点的状态达成共识。

(2)并发(Concurrency)

写请求在发送到主分片时,同时会以并行的形式发送到备份分片,但是这些请求的送达时间可能是无序的。在这种情况下,Elasticsearch用乐观并发控制来保证新版本的数据不会被旧版本的数据覆盖。

(3)一致性(Consistency)

Elasticsearch集群保证写一致性的方式是在写入前先检查有多少个分片可供写入,如果达到写入条件,则进行写操作,否则,Elasticsearch会等待更多的分片出现,默认为一分钟。

(4)脑裂

在Elasticsearch集群中主节点通过ping命令来检查集群中的其他节点是否处于可用状态,同时非主节点也会通过ping命令来检查主节点是否处于可用状态。

6. 性能调优

(1)写优化

批量提交、优化存储设备、合理使用段合并、减少refresh的次数(为了提高写的性能,采用的是延迟写入的策略,即将要写入的数据先写到内存中,当延时超过1秒(默认)时,会触发一次refresh,refresh会把内存中的数据以段的形式刷新到操作系统的文件缓存系统中)、减少flush的次数、减少副本的数量

(2)读优化

避免大结果集和深翻、选择合适的路由、SearchType、定期删除

7. Elasticsearch堆内存的分配

(1)最好不要超过物理内存的50%

(2)堆内存的大小最好不要超过32GB

Java使用内存指针压缩技术来解决这个问题。

第6章 全面揭秘分布式定时任务

1. 怎么让某一个定时任务在一个触发时刻上仅有一台服务器在执行?

(1)只在一台服务器上执行

但这样有明显缺陷:单点风险和资源分布不均衡。

(2)通过配置参数分散运行

比如创建一个配置项,指定该台服务器需要执行的定时任务类名,这样可以在部署服务时手动分散定时任务到不同的服务器上。

(3)通过全局“锁”互斥执行

可使用ZooKeeper、Redis或者数据库(依靠唯一索引)等方式。

2. TBSchedule

(1)简介

是阿里开源的分布式调度框架,可以使批量的动态变化的任务被动态地分配到多个机器的JVM中并行执行,而且有失效转移等优点。

(2) TBSchedule的动态可伸缩原理

TBSchedule在硬件主机与工作处理器之间又抽象出一个调度服务器层IStrategyTask,通过这个调度服务器层完成解耦。工作处理器ScheduleProcessor是真正负责具体任务处理的,工作处理器内部以多线程的方式并行进行任务处理,与调度服务器是一对一的关系。调度服务器依赖当前可以使用的主机数量进行自定义分配或均匀分配。

(3) TBSchedule的分片机制

TBSchedule可以将批量任务进行分片,分片的逻辑由 TBSchedule使用方根据具体业务自行定义,一个分片对应一个调度处理器,但一个调度处理器可对应多个任务分片,进而由调度处理器对应的工作处理器进行并发处理。

3. Elastic-Job

(1)简介

Elastic-Job是当当网开源的分布式调度解决方案,支持任务分片功能,能充分利用资源。

(2)Elastic-Job分为Elastic-Job-Lite和Elastic-Job-Cloud组成

Elastic-Job-Lite被定位为轻量级无中心化解决方案,通过Jar包的形式提供分布式任务的协调服务。而Elastic-Job-Cloud使用Mesos+Docker的解决方案,额外提供资源治理、应用分发及进程隔离等服务。

(3)任务分片

1》目的在于将一个任务分散到不同的机器上执行,既可以解决单机计算能力有限的问题,也能减少部分任务失败对整体系统的影响。开发者需要自行处理分片项与真实数据的对应关系,同时要注意任务失败重试的幂等性。

2》通常将分片项设置为大于服务器的数量,最好是大于服务器倍数的数量,作业将会合理地利用分布式资源,动态地分配分片项。

=============================继续看下篇=======================================

=========================请看上篇================================

第7章 RPC服务的发展历程和对比分析

1. RPC采用客户端/服务端模式,请求程序就是一个客户端,服务提供程序就是一个服务端。

RPC是构建在语言级之上的,是跨语言的,它在OSI七层模型中介于会话层和表示层之间。

2. RPC实现透明的远程过程调用的重点

就是创建客户存根(client stub),存根(stub)就像代理(agent)模式里的代理(agent),在生成代理代码后,代理的代码就能与远程服务端通信了,通信的过程都由RPC框架实现,而调用者就像调用本地代码一样方便。在客户端看来,存根函数就像普通的本地函数一样,但实际上包含了通过网络发送和接收消息的代码。

(1)客户端存根的方法会将参数打包并封装成一个或多个网络消息体并发送到服务端。将参数封装到网络消息中的过程被称为编码(encode),它会将所有数据序列化为字节数组格式。

(2)服务端存根(server stub)接收客户端发送的消息,并对参数消息进行编码(decode),通常它会将参数从标准的网络格式转换成特定的语言格式。

(3)服务端存根在将该返回值进行编码并序列化后,通过一个或多个网络消息发送给客户端。

3. 在使用RMI的JVM中,Java支持两种操作:标记脏数据和清理。当对象仍在使用时,本地JVM会定期向服务器的JVM发送一个标记脏数据的调用。标记脏数据基于服务器给定的时间间隔定期重新发送心跳信息。当客户端没有更多的本地引用远程对象时,会发送一个清理的调用给服务器。

第8章 Dubbo实战及源码分析

1. Dubbo的三大类配置

(1)服务发现类:表示该配置项用于服务的注册与发现,目的是让消费者找到提供者。

(2)服务治理类:表示该配置项用于治理服务间的关系,或为开发测试提供便利条件。

(3)性能调优类:表示该配置项用于调优性能,不同的选项会对性能产生不同的影响。

2. 由服务提供者设置超时,因为服务提供者更清楚一个方法需要执行多久。

3.

(1)如果服务需要预热的时机,比如初始化缓存、等待相关资源就位等,就可以使用delay属性进行服务延迟暴露。

(2)如果一个服务的并发量过大,超出了服务器的承受能力 --- 使用executes属性控制并发。

(3)客户端进行并发控制,通过actives属性限制。

4. 服务隔离

是为了在系统发生故障时限定传播范围和影响范围,从而保证只有出问题的服务不可用,其他服务还是正常的。

隔离一般有线程隔离、进程隔离、读写隔离、集群隔离和机房隔离,而Dubbo还提供了分组隔离,即使用group属性分组。

5. Dubbo中的异步调用时基于NIO的非阻塞机制实现的。

在远程调用的过程中如果出现异常或者需要回调,则可以使用Dubbo的事件通知机制。

6. Dubbo支持的协议

Dubbo协议 --- 通信数据包小、并发高的服务

Hessian协议 --- 传输数据大且提供者比消费者数量多的服务

HTTP或Hessian协议 --- 对于外部与内部进行通信的场景,若想要穿透防火墙的限制

7. Dubbo协议的使用注意事项

(1)在实际情况下消费者的数据比提供者的数量更多

(2)不能传大的数据包

(3)推荐使用异步单一长连接方式

长连接可以减少连接握手验证等,并且使用异步I/O,可以复用线程池,防止出现C10K问题。

8. Dubbo的I/O线程模型

Dubbo的服务提供者主要有两种线程池类型:一种是I/O处理线程池;另一种是业务调度线程池。

Dubbo默认配置无限制大小的CachedThreadPool线程池,这意味着它对所有服务的请求都不会拒绝,但是Dubbo限制了I/O线程数,默认是核数+1。

9. 服务熔断与服务降级

(1)服务熔断

是一种保护措施,一般用于防止在软件系统中由于某些原因使服务出现了过载现象,从而造成整个系统发生故障。

(2)服务降级

是在服务器压力剧增的情况下,根据当前的业务情况及流量对一些服务和页面有策略地进行降级,以释放服务器资源并保证核心任务的正常运行。

10. Dubbo是通过JDK的ShutdownHook来完成优雅停机的。

11. 线上问题排查(此处敲敲黑板)

(1)发现问题 

1》系统层面的监控

对系统的CPU利用率、系统负载、内存使用情况、网络I/O负载、磁盘负载、I/O等待、交换区的使用、线程数及打开的文件句柄数等

2》应用层面的监控

对服务接口的响应时间、吞吐量、调用频次、接口成功率及接口的波动率

3》资源层的监控

对数据库(负载、慢SQL、连接数)、缓存(连接数、占用内存、吞吐量、响应时间)和消息队列(响应时间、吞吐量、负载、积压情况)的监控

(2)定位问题

考虑如下问题:

1》问题系统最近是否进行了上线?

2》依赖的基础平台和资源是否进行了上线或者升级?

3》依赖的系统最近是否进行了上线?

4》运营人员是否在系统里做过运营变更?

5》网络是否有波动?

6》最近的业务是否上量?

7》服务的使用方是否有促销活动?

(3)解决问题

每个系统都会对各种严重情况设计止损和降级开关,因此在发生严重问题时先使用止损策略,在恢复问题后再定位和解决问题。解决问题要以定位问题为基础,必须清晰地定位问题产生的根本原因,再提出解决问题的有效方案,切记在没有明确原因之前,不要使用各种可能的方法来尝试修复问题,这样可能导致还没有解决这个问题又引出另一个问题。

(4)消除影响

首先,找运维看日志。

其次,保存现场并恢复现场。在JVM中保存现场快照通常包括保存当前运行线程的快照和保存JVM内存堆栈快照。

12. SPI(Service Provider Interface)

是Java提供的一种服务加载方式,可以避免在Java代码中写死服务提供者,而是通过SPI服务加载机制进行服务的注册和发现,实现多个模块的解耦。

第9章 高性能网络中间件

1. 在OSI中,每一层都使用下一层的协议和服务。每一层的数据包都包含自己的一个包头,这个包头包含本层定义的典型信息如IP地址、TCP端口号,以及本层服务需要的信息如IP分片信息、TCP的流量控制信息等。

因为TCP层的数据包是在IP层的数据包内增加了自己的TCP头,所以在IP层看来,任意一个TCP包都是一个普通的IP包。

2. TCP的核心还是接收序号和确认序号,通过它们可以在中途有数据丢失的情况下,通过返回ACK的序号进行重发。

3. 在应用层进行失效转移、降级、备份方案、重试等操作。
————————————————

猜你喜欢

转载自www.cnblogs.com/cx2016/p/12018214.html