分布式系统是由多个节点组成的系统,每个节点就是个计算机机器,用户访问网站感知不到是多个机器组成的系统。当用户访问某个网站的时候,给浏览器发出一个请求,背后是一个大型分布式系统为我们提供服务,有的提供存储数据,有的提供计算,有的负责处理请求,最终他们相互调节把数据返回给浏览器。
随着业务需求越来越大,系统越来越繁杂,这时候处理业务,或者用户信息可能就会单独放在一个独立的服务器上,因为单个计算机有上线瓶颈,另外出于稳定性和可用性的考虑,分布式系统是优选。
前面我们说了分布式系统是由单个节点组成,而单个节点就是单个计算机,我们看一下计算机的组成要素。
计算机主要分为五大组件:内存,外存,输入设备,输出设备,CPU。
输入设备和输出设备很好理解,就是鼠标键盘和显示器。在计算器断电时候,计算器,内存里的数据重启会消息,而外存里的数据会持久化。CPU则负责两个主要功能运算器和控制器。
对计算机有了基础的了解之后,我们可以看看计算机是如何工作的,我们写的代码是在计算机进程里的线程运行的,在我们刚接触写代码时,都是在单线程下运行代码,当时对这些也不是很了解,就是写一段代码执行就完事。相对于单线程,多线程就复杂的多。这里的多线程指的是单个进程下的多线程。
线程模式
如果多线程之间互相不通信,也就是不会访问共有的对象进行修改,每个线程之间互不干扰独立运行,这样是没有什么问题的。
但如果多线程之间修改同一个共有对象,为了保证我们同一份数据访问的正确性,我们可以通过加互斥锁来解决,如果对于频繁访问读写的数据,互斥锁太影响效率,我们也可用读写锁来解决。当然,我们可以用很多线程安全的容器,对于这些线程安全的容器与简单的互斥锁相比较,效果更好。
还会存在通过事件协同的多线程模式,这种是当两个线程之间需要相互协调,比如A、B两个线程,当A线程继续运行下去,必须等到B线程里面吧某个状态改变才可以继续让A运行,那么这个场景下就需要线程之间完成协调。
当T1代码:A.lock();B.lock()。T2代码B.lock();A.lock()。
这种情况下就有机会发生死锁,当A获取锁之后,获取B的锁失败,一直未吧A的锁释放,导致T2获取的B锁也一直因为获取不到A锁也释放不了,最终形成死锁。这时候其实可以改成:
当T1代码:A.lock();B.lock()。T2代码A.lock();B.lock()。
这时候就不会发生死锁了,因为获取锁的顺序发生了变化,他们都必须先获取到A锁才可以运行。
进程模式
多线程和进程有很多相似之处,也有不同。进程里面是包含线程的,多个进程之间的内存空间是独立的,而进程里面的线程内存是共享的,因此进程之间共享数据有所不同。此外,进程间通信协调互,以及通过一些事件通知等待互斥的释放方面,也会和线程不一样。这些在不同的平台所支持的方式不同。
多进程对于单进程线多线程方式来说,资源控制会更容易实现,此外,多进程中的单个进程如果出了问题,不会造成整体不可用。这两点应该数据多进程的特点。当然多进程之间也可以共享对象数据,这时候也会比多线程复杂点,会有序列化和反序列化的开销。
而我们分布式系统可以看做把单机的多进程变成了多机的多进程。单机到多机的变化就是,原来单机OS上支持的功能需要另外去实现。当然也有好处,就是一个单机宕机不会造成整体不可用。
单线程和单进程多线程程序遇到机器故障、OS问题会造成整个功能不可用。对于多进程系统,如果遇到机器故障或者OS问题也会造成整体不可用,但如果是多进程中某个进程问题,那么可能保持一部分功能正常使用。
而如果在分布式多进程的情况下,也就是多系统,遇到某些机故障、OS问题,我们都有机会保证整体功能正常使用。
我们处在高速发展的网络时代,无论有限网络还是无线网络,无论是LAN、MAN还是WAN等众多节点联系在一起的方式,都是需要通过通信来解决。
我们常用的则是TCP/IP模式,那么我们使用Socket套接字进行网络通信开发的时候,有哪些方法实现呢?三种实现方法:BIO、NIO、AIO。
BIO
全称是BlockingIO,采用阻塞方式来实现通信。也就是一个socket用一个线程来处理。在建立连接、读数据、写数据的操作时,都会阻塞。这样做的好处就是简单,但主要问题就是只有一个线程能处理一个socket,如果是server端,在支持高并发时,就要更多的线程来完成工作。
NIO
全称NonblockingIO,基于事件驱动思想,顾名思义也就是没有阻塞的方式来实现通信。采用Reactor模式,这也是java服务端采用较多的一种方式。这个好处最大就是不需要一个socket分配一个线程,一个线程可以处理多个socket套接字工作。
Reactor会管理所有的event handler,并把出现的时间交给handler去处理。
AIO
全称AsynchronousIO,也就是异步IO。采用Proactor模式,AIO在进行读写操作时候,只需要调用read、write方法,并把需要的传入到completionHandler处理器中,在动作完成后,会调用completion处理器。
AIO是在java7中引入的饿,与NIO最大的区别就是NIO在通知时可以进行相关操作,而AIO在通知时候表示相关操作已完成。
BIO/NIO/AIO这几种模式并不需要客户端与服务端保持一致。此外,在实践中有些场景也会使用UDP,但还是TCP使用的更广泛。