JAVA集群分布式和网络设计的浅析(转载!)

转载自:http://blog.csdn.net/xieyuooo/article/details/6371207


     在java中常用于实现多线程的方法有3中:
     1、继承于Thread类,重写run方法
     2、实现Runable接口,实现run方法
     3、实现Callable接口,实现call方法(具有返回值)

     至于调用的方法多种多样,可以直接用start启动,也可以使用java.util.concurrent.Executors来创建线程池来完成。


     如果系统是一个WEB应用,建议尽量不要在web应用中做多线程,因为这部分线程控制主要是由web容器控制的,如果在非得必要的情况下建立,尽量建立较少,或者尽量将可以不太频繁调度的线程使用完后直接释放掉,哪怕下次重建也无所谓。
     如果多线程序是独立运行的,专门用于接受和和处理一些消息,那么最少有一个线程是不断探测的(有很多程序会先休眠一点时间,如:TimeUnit.MINUTES.sleep(SLEEP_TIME)此方法是按照毫秒级进行休眠一段时间),这类程序,最好将线程设置为后台线程(setDaemon(true),一定要在线程调用run之前调用该方法有效),后台线程和非后台线程最大的区别在于:后台线程在所有非后台线程死掉后,后台线程自动会被杀死和回收;而正如写其他的多线程程序,即使main方法完成(主线程),但是在main中申请的子线程没有完成,程序仍然不会结束。
     为什么类似web应用的前端会把多线程早就处理好呢,一个是因为为了减少程序和bug,另外一个就是要写好多线程的确不容易,这样会使得程序员去关心更多没有必要关心的东西,也需要程序员拥有很高的水准,但是如果要成为好的程序员就一定要懂多线程。 
      如果一个系统专门用于时钟处理、触发器处理,这个系统可能是分布式的,那么在一个系统内部应该如何编写呢?另外多线程中编写的过程中要注意是:多线程现在的运行状况是怎样的?这个线程不能死掉,如果死掉了怎么发现?发现到了如何处理(自动、人工、难道重启)?

如何解决死锁

1.java默认线程的状态有哪些?
     NEW :刚刚创建的线程,什么也没有做,也就是还没有使用start命令启动的线程。
     BLOCKED :阻塞,也就是线程此时由于锁或者某些网络原因造成阻塞,有焊住的迹象。
     WAITING:等待锁状态,它在等待对一个资源的notify,即资源的一个锁机会,这个状态一般和一个静态资源绑定,并在使用中有synchronzed关键字的包装,当使用obj.wait()方法时,当前线程就会等待obj对象上的一个notify方法,这个对象可能是this,如果是this的话那么在方法体上面一般就会有一个synchronized关键字。
     TIME_WAITDE:基于时间的等待,当线程使用了sleep命令后,就会处于时间等待状态,时间到的时候,恢复到running状态。
     RUNNING:运行状态,即线程正在处于运行之中。
     TERMINATED:线程已经完成,此时线程的isAlive()返回为false。
     一般默认的线程状态就是这些,部分容器或者框架会把线程的状态等进行进一步的封装操作,线程的名称和状态的内容会有很多的变化,不过只要找好对应的原理也不会脱离于这个本质。

2.线程一般在什么情况下会死掉?
     锁,相互交叉派对,最终导致死锁;可能是程序中自己导致,编写共享缓存以及自定义的一部分脱离于容器的线程池管理这里就需要注意了;还有就是有可能是分布式的一些共享文件或者分布式数据库的锁导致。
     网络阻塞,网络不怕没有,也不怕太快,就怕时快时慢,当去网络通信调用内容的时候(包括数据库交互一般也是通过网络的),就很容易产生焊住的现象,也就是假死,此时很难判定线程到底是怎么了,除非有提前的监控预案。

