通过Erlang构建TCP服务器

文章来源:公众号-智能化IT系统。


走进Erlang

试想一个场景,在一个炎热的夏天,一群员工进入了一个会议室准备开会,刚进会议室坐下,大家都满头大汗,需要等大家都擦完汗,才好开始会议。


会议室的中间有一盒抽纸,于是按照顺序,每一个人在这盒抽纸里抽出一张来擦汗,直到最后一个抽出,擦完,OK,会议开始。


在另外一个会议室,同样的情形,只是没有抽纸,在每个人的座位上摆了一张纸巾来擦汗,大家就可以一起擦汗了。


两个场景比较,自然是第二个能够早点开始会议。回到软件开发,第一个场景中,那盒抽纸就像我们的共享内存,是共享的就要加锁,如果不加锁,大家都来抢,就等着崩溃吧。加了锁,要按顺序一个个的来,自然效率就低了。最好的方式是从根源上解决,就是完全避开共享内存这个概念,改变思路去完成同样的目标。于是乎有了第二种方式。在这里每一个人相当于一个独立的进程,各自擦汗,如果纸巾不够,就问别的进程请求交互纸巾。


第二个场景是快,但是每个人的桌面都放一张纸巾,这也是工作,需要时间啊。不用担心,在有的技术中,已经为你做好了,你要做的只是转变思路,用另一种方式解决问题。这里和大家分享的Erlang,一种面向消息的编程语言,就能很好的解决这个问题。


小编知道,第二种场景说起来容易,但是我们很多时候业务复杂度决定了无法这样解决。小编不是说完全抛弃谁,使用谁,我们可以做的是结合一些特性去思考,可以独立出一些模块,一些适用于场景二的模块,以提升效率。现实中的大型系统往往不是独立用一种技术完成,而是划分好模块,多种技术共用。


案例介绍

本文介绍的案例是TCP网络服务器的构建,用最原始的方式(非OTP)。其功能很简单,通过网络TCP接口接收数据,按照指定的格式解析,并把数据存储至MongoDB。接口消息的格式有明确规定,每条消息之间用“|”分割,用“#”标识一群消息的结束,消息中的每个字段格式为“字段名:字段值”,每个字段之间以空格分割。


该案例原先是用C#开发,后期随着数据的增长,一天到了数十亿,一连串的问题接连而来。频繁崩溃,性能跟不上等等。当时单机处理的上限是一天6亿。


后来小编在休闲时间用erlang语言实现了同样的功能,最后测试的性能和稳定性有了不错的体现,因为当时时间有限,没有把他优化的很好,但是小编觉得是一个很有意思的使用,所以在此分享。


为什么考虑Erlang呢?(有Erlang经验者请直接跳过该部分)

当前早就进入了多核时代,一台服务器的CPU不可能只有一个,系统会否合理的利用是一个问题,大部分系统无法完全的利用,甚至只用了一个CPU,无疑这是对资源的浪费。而Erlang可以让程序员轻松的解决。


Erlang在国内尚未大范围的普及,大型的公司和系统能见到的非常少。但是价值不容小看。目前很流行的rabbitMQ ,就是用Erlang开发的。Erlang的特点可以用两个字形容之:土,快。


土:Erlang语言非常难以上手,甚至被评价为对程序员的眼睛最有杀伤力的语言,一开始编写会非常头疼。而且,其数据抽象能力实在不敢恭维,复杂的逻辑使用之会非常难受。


快:即其性能的优异表现了,使用Erlang可以充分的利用上多核资源,效率非常的快。那么为什么其有这种功力呢,有以下几点特征,供参考:

  1. 如果您是一个java或C++等常用语言的开发者,您可以认为Erlang没有变量,所有的变量只能赋值一次。

  2. Erlang没有线程的概念,Erlang由轻量级的进程组成。以充分利用多核CPU。

  3. Erlang没有共享内存的概念,所有的进程通过消息来传递信息。这一点就是和文章开端的描述是对应的。

  4. Erlang进程之间传递数据非常快


综合以上的特征,需要高并发,高稳定性,并且中间逻辑简单的系统,Erlang是非常不错的选择。


案例实现

该案例实现的关键点将在这里分享,包括关键的源码。

1. 建立TCP socket,并监听

{ok, Listen} = gen_tcp:listen(28801, [list, {packet, 0}, {reuseaddr, true}, {active, once}]),

这里注意消息接收模式是active once,即半阻塞模式。接收模式的不同对系统影响很大,具体使用需要谨慎。


2. 建立MongoDB的连接,并保存

MongoDB需要下载公共包,mongodb-erlang-refactor,然后编译,小编阅读了其中的代码,部分的MongoDB需要略做修改。

--建立连接

mongo_id_server:init([])

--存储数据

savemongo(List,Con,Cat) ->

 try

 [mongo:do(unsafe, master, Con, 'SOC_ToBeBuffered', fun() ->

mongo:insert(list_to_atom(Cat), Val)

end) || Val <- List]

 catch

_:_ ->  io:format("Error~p~n",[calendar:local_time()]),

exit("stop")

end.


3.在接收到数据后,开启进程处理

Erlang创建进程使用Spawn,Pset = spawn(fun tcpReceiverMulBloPool:dataprocess1/0),具体处理逻辑见下。


该案例中没有用Erlang OTP来开发,所以算是最原始的代码,现在把主要的逻辑实现的代码分享如下,因为时间急迫,注释没有加,感兴趣的可以通过扫描文章下方的二维码关注公众号:智能化IT系统,进一步的沟通了解。最终,在笔记本电脑上测试,每天处理的数据量可以达到10亿,笔记本的配置是4核,主频2.5,内存4G,大家可以按照案例的业务场景自己来测试。









公众号-智能化IT系统。每周都有技术文章推送,包括原创技术干货,以及技术工作的心得分享。扫描下方关注。

猜你喜欢

转载自blog.csdn.net/weixin_42488570/article/details/80773177