大型网站系统与Java中间件实践

第1章 分布式系统介绍

1.1 初识分布式系统

1.1.1 分布式系统的定义

一是组件分布在网络计算机上,二是组件之间仅仅通过消息传递来通信并协调行动

分布式系统一定是由多个节点组成的系统

  • 互相连通

  • 协同

1.1.2 分布式系统的意义

  • 升级单机处理能力的性价比越来越低

  • 单机处理能力存在瓶颈

  • 出于稳定性和可用性的考虑

1.2 分布式系统的基础知识

1.2.1 组成计算机的5要素

computer5Elements

图1-3 组成计算机的5要素

1.2.2 线程与进程的执行模式

多进程相对于单进程多线程的方式来说,资源控制会更容易实现,此外,多进程中的单个进程问题,不会造成整体的不可用

1.2.3 网络通信基础知识

图1-8 ISO的OSI网络模型

图1-9 OSI与TCP/IP对照

网络IO实现方式

  1. BIO方式:即Blocking IO,采用阻塞的方式实现。也就是一个Socket套接字需要使用一个线程来进行处理

  2. NIO方式:即Nonblocking IO,基于事件驱动思想,采用的是Reactor模式

  3. AIO方式:即AsynchronousIO,就是异步IO。AIO采用Proactor模式

AIO和NIO的一个最大的区别是

  • NIO在有通知时可以进行相关操作,例如读或者写

  • AIO在有通知时表示相关操作已经完成

1.2.4 如何把应用从单机扩展到分布式

图1-14 使用硬件负载均衡的请求调用

图1-15 使用LVS的请求调用

图1-16 采用名称服务的直连方式的请求调用

图1-17 采用规则服务器控制路由的请求直连调用

图1-18 Master+Worker的方式

图1-25 规则服务器管理的多日志处理服务器的日志处理

使用规则服务器来分配任务可能存在的最大问题是任务分配不均衡

1.2.5 分布式系统的难点

  1. 缺乏全局时钟

  2. 面对故障独立性

    • 在分布式系统中,整个系统的一部分有问题而其他部分正常

  3. 处理单点故障

    • SPoF(Single Point of Failure)

    • 避免单点的关键就是把这个功能从单机实现变为集群实现

    • 一般还有另外两种选择:

      • 给这个单点做好备份

      • 降低单点故障的影响范围

  4. 事务的挑战

第2章 大型网站的架构演进

2.1 什么是大型网站

访问量和数据量二者缺一不可

2.2 大型网站的架构演进

2.2.4 应用服务器负载告警,如何让应用服务器走向集群

解决应用服务器变为集群后的Session问题,具体实现方式为:

  • 在会话开始时,分配一个唯一的会话标识(SessionId),通过Cookie把这个标识告诉浏览器,以后每次请求的时候,浏览器都会带上这个会话标识来告诉Web服务器请求是属于哪个会话的

  • 在Web服务器上,各个会话有独立的存储,保存不同会话的信息

解决方案

  1. Session Sticky:需要负载均衡器能够根据每次请求的会话标识来进行请求转发

  2. Session Replication:靠应用容器来完成Session的复制从而使得应用解决Session问题

  3. Session数据集中存储

  4. Cookie Based:通过Cookie来传递Session数据

图2-10 Session Sticky方式

图2-11 Session Replication方式

图2-12 集中存储Session方式

图2-13 Cookie Based的方式

对于大型网站来说,Session Sticky和Session数据集中存储是比较好的方案

2.2.5 数据读压力变大,读写分离吧

  1. 采用数据库作为读库

    • MySQL支持Master(主库)+Slave(备库)的结构,提供了数据复制的机制

    • Oracle主要是Data Guard方案,分为物理备库(物理StandBy)和逻辑备库(逻辑StandBy)

  2. 搜索引擎其实是一个读库

    • 可以从两个维度对于搜索系统构建索引的方式进行划分,一种是按照全量/增量划分,一种是按照实时/非实时划分

  3. 加速数据读取的利器——缓存

2.2.6 弥补关系型数据库的不足,引入分布式存储系统

图2-19 引入分布式存储系统的结构

