来自《大型网站系统与Java中间件实践》、《企业IT架构转型之道》。
   读完最深的感触就是一句话:现在的网站在考虑功能之间就要先考虑可拓展性了。
   相比大公司通过分布式完成大型业务实现,使用服务框架使业务可重用,积累IT经验。只是单独搭个网站,做点东西实在是太不值一提了。今后还得看更多和分布式存储、服务框架的东西。
## 基础知识


  分布式系统定义:组件分布在网络计算机上,组件之间仅通过消息传递来通信并协调行动。

  为什么会存在分布式系统?1.单机升级性价比往后越来越低。2.单机处理能力(CPU、内存、网络、磁盘)存在瓶颈。3.出于稳定性与安全性的考虑。

分布式系统

1.计算机组成

  冯诺依曼计算机:输入设备、输出设备、运算器、控制器、存储器。而分布式也是基于这个思想的。

  输入设备 ->1.传统输入设备 2.其他机子传过来的都可以视为输入。
  输出设备 ->1.传统输出设备 2.传到其他机子的都可以视为输出。
  运算器电子元件 ->多个节点的运算能力。
  控制器CPU —>一种控制方式,软硬件负载均衡方案。如:Master+Worker
  存储器内存、外存 ->把有存储功能的多个节点组合起来。如代理服务器、规则服务器。

2.进程与线程

  为什么要了解线程?因为我们的程序大多数是通过进程中的线程运行的。而现实因为单核CPU早已达到瓶颈,故需要多线程开发,于是我们更需要注意:线程间通信、线程并发控制。

  从摩尔定律到阿姆达尔定律。

单进程中多线程交互模式:

  1.互不通信的多线程模式。即两线程没有交集。
  2.基于共享容器协同的多线程模式。例如生产者消费者就是用了一个队列作为容器。然而具体的访问对象不同,有线程安全不安全之分;选加锁或copy on write(更改前先copy一份一样的,然后修改copy的那份,原件不受影响,适用于读多写少的地方)方式;如果读写较多选读写锁而非互斥锁;或者直接选线程安全的容器,如concurent包里的。
  3.通过事件协同的多线程模式。例如:A要在B后执行。
  等等。

多进程模式:

  区别:多个进程的内存空间是独立的,同一进程中的多个线程内存空间是共享的。
  于是多进程比多线程稍微复杂,需要考虑序列化与反序列化。

分布式,即多机系统,按上面的依次范围递增。

3.通信基础

OSI、TCP网络模型。

socket套接字网络IO实现方式:

BIO:Blocking IO,阻塞实现,即一个socket套接字需要一个线程来处理。各种操作都可能造成阻塞。
服务器端多个连接就需要多个线程。

NIO:Nonblocking IO,基于事件驱动思想。可以一个线程处理多个套接字了。Reactor反应堆通过wait/notify的方式把不同套接字的事件进行处理。JDk4引入。

AIO:异步IO,采用Proactor模式,预先进行操作。NIO有通知时开始才做,AIO有通知时已经做完了。JDK7引入。

另外一些API也可以用来通信,如MINA、Netty。

4.分布式难点

缺乏全局时钟 --> 把这个工作交给一个单独的集群去做,通过这个集群区分多个动作顺序。
面对故障独立性 --> 1.单点故障,给单点做备份,把一个数据库拆分为多个数据库。
事务的挑战 -->

JVM

1.JVM是Java中间件运行的基础。

2.Hotspot JVM中内存的堆布局:新生代、老年代、持久代。

Hotspot中新生代GC:

Serial Copying -- 串行GC
Parnew -- 并行GC
Parallel Scavenge -- 并行回收GC

老年代GC:

Serial MSC -- 串行GC
Parallel MSC -- 并行GC
Parallel Compating -- 并发 Compacting GC
CMS -- 并发GC

JDK6中引入G1,目标是取代CMS,JDL9中正式取代。

3.线程池
线程池在线程执行后执行回收而非销毁操作。于是使用线程池可以复用线程。建议使用有线程上线的线程池,而非无上限的线程池。

4.synchronized
synchronized可以声明方法或者声明代码块。
synchronized后可以加参数,用来表示同步锁所属的对象,且这个参数可以是任意对象。

5.ReentrantLock
ReentrantLock是concurrent.locks中的一个类,JDK5引入。用法类似synchronized,然而ReentrantLock需要显示unlock(一般放在finally),否则会出问题。

使用的主要目的是:

1.ReentrantLock提供了tryLock方法,即先看看这个锁有没有被其他线程持有,返回true或false
2.构造ReentrantLock对象时,会判断锁公平与否,公平锁保证线程不会饿死,但整体效率偏低;非公平锁反之,即非公平锁允许抢占。
3.ReentrantLock提供读写锁,用于读多写少且读不互斥的场景,效率比互斥锁高。

