高并发通信技术的学习之路

           以前从未感觉到socket通信的重要性,上学的时候老师讲《计算机网络》的时候,只是用了这项技术实现了客户端和服务器通信的过程,当时肤浅的认为Socket技术,只是实验用的Demo,但是随着近年来对网络编程的深入理解,慢慢发现无论开发高并发的网站架构进行远程消息调用还是hadoop的底层进行集群间的通信,没有不用Socket进行通信的。掌握最基本的Socket是集群开发技术的基础。

         要想掌握开发服务器集群通信框架,其基本知识体系脉络如下图所示:


一、基础阶段需要掌握三项技术:

        多线程技术:多线程可能在javaWeb中,还不那么重要,程序员只需要遵循JSP和Servlet的规范开发实现业务功能就好,但是程序员要想向架构师发展,必须要掌握开发高并发应用系统的技术,哪个骨灰级的架构师不会多线程?嘿嘿,我以前好像听到过哪个不会游泳的教练,居然带出了世界冠军,这也是奇葩了。多线程的技术内容很多,而且初学者学起来也不那么容易,看着厚厚的一本开发辞典,都不知道所云。其实科学的学习方法是:先弄懂线程是做什么的,然后做几个“线程的两种实现方式”的小Demo,仔细体会开启线程执行业务逻辑和只在主线程运行业务逻辑的不同(注意:一定要慢慢揣摩,最好形成条件反射)这样就算线程入门了,接着是线程的同步、死锁和线程的通信。一定要理解开启多线程执行程序的痛点所在,那就是:多个线程在访问同一共享资源(变量或者对象)的时候,会争夺这个资源,必须让某一个线程访问共享资源的时候,不被其它线程所打扰,这样就要将访问这个共享资源的代码锁住,执行完成之后,释放这个锁资源,供其它线程任务访问(想想火车上的卫生间是怎么个访问方式,火车上的卫生间是共享资源,是不是一个人进去就锁住,方便完之后,开锁,走人,别人才能进去方便)。线程的通信中有一个经典的模型:生产者和消费者模型,在这个模型中有两种角色:生产者和消费者(其实线程任务可能就是这两个消费角色)。没有生产的话,不能消费,没有消费完成的话,不能生产,生产的是要消费的资源。这里存在一个制约因素,那就是资源,受资源的制约,线程任务要执行的话,必须交替执行,这也算控制线程执行的“潜规则”吧。

        进一步要深化还要学线程池和消息队列。哦,对了,在这之前,还要弄懂jdk1.5的Lock锁,一说起来又没完了,先挑主要的脉络阐述吧。线程池的技术很有用,就像连接池。只不过一个池子是放线程的,一个池子是放连接的。为什么要用线程池呢?因为一个服务器能力再强也不能无限制的开启线程,每开启一个线程就要耗费内存资源(每个线程都有独立的栈运行空间)、CPU资源(线程越多,CPU轮询照顾的线程就越多,总的负载就增加了)因此,事先在线程池子里初始化固定数量的线程,就这些线程(可能也不是绝对的,看使用什么样的线程池),每当有新的线程任务来了,都要从线程池里挑一个线程去跑这个任务,执行完了,再将线程任务再交还给池子,供下一个线程任务继续使用。这样就避免了服务器资源被无休止的胡乱开辟。如果池子里的线程都在忙着(前面的任务都没有运行完),新来的线程任务怎么办?最好的办法是排队(总得有个先来后到吧),因此先放到消息队列中存在,等有线程忙完了,再从消息队列中按顺序取出线程任务执行。线程池加消息队列是一个很好的伴侣,就像咖啡加伴侣的关系似的。淘宝即使再牛双十一的时候也不能同步处理用户的请求(并发量太大了),来不及处理的请求就先在消息队列中存着,等服务器中的线程池有返回的线程了,再来执行队列中的消息任务(客户请求)。

         IO流技术:初学者可以从字符流入手,掌握如何读写本地文本文件,但是会慢慢发现经常操作的并不仅仅是文本文件,可能是avi的或者jpg等类型的文件,所以还要掌握字节流。字节流是字符流的底层,以0/1字节代码的形式保存和传输文件,但是学到这并没有完,更深层的是对象的序列化。为什么要进行对象的序列化?因为我们new了一个对象后,是在内存的堆区保存的,当服务器关闭的时候,这个对象就消失了,能不能将内存中的这个“活生生”对象持久化到磁盘文件中呢,这就叫“序列化”,有了“序列化”这个概念就有“反序列化”的概念。就是将磁盘文件中的内容,还原到内存中,变成程序可访问的对象。在java中,将对象序列化到磁盘上(或者传输成字节)需要用到“ObjectOutputStream”类,反序列化重新加载到内存(或者传输还原成对象)使用“ObjectInputStream”类。这很重要哦!目前有很多第三方的对象的序列化反序列化框架,比较有名的如:谷歌的Protobuffer框架。这个框架被封装到了Netty中使用了,所以掌握序列化和反序列化基础后,最好研究一下这个框架。

        Socket通信技术:主要是通过调用jdk的java.net包中的Socket和ServerSocket类完成网络通信的过程技术,这两个类已经封装了很多底层通信的细节,对于程序员来说只是调用就好。写到这里,突然想起了上学的时候老师讲的计算机底层网络通信原理:在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能……,算了,太抽象了,就此打住。当客户端和服务器端建立了Socket通道后,客户端可以传输数据给服务器端数据,数据可能是字符串也可能是Person对象,什么,还能传对象?咋传啊?想想!能不能将对象序列化成字节,通过网络传输,然后在通信的另一端,将这些字节反序列化重构成对象(就像科幻片中传输人似的),哦,这里用到了流的技术,没错,还能用到线程呢!客户端和服务器端都要开启线程去执行通信的任务,为啥要开启线程啊?难不成服务器端啥也不干,就等着收发网络上的消息吧,让你一天天啥也不干就等着接孩子放学,你干吗?你不干的话,就老老实实的开启线程运行通信的任务吧。但是这种传统的通信模型中,有个弊端,就是伪异步通信,因为通信服务器建立后,要一直循环监听是否有客户端请求连接,这种循环监听是阻塞的(什么也干不了,就静静守候有人访问),虽然有客户端接入后,可以开启一个线程去执行,但是守候的过程是阻塞的,效率低,正因如此,从jdk1.4开始,推出了NIO(non-blockingIO,异步非阻塞IO)技术,jdk1.5的版本继续改进并发扬光大,到1.7的时候退出了更牛的AIO技术,AIO目前国内好像没有什么太权威的翻译资料。

