1. 大型网站的架构演进
用Java技术和单机来构建的网站
单机负载告警,数据库与应用分离
应用服务器负载告警,如何让应用服务器走向集群?出现如下问题:
- 用户对服务器的访问的选择问题(可以通过加一个负载均衡器)
- Session问题,解决方法:
- Session Sticky:针对同样的请求发送到相同的服务器上
- Session Replication:应用服务器通过相互复制Session来实现。
- Session数据集中存储:通过其他机器来存储Session的信息
- Cookie Based:通过Cookie来传递Session数据
数据读压力变大,读写分离吧
- 采用数据库作为读库,出现的问题:
- 数据复制问题
- 应用对数据源的选择问题
- 搜索引擎其实就是一个读库:加入了搜索引擎之后,我们得应用也需要什么数据该走搜索引擎,什么数据该走数据库。
- 加速数据读取的利器——缓存
- 数据缓存:主要是存放“热点”数据而不是全部数据
- 页面缓存:用于页面渲染
弥补关系型数据库的不足,引入分布式存储系统
- 分布式文件系统
- 分布式Key-Value系统
- 分布式数据库
读写分离后,数据库又遇到瓶颈
- 专库专用,数据垂直拆分:垂直拆分就是把数据库中不同的业务的数据拆分到不同的数据库中。(将订单,商品拆分到订单库和商品库)
- 数据水平拆分:水平拆分就是把同一个表中的数据拆分到两个数据库中,产生数据水平拆分的原因是某个业务表的数据量已经达到单个数据库的瓶颈。
数据库问题解决后,应用面对新挑战
- 拆分应用:根据业务的特性把应用拆开
- 服务化之路:将各个业务做成供系统调用的服务中心,减少代码冗余,代码复用性高。
消息中间件(MOM(Message oriented middleware):面向消息的系统)
- 异步:将数据发往消息中间件后直接返回成功
- 解耦:多个系统之间消息的传递通过消息中间件
2. 构建Java中间件
- 远程过程调用和对象访问中间件:解决分布式环境下应用互相访问问题。服务化的基础。
- 消息中间件:解决应用之间的消息传递,异步,解耦问题
- 数据访问中间件:解决应用访问数据库的共性问题的组件
构建Java中间件的基础
- 跨平台的Java运行环境–JVM
- 垃圾回收与内存布局
- Java并发编程的类、接口和方法
- 线程池:降低创建线程和销毁线程的开销,线程复用(ThreadPoolExecutor)
- synchronized:可以进行互斥访问,保证原子性
- ReentrantLock
- volatile: 保证修饰变量的可见性
- Atomics: 封装了大部分的原子操作,通过JNI的方式使用了硬件支持的CAS指令
- wait、notify、notifyAll:对它们的调用必须在synchronized中
- CountDownlatch:当多个线程都到达了预期状态或完成预期工作时触发事件
- CyclicBarrier:循环屏障,协同多个线程,让多个线程在这个屏障前等待,直到所有的线程都到达这个屏障,在执行后续的动作
- Semaphore:控制并发数量
- Exchanger:用于两个线程之间交换数据
- Future、FutureTask:
- 并发容器:强调并发,不强调安全,CopyOnWriteArraySet、CopyOnWriteArrayList、Concurrent开头的都是并发容器
- 动态代理
- 静态代理:代理类实现委托类的接口,并持有委托类的对象引用。
- 动态代理:动态的生成具体委托类的代理类实现对象
- 反射
在运行中, 对于任意一个类,都能够知道这个类所有的属性和方法,对于任意一个对象,都能调用它的任意一个方法和属性,提供的功能如下:
- 在运行时判断一个对象所属的类:Class<> aClass = application.getClass();
- 获取类信息:String className = aClass.getName();
- 在运行时构造任意一个类的对象:Class.forName(“className”).newInstance();
- 在运行时判断一个类具有的成员变量和方法
- 在运行时调用一个对象的任意方法
- 生成动态代理
- 网络通信实现选择
- BIO
- NIO
- AIO
分布式系统中的Java中间件
3. 服务框架
服务框架的设计与实现
- 应用从集中式走向分布式
- 高易用性和降低性能损失
- 通过对请求编码和对收到的请求进行解码来完成服务之间的通信
- 服务框架原型
- 单机方式:直接调用
- 实现远程服务的调用客户端:找到提供服务的地址和端口,封装请求(序列化),进行调用
- 服务器端:接受到请求后,进行反序列化,找到对应的处理方法,进行处理完成后返回结果给客户端
- 服务调用端的实现
- 服务调用者和服务提供者之间的通信选择:利用服务注册中心
- 引入基于接口、方法、参数的路由
- 多机房场景:调用尽量走同一机房
- 服务调用端的流量控制
- 序列化和反序列化
- 网络通信选择:BIO、NIO、AIO
- 异步调用的方式:OneWay、CallBack、Furute、可靠异步等四种方式
- 使用Feture方式对远程服务调用的优化
- 服务提供端的实现
- 暴露自己的服务:将自己的服务注册到注册中心
- 服务端对请求的处理:采用NIO的方式实现,收到请求后,通信协议解析以及反序列化
- 执行不同服务的线程池隔离:将处理不同服务的线程池进行隔离
- 服务提供短的流控处理
- 服务升级
- 新增方法,不影响原来的调用
- 修改调用方法的参数列表处理办法:
- 对使用原来方法的代码都进行修改,然后发布
- 通过版本号解决
- 在设计方法上考虑参数的扩展性
实战中的优化
- 服务的拆分:要拆分的服务是需要为多方提供公共的功能
- 服务的粒度
- 优雅和实用的平衡
- 合并请求
服务治理
- 服务上下线
- 服务路由:服务路由策略的管理,基于接口,方法,参数的路由
- 服务限流降级
- 服务归组
- 服务线程池管理
- 服务授权
4. 数据访问层
数据库从单机到分布式的挑战和应付
- 单机数据库
- 数据库垂直/水平拆分的困难,对数据库进行减压
- 优化应用(优化数据库和SQL)
- 引入缓存,搜索引擎
- 把数据库中的数据和访问分到多台数据库上,分开支持
数据库垂直拆分和水平拆分
- 垂直拆分:垂直拆分带来的影响
- 单机的ACID保证被打破
- 一些join操作变得困难
- 靠外键去约束变得困难
- 水平拆分:水平拆分带来的影响
- ACID被打破
- join操作被影响
- 靠外键去约束的场景被影响
- 依赖单库的自增序列生成唯一ID受影响
- 针对单个逻辑意义上的表查询就需要跨库了
单机变为多机后,事务如何处理
- 分布式事务
- 分布式事务的模型与规范–XA(AP:应用程序,RM:资源管理器,TM:事务管理器)
- 两阶段提交(1阶段准备提交,2阶段真正提交)
- 一致性基础理论——CAP理论
- Consistency: 所有节点在同一时间读到同样的数据
- Availability:保证无论成功还是失败,总能收到一个反馈,系统总是可用的
- Partition-Tolerance:即便系统中有部分问题或者消息丢失,但系统仍能够继续运行
- Base理论
- Basically Available:基本可用,允许分区失败
- Soft state:软状态,接受一段时间的状态不同步
- Eventually consistent:最终一致,保证最终的数据是一致的
- 比两阶段更轻量级的Paxos协议
- 节点之间交换信息的两种方式:共享内存和消息传递
- Proposers:提出议案者,就是提出议案的角色
- Acceptors:收到议案进行判断的角色
- Learers:只能“学习”被批准的议案,相当于通过的议案进行观察的角色
- Proposal:议案,由Proposers提出,被Acceptors批准或否决
- Value:决议,议案的内容
- 集群内数据一致性的算法实例
- Quorum算法
- Vector Clock算法
多机的Sequence问题处理
- 唯一性:如果只考虑唯一性,那么可采用UUID
- 连续性:保证整个分布式环境中生成的id的连续性,解决方案:把所有的Id集中放在一个地方进行管理,对每个Id序列独立管理,每台机器使用Id时都从这个Id生成器取。不过需要解决以下问题:
- 性能问题:每次去取都会有资源消耗,可采用取一段来本地缓存起来
- 生成器的稳定性问题:需要保证可用性
- 存储的问题:1.ID生成器独立部署,2.ID嵌入到应用中
应对多机的数据查询
- 跨库join
- 在应用层把原来数据库的join操作分成多次的数据库操作
- 数据冗余
- 借助外部系统(搜索引擎)
- 外键约束:要求分库后每个单库的数据是内聚的
- 跨库查询的问题
- 排序:如果查询时已排序,那么就需要进行归并排序,如果查询时未进行排序,那就需要进行全排序
- 函数处理:Max、Min、Sum、Count等函数处理
- 求平均值
- 非排序分页
- 排序后分页
数据访问层的设计与实现
数据访问层就是为了方便应用进行数据读/写访问的抽象层,在这个层上解决应用通用的访问数据库的问题,该层也简称we8数据层
- 如何对外提供数据访问层的功能
- 对外提供数据访问层的方式(1.专有API,2.采用JDBC,3.采用ORM框架)
- 如何选择数据源
- 实战分享
- 复杂的连接管理
- 三层数据源的支持和选择
读写分离的挑战和应付
- 主库从库非对称
- 多从库对应一个主库:采用MySQL Replication实现,该实现有较低的延迟
- 主/备库分库方式不同的数据复制
- 引入数据变更平台
- 数据库如何平滑的迁移:在进行数据迁移时,记录增量的日志,在迁移结束后,再对增量的变化进行处理。在最后,可以把要迁移的数据的写暂停,保证增量的日志都处理完毕后,在切换规则。
- 引入数据迁移平台
5.消息中间件
消息中间件实现解耦
- 通过服务调用让其他系统感知事件发生的方式(直接通过接口调用)
- 通过引入消息中间件解耦服务调用
互联网时代的消息中间件
- 如何解决消息发送一致性
- 消息发送一致性的定义:产生消息的业务动作要和消息发送一致,意思就是如果业务操作成功了,那么消息一定会被发出去,如果业务操作失败了,那么消息不应该被发出去。
- 难以保证消息发送一致性
- JMS
- 消息一致性解决方案
- 发送消息给消息中间件
- 消息中间件对消息进行存储
- 消息中间件返回存储结果
- 业务处理
- 发送业务处理结果
- 消息中间件根据业务处理结果来判断消息是否投递还是删除
- 消息回查机制(补偿),主要是回查业务操作状态
- 解决消息中间件与使用者的强依赖的问题
- 提高消息中间件的可靠性
- 对于消息中间件系统中影响业务操作的部分,使其可靠性与自身业务的可靠性相同
- 可以提供弱依赖支持,能较好的保证一致性
- JMS消息模型
- Queue:每个应用都从中消费消息,多个应用组成完整的消费
- Topic(Pub/Sub):每个应用都从中消费消息,每个应用消费的消息都是完整的
- 消息订阅着订阅消息的方式
- 持久订阅:当应用down机时,消息中间件会保存消息
- 非持久订阅:当应用down时,消息中间件不保存消息
保证消息的可靠性
- 消息发送端的可靠性:发送者需要把消息发送结果准确的传给应用,应用才能进行后续处理
- 消息中间件保存消息
- 基于文件存储
- 采用数据库作为消息存储:采用表来存储消息,可以分消息表和投递消息表
- 基于双机内存的消息存储:采用两台机器的内存来存储,如果一台机器down机,那么另一台将数据持久化,该场景主要用于消息到达中间件后能快速的被消费的情况
- 消息系统的扩容处理
- 消息中间件自身如何扩容:通过软负载扩容现有的消息中间件
- 消息中间件的存储如何扩容
- 消息中间件把消息投递给消息消费者:消息中间件需要显示的接收到消息确认的完毕信号后才能删除消息;消息中间件不能通过网络层判断消息是否送到接收者,进而决定消息是否删除,而一定要从应用层响应入手。
消息重复的产生的原因和应对手段
- 消息发送端发送的消息重复
- 发送端发送消息到中间件,中间件收到消息后并成功存储,这时中间件出现问题,导致应用端没有收到消息发送成功的响应,因此进行重试产生的重复
- 消息中间件负载过高,成功存储后,返回“成功”时超时
- 消息中间件存储消息成功后,在返回结果时网络出现问题,导致应用重新发送消息
解决办法:重试发送消息时使用同样的消息Id,而不要由中间件端产生ID
- 消息中间件在投递的时候产生重复
- 消息投递到应用进行处理,处理完毕后应用出现问题,导致中间件不知道处理结果,进行重投
- 消息投递到应用进行处理,处理完毕后网络出现问题,导致中间件不知道处理结果,进行重投
- 消息投递到应用进行处理,处理时间较长,超时了,进行重投
- 消息投递到应用进行处理,处理完毕后中间件出现问题了,未对消息结果进行处理,进行重投
- 消息投递到应用进行处理,处理完毕后消息中间件收到消息,在进行消息存储的时候,中间件出现问题了,未对消息投递状态进行变更,进行重投
解决办法:在消息应用端进行幂等处理
消息投递的其他属性
- 消息优先级
- 订阅者消息处理顺序和分级订阅
- 自定义属性:消息自身的创建时间、类型、投递次数等基础属性
- 局部顺序
保证顺序的消息队列的设计
- 保证写入是按照顺序写入的
- 在消息接收者上有一个当前消费位置
- 单机多队列的问题和优化
- 如果队列数量太多,消息写入就类似于随机写了
- 根据队列做索引,每个队列的索引独立,保存的只是相对于存储数据的物理队列(CommitLog)的索引位置
- 解决本地消息存储的可靠性:采用消息同步复制
Push和Pull方式
- 数据传输状态:Push 保存在服务端,Pull 保存在消费端
- 传输失败、重试:Push 服务端需要维护每次传输状态,遇到失败需要重试,Pull 不需要
- 数据实时性:Push 非常实时, Pull 默认的短轮询,实时性依赖于Pull的时间,如果采用长轮询实时性与Push一致
- 流量控制:Push 服务端需要根据消费端的情况去做控制, Pull 消费端根据自身的情况决定是否去Pull消息
6.软负载中心与集群配置
软负载中心的基础职责
- 聚合地址信息:比如聚合中间件的地址信息,返回给消息消费端整个聚合后的列表
- 生命周期感知:感知服务的上下线,根据这些变化去更新服务地址数据
内容聚合功能
- 保证数据的正确性
- 高效聚合数据
解决服务上下线的感知
- 通过客户端与服务器端的链接感知
- 通过发布数据中心提供的端口进行链接的检查(检查应用的端口是否可用)
提升数据分发性能
- 数据压缩
- 全量与增量
集中配置管理中心
- 客户端
- 服务器端
7. 构建大型网站的其他要素
- CDN(Content Delivery Network):内容分发网络
- 存储支持:
- 分布式文件系统:TFS、GFS、Hadoop
- NoSQL: MongoDB、HBASE
- 缓存系统: Redis、Memcache
- 搜索系统
- 爬虫
- 倒排序索引
- 查询预处理
- 相关度计算
- 数据计算
- 离线计算:MapReduce
- 在线计算:实时计算,流式计算:Storm
- 发布系统
- 分发应用
- 启动校验
- 灰度发布
- 应用监控系统
- 依赖管理系统