6.volatile
volatile只保证同一变量在多线程中的可见性,更多是用来修饰作为开关状态的变量。并不保证原子性,只有互斥才能保证原子性。
volatile 修饰的i++在多并发的情况下可能会有问题

7.Atomics
Atomics是JDK5中加入的包,包中有一些以Atomics开头的类,用来提供相关原子操作。如:AtomicsInteger修饰的i++

8.wait、notify、notifyAll
三个都是Object上的的方法;wait等待,后面两个是通知。notify唤醒一个等待进程,notifyAll唤醒所有等待进程。需要注意的是三者的调用都必须在对象的synchronized块中。

9.CountDownLatch
CountDownLatch主要功能是当多个线程都到达了预期状态或完成预期工作时触发事件,其他线程可以等待这个事件来触发自己的后续工作。即CountDownLatch可以唤醒多个等待的线程。即运行到CountDownLatch就去执行CountDownLatch中的事。

10.CyclicBarrier
CyclicBarrier,循环屏障。可以协同多个线程,让多个线程在这个屏障前等待,直到所有线程到达该屏障再一起执行后续动作。

11.Semaphore
信号量,如果只有一个信号量就退化为互斥锁了。用acquire、release执行。

12.Exchanger
用于两个线程的数据交换,A运行到Exchanger,阻塞;等B也运行到Exchanger,进行交换;交换完各自执行自己的代码。

13.Future与FutureTask
先调用futuere对象的返回值,后续与该值无关的代码继续执行,而非阻塞着等这个值。

动态代理

静态代理:为每个被代理的对象构造相应的代理类,如果多个类中需要相同的代码时较为麻烦。

动态代理:动态生成具体委托类的代理类使用对象。即为一类行为写一个具体类实现。

反射

反射指在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。

主要提供功能为:
1.运行时判断任一对象所属的类 Class clazz = object.getClass();
2.运行时构造任意一个类的对象 Class.forName("ClassName").newInstance();
3.运行时判断任意一个类所具有的成员变量和方法 Method[] methods = clazz.getDeclaredMethods();
4.运行时调用任意一个对象的方法 Method method = clazz.getDeclaredMethod();
5.生成动态代理。
正因为有了代理,Java的代码可以变得更灵活,可以在运行时去认识编译时并不了解的对象、信息。

其他要素

1.CDN

内容分发网络。整个CDN系统分为CDn源站和CDN节点。把相对稳定的资源放到距离最终用户较近的机房。
需要考虑:全局调度、缓存技术、内容分发、带宽优化。

2.存储支持

关系型数据库建立在key-value的基础上,但大型网站不止需要关系型数据库。
对于一些图片、大文本的存储,考虑使用NAS网络存储设备,或者为分布式文件系统。
NoSql:nosql也是基于Key-value。
缓存系统:Redis和Memcache是两个使用非常广泛的开源缓存系统,redis已有对集群的支持。使用起来可以直接在应用和数据库间用缓存、也可以把缓存当做数据库的一个选项、并且页面缓存也十分常用。

3.搜索系统

站内搜索,数据量小的时候直接like即可,数据量大的时候需要引入相应中间件。

4.容灾管理

同城机房、异地机房。

网站演进

大型网站架构演进

大型网站最重要的功能就是计算和存储。即DB和Application Server

我们用框架还是原生Socket,各个功能模块的调用都是基于JVM内部的方法调用实现的,而应用和数据库是通过JDBC进行访问的,我们进行优化也是基于这两个方面。

start:单机部署

--> 单机负载告警,数据库和应用分离到两台服务器上。

--> 应用服务器负载告警,将应用服务器走向集群。把服务器放成两个,用户访问通过负载均衡实现。这里对于Session的问题还需要特别考虑几种解决方式(Session Sticky、Session Replication、Session数据集中存储、Cookies Based)

--> 数据读压力大,来读写分离。采用数据库作为读库、搜索集群、数据缓存、页面缓存。

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

--> 读写分离后、又遇到瓶颈,数据垂直拆分,即专库专用,例如:把交易、用户、商品的数据分开。把一个数据库里的东西拆到不同数据库了。同时使用分布式事务,或者直接去掉事务,并重新实现表相连查询。

--> 垂直拆分又遇到瓶颈,数据水平拆分,即把一个表的数据拆到两个数据库里。故需要解决:SQL路由问题。

--> 数据库解决了,把应用进行拆分,引入微服务思想。

消息中间件:

最大的好处即异步和解耦。可以让两个应用仅通过消息中间件打交道,双方互不影响。

Java中间件