二、提高阶段

        有了前面的技术做铺垫,可以掌握整个通信环节的细节并理解痛点所在了。那就是服务器需要一直守候客户端的接入,这种守候降低了服务器的性能,阻碍了服务器去做更多的事情。因此有了NIO技术,NIO技术其实是在解放服务器的生产力,让其在同等资源消耗的情况下执行更多的任务。因为NIO中有一个selector,这个selector负责调用Linux内核完成和客户端的通信,当客户端发送连接请求、建立连接、并发送数据的时候,都需要向服务器端这个selector上注册事件,成功注册后,由selector负责监听事件的结果,此时selector还可以对其它信道进行监听,是哪个内核组件要完成的任务,selector就告诉组件,由内核组件去执行,这么说来,selector所做的仅仅是轮询、监听、调度,负载并不大。虽然这项技术处理的数据并不比同步IO快,但是提高了服务器的负载,所以才能有广泛的应用。

        Netty框架说白了就是是在NIO的基础上进行了再次封装,完成异步通信。程序员的代码更加简洁,Netty框架技术应用非常广泛,以下摘自互联网:

        “阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信。”

        “除了 Dubbo 之外,淘宝的消息中间件 RocketMQ 的消息生产者和消息消费者之间,也采用 Netty 进行高性能、异步通信。”

        “游戏行业:无论是手游服务端、还是大型的网络游戏,Java语言得到了越来越广泛的应用。Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈,非常方便定制和开发私有协议栈。账号登陆服务器、地图服务器之间可以方便的通过 Netty 进行高性能的通信”

        “大数据领域:经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,默认采用 Netty 进行跨节点通信,它的 Netty Service 基于 Netty 框架二次封装实现,大数据计算往往采用多个计算节点和一个/N个汇总节点进行分布式部署,各节点之间存在海量的数据交换。由于 Netty 的综合性能是目前各个成熟 NIO 框架中最高的,因此,往往会被选中用作大数据各节点间的通信。”

        总之一句话:还得学啊!

三、终极阶段

        在这个阶段,我们要自己手动封装一个RPC框架,底层用的技术就是Netty,经历这个阶段整个任督二脉会被彻底打通,会有种通透的感觉,这个过程就像洗完桑拿,一个字:爽!但是经历的过程是艰难的,必须熟练的将J2EE各项技术组装到一起,而且还要明白为什么要用这些技术。以前在javaWeb阶段,可能线程、反射、动态代理、IO流都属于高级应用,而且关联性没那么大,但是要完成自定义的RPC的封装,就要将这些看似毫不相干的技术组合在一起,这种组合不是毫无道理的堆砌,而是像组装汽车似得,让每个零部件各司其职,发挥到极致。

        在服务器端,通过启动spring容器,初始化容器中对象,通过扫描业务方法上的自定义注解,将通信协议(其实就是接口)和实现对象存储到map集合中,并在spring实例化对象的时候,“偷偷的”开启Netty框架的服务器。

        在客户端,也遵循和服务器端共同的业务接口,调用的方法看似是客户端上的方法,其实是运行在远程服务器上的方法,其原理是:客户端在进行业务方法调用的时候,会用动态代理生成一个业务接口的代理对象,这个代理对象,其实拦截并改写了原有的方法,底层会与服务器端进行通信(Netty框架实现的),并向服务器端传递一个要调用方法的对象(包含方法描述、参数类型、实参对象等),服务器端接收到这个方法之后(肯定涉及到对象的序列化和反序列化),通过比对map中的key,找到真正的实现类(服务器开启的时候,spring已经实例化好了,并放到了map中)进行反射调用,调用成功后返回给客户端数据。

        这个过程是复杂的,不配图说的话,仅凭几句话,是无法深入理解的,本篇的目的,也仅仅是个学习路线导读,更多细节和原理,不在此文中阐述,会在后续的篇幅阐述清楚的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值