3. 多线程焊住或者死掉如何捕获和处理?
     这需要从设计层面下手,对于后来java提供的线程池也可以比较放心的使用,但是对于很多非常复杂的线程管理,需要自己来设计管理。
     首先多线程自己死掉了它肯定不知道,那么如何才能知道它的现状了?提出两种现实思路,一个是有一个跟班的人,而另一种是它上面有一个领导带一群人出来玩,下面人丢了一个它肯定要去找。
     先看看第一种思路,跟班那个假如他平时什么也不做,就跟在前者后面,当发现前者倒下,自己马上跟上去顶替工作,这也是系统架构上经常采用的冗余主从切换,可能一主多从;而云计算也是在基础上的进一步做的异地分流切换和资源动态调度,但是这样冗余太大,成本将会非常巨大。
     再看看第二种服务,上面有一个老大,它过一小会看看这帮小弟在做什么,是不是遇到了困难,那里忙它在上面动态调配这资源。但是要是多了,它就忙不过来了,因为资源的分配是需要提前明白下面资源的细节的;那么再细想下去,可以用多个老大,每个老大带领一个小团队,团队之间可以资源调配,但是团队内部可以由老大自己掌控一切,老大的上面还有个老总它只关心于老大再做什么,而不需要关心小弟们的行为,这样大家的事情就平均起来了;那么问题了又出来了,小弟的问题是可以透明的看到了,要是那个老大出事了甚至于老总出事了怎么办?此时结合第一种思想,此时就只需要再老总下面挂一个跟班的,集合两种模式的特征,也就是小弟不需要配跟班的,这样就节约了很大的成本(因为叶子节点的数量是最多的),而上面的节点需要有跟班,如果想最大程度节约成本,只需要让主节点配置一个或者多个跟班就可以,但是这样恢复成本就上去了,因为恢复信息需要逐层找到内容才行,一般没有必要在这个基础上再进一步去节约成本。

如何设计一个具有可扩展性的多线程处理器

     如果不使用任何开源技术,要做一个多线程处理的框架应该从何下手,在上面分析的基础上,我们一般会将一个专门处理多线程的系统至少分解为主次二层,也就是主线程引导多个运行线程去处理问题;好了,此时需要解决以下几个问题:


     a)多个线程处理的内容是类似的,如何控制并发征用数据或者说降低并发热点的粒度。
     方法1:hash散列思想将会是优秀的原则,按照数据特征进行分解数据框,每个框的数据规则按照一种hash规则分布,hash散列对于编程容易遍历,而且计算速度非常迅速,几乎可以忽略掉定位分组的时间,但结构扩展过程比较麻烦,但在多线程设计中一般不需要考虑这个问题。
     方法2:range分布,range范围分布数据是提前让管理者知道数据的大致分布情况,并按照一种较为平均的规则交给下面的运作线程去去处理自己范围内的数据,相互之间的数据也是没有任何交叉的,其扩展性较好,可以任意扩展,如果分解的数量不受控制的话,分解过多,会造成定位范围比较慢一点,但是多线程设计中也一般不用考虑这个问题,因为程序是由自己编写的。
     方法3:位图分布,即数据具有位图规则,一般是状态,这种数据按照位图分布后,线程可以设立为位图个数,找到自己的位图段数据即可做操作,而不需要做进一步的更新,但是往往位图数量有限,而需要处理的数据量很大,一个线程处理一个位图下的所有数据也往往力不从心,若多个线程处理一个位图又会重蹈覆辙。

     三种方法各自有优缺点,所以往往采用组合模式来将这个系统的架构达到比较完美的状态,当然没有完美的东西,只有最适应于当前应用环境的架构,所以设计前需要考虑很多预见性问题;关于这种数据分布更多的用于架构,但是架构的基础也来源于程序设计思想,两者思想都是一致的。


     b)线程死掉如何发现(以及处理):
     管理线程除有运行动作的线程外,还有1~N跟班,个数根据实际情况决定,至少要有一个当管理线程挂掉可以马上顶替工作,另外还有应当有一个线程去定期检测线程的运行情况,由于它只负责这件事情,所以很简单,而且这一组中的线程谁死掉都可以相互替换工作并重启新的线程去替代,这个检测的周期不用太快、也不用太慢,只要应用可以接受就可以,因为挂掉些东西,应用阻塞一点时间是非常正常的事情。
     发现线程有阻塞现象,在执行中找到了某种以外而阻塞,导致的原因我们上面已经分析过,解决的方法一般是在探测几次(这个次数一般是基于配置的)后发现都是处于阻塞状态,就基本可以认为它是错误的了;错误的情况此时需要给该线程执行一个interrupt()方法,此时线程内部的执行会自动的抛出一个异常,也就是理解执行线程的内容的时候尤其是带有网络操作的时候需要带上一个try catch,执行部分都在try中,当出现假死等现状的时候,外部探测到使用一个interrupt()方法,运行程序就会跳转到catch之中,这个里面就不存在征用资源的问题,而快速的将自己的需要回滚的内容执行完,并认为线程执行结束,相应的资源也会得到释放,而使用stop方法之所以现在不推荐是因为它不会释放资源,会导致很多的问题。

     另外写代码之前如果涉及到一些网络操作,一定要对你所使用的网络交互程序有很多的深入认识,如socket交互时,一般情况下如果对方由于网络原因(一般是有IP当时端口不对或者网段的协议不通)导致在启动连接对方时,socket连接对方好几分钟后才会显示是超时连接,这是默认的,所以你需要提前设置一个启动连接超时保证网络是可以通信的,再进行执行(注意socket里面还有一个超时是连接后不断的时间,前者为连接之前设置的一个启动连接超时时间,一般这个时间很短,一般是2秒就很长了,因为2秒都连接不上这个网络就基本连接不上了,而后者是运行,有些交互可能长达几小时也有可能,但类似这种交互建议采用异步交互,以保证稳定运行)。


     C)如果启动和管理二级管理线程组:
    上面有一个主线程来控制启动和关闭,这里可以将这些线程在start前的setDaemon(true),那么该线程将会被设立为后台线程,所谓后台线程就是当主线程执行完毕释放资源后,被主线程创建的这些线程将会自动释放资源并死掉,如果一个线程被设置为后台线程,若在其run方法内部创建的其他子线程,将会自动被创建为后台线程(如果在构造方法中创建则不是这样)。
     管理线程也可以像二级线程一样来管理子节点,只要你的程序不怕写得够复杂,虽然需要使用非常好的代码来编写,并且需要通过很复杂的测试才会稳定运行,但是一旦成功,这个框架将会是非常漂亮和稳定,而且也是高可用的。