2.2.7 读写分离后,数据库又遇到瓶颈

  1. 垂直拆分:把数据库中不同的业务数据拆分到不同的数据库中

  2. 水平拆分:把同一个表的数据拆到两个数据库中

数据水平拆分与读写分离的区别是,读写分离解决的是读压力大的问题,对于数据量大或者更新量的情况并不起作用

数据水平拆分与数据垂直拆分的区别是

  • 垂直拆分是把不同的表拆到不同的数据库中

  • 水平拆分是把同一个表拆到不同的数据库中

2.2.8 数据库问题解决后,应用面对的新挑战

  1. 拆分应用

  2. 走服务化的路

2.2.9 初识消息中间件

维基百科上对消息中间件的定义为

  • Message-oriented middlewareMOM) is software infrastructure focused on sending and receiving messages between distributed systems.

  • 意思是面向消息的系统(消息中间件)是在分布式系统中完成消息的发送和接收的基础软件

2.2.10 总结

overallStructureDiagram

图2-26 整体结构图

第3章 构建Java中间件

3.1 Java中间件的定义

  • 远程过程调用和对象访问中间件

  • 消息中间件

  • 数据访问中间件

3.2 构建Java中间件的基础知识

3.2.1 跨平台的Java运行环境——JVM

图3-1 Java从源码到运行的过程

3.2.2 垃圾回收与内存堆布局

图3-2 Oracle Hotspot JVM内存堆布局

Young/Tenured/Perm:新生代/年老代/持久代

图3-3 JRockit内存堆布局

Nursery就相当于Hotspot中的Young

图3-4 IBM JVM内存堆布局

3.2.3 Java并发编程的类、接口和方法

  1. 线程池

    • 使用线程池的方式是复用线程的,而不使用线程池的方式是每次都要创建线程的

  2. synchronized

  3. ReentrantLock

  4. volatile

  5. Atomics

    • 性能提升的原因主要在于AtomicInteger内部通过JNI的方式使用了硬件支持的CAS指令

  6. waitnotifynotifyAll

  7. CountDownLatch:主要提供的机制是当多个线程都到达了预期状态或完成预期工作时触发事件,其他线程可以等待这个事件来触发自己后续的工作

  8. CyclicBarrier:可以协同多个线程,让多个线程在这个屏障前等待,直到所有线程都到达了这个屏障时,再一起继续执行后面的动作

  9. Semaphore:用于管理信号量的,构造的时候传入可供管理的信号量的数值

  10. Exchanger:用于在两个线程之间进行数据交换

  11. FutureFutureTask

  12. 并发容器

3.2.4 动态代理

动态代理是动态地生成具体委托类的代理类实现对象

3.2.5 反射

Java反射机制主要提供了以下功能:

  1. 获取对象属于哪个类

  2. 获取类的信息

  3. 构建对象

  4. 动态执行方法

  5. 动态操作属性

3.2.6 网络通信实现选择

3.3 分布式系统中的Java中间件

中间件是指用来支撑网站从小到大的变化过程中解决应用拆分、服务拆分、数据拆分和应用解耦问题的产品

overallStructureDiagram

图3-10 网站整体结构图

overallStructureDiagram_Middleware

图3-11 引入中间件的结构图

第4章 服务框架

4.1 网站功能持续丰富后的困境与应对

4.2 服务框架的设计与实现

4.2.3 服务调用端的设计与实现

  1. 确定服务框架的使用方式

    • IDL(Interface Description Language,接口描述语言)

    • 服务框架自身的部署方式问题

      • 一种方案是把服务框架作为应用的一个依赖包并与应用一起打包

      • 另外一种方案是把服务框架作为容器的一部分,服务框架自身就需要变为一个容器来提供远程调用和远程服务的功能

  2. 服务调用者与服务提供者之间通信方式的选择

  3. 引入基于接口、方法、参数的路由

  4. 多机房场景

  5. 服务调用端的流控处理

    • 一种是0-1开关,也就是说完全打开不进行流控

    • 另一种是设定一个固定的值,表示每秒可以进行的请求次数,超过这个请求数的话就拒绝对远程的请求了

  6. 序列化与反序列化处理

    • 序列化:把内存对象变为二进制数据的过程

    • 反序列化:把二进制数据变为内存中可用对象的过程

  7. 网络通信实现选择

  8. 支持多种异步服务调用方式

    • Oneway:是一种只管发送请求而不关心结果的方式

    • Callback:请求方发送请求后会继续执行自己的操作,等对方有响应时进行一个回调

    • Future

    • 可靠异步:要保证异步请求能够在远程被执行,一般是通过消息中间件来完成这个保证

  9. 使用Future方式对远程服务调用的优化

