大型网站系统与java中间件实践 读书笔记
- 分布式系统介绍
- 大型网站的框架演进过程
大型网站访问量和数据量缺一不可
大型网站最核心的就是计算和存储
应用服务器从一台变为两台,,需要解决两个问题
- 用户对服务器如何选择(负载均衡)
- Session问题
session问题如何解决
Session sticky
需要负载均衡器对每次会话的标志进行请求转发,让同样的session每次都送到同一个服务器上
会产生一些问题:宕机数据丢失,在选择的过程中有状态的开销
Session Replication
让web服务器之间增加了会话数据的同步,同步保证了不同服务器的session的一致
缺点:网络开销,磁盘开销
Session数据集中存储
这样web服务器不在本地保存session,而是集中存储在一个地方,需要就自己取。
缺点:时延
Cookie Based
把session数据放在cookie中,相当于自己带着筷子和碗去吃饭
缺点:cookie长度受限,安全问题,性能问题(每次都带)
读写分离
- 数据复制问题,延迟导致不是最新的的数据
- 应用对于数据源的选择
增加一个读库,
搜索引擎就是一个读库
搜索引擎就是根据搜索的内容,不断的更新索引
缓存 分为数据缓存,和页面缓存(同步appache或者自己搭建租用的服务器)
分布式存储系统
为了弥补关系型数据库的不足
数据库垂直拆分
数据库水平拆分
单表数据存不下的时候,分到不同的数据里,所以select的时候得找几个数据库
拆分应用
前面讲的读写分离,分布式存储,数据垂直拆分和数据水平拆分,都是解决了数据方面的问题。应用也需要拆分
按照应用功能尽心拆分
走服务化的道路
总结
- java中间件(软件胶水)
中间件不是操作系统的一部分,不是数据库系统的一部分,不是软件应用的一部分,而是能够让软件开发者方便地处理通信,输入,输出,能够更加专注自己的应用部分。
这章主要介绍了一些中间件需要的并发基础知识
第四章服务框架
将公共的部分放在服务层,冗余更少,易维护等优点主要通过远程调用进行实现
服务端框架的设计与实现
服务调用者与服务提供者之间的通信方式
两种
第二种通过列表的方式,每次调用具体的服务,都是通过查表实现,其实这个过程还可以进行负载均衡,随机,轮询,最小连接,加权,一致性哈希等等
(服务调用端)基于接口,方法,参数的路由(服务调用端)
更加细粒度的控制服务路由的需求
针对不同接口方法的执时间不同,可以采用两种思路进行优化
- 多线程,提高性能
- 进行隔离
将服务A放在右上的集群中,将服务B放在下面的集群中,导致客户端的请求进行分流。
如何定位呢: 根据服务 定位提供服务的那个集群的地址,然后与接口路由规则中的地址一起取交集,得到的地址再进行接下来的负载均衡算法,最终得到一个可用的地址进行调用。
多种异步服务调用方式
借口暴露通过注册表的形式
服务提供端的设计与实现
执行不同服务的线程池隔离
在服务提供端,工作线程不是一个,而是多个,当定位到服务之后,我们根据服务名称,方法,参数来确定使用哪个线程池,实现线程池的隔离
服务升级
往往需要改接口,改参数等等,进行升级
往往通过版本号进行控制
实战中的优化
- 服务拆分有必要的拆,没必要的不要拆
- 服务的粒度,一定要合适
- 优雅和实用的平衡
通过这种方式,减少网络通信,避免大规模服务调用者替代服务调用者访问数据库
- 分布式环境中的请求与合并
如果其他线程有一样的计算,就自己不算了,拿来用。但是在分布式下,涉及到多个节点。不论是使用分布式锁服务,还是采用路由的方式将请求送到特定的机器上,都可以实现,需要具体情况具体使用。
服务框架与ESB(企业服务总线)对比
- 服务框架是点对点,ESB是总线型
- 服务框架是面向同构框架的,ESB趋向于不同厂商服务的整合
第五章数据访问层
数据库垂直拆分,水平拆分遇到的问题
最基本问题,事务遭到破坏
分布式事务
分布式事务模式
里面有三个组件,AP应用程序了,RM资源管理器,TM事务管理器
两阶段提交
如果第一阶段出现error,第二阶段会回滚。
大网站一致性基本理论 CAP/BASE
- CAP模型
Consistency(一致性) availability(可用性) Partion-Tolerance(分区容忍性)
- BASE模型
Basically available:基本可用,允许分区失败
Soft state:软状态,接受一段时间的状态不同步
Eventually consistent:最终一致性,保证最终数据状态是一致的。
在分布式系统中选择了CAP中的A和P,对于C,我们采用的方式和策略就是保证最终一致。
比两阶段更轻量级的一些 Paxos协议
Paxos协议有一个前提,就是不存在拜占庭将军问题(内部出了戒牒),也就是要求是一个可信的通信环境,消息都是准确的,没有被篡改
协议的原则就是少数服从多数,有三个角色
- Proposers 提出议案者,就是提出议案的角色
- Acceptors 接受到议案进行判断的角色。
- Learner 只能学习被批改的议案。
集群内数据一致性算法实例
通过《版本号,修改者》这种措施进行
多机的sequence问题与处理
也就是id的 唯一性和连续性
这种方式存在问题:
- 性能问题 每次都需要从远程取
- 生成器的稳定性
- 存储问题
以上两种方式各有利弊
分布式多机数据查询
分库分表之后,需要对查询结果进行合并。合并难点
- 排序
- 函数处理 max,min,sum,count
- 求平均值
- 非排序分页
- 排序分页
分布式数据访问层的设计与实现
对外提供数据访问层的方式
从左到右,依次是,数据库专有ApI方式,采用JDBC方式,基于某个ORM类/ORM接口方式
数据层流程的顺序看数据层的设计
Sql解析
主要是查看sql是不是支持,支持多少方言等等
规则处理
主要是找对应数据位置,哪个库,哪个表
- 固定hash算法
- 一致性hash算法
Sql改写
因为分库,分表,以后,每个表的名字不一样
数据源选择
往往采用master-slave形式,但是也要找到选择哪个slave
Master-slave模式的挑战
数据结构相同,多从库对应一主库的场景
每个slave可能只是master的一部分
主/备库分库方式不同的数据复制
就像是镜像一样,进行按模块备份。
引入数据变更平台
如何做到数据平滑迁移
就是建立一个增量日志,记录迁移过程中的日志,迁移完成以后,继续执行增量日志的东西。
第六章消息中间件
消息中间件的优点
通过消息中间件解耦,登录系统就不需要关系到底有多少个系统需要知道登录成功这件事,只需要把登录成功这件事转化为一个消息发送到消息中间件就可以了,同时实现异步
两个特征,一个解耦,一个异步
发送消息的一致性
消息的业务动作与消息发送的一致性。就是说业务操作成功,消息就要发送到消息中间件,反义发送不成功。
在待处理消息,需要了解业务操作的结果做出下一步的动作,需要一个反向的过程如下
解决消息中间件与使用者的依赖关系
- 方案一:把消息中间件所需要的消息表和业务表放在一个数据库中,消息是中间件自己取,或者虚线发送,不是必须的
- 方案二,轮询消息交给业务层
- 方案三:可以加磁盘进行本地存储,防止消息中间件挂掉
消息模型对消息接受的影响
分为两种模型:queue和topic(发布/订阅)两种
- Queue 每个消息只能让一个应用消费,所以queue又称为点对点模式
- Topic 每个应用都可以消费所有的消息
- 假设需求是:消息发送方和接受方都是集群
同一个消息的接收方可能有多个集群进行消息的处理
不同集群对于同一条消息不能互相干扰
通过queue和topic进行结合,集群与集群之间用topic,集群内部用queue
消息订阅者订阅消息的方式
两种方式:持久订阅,非持久订阅
非持久订阅:当订阅者下线的时候,就不会发消息
持久订阅:当订阅者下线与否,都会发消息。
保证消息的可靠性
从以下三个方面进行保证
发送端的可靠性保证
通过合适的交互,说明信息有序的推进,存储信息进行返回
消息存储的可靠性保证
- 采用文件进行消息存储
- 采用数据库进行存储
- 基于双机内存 消息存储,使用两个机子的内存进行处理消息,如果一个机子宕机了,才进行持久化操作。
消息投递的可靠性保证
消息中间件需要显示的接收到 接受者确认消息处理完的信号才能删除消息。
消息重复的产生和应对
- 消息发送端应用重复发送消息,导致内存了多存了几份,原因就是没有交互好
- 消息中间件向外投递时,重复,也是因为没有及时的反馈导致
解决方案,幂等性操作,多次操作也一样
第七章软负载中心和集中配置
初识软负载中心
具备基本的职责:地址聚合 生命周期感知
软负载中心的结构
包括两部分,一是软件负载中心的服务器,另外一个软负载中心的客户端。服务端主要是感知提供服务的机器是不是在线。客户端有两个角色,一个服务提供者,一个是服务使用者。
内容聚合功能的设计
内容聚合功能完成的工作,两部分:保证数据正确性,高效聚合数据
- 并发下的数据正确性的保证 使用concurrentHashMap
- 数据更新删除顺序保证 采用NIO selector 出现问题很难排查
- 大量数据同时插入,更新时的性能保证 加入任务队列
解决服务上下线的感知
软负载负责可用的服务列表,当服务可用时,需要自动把服务添加到地址列表中,服务不可用时,自动从服务列表中删除。
通过两种方式实现:
- 通过客户端和服务端的连接感知 通过心跳或者数据的发布进行判断
- 对上面方法的补充,如果心跳感知下线,那么ip,端口号进行主动检测是不是下线
软负载中心的数据分发特点和设计
数据分发与消息订阅的区别
- 消息中间件要求信息不能丢失,详细操作都要发送,软负载中心只要求把最新数据发送到相关订阅者
- 消息中间件中同一个集群中每个机子只能处理一个消息,而软负载中心是将消息发送给所有机子
提升数据分发性能
- 数据压缩
- 全量与增量的选择 增量只发最新的数据,全量是发全部数据
软负载中心从单机到集群
需要关注两个问题:数据管理 连接管理
- 数据管理方案一,数据统一管理
把聚合数据的任务和推送数据的任务分别专门的机器进行处理
- 数据对等处理
把软负载中心的节点分为两种,一种数据分发节点,一种是数据聚合节点,只负责和数据发布者连接,每个节点的数据是通过同步一致,通过一个固定的时间进行同步
第八章构建大型网站的其他要素
静态内容cdn加速
Cdn就是网络缓存技术,把一些静态页面加载到近端。
大型网站的存储支持
- 分布式文件系统hdfs 还有google 的gfs
在文件读取过程中,首先会将文件名称发送给Master中心服务器,由名称节点根据文件名找到发文件对应的数据节点,并将数据节点的序列返回给客户端,客户端获取后就直接请求对应的数据节点从服务器获得多需要的文件数据。实现高并发,减少master压力
每个文件块会被同时保存在三个地方
(1)加快数据响应数据,因为可以实现文件块的并发访问。
(2)容易检查数据错误
(3)保证数据的可靠性,实现高可用、高性能。
- Nosql
- 缓存技术
静态内容进行缓存处理
搜索系统
- 全网搜索 爬虫
- 站内搜索 定时更新索引 根据数据变更实时更新索引
- 倒排索引 把原来的值的内容拆分作为索引的key,而原来用作索引的key变为值
- 查询预处理: 同义替换,纠错,相关度计算等等
数据计算支撑
- 离线计算
在Map阶段,我们更具设定的规则把整体数据映射给不同的worker来处理,并且生成各自不同的处理结果,而在Reduce阶段,是对前面处理的数据进行聚合,形成最后的结果。当然一个任务可能不止一次MapReduce过程
- 在线计算
其他总结
1.分布式系统相对集中式而言,是指多台计算机互相通过消息通信进行协作而对外提供服务;可解决大型机的伸缩性和单点等问题;
2.网络i/o有bio/nio,还有aio,aio是指线程拿到消息后并不自己处理或等处理结束之后再响应,而是将消息投递之后继续后面的处理,只将回调传递给被调用方,消息处理完成之后自动由被调用方完成回调,也就是异步io,java7支持aio;
3.分布式系统有几个难点:缺乏全局时钟(可以把时间序号获取交给单独集群来做);面对故障的独立性(要考虑其它模块可靠与不可靠的情况);单点故障处理(能拆分的拆分,能换集群的换集群,不能拆分到最细的做到备份以备自动恢复);事务的挑战(两阶段提交协议,最终一致性等);
4.单机应用演化过程:
1)单机负载告警后,数据库与应用服务器做分享;
2)应用服务器负载告警后应用服务器做集群,涉及负载均衡设备,涉及session问题可将session存cookie或session拷贝或统一session服务管理中心;
3)数据库压力变大,读写分离,涉及数据复制问题和应用对数据源的选择问题,读库前面也可再加缓存,数据实时要求不高还可采用搜索引擎作为读;
4)缓存除了可用来缓存数据,还可用来缓存页面,缓存一切对实时性要求不高的内容;
5)引入分布式存储(文件/kv)系统缓解数据库的存取;
6)读写分离后数据库仍有瓶颈,采用专库专用,数据库的垂直拆分;
7)数据库垂直拆分后再遇到数据库单机瓶颈,考虑水平拆分;此时引入数据存取中间件;
8)数据库问题解决后,新的挑战时采用应用拆分服务化方式;此时引入消息中间件的服务框架中间件;
5.Java中间件主要有三类:服务框架中间件,消息中间件和数据访问中间件;
6.votatile和synchronized的区别:volatile表示每次读写都直接读写主存,不会拷贝到线程存储中,也就是所有线程操作的都是同一份,这并不表示只有一个线程能同时操作;sychronized是表示加锁,保持数据的一致性;
7.Java的Atomics包中的类是一些支持原子性操作的类,它们的性能会有明显的提升是因为Java使用了硬件特性来支持;
8.CountDownLatch是等待线程调用latch.await,等待latch减少到0就能往下走了,而释放线程则使用latch.countDown来每次调用释放1;
9.CyclicBarrier是指所有调用barrier.await的线程都要等到其它线程都执行到这一条语句之后才往后执行,barrier还可以预设一个线程在满足条件后运行;
10.Exchanger是指两个线程都执行到这条语句时互换一个信号,然后各自继续执行;
11.Future是指获取这个结果的线程调用是异步的,代码可以先往下执行,等到要真正要使用这个返回值时如果仍未返回才会停下来,FutureTask是其实现类。
12.并发容器如果有合适场景尽量使用Java提供的,不要自己基于锁去实现;
13.动态代理,是指把类和接口,接口Handler等名称传递进去给Proxy.newProxyInstance方法来动态创建代理的方式,可在方法触发前后甚至触发时进行对应处理,这在服务调用框架之类的客户端或服务端,一些能用Bean可以让用户配置接口,从而生成代理,由代理来处理来自外部的调用,而在调用前后进行替换。
14.服务框架大致是这样实现的:
1)调用端:通过bean配置方式来配置远程服务,服务提供方地址一般通过一个配置中心来给出,动态代理在接口调用时通过服务框架的方法来获取远程对应服务提供方地址,接装请求参数等来进行序列化进行Socket直连(采用bio/nio等),得到响应结果后反序列化为对应的对象返回给上层调用方;超时等问题的处理可通过Future来实现,还可以实现多个调用之间的合并优化,即发起多个类似异步调用,在使用到它们结果时才会卡住;
2)服务方:通过bean配置方式来配置远程服务提供者,启动后注册到对应的配置中心,启动若干个线程(池,用来处理流控)来监听某一个端口的请求,当连接到来时调用具体实现类之前做一些返序列化工具,调用之后再进行一些序列化操作返回给远程调用者; 应用本身与框架的jar包冲突问题通过ClassLoader来解决;
3)服务注册中心:主要用来管理注册来上的调用者和服务提供方,同时可兼具路由管理,信息查看等功能;路由可细化到类或方法,还可通过分组来把同一机房的提供者标识出来;服务升级一般是采用版本号的方式来,即先将服务升级,新老并存,老的调用方全升级后再下线老服务;
15.服务框架实战中的优化,即服务治理需要的内容:服务信息管理,服务质量管理评估,服务容量评估,服务依赖展示,服务分布展示,服务统计,服务报表,服务元数据等;服务管理还有服务的限流,上下线,降级,路由,服务授权管理等等;
16.服务框架与ESB异同,都是面向服务化,服务框架主要考虑同构系统不考虑异构,ESB会考虑不同厂商的实现;
17.数据库水平/垂直拆分的困难:
1)单机的事务机制被打破,要考虑分布式事务的控制;
2)一些单库操作需要到多个库中操作,表连接需要用应用方式来实现;
3)外键约束需要程序来保证而非数据库;
4)依靠单库的自增序列方式要改变;
18.分布式事务有两阶段提交协议,但对大型网站来说还是太复杂而且会有性能问题,比之更轻量的有Paxos协议,其核心原则是少数服务多数;一般能不引入分布式事务就不要引入,如果一定要引入也不追求强一致性而只要求最终一致性,即通过重试的方式把未完成的做完而不回滚;
19.大型网站一致性理论:CAP,即一致性,响应/可用性,部分出问题时仍能工作(Partition-Tolerance),这几个属性之间是互斥的,因此更多是放弃完全一致性,只追求最终一致性即可;
20.跨库查询且要排序是最难处理的问题,等于要将所有分库的数据查出来再运算,应该尽量避免这种情况,尤其是分页到后面数据量更在,如果数据量巨大的,可考虑使用搜索引擎;
21.数据访问中间件的设计一般是在jdbc/orm框架之下加一层用来处理路由,sql分析等;一般会有一堆的数据源需要处理,包括数据的划分规则(取模不易于扩展,考虑一致性哈希,有人退出时旁边的接管之,可以将一个物理结点虚出多个虚拟结点方式缓解任务过于集中的问题),而数据层需要进行如下转换:sql解析,规则处理,sql改写(在各库中表名可能不一样),数据源选择,sql执行,结果集返回合并处理这些步骤。
22.数据访问中间件可以在应用中以jar包方式引入,也可以考虑单独部署一个应用,业务应用只与这个应用打交道,而各种规则由这个应用来解析与执行。
23.读写分享等的数据同步,主要由otter来完成,基于数据库日志解析的,国际站可做到秒级延时,国内可做到ms级,阿里内部现在在做单元化,多地多活等方案,也需要这些同步技术。
24.平滑数据迁移其实是先全量迁移,然后再将这一过程中老库的变更记录在新库执行,这个递归过程会越来越短,当需要迁移的增量很少时,暂停对这部分数据的写操作,然后快速完成处理,切换路由到新库。
25.消息中间件可解耦应用之间的依赖,而且一般用业解决异步调用等实时性要求不高的问题,比如登录后发短信或是数据同步,记录日志这些。
26.消息投递与业务处理的一致性问题很长时候都需要保证,一些可让用户随意重复的除外,因此需要引入事务类似的方式,但分布式事务成本太高,所以一个相对折中的方案是:
1)发送消息给消息中间件;
2)消息中间件入库消息;
3)消息中间件返回结果;
4)业务操作;
5)发送业务操作结果给消息中间件;
6)更改存储中的消息状态;
这个方案只有第5第6步可能引发不一致性问题,但这种情况下消息都可以通过消息中间件的未处理消息状态来问消息发送源进行反查。
27.消息中间件一般有topic和queue两种,有不同的应用场景,有时候还需要级联的方式来处理。
28.消息发送端及发送过程的可靠性是通过本地存储+重试的方式来保证,对于失败的消息会保留下来并在消息中心恢复之后重新发送;而对于存储端则一般可采用数据库来存储消息,还可以采用双机内存的方式来加快速度,使存储只在内存中运转并两台机器之间相互备份,一旦一台挂掉则另一台落磁盘存储并接管消息。
29.消息处理端一般都需要保证消息处理操作的等幂性以防止消息投递出错有重复的消息产生;