多线程在多主机下的扩展-集群

     其实在上面以及提及了一些分布式的知识,也可以叫做数据的分区知识(在网络环境利用PC实现类似于同一个主机上的分区模式,基本就可以称为数据是分布式存储的)。
     但是这里提到的集群和这个有一些区别,可以说分布式中包含了集群的概念,但是一般集群的概念也有很多的区别,并且要分app集群和数据库集群。
     集群一般是指同一个机组下多个节点(同一台机器也可以部署多个节点),这些节点几乎去完成同样的事情,或者说类似的事情,这就和多线程扯在一起了,多线程也正是如此,对比来看就是多线程调度在多主机群组下的实现,所以参照app集群来说,一般有一个管理节点,它几乎干很少的事情,因为我们不想让它挂掉,因为他虽然干的事情少,但是却非常重要,一个是从它那里可以得到每一个节点的一些应用部署和配置,以及状态等等信息;另外是代理节点或者叫做分发节点,它几乎在管理节点的控制之下只做分发的,当然要保证session一致性。
     集群在多线程中的另一个体现就是挂掉一台,其余的可以顶替,而不会导致全盘死掉;而集群组相当于一个大的线程组,相关牵制管理,也相互可以失败切换,而多个业务会或者多种工具项会划分为不同的集群组,这就类似于设计线程中的三层线程模式的中多组线程组的模式,每组线程组内部都有自己个性化的属性和共享属性。
     而面对数据库集群,就相对比app集群要复杂,app在垂直扩展时几乎只会受到分发节点能力的限制,而这部分是可以调整的,所以它在垂直扩展的过程中非常方便,而数据库集群则不一样,它必须保证事务一致性,并实现事务级别切换和一定程度上的网格计算能力,中间比较复杂的也在内存这块,因为它的数据读入到内存中要将多个主机的内存配置得像一个内存一样(通过心跳完成),而且需要得到动态扩展的能力,这也是数据库集群下扩展性收到限制发展的一个原因之一。
     App难道没有和数据库一样的困难吗?有,但是粒度相对较小,app集群一般不需要考虑事务,因为一个用户的session一般在不出现宕机的情况下,是不会出现复制要求的,而是一直会访问指定的一台机器,所以它们之间几乎不需要通信;而耦合的粒度在于应用本身的设计,有部分应用系统会自己写代码将一些内容初始化注入到内存中,或者注入到app本地的一个文件中作为文件缓存;这样当这些数据发生改变时他们先改数据库,再修改内存或者通知内存失效;数据库由于集群使用心跳连接,所以保持一致性,而app这边的数据由于只修改掉了自身的内存相关信息,没有修改掉其他机器的内存信息,所以必然导致访问其他数据的机器上的内容是不一致的;至于这部分的解决方案,根据实际项目而定,有通过通信完成的,也有通过共享缓冲区完成(但这种方式又回到共享池资源征用产生的锁了),也有通过其他方式完成。
     大型系统架构最终数据分布,集中式管理,分布式存储计算,业务级别横向切割,同业务下app垂直分隔,数据级别散列+range+位图分布结构,异地分流容灾,待命机组和资源调配的整合,这一切的基础都来源于多线程的设计思想架构在分布式机组上的实现。

