分布式系统的定义:组件分布在网络计算机上,组件间仅仅通过消息传递来通信并协调行动。
分布式系统的意义:
摩尔定律:当价格不变时,每隔18个月,集成电路上可容纳的晶体管数目会增加一倍,性能也将提升一倍。
冯诺依曼结构:输入设备、输入设备、运算器、控制器、存储器。
基于共享容器协同的多线程模式:经典如生产者消费者问题,对于存储数据的容器或对象,有线程安全和不安全之分,对于不安全的容器或对象,一般可以通过加锁或者通过Copy On Write的方式控制并发。
通过事件协同的多线程模式:避免死锁
多进程模式:
OSI七层模型与TCP/IP模型:
Socket套接字进行网络通信开发时,用到的三种方式:BIO、NIO和AIO
BIO:Blocking IO,采用阻塞的方式实现,一个线程处理一个Socket,发生建立连接、读数据、写数据的操作时,都可能会阻塞。
NIO:Nonblocking IO,基于时间驱动思想,采用Reactor模式,可以在一个线程中处理多个Socket套接字
AIO:AsynchronousIO,异步IO,采用Proactor模式,与NIO的差别是,AIO在进行读写操作时,只需要调用响应的read/write方法,并且需要传入CompletionHandler,在动作完成后会调用。
输入设备的变化
输出设备的变化
控制器的变化
方式1和2,透明代理:对发起方和处理方都是透明的
缺点:
方式3,采用名称服务器直连的方式:
请求发起方和处理方直接没有代理服务器,而是直接连接。外部多了一个“名称服务”的角色,作用有:
名称服务只是起到一个地址交换的作用,在发起请求的机器上,需要根据从名称服务得到的地址进行负载均衡的工作。
优点如下:
缺点就是代码升级较复杂
方式4,采用规则服务器控制路由的请求直连调用
与名称服务器不同的是,规则服务器并不和请求处理的机器交互,只负责把规则提供给请求发起的机器。
方式5,Master+Worker的方式
存在一个Master节点来管理任务,由Master把任务分配给不同的Worker进行处理。
运算器的变化
存储器的变化
同控制器的变化,加代理服务器、or名称服务器、or规则服务器
大型网站:访问量(PV)、数据量、业务复杂度
Session保存会话状态,在Web服务器上,各个会话独立存储,多台服务器不能保证每次请求都落在同一边的服务器上。解决方案如下:
1、Session Sticky:负载均衡根据会话标识进行转发,让同样的Session请求每次都发送到同一个服务器端处理
缺点:
2、Session Replication:会话在多态服务器上复制同步
缺点:
3、Session数据集中存储
Session数据不再Web服务器上,而是放在另一个集中存储的地方。
缺点:
4、Cookie Based:把Session数据放在Cookie中
缺点:
1、采用数据库作为读库
缺点:
数据复制问题;
应用对于数据源的选择问题:写操作和事务走主库,考虑从库相对主库的延迟
2、搜索引擎其实是一个读库
3、加速数据读取的利器——缓存
尽管读写分离以及分布式存储系统,能够降低主库的压力,但是交易、商品、用户的数据都还在一个数据库中,压力还在继续增加,我们有数据垂直拆分和水平拆分两种选择;
1、专库专用,数据垂直拆分
垂直拆分即把不同的业务数据分到不同的数据库中。
问题:
2、单表达到瓶颈,数据水平拆分
水平拆分就是把同一个表的数据拆到两个数据库中。
问题:
拆分应用
消息中间件是在分布式系统中完成消息发送和接收的基础软件。两个明显好处:异步、解耦。
三个领域的中间件:
JVM中堆分为三块:Young/Tenured/Perm,新生代/年老代/持久代
一般来说,新对象分配在新生代的Eden区,也可能直接分配在年老代,在进行新生代垃圾回收时,Eden区存活的对象被复制到空的Survivor区,在下次新生代回收时,Eden区存活的对象和这个Survivor存活的对象被复制到另外那个Survivor区,并且清空当前Survivor区,经过多次新生代垃圾回收,还存活的对象会被移动到年老代。
线程池
ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit, SECONDS, new LinkedBlockingQueue<Runnable>(count));
tp.execute(new Runnable(){
public void run(){}
});
使用线程池的方式是复用线程的,不用每次都创建线程。而创建线程的开销占比较大。
synchronized
synchronized修饰静态方法、对象方法、代码块
ReetrantLock
volatile
可见性指一个线程修改变量值后,其他线程中能够看到这个值。volatile虽然解决了可见性问题,但是不能控制并发
Atomics
原子操作,如AtomicInteger内部通过JNI的方式使用了硬件支持的CAS指令
wait、notify和notifyAll
wait是等待线程,notify是唤醒一个等待线程(并不能指定,随机),notifyAll是唤醒所有的等待线程。
CountDownLatch
java.util.concurrent包中的一个类,主要提供的机制是当多个线程都到达了预期状态或完成预期工作时触发事件,其他线程可以等待这个事件来触发自己后续的工作。
CyclicBarrier
循环屏障,可以协同多个线程,让多个线程在这个屏障前等待,知道所有线程都到达了这个屏障时,再一起继续执行后面的动作。
Semaphore
Semaphore是用于管理信号量的,构造时传入可供管理的信号量的数值。如果信号量只有一个,就退化到互斥锁了,如果多于一个,则主要用于控制并发数。
Exchanger
用于两个线程之间进行数据交换,线程会阻塞在exchange方法上,知道另外一个线程也到了同一个Exchanger的exchange方法时,二者进行交换。
Future和FutureTask
Future是一个接口,FutureTask是一个具体实现类
Future<HashMap> future = getDataFromRemote2();
......
HashMap data = (HashMap) future.get();
public Future<HashMap> getDataFromRemote2(){
return threadPool.submit(new Callable<HashMap>(){
public HashMap call()throws Exception{
return getDataFromRemote();
}
});
}
getDataFromRemote2还是使用率getDataFromRemote完成操作,并且用到了线程池:把任务加入线程池中,把Future对象返回出去。
并发容器
CopyOnWrite:更改容器时,把容器复制一份进行修改,用于读多写少
Concurrent:尽量保证读不加锁,并且修改时不影响读,所以比读写锁更高的并发性能
动态代理
继承InvocationHandler
反射
Java反射机制是指在运行状态,对于任意一个类,都能知道这个类所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
Class clazz = Object.getClass();
String className = clazz.getName();
Method[] methods = clazz.getDeclaredMethods();
Field[] fields = clazz.getDeclaredFields();
// 构建对象
Class.forName("ClassName").newInstance();
// 动态执行方法
Method method = clazz.getDeclaredMethod("add", int.class, int.class);
method.invoke(this, 1, 1);
// 动态操作方法
Field field = clazz.getDeclaredField("name");
field.set(this, "test");
网络通信的选择
BIO、NIO、AIO
第三方框架,MINA,Netty
调用发起==>寻址路由==>协议适配和序列化==>网络传输
==>反序列化 协议解析==>得到结果返回给调用方
1、确定服务框架的使用方式
2、服务调用者与服务提供者之间通信方式的选择
3、引入基于接口、方法、参数的路由
4、多机房场景,避免跨机房调用,一是在服务注册中心甄别,二是地址过滤
5、服务调用端的流控处理
6、序列化与反序列化处理,Java本身的序列化性能问题、跨语言问题、序列化后语言长度等
7、网络通信实现选择:BIO、NIO、AIO
8、支持多种异步服务调用方式:Oneway,Callback,Future,可靠异步
1、如何暴露远程服务
2、服务端对请求处理的流程
3、执行不同服务的线程池隔离
4、服务提供端的流控处理
两阶段提交:2PC
一致性理论:CAP、BASE
Paxos协议
考虑唯一性和连续性,UUID生成方式(IP、MAC、时间等)连续性不好
实现方案1:把ID集中放在一个地方进行管理,对每个Id序列独立管理,每台机器使用Id时都从这个Id生成器上取。
缺点:
实现方案2:舍掉Id生成器,把相关的逻辑放到需要生成Id的应用本身。每个生成器读取可用的Id,然后给应用使用,但是数据的Id并不是严格按照进入数据库顺序而增大的。
跨库Join
外键约束
外键约束比较难解决,不能完全依赖数据库本身来完成之前的功能了。
跨库查询的问题及解决
一张逻辑表,对应多个数据库的多张数据表,在一些场景下比较复杂,如排序、最大最小求和等函数处理、求平均值、非排序分页、排序后分页。
1、为用户提供专有API
2、通用的方式,数据层JDBC
3、基于ORM或类ORM接口的方式
直接基于JDBC驱动方式较好~
SQL解析==>规则处理==>SQL改写==>数据源选择==>SQL执行==>结果集返回合并处理
1、SQL解析阶段
2、规则处理阶段
3、为什么要改写SQL
分库分表后,同一个卖家的商品可能会分在多个库中,查询就要跨库。分布的不同数据库中的表的结构虽然一样,但是表的名字、索引名字未必一样,所以要修改SQL。
还有需要修改SQL的地方,如跨库计算平均值,必须修改SQL获取数量、总数后再进行计算。
4、如何选择数据源,读写分析
5、执行SQL和结果处理阶段,异常处理和判断
JMS,Java Message Service是Java EE中关于消息的规范,ActiveMQ等是对这个规范的实现。如果是小型系统直接使用JMS是一个经济的选择,在大型系统中不适合使用JMS。
消息发送一致性是指产生消息的业务动作与消息发送一致,即如果业务操作成功了,那么由这个操作产生的消息一定要发送出去。
1、发送消息给消息中间件
2、消息中间件入库消息
3、消息中间件返回结果
4、业务操作
5、发送业务操作结果给消息中间件
6、更改存储中消息状态
……
注:后面内容略,不方便摘要
参考:《大型网站系统与Java中间件实践》