4.2.4 服务提供端的设计与实现

  1. 如何暴露远程服务

  2. 服务端对请求处理的流程

  3. 执行不同服务的线程池隔离

  4. 服务提供端的流控处理

4.3 实战中的优化

  1. 服务的拆分

  2. 服务的粒度

  3. 优雅和实用的平衡

  4. 分布式环境中的请求合并

4.4 为服务化护航的服务治理

将服务治理分为管理服务和查看服务

4.5 服务框架与ESB的对比

企业服务总线(ESB):是对多样系统中的服务调用者和服务提供者的解耦

ESB与服务框架主要有两个差异

  1. 服务框架是一个点对点的模型,而ESB是一个总线式的模型

  2. 服务框架基本上是面向同构的系统,不会重点考虑整合的需求,而ESB会更多地考虑不同厂商所提供服务的整合

第5章 数据访问层

5.1 数据库从单机到分布式的挑战和应对

5.1.2 数据库垂直/水平拆分的困难

减压的思路有三个

  • 一是优化应用,看看是否有不必要的压力给了数据库(应用优化)

  • 二是看看有没有其他办法可以降低对数据库的压力,例如引入缓存、加搜索引擎等

  • 最后一种思路就是把数据库的数据和访问分到多台数据库上,分开支持

5.1.3 单机变为多机后,事务如何处理

  • Application Program(AP),即应用程序

  • Resource Manager(RM),资源管理器

  • Transaction Manager(TM),事务管理器

图5-2 分布式事务AP/TM/RM之间的关系

图5-3 DTP模型

两阶段提交:在分布式系统中,在提交之前增加了准备的阶段

CAP

  • Consistency:all nodes see the same data at the same time,数据的一致性

  • Availability:a guarantee that every request receives a response about whether it was successful or failed,数据的可用性

  • Partition-Tolerance:the system continues to operate despite arbitrary message loss or failure of part of the system,分区容忍性

图5-8 CAP理论

BASE模型

  • Basically Available:基本可用,允许分区失败。

  • Soft state:软状态,接受一段时间的状态不同步。

  • Eventually consistent:最终一致,保证最终数据的状态是一致的。

在大型网站中,为了更好地保持扩展性和可用性,一般都不会选择强一致性,而是采用最终一致的策略来实现

比两阶段提交更轻量一些的Paxos协议

  • 在分布式系统中,节点之间的信息交换有两种方式,一种是通过共享内存共用一份数据;另一种是通过消息投递来完成信息的传递

  • 使用Paxos协议有一个前提,那就是不存在拜占庭将军问题

5.1.4 多机的Sequence问题与处理

图5-9 独立Id生成器方式

图5-10 生成器嵌入到应用的方式

5.1.5 应对多机的数据查询

  1. 跨库Join

  2. 外键约束

  3. 跨库查询的问题及解决

5.2 数据访问层的设计与实现

5.2.1 如何对外提供数据访问层的功能

fetch size是指JDBC驱动实现时设置的每次从数据库返回的记录数,要考虑平衡网络的开销

5.2.2 按照数据层流程的顺序看数据层设计

图5-20 数据层的整理流程

  1. SQL解析阶段的处理,主要考虑的问题有两个

    • 一是对SQL支持的程度,是否需要支持所有的SQL

    • 二是支持多少SQL的方言,对于不同厂商超出标准SQL的部分要支持多少

  2. 规则处理阶段

    1. 采用固定哈希算法作为规则

    2. 一致性哈希算法带来的好处:把节点对应的哈希值变为了一个范围,而不再是离散的

    3. 虚拟节点对一致性哈希的改进:以通过一个物理节点对应非常多的虚拟节点,并且同一个物理节点的虚拟节点尽量均匀分布的方式来解决增加或减少节点时负载不均衡的问题

    4. 映射表与规则自定义计算方式:映射表是根据分库分表字段的值的查表法来确定数据源的方法

  3. 为什么要改写SQL

  4. 如何选择数据源

  5. 执行SQL和结果处理阶段