WEB应用的多线程以及长连接原理

      WEB应用中会对一些特殊的业务服务做特殊的服务器定制,类似一些高并发访问系统甚至于专门用于瞬间高并发的系统(很多时候系统不怕高并发,而是怕瞬间高并发),但他们的访问往往比较简单,主要用于事务的处理以及数据的一致性保障,他们在数据的处理上要求在数据库端也不允许有太大的计算量,计算一般在app中去完成,数据库一般只是做存、取、事务一致性动作,这类一般属于特殊的OLTP系统;还有大分类一类是属于并发量不算太大,但每次处理的数据和计算往往比较多,一把说的是OLAP类的系统,而数据的来源一般是OLTP,OLAP每次处理的数据量可能会非常大,一般在类型收集和统计上进行数据dump,需要将OLTP中的数据按照某种业务规则方面查询和检索的方法提取出来组织为有效信息存储在另一个地方,这个地方有可能还是数据库,但也有可能不是(数据库的计算能力虽然是数据上最强的但是它在实际应用中它是最慢的一种东西,因为数据库更多的是需要保证很多事务一致性和锁机制问题,以及一些中间解析和优化等等产生的开销是非常大的,而且应用程序与之交互过程是需要通过网络完成,所以很多数据在实际的应用中并不一定非要用数据库)。
      这类系统如:高并发访问中,而且需要将同一个平台下的数据让客户端较为实时的得到内容,这类网站不太可能一次获取非常多的内容到客户端再访问,而肯定是通过很多异步交互过程来完成的,下面简单说下这个异步交互。
      Web异步交互的所有框架基础都是ajax,其余的类似框架都是在这个基础上完成的;那么此时ajax应该如何来控制交互才能得到几乎接近于实时的内容呢?难道通过客户端不断去刷新相同的URL?那要是客户端非常多,类似于一个大型网站,可能服务器端很快会宕机,除非用比正常情况高出很多倍的服务器成本去做,而且更多的服务器可能在架构上也需要改造才能发挥出他们的性能(因为在服务器的架构上,1 + 1永远是小于2的性能,更多的服务器在开销)。
      想到的另一种办法就是从服务器端向客户端推送数据,那么问题是如何推送,这类操作是基于一种长连接机制完成,长连接即不断开的连接,客户端采用ajax与后端通信时,后端的反馈信息只要未曾断开就可视为一种长连接的机制;很多是通过socket与服务器端通信,也可以使用ajax,不过ajax需要在其上面做很多的处理才行。
      服务器端也是必须使用对应的策略,现在较多的是javaNIO,相对BIO性能要低一点,但是也是很不错的,它在获取到用户请求时并不是马上为用户请求分配线程去处理,而是将请求进行排队,而排队的过程可以去控制粒度,而线程也将作为线程池的队列进行分配处理,也就是服务器端对客户端的请求是异步响应(注意这里不是ajax单纯的异步交互,而是服务器端对请求的异步响应),它对很多请求的响应并非及时,当发生数据变化时,服务器第一时间通过请求列表获取到客户端session列表并与之输出内容,类似于服务器端主动推送数据向客户端;而异步交互的好处是服务器端并不会为每一个客户端分配或新申请一个线程,这样会导致高并发时引起的资源分配不过来导致的内存溢出现象;解决了上述两个问题后,另外还有一个问题需要解决的是,当一个线程在处理一个请求任务时,由于线程处理一个任务完成前除非死掉或者焊住,否则是不会断开下来的,这个是肯定的(可以将一些大任务切割为一些小任务,线程就处理的速度就会快很多了),但是有一个问题是,服务器端的这个线程可能很快处理好了需要处理的数据内容并向客户端推送,但是客户端由于各类网络通信问题,导致迟迟不能接受完成,此时该线程也会被占用些不必要的时间,那么是否在这个中间需要进一步做一层断点传送的缓存呢?缓存不仅仅是属于在断点数据需要时取代应用服务器的内容,异步断点向客户端输出信息,同时将应用服务器处理的时间几乎全部集中在数据和业务处理,而不是输出网络上的很多占用。


你可能感兴趣的:(JAVA集群分布式和网络设计的浅析(转载!))