可以这么理解:中间件是应用与应用之间的桥梁,也是应用与服务的桥梁。特定的中间件是用来解决特定场景的组件。

先主要了解三种中间件:

1.远程过程调用和对象访问中间件,分布式必备。 服务框架——对应用拆分
2.消息中间件,解决应用之间的消息传递、解耦。 消息中间件——应用解耦、分布式下完成事务
3.数据访问中间件,解决访问数据库的共性组件。 数据层——数据拆分、数据管理、扩容、迁移。

网站服务框架

把单层的Web应用结构改为多层的、有服务层的结构。目标:尽量把远程服务的使用做得和本地服务一样。即客户端去调用服务端的方法(类似远程连接?)。

服务端:通过配置服务地址、目标机器、建立连接、序列化、发送请求、接受结果、解析结果。即1.对本地服务的注册管理。 2.根据进来的请求定位服务并执行。

客户端:调用发起、寻址路由、协议配饰/序列化、网络传输、反序列化/协议解析、得到结果返回给调用方

Java的系统可以用Spring作为组件的容器,即Spring Bean。

上述过程中的难点:

1.远程通信的问题,怎么调用服务时怎么寻址路由?

采用透明代理或者服务注册中心还有负载均衡。
并且为了更加精细的确定某个接口的位置,用基于参数的路由把请求分流,这又引入了流量控制

2.序列化的问题,即把类、对象变成能进行传输的二进制数据。

需要考虑易用性、跨语言、性能开销、序列化后数据长度。如:XML、JSON序列化,HTTP协议传输。

实战中的优化的难点:

1.服务需不需要拆分?

2.服务需要拆分到多细?

3.架构优雅与实用的平衡?

4.分布式中的请求合并?

以下观点来自《企业架构转型之道——阿里巴巴中台战略思想与架构实现》

比起完成业务,组织架构、项目模式的改变影响更深远。也即企业信息中心与业务中心的发展。

1.“烟囱式”系统建设的伤害:1.重复功能的建设和维护。 2.不同系统相互集成的困难。 3.不利于业务的沉淀和持续发展。

2.构建业务中台的基础——共享服务体系。SOA架构,服务重用,让服务成为组件。把服务成长为企业最珍贵的IT资产。并且这种方式也较原来更好得培养了人才。

3.大数据的难点:1.数据分布广、格式不统一、不标准。 2.缺少能基于数据有业务建模能力的专家。

4.几百人维护单个war包的缺点:1.团队协同成本太高。 2.应用复杂度超出人的认知负载。 3.错误难于隔离 4.数据库连接能力很难拓展。 5.应用拓展成本太高。

5.搭建共享服务体系需要——分布式服务框架。优点:1.比上文的ESB企业服务总线结构快一倍,从4次会话到两次会话。 2.避免了单个节点引起的“雪崩效应”,崩只崩一块。

6.阿里巴巴开源分布式服务框架HSF(Dubbo也是阿里开源的),使用组件化思想,把应用划分为服务提供者、服务调用者、地址服务器、配置服务器、Diamond服务器。
HSF框架采用Netty+Hession数据序列化,主要考虑在大并发量时,服务交互性能达到最佳。这类RPC采用多路复用的TCP长连接方式。避免反复建立连接和线头阻塞。
HSF框架具有容错机制且支持线性拓展支持。

7.微服务。微服务可以视为SOA演变后的一种形态,并无本质区别,只是一些具体应用思想会有变化。

8.服务中心提供的能力就像接口?不全对。大致上可分三类:1.依赖接口的能力。 2.依赖工具的能力。 3.依赖数据的能力。

9.业务服务化带来的问题:各个服务之间调用关系变得纷繁复杂,“点对点”交互带来恐怖的复杂调用。于是我们需要用分布式服务调用链跟踪平台,如:阿里“鹰眼”,Twitter的Zipkin,都是分布式服务器跟踪平台。可以处理日志文件,埋点和输出日志,TraceID传递,海量日志分布式处理平台、Tlog、日志收集控制。

数据库方法

大型系统底层的数据量和访问量逐步增大的过程中,该系统要面临的问题和相应的解决方案。

升级硬件 --> 优化应用 --> 引入缓存、加搜索引擎 --> 把数据库的数据和访问拆到多台数据库上(垂直拆分、水平拆分),分开支持。这也是核心思路

不管是垂直还是水平拆分,都会导致单机数据库支持的特性现在不一定支持了。

垂直拆分:把一个数据库中不同业务单元的数据拆到不同数据库中。会带来:

1.单机的ACID保证被打破,单机的事务逻辑会受到很大影响。只能选择修改或者引入分布式事务。 解决方法:分布式事务。
2.一些Join操作变得困难,因为分到两个数据库了。 解决方法:跨库join,去多个库进行join
3.依靠外键约束的场景变得困难。 解决方法:很难解决。于是阿里就完全舍弃外键了。