可以进行组内的扩容、缩容、主备切换等工作,这也是groupDataSource最大的价值

5.2.3 独立部署的数据访问层实现方式

如果采用Proxy方式的话,客户端与Proxy之间的协议有两种选择:数据库协议和私有协议

5.2.4 读写分离的挑战和应对

主库从库非对称的场景

  1. 数据结构相同,多从库对应一主库的场景

  2. 主/备库分库方式不同的数据复制

  3. 引入数据变更平台

    • Extractor负责把数据源变更的信息加入到数据分发平台中

    • Applier的作用是把这些变更应用到相应的目标上

图5-37 数据变更平台

5.3 总结

dataLayerStructureDiagram

图5-41 数据层的结构图

第6章 互联网时代的消息中间件

6.1 消息中间件的价值

6.1.2 透过示例看消息中间件对应用的解耦

图6-5 消息中间件对服务调用进行解耦

6.2 互联网时代的消息中间件

JMS是Java Message Service的缩写,它是Java EE(企业版Java)中的一个关于消息的规范

6.2.1 如何解决消息发送一致性

表6-1 Queue模型和Topic模型下各要素对比

JMS Common PTP Domain Pub/Sub Domain
ConnectionFactory QueueConnectionFactory TopicConnectionFactory
Connection QueueConnection TopicConnection
Destination Queue Topic
Session QueueSession TopicSession
MessageProducer QueueSender TopicPublisher
MessageConsumer QueueReceiver TopicSubscriber

表6-2 XA系列接口与对应的非XA系列接口

XA系列接口名称 对应的非XA接口名称
XAConnectionFactory ConnectionFactory
XAQueueConnectionFactory QueueConnectionFactory
XATopicConnectionFactory TopicConnectionFactory
XAConnection Connection
XAQueueConnection QueueConnection
XATopicConnection TopicConnection
XASession Session
XAQueueSession QueueSession
XATopicSession TopicSession
  • ConnectionFactory→Connection→Session→Message

  • Destination + Session→MessageProducer

  • Destination + Sessoin→MessageConsumer

图6-7 最终一致性方案的正向流程

图6-8 最终一致性方案的补偿流程

解决一致性的方案是只增加了一次网络操作和一次更新存储中消息状态的操作

6.2.2 如何解决消息中间件与使用者的强依赖问题

业务操作与发送消息一致性的方案所带来的两个限制

  • 需要确定要发送的消息的内容

  • 需要实现对业务的检查

6.2.3 消息模型对消息接收的影响

JMS Queue模型也被称为Peer To Peer(PTP)方式

图6-12 JMS Queue模型

JMS Topic模型也被称为Pub/Sub方式

图6-13 JMS Topic模型

multi-clusterSubscribers

图6-19 多集群订阅者解决方案

JMS_cascade

图6-20 通过JMS级联的解决方案

6.2.4 消息订阅者订阅消息的方式

  • 非持久订阅:消息接收者和消息中间件之间的消息订阅的关系的存续,与消息接收者自身是否处于运行状态有直接关系

  • 持久订阅:消息订阅关系一旦建立,除非应用显式地取消订阅关系,否则这个订阅关系将一直存在

6.2.5 保证消息可靠性的做法

消息从发送端应用到接收端应用,中间有三个阶段需要保证可靠,分别是:

  1. 消息发送者把消息发送到消息中间件

  2. 消息中间件把消息存入消息存储

  3. 消息中间件把消息投递给消息接收者

通过冗余数据可以让查询只走一个表,因此提升了性能

对于消息来说,可以把需要存储的数据分为以下三块

  • 消息的Header信息:指消息的一些基本信息

  • 消息的Body:是消息的具体内容,消息的Body是否与消息的Header信息放在一条记录中是需要考虑的

  • 消息的投递对象:指单条消息要投递到的目标集群的ClusterId

表6-11 消息表

