基于javanio的可伸缩非阻塞tcp客户机-服务器设计:向客户机发送大响应的推荐方法

5uzkadbs  于 2021-07-07  发布在  Java
关注(0)|答案(1)|浏览(428)

我尝试用java实现一个高度可伸缩的服务器,用于以下用例
客户端以命令参数的形式向服务器发送请求
服务器可以向大文本数据(大小为6-8gb,相当于客户端ram)发送一个大小不等的响应,从几个字符(10字节)到大文本数据
在这些情况下,发送响应的适当方式应该是什么。我需要支持多个并发客户端。有人能给我介绍一个参考/示例实现吗。

kr98yfug

kr98yfug1#

有许多现有的解决方案在幕后使用nio,比如netty和grizzly之类的框架。它们是边缘火箭科学。正确对待nio是极其复杂的。使用这些框架。
java中的高度可扩展服务器
nio通常比较慢。nio的主要好处是,您可以手动滚动缓冲区,而不是锁定堆栈大小的线程模型(您可以在启动java时为所有堆栈配置堆栈大小) java -Xss1m 例如,对于1mb堆栈(也就是说,100个线程只需要1gb的内存用于堆栈,更不用说堆了)。
通常,在你的箱子里扔一个大的木棍会有效很多个数量级。
当以下所有事情都是真的时,nio就会发光:
你需要同时处理很多连接,但不是很多(因为很多连接现在不能由一台计算机来处理,不管你写的有多高效;然后解决方案是分片和分布式设计。 google.com 不会在一台计算机上运行,也永远不会,twitter也不会——这是一个同时连接需求超过nio可用范围的例子)。
nio意味着你可以在任何时候被迫“调换”。例如,在解析命令的过程中。这意味着您需要缓冲区来存储状态。需要存储的状态必须很小,否则nio不是很有用。
需要执行的任务不需要受到cpu的限制。如果是的话,nio只会让事情变得更慢。
需要做的任务不必被限制。例如,如果作为处理连接工作的一部分,您需要连接到数据库,除非您千方百计以非阻塞的方式找到这样做的方法,否则就不能使用nio。nio要求永远不要因为任何原因阻塞线程。这意味着地狱。你可以查一下。
性能上的好处是如此重要,它值得让你的应用程序的开发复杂化一个数量级来适应它。
这只留下了一个很小的窗口,完全nio是可取的。而且,有意义的异域应用程序的小窗口很快就会变得更小,因为projectloom有望在java17中发布预览版(最快可能是9个月左右),进一步减少nio带来的任何收益。
nio的一般设置如下:
首先,制作x个线程,其中x大约是现有内核数量的两倍。
然后,定义一组作业处理程序;每个处理程序都是一个对象,用于维护要执行的作业的状态,在您的示例中,一个表示等待传入的套接字连接,对于每个打开的连接,您还将拥有一个作业对象。
每个线程尝试同时管理所有作业。这是通过为每个作业创建异步通道来实现的。对于每个通道,您注册什么是“有趣的”(意味着您可以在不等待i/o的情况下进行工作)。然后,您进入睡眠状态,要求java在任何工作中发生任何有趣的事情时唤醒您的线程。然后与其他线程进行代理,其中一个线程将处理任何需要执行的特定任务,并执行该任务。
“我感兴趣”的状态是连续波动的。
例如,在开始时,任何连接都对“if data come in”感兴趣。但是当数据传入时,可能只有“hel”通过(例如,完整的命令是“hello\n”),您必须记住这一点,然后直接返回循环。一旦输入完整的hello命令,您就要写“hey there!”回来了,但是当在那个频道呼叫发送时,到目前为止只发送了“hey t”。然后你想停止对“数据可用”感兴趣,开始对“你现在就可以发送数据”感兴趣。你不想在那之前感兴趣,因为那时你的线程被不断唤醒(你可以发送!你可以发送!你可以发送!你可以把它送过去,导致你的球迷旋转起来,一切都变得像糖蜜缓慢。
一旦你发了全套的嘿!,你想不再对“可以发送”感兴趣,而开始对“可用数据”感兴趣。
处理线程和频道上的兴趣标志之间的代理是非常复杂的。你也可以从这个小道消息中得到乐趣:
如果你阻止任何东西,你的应用程序是坏的,但你不会知道。这将是非常低效和缓慢,但只有当多个连接开始进来。这真的很难测试,而且很容易落入陷阱。例如,不会发生异常。你也会死在地狱里。
这让我们回到:这是火箭科学。使用netty或grizzly。

编辑:您的特定用例

根据对这个问题的评论,您希望编写一个服务器来处理“范围100000000-100002000”的请求,这将被解释为:从某个大文件向我发送指定索引范围的字节。
在那种情况下,我认为nio对你没用。但可能是。系统的设计大致如下:
您必须使用nio2来异步访问磁盘。这里有一个教程介绍这一点。如果都在记忆里,那就简单多了。
即使您使用异步nio2文件访问,如果底层磁盘不是很快也不是完全随机访问的话,您也只是在旋转轮子,不会使事情变得更快。
如果你对一个碟片这么做,小子。糟糕的表演。
问题是,传入的请求需要一个连续的字节块。旋转磁盘可以做到这一点,比从分散在磁盘上的一堆小位置获取一堆字节要快得多:读取整个磁盘需要磁头四处跳跃,这非常慢。
这里最大的风险是,通过对磁盘访问进行积极的nio2化,您可以有效地将程序转换为“在磁盘上跳跃”模式,并且性能会变得更差,而不是更好。
更好的选择可能很简单,比如:
你有一个线程池。可能低至50个线程,可能高达1000个。
这还远远不足以让虚拟机大汗淋漓。
你的 socketServer.accept() call将在其“accept a socket,make the handler object,hand it to the pool to process”循环中等待池中的空闲线程,因此,如果因为没有可用线程而需要等待,accept调用将被有效地阻塞一段时间。那很好。
有效的话,你的应用程序将同时处理第一个x(x=池大小)呼叫,然后让电话响一点,可以说,直到一个完成。无论如何,这可能是您想要的—积极的并行磁盘访问并不是读取磁盘的最快方式。
如果你真的想知道,你必须写两次这个应用程序,并比较两者。

相关问题