水平拆分:把同一业务单元的数据拆分到不同数据库中。会带来:

1.垂直拆分的三种水平依旧有。
2.单库自增序列的唯一ID受到影响。 解决办法:给一台独立服务器来提供ID生成器
3.单个逻辑意义上的查表要跨库了。 解决方法:跨库查询。但是性能消耗很大,尤其是分页排序操作,最好尽量避免。

分布式事务

与单机事务不同,分布式事务要多个节点参与。
分布式事务模型:X/Open DTP模型。分布式规范:XA。数据一致理论:CAP/BASE、PAXOS。

数据访问层

方便进行数据读写访问的抽象层,在这个层上解决对数据库的操作、访问。
Java中就有提供API、JDBC驱动访问、ORM框架访问。其中在大数据情况下JDBC驱动是优势明显,比ORM与数据库交互更少。

读写分离的挑战

主从库非对称情况下:
1.多从库对应一主库。 2.主从库分库方式不同的数据复制。 3.引入数据变更平台。 4.在运行时进行平滑的扩容、缩容处理。

以下观点来自《企业架构转型之道——阿里巴巴中台战略思想与架构实现》

  分布式数据库,有:阿里巴巴分布式数据层框架TDDI 。数据库同步系统:阿里巴巴精卫系统。

  1.数据垂直分区 -> 数据库的读写分离 -> 数据尽可能平均拆分 -> 尽量减少边界事务(你拆成了8个库就别经常查8个库)-> 异构索引表尽量降低全表扫描频率 -> 将多条件频繁查询引入搜索引擎平台。

  2.数据安全的保障:1.平台稳定性的保障。 2.心跳+报警。 3.Mysql主备切换。 4.Mysql异常挂掉

  3.异步化。例如:1.业务流程异步化,用户订单创建需要200个服务,将严格先后调用关系的服务保持顺序执行,对能同步执行的所有业务服务均采用业务化处理。 2.数据库事务异步化,通过数据库的事务特性来实现这个稍显复杂的业务一致性。即对长时间被事务锁住的数据库,把大事务拆成小事务,尽量异步化。

  4.事务与柔性事务。事务一致性的CAP理论(即一个分布式系统最多只能同时满足一致性、可用性、分区容错性这三项其中的两项)以及其衍生的BASE理论(即使无法做到强一致性,但应用可以采用合适的手段达到最终一致性)。ACID是传统数据库常用的设计理念,追求强一致性模型;BASE支持的是大型分布式系统,通过牺牲强一致性获得高可用性。

  5.Paxos协议,在多机通信不存在伪造或者篡改的情况下,可以经过Paxos协议达成一致。成本是发给Paxos系统的信息(数据)需要至少同步发送到一半以上多数的机器确认后,才能认为是成功。这样大幅度增加了信息更新的延迟,因此分布式系统的首选不是这种强同步而是最终一致。
而采用最终一致就一定会导致柔性状态。

  6.柔性事务如何解决分布式事务问题?1.引入日志和补偿机制。 2.可靠消息传递。 3.实现无锁。
柔性事务在阿里巴巴内部的实现:1.消息分布式事务,又不支持分布式事务的TDDL、DRDS研发出支持分布式事务的TXC,TXC实现了两阶段提交,事务自动回滚。 2.分布式缓存产品,Tair。其他的开源缓存平台,如redis也被采用。

消息中间件

JMS是JavaEE中一个关于消息的规范。后很多产品是对该规范的实现,小型系统可以直接使用JMS,大型系统需要特化一些地方。

JMS:

  JMS可以解决消息发送一致性的问题,但是存在一些限制且成本较高。

  业务操作与发送消息一致性方案的两个限制:首先要确定发送消息的内容;需要实现对业务的检查。

  消息模型(如JMS中额Queue和Topic两种模型)对消息接收有很大影响,并且以上两种并不够,我们需要自己设计一个模型。

负载中心配置

  以上的三种中间件,都是通过负载中心(软负载中心和集中配置管理中心)进行支持。

  软负载中心:负载把各个机子服务地址等信息聚合起来,且标注每台机子能否使用。聚合数据、订阅关系、连接数据。

  此外还有数据压缩、全量增量设置、按优先级隔离、分环境区分、自动感知上下线机制、维护管理路由规则、管理集群。

  集中配置管理中心:以上信息可分为是否持久、是否需要聚合。于是需要配置管理中心,来对稳定性、异常容灾策略、性能及发送延迟进行设置,通过配置管理中心可以改变配置从而影响应用的行为。