消息Id 创建时间 自定义属性 发送者ClusterId 消息内容

表6-12 投递表

唯一Id 消息Id ClusterId 投递次数 下次投递时间

表6-13 含投递记录的消息表

消息Id 创建时间 自定义属性 发送者ClusterId 投递列表 下次投递时间 消息内容

数据的容灾方案

  • 单机的Raid

  • 多机的数据同步

  • 应用双写

6.2.6 订阅者视角的消息重复的产生和应对

消息重复的产生原因

  • 第一类原因是消息发送端应用的消息重复发送

  • 第二类原因是消息到达了消息存储,由消息中间件进行向外的投递时产生重复

JMS的消息确认方式与消息重复的关系

  • AUTO_ACKNOWLEDGE

  • CLIENT_ACKNOWLEDGE

  • DUPS_OK_ACKNOWLEDGE

  • at least once(至少一次)

  • at most once(至多一次)

6.2.7 消息投递的其他属性支持

局部顺序:指在众多的消息中,和某件事情相关的多条消息之间有顺序,而多件事情之间的消息则没有顺序

6.2.8 保证顺序的消息队列的设计

  1. 单机多队列的问题和优化

  2. 解决本地消息存储的可靠性

  3. 如何支持队列的扩容

图6-38 扩容示意图

6.2.9 Push和Pull方式的对比

表6-14 Push和Pull方式的对比

Items Push Pull
数据传输状态 保存在服务端 保存在消费端
传输失败,重试 服务端需要维护每次传输状态,遇到失败情况需要重试 不需要
数据传输实时性 非常实时 默认的短轮询方式的实时性依赖于Pull间隔时间,间隔越大实时性越低。长轮询模式的实时性与Push一致
流控机制 服务端需要依据订阅者的消费能力做流控 消费端可以根据自身消费能力决定是否去Pull消息

第7章 软负载中心与集中配置管理

7.1 初识软负载中心

软负载中心有两个最基础的职责

  1. 聚合地址信息

  2. 生命周期感知

7.2 软负载中心的结构

软负载中心包括两部分

  • 一个是软负载中心的服务端

  • 另一个是软负载中心的客户端

图7-5 软负载中心与使用者

  • 聚合数据:聚合后的地址信息列表

  • 订阅关系

  • 连接数据:指连接到软负载中心的节点和软负载中心已经建立的连接的管理

7.3 内容聚合功能的设计

7.4 解决服务上下线的感知

7.5 软负载中心的数据分发的特点和设计

7.5.1 数据分发与消息订阅的区别

  1. 消息中间件需要保证消息不丢失,每条消息都应该送到相关的订阅者,而软负载中心只需要保证最新数据送到相关的订阅者,不需要保证每次的数据变化都能让最终订阅者感知

  2. 在消息中间件中,同一个集群中的不同机器是分享所有消息的,而在软负载中心需要把这个数据分发给所有的机器

7.5.2 提升数据分发性能需要注意的问题

提升数据分发性能

  • 数据压缩

  • 全量与增量的选择

7.6 针对服务化的特性支持

分组:为了进行隔离,分组本身就是一个命名空间,用来把相同的dataId的内容分开,也就是给dataId加上了一个namespace

7.7 从单机到集群

7.8 集中配置管理中心

7.8.1 客户端实现和容灾策略

  • 数据缓存:指每次收到服务端的更新后对数据的缓存

  • 数据快照:保存的是最近几次更新的数据,数据是比缓存的数据旧一些,但是会保持最近的多个版本

  • 本地配置:如果在本地配置的目录中有对应的数据配置内容的话,这个优先级是最高的

  • 文件格式

7.8.2 服务端实现和容灾策略

  • 通过当前服务端更新数据库

  • 定时检查服务端的数据与数据库中数据的一致性

第8章 构建大型网站的其他要素

8.1 加速静态内容访问速度的CDN

CDN(Content Delivery Network)是内容分发网络

  • 作用是把用户需要的内容分发到离用户近的地方,这样可以使用户能够就近获取所需内容

  • CDN源站:提供CDN节点使用的数据源头

  • CDN节点:部署在距离最终用户比较近的地方,加速用户对站点的访问

图8-3 引入CDN后浏览器访问网站的流程

CDN中的几个关键技术

  • 全局调度:根据用户地域、接入运营商以及CDN机房的负载情况去调度

  • 缓存技术

  • 内容分发:主要是对内容全部在CDN上不用回源的数据的管理和分发,例如一些静态页面等

  • 带宽优化

8.2 大型网站的存储支持

8.2.1 分布式文件系统

分布式文件系统

  • 淘宝的TFS

  • Google的GFS(Google File System)

图8-4 GFS结构图

8.2.2 NoSQL

Not Only SQL

8.2.3 缓存系统

如果想把多个节点构建成一个集群是需要去考虑的,常见的是采用一致性哈希的方式

ESI(Edge Side Includes),是通过在返回的页面中加上特殊的标签,然后根据标签的内容去用缓存进行填充的一个过程

图8-12 ESI标签处理流程

图8-13 ESI处理模块部署结构

两种方式对比如下

  • 处理效率会提升

  • 对于后端来说可以不单独考虑ESI标签的问题

8.3 搜索系统

8.3.1 爬虫问题

更新索引的方式一般有如下两种

  • 定时从数据源中拉取,称之为增量Dump

  • 及时通知搜索引擎构建索引

8.3.2 倒排索引

倒排索引:是把原来作为值的内容拆分为索引的Key,而原来用作索引的Key则变成了值。搜索引擎比数据库的Like更高效的原因也在于倒排索引

8.3.3 查询预处理

查询预处理:负责对用户输入的搜索内容进行分词及分词后的分析,包括一些同义词的替换及纠错等

8.3.4 相关度计算

相关度计算:在不指定按照某个字段排序的基础上对搜索结果的排序,排序的原则就是被搜索到的内容与要搜索的内容之间的相关度

8.4 数据计算支撑

离线计算:业务产生的数据离开生产环境后进行的计算

MapReduce处理的过程。主要分为两个阶段

  • 第一个是Map

  • 第二个是Reduce

图8-14 MapReduce模型

Hadoop是MapReduce的一个开源实现,Hadoop使用HDFS进行数据存储,而Spark则提供了基于内存的集群计算的支持

在线计算:比较实时的计算,常见的方式是流式计算

表8-3 Storm与Hadoop的对比

Storm Hadoop
Nimbus JobTracker
Supervisor TaskTracker
Worker Child
Topology Job
Spout/Bolt Mapper/Reducer
  • Nimbus,负责资源分配和任务调度

  • Supervisor,负责接受Nimbus分配的任务,启动和停止属于自己管理的Worker

  • Worker,具体处理组件逻辑的进程

  • Task,Worker中的每一个Spout/Bolt线程称为一个Task,在0.8版本以后的Storm中,Task不再与物理线程一一对应,同一个Spout/Blot的Task可能会共享一个物理线程,称为Executor

图8-15 Storm实例的拓扑结构

8.5 发布系统

如果应用服务器数量过多的话,可以采用P2P技术来进行程序包的分发,进而加快分发速度

灰度发布:对新应用进行分批发布,主要是针对新应用在用户体验方面完全感知不到的更新

8.6 应用监控系统

  • 数据监视维度

    • 系统数据:是当前应用运行的系统环境的信息

    • 应用自身的数据:是不同应用有不同的数据

  • 数据记录方式

  • 数据采集方式

    • 主动推送给监控中心

    • 等待监控中心来拉取

  • 展现与告警

    • 降级:是遇到大量请求且不能扩容的情况时所进行的功能限制的行为

    • 切换:是当依赖的下层系统出现故障并且需要手工进行切换时的一个管理

8.7 依赖管理系统

  • 静态检测:是分析应用A的代码来确定所调用的具体外部应用,从而获得依赖关系,静态检测很难检测依赖的强弱性

  • 动态检测:是在系统运行的阶段,通过功能的调用来发现应用的依赖关系,并且可以进行依赖强弱的检查

8.8 多机房问题分析

多机房主要用于容灾,以及改进不同地域的用户的访问速度

同城机房的价值主要是容灾,以及突破单机房的服务器规模的限制

8.9 系统容量规划

8.10 内部私有云

References

  • 《大型网站系统与Java中间件实践》