V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  GuuJiang  ›  全部回复第 13 页 / 共 20 页
回复总数  394
1 ... 5  6  7  8  9  10  11  12  13  14 ... 20  
2021-04-29 11:23:00 +08:00
回复了 James369 创建的主题 数学 概率中的 P(Ω)=1 应该怎么理解?
数学问题要从定义出发,不要从直觉出发,“独立”在概率论里只有一个明确的定义,那就是 P(A)P(B)=P(AB) ⇔ A 和 B 独立,因此把 P(Ω)=1 代进去,显然成立
2021-04-25 14:54:25 +08:00
回复了 Joker123456789 创建的主题 Java 问几个有关 NIO 的问题
我补充一下,确实存在同一个端口同时支持多种协议的情况,但这个和提问者想象的那个不是一回事,可以认为这个分发工作仍然是处在协议处理层,相当于一个复合的协议,具体实现时类似状态机,总之只需要记住一点,IO 部分是不关心上层协议的
2021-04-25 14:43:53 +08:00
回复了 Joker123456789 创建的主题 Java 问几个有关 NIO 的问题
恭喜你终于开始有点要逐步转到正确方向的迹象了,我来依次回答下你的每个问题,希望你哪天能正确认识到你在原来那个帖子里犯的错误是多么的基础和低级,并且向所有指出你问题的人一一道个歉
1. 不保证
2. 会
随时记住一点:TCP 是流式协议

关于分片的问题,任何分法都是有可能的,随时记住一点:TCP 是流式协议、TCP 是流式协议、TCP 是流式协议,对上层协议一无所知

关于协议解析的问题,在你选择了监听某个端口之前,这个端口期望收到的协议是已经提前确定了的,换句话说你需要自己负责按照预先的期望去解析协议,并不存在你想象中的“根据收到的内容自动判断协议”这种东西
@JasonLaw 以下描述不针对负载均衡类应用,而针对广义的抽象的 IO+业务处理
想象收银台(IO 模块)和厨房(业务模块)是两个独立的部门,对彼此是黑盒,收银台收到订单后将菜单交给厨房制作(读到数据后调用业务模块),厨房制作完成后将食品让收银台交给用户(业务处理完成后结果写回客户端),针对你的问题“当咖啡处理好了之后,是收银员将咖啡拿给顾客吗”,答案是是的,只有收银台才能和顾客打交道(持有连接)
那么每个部门内部可以分别独立地作出以下选择
收银台:
1. 一个顾客首次到来(建立连接)后指派一个专门的收银员(IO 线程)全程服务这个顾客直到他离开(断开连接),这就是阻塞式 IO,在这个过程中顾客完全可以点一样东西,思考几分钟,再点下一样,他思考的时候收银员就在等待(阻塞)
2. 一个收银员服务轮流服务所有顾客,并且只服务那些已经想好了(数据就绪)的顾客,如果顾客点了一样东西然后卡壳了,需要继续思考(就绪数据已读完),那么对不起,请你马上让开给下一个想好的顾客,等你想好了再来(下一轮 select/poll/epoll),当然收银员有职责记住每个顾客已点的部分食品,下一轮继续回来点的东西能够拼接上,最终形成完整订单(即上层协议的分割和解析,当然这一步严格来说到底算 IO 模块还是算业务模块暂时存疑,具体取决于期望 IO 模块的输出是 TCP 流还是上层协议内容),这就是非阻塞 IO
厨房:
1. 收银员递交订单后需要一直等着,直到拿到做完的东西后才能转身继续服务顾客,这就是同步调用
2. 收银员递交完订单后就转身继续服务顾客,厨房有需要的时候再把东西交给收银台,这就是异步调用

建立完这个模型后再来逐一进行整体的分析
1. 收银台采用阻塞式
优点
1) 和厨房打交道时既可以选择同步方式也可以选择异步方式,当然实际情况多采用同步,因为即使使用了异步,仍然会搭配 wait 等调用,本质上还是转化成同步了
2) 在描述收集整个订单(即上层协议解析)时可以站在收银员的角度,在写一些复杂协议时比较符合人的直觉,即当读完某一部分数据后可以根据需要主动地进行读操作,如果无数据可读就进入阻塞
缺点
1) 要么保证需要的收银员数量完全等于此刻所有处在点单过程中的顾客数量(即一客户端一线程),造成了大量的资源消耗(线程的内存等资源开销),甚至耗尽资源,要么给收银员人数设上限,从而导致了某些顾客无法分配到收银员(客户端连接被阻塞)
2) 即使收银员数量是充足的,但是收银员只有在收银台才能和顾客交互,所有的收银员只能轮流使用收银台(CPU 时间片),而收银台数量是有限的,且一个收银员用完收银台换下一个收银员用时必须要进行一些交接工作(线程上下文切换),那么当收银员数量多到一定程度时可能花在交接收银台上的时间比真正使用收银台的时间还要多
2. 收银台采用非阻塞式
优点
只需要一个或少数几个收银员就可以服务大量顾客
缺点
1) 通常来说和厨房打交道时就只能选异步方式了,当然这个没有任何强制,开发者也可以自行选择同步方式,但是如果业务是耗时操作,那带来的灾难就远大于阻塞式,因为这时不单阻塞了一个顾客,而是阻塞了所有顾客,这其实是一种典型的误用,我个人觉得真出现了这种情况,需要为之负责的是开发者自己,但是却有人觉得这是非阻塞式 IO 的缺陷,我是难以认同的
2) 协议解析、业务处理等部分不能再站在收银员的角度,即收银员无权主动要求读下一块数据,只能被动地接收数据,由顾客来驱动,这有点违反直觉,在写一些复杂协议的解析时需要人工改写为状态机,这也是有的人不习惯使用非阻塞 IO 的原因,无法扭转这个视角

回到你的问题来,我没深入研究过负载均衡类系统,所以不敢妄下结论,只能说说自己的猜测,即如果我自己来实现一个负载均衡系统的话会选择怎么做
还是老话,IO 和业务独立分析,IO 部分既然它们自己说了是非阻塞那就是非阻塞了,所以重点看业务部分,作为一个负载均衡系统,假设它的业务就是选择合适的后端->透传数据,从这个角度讲其实就是把两个反向的 IO 模块背靠背连接起来,并且以无状态的方式透传数据,那么可以认为它的业务是非常轻量的,不好用厨师的例子来做类比
@JasonLaw 其实关于非阻塞 IO 有一个非常普遍非常具有迷惑性的误解,就是把 IO 和业务处理这两者混为一谈,这就会产生两种错误的理解
其一:误以为非阻塞 IO 对于业务处理也是有帮助的,觉得只要用上了非阻塞 IO 就能用单线程或者少量线程处理大量的业务
其二:虽然没有上述的误解,但是会觉得反正到了业务处理阶段还是要多线程的,那 IO 处的单线程就是无意义的,从而觉得非阻塞 IO 没用

事实上这二者是完全独立的两个问题,你可以尝试下面的思考方式
第一步:先假设最简单的一种业务,就是一个黑洞,读到数据后直接丢弃,这样抛开业务处理的影响来单独分析 IO,在这个前提下对比阻塞式和非阻塞式的区别并正确理解非阻塞 IO 的意义
第二步:此时加入业务处理,在读到数据后进行异步处理(具体的方式可能是开线程、投递到线程池、发送到消息队列、异步 RPC 等等),总之具体方式不重要,重要的是异步处理,由于是异步处理,那么对于 IO 的影响可以认为和上述的直接丢弃几乎没区别,所以上一步中得到的各种结论仍然成立

在充分理解了这一切之后,再回过头来看你在标题中的问题,答案是肯定的,是否使用非阻塞 IO 只影响收银员(即 IO 线程)与顾客之间的关系,也就是实现了用少量收银员服务大量顾客,至于能否更快提供咖啡,这已经是业务处理阶段的问题了,与是否使用非阻塞模型没有关系,非阻塞只负责用户不会卡在收银这个环节,至于后面怎么提供服务,一个厨师还是多个厨师,这些都不是收银员需要关心的问题
@JasonLaw 我之所以有点犹豫到底要不要用这个来举例,就是因为很容易让人联想到日常生活中排队办事的场景,你可以理解为在另一个宇宙里,真正办事所需要的时间远远小于准备材料的时间,所以对于单独的每一个人来说,每当他准备好材料回去时,办事员都是空闲的,能够第一时间为他服务
那么有没有可能出现由于准备材料时间太短导致一个办事员服务不过来造成了堆积呢,当然是有可能的,这时候就需要增加办事员了,非阻塞 IO 说的是“少量”线程可以处理大量连接,并没有说过永远是单线程,实际应用中也需要选择合适的 IO 线程数来保证宏观上不会出现 IO 的堆积
其实说白了就是时分复用,利用“每个连接实际进行 IO 的时间远小于等待的时间”这一特性,把碎片时间充分利用起来
另外一个易混淆的概念是,这里提到的 IO 线程仅负责单一的一个职责,就是读写,至于读到业务数据后具体的业务处理到底是同步还是异步(实际情况通常都会是异步,除非是一些非常简单的例如 echo server 这种只用于演示级别),完全是另一个独立的问题,不在 IO 框架需要考虑的范围内,使用者根据自己的情况自行抉择
@JasonLaw 打个比方,你去某部门办事,分好几个阶段,每个阶段对方会告诉你回去准备一些新的材料,回来交了以后再回去准备下一阶段的材料,如此重复若干次

那么某部门有两种选择

plan A:每来一个新的用户,就给他指派一个办事员,这个办事员只负责这一个用户,当用户回去准备材料时这个办事员就干等着,这就是阻塞式,缺点显而易见,就是需要的办事员数量和用户数相关,大多数时候办事员无所事事
plan B:只需要一个办事员,当一个用户回去准备材料时继续服务下一个用户,这就是非阻塞

办事员 == IO 线程
办事 == 实际的 IO 读写
用户回去准备材料 == 无数据可读写

在 plan B 中我之所以故意不提"用户在部门排队",是为了避免引入不必要的误解,因为实际情况中网络设备相比起 CPU 是慢速设备,并且单个连接大部分情况下处于无数据传输状态,真正花在 IO 上的时间只占每个连接生命周期中的很小一部分,所以单个线程足以支撑大量的连接
2021-04-23 17:10:32 +08:00
回复了 ly90907 创建的主题 问与答 请问这个直线的方程怎么求
总结下,首先“其他所有点都在直线之下”这个只是必要非充分条件,因为上凸包里的每一段所在的直线都是满足这个条件的,所以问题转化成了求上凸包里某一段所在的直线,关键在于这一段应该满足什么条件,上面有不少人说一定过最高点的,还有说斜率最小的,这两个都是不对的,反例都已经在#8 的图里给出来了
其实 LZ 你最开始描述问题的方式已经非常接近真相了,想象下这个物理过程,线落下来接触了一个线段以后为什么还会旋转,就是因为中心落在了线段之外,所以所求线段需要满足的条件已经很明显了,就是让重心落在内部,由于假设是质量均匀直线,所以重心自然就是中点(PS:就算题目换成质量不均匀的线段也可以用同样的方法求解,只需要换成真正的重心即可)
另外,相比起传统的求上凸包方法,针对这个问题可以做一些优化,起点只需要从最高点开始,并且根据重心位置只用考虑它的一侧,另一侧直接丢弃,另外过程中只需要碰到第一个包含了重心的线段就可以停止了,因为重心只可能包含在一个线段内(在交点的情况忽略)
2021-04-23 10:37:17 +08:00
回复了 ly90907 创建的主题 问与答 请问这个直线的方程怎么求
其实就是先求上凸包,然后在构成上凸包的各线段中取包含 m 的,必然会有一段或两段,如果只有一段即为所求,如果有两段说明 m 是它们的交点,此时结果取决于定义
2021-04-23 10:03:53 +08:00
回复了 ly90907 创建的主题 问与答 请问这个直线的方程怎么求
@GuuJiang 这题要有解,两侧必须是有界的,换句话说你想求的其实不是“直线”,而是线段,否则的话就以你在#8 发的那个反例来说,最开始的两个点在图里看起来不满足,但是只要往右边延伸足够长度,又变成满足的了,继续往右延伸,再次变成不满足,且正解变到了它们的右侧,所以必须要先定义范围
2021-04-23 09:54:18 +08:00
回复了 ly90907 创建的主题 问与答 请问这个直线的方程怎么求
首先你得定义整个范围的左右边界,找出其中点 m,然后找到 y 最大的点,如果 m 在其左侧则逆时针旋转,在右侧则顺时针旋转,找到下一个碰到的点,如 m 介于两点的 x 之间,则中止,者两点就是所求,否则 m 必然位于两点同侧,那继续在哪侧就像哪侧旋转,重复直至 m 位于两点之间
2021-04-22 16:03:29 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
@Joker123456789 #43 指出的问题和我一直在说的是同一个问题,如果这个弯转不过来那你永远不可能写出正确的 NIO,你自己 debug 看一下是不是客户端没有发数据的时候也在读,然后读到 0 字节?
试图用协议层去驱动网络层就是导致你所有这些错误的根源,换句话说你可能用了下 NIO,但是以一种错误的姿势在使用,然后觉得是框架自身的问题,于是准备解决这个“问题”,你有没有想过如果真的存在如此低级的问题,别人就不会发现吗?难道所有人都在默默忍受?
正确认识自己确实不是一件容易的事,言尽于此,我也不会再继续回复了
2021-04-21 14:58:18 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
既然难得你听得进去,那我就再多啰嗦几句

你这个东西不是基于现在已经指出的问题修修补补就能完成的,只能推倒重写,因为你的根本思路就是反的
作为一个网络框架,它的职责是什么,是负责网络 IO,所以要做的事情就是用尽量少的资源管理尽量多的连接,负责网络数据的读写,对于上层协议应该一无所知,也就是只实现 TCP/UDP 层,有数据可读就读,读到的数据直接丢给上层协议,至于协议数据什么时候完整,如何划分边界,得到业务数据后怎么进行业务处理,用什么线程模型,这些通通都是上层协议的事情,已经和网络层无关了,而你现在的思路是由协议层来主导读,我是一个 http 协议,所以我主动调 read 方法直到读到一个完整的 http 请求,我是一个 websocket 协议,那我就定时读取数据,这种思路首先只适用于 bio,其次也不推荐,这也就是我一开始说的,从 bio 到 nio 并不只是一个简简单单的 configureBlocking(false)就完事了,而是思路的转变

你真的想要实现一个网络框架,那最基本的能力之一就是使用者能自由扩充应用层协议,你确实可以内置一部分常用协议,但这种扩充应该是无侵入性的,更大的意义在于让使用者能开箱即用,同时对扩展协议提供参考
再回过头来看你图里的后半部分,我为什么管它叫线程池的雏形,因为它确实跟线程池的基本原理有点类似,但是很可惜实现也是错误的,你的队列和线程是一一对应的,这就导致线程无法得到充分利用,会出现有的线程空闲,而有的任务又被堵塞得不到处理,你可以去看看正确的线程池实现方式,给你点提示,看看队列和线程之间到底是什么关系

综上所述,你的这个东西,网络层和协议层之间的关系是错的,对 NIO 的使用方式是错的,线程池实现也是错的,并且这些错误都属于非常基础非常低级但是在初学者身上又很容易出现的,说实话,我发完第一条回复后好奇点了下你的发帖历史,我就已经后悔了,因为我看得出来你对自己的开源项目是非常自信的,但是很可惜你实际写出来的东西和你的自我认知之间存在非常巨大的差距,这样说也许很残忍,但是如果你继续在这条路上越走越偏直到哪天摔下来的时候,在隔壁帖子里对你表示过支持的所有人都有责任
2021-04-20 23:06:41 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
accept 阻塞的是 connect 阶段,我说的是客户端 connect 后没有发数据,假设你总共有 n 个线程,现在有 n 个客户端连接上但是只发了一个完整 http 请求的一部分数据,这时候第 n+1 个以后的客户端是不是永远被卡住得不到处理了?你这个完全就是同时抛弃了 bio 和 nio 的优势,结合了二者的劣势,bio 虽说占用线程数多点吧,好歹还能继续处理后续的客户端,并且没数据可读时是在阻塞,而你这个本质上还是一个线程服务一个连接,并且没数据时也在空转,而且线程数还是有上限的,所以连最原始的 bio 都不如
2021-04-20 22:51:11 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
“selector 里有正在处理的 channel”这个没有任何问题啊,你可能误解了 selector 的含义,每一次 select 操作返回的是处于就绪状态的 channel,所谓的就绪对于 read 操作来说就是有数据可读,那你读就行啊,内层 while 是错误的,你还停留在 bio 的思想,有数据可读不代表能完整地读到一个上层协议所需要的结构,你能读到多少读多少,至于什么时候得到上层所需要的完整数据这又是另一个话题了,暂时按下不表,所以一个 channel 多次出现在 select 的结果中是再正常不过的行为
你画个时序图,模拟下多个连接同时存在,然后以随机的间隔发送下一个数据片段,然后对比下在这种情况下你的程序和 nio 的区别
要知道,一个 channel 不是你想读就能读到数据的,而 select 的存在就是保证了你只在真正有数据可读时才去读
2021-04-20 22:09:46 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
@Joker123456789
第一句话就错了,你再好好看看 tcp 里 accept 到底是在哪个阶段
至于你说的 select 重复处理的问题,在看到代码之前不好下结论,不过我猜十有八九是没有调 remove,因为这个是个很典型的错误
早期版本的 jdk 安装后有个 samples.zip ,里面就有一个用 nio 实现的 echo server 的例子,建议你多读几遍,而实际应用到真实项目中时,只需要把直接 reply 的那一行代码换成投递到工作线程池即可,这也是业界通用的做法
我在 16 楼的那句话可能刺伤了你,对此我道歉,但是等你弄明白所有这些问题之后相信你能体会当时的心情,换作你也会无话可说的
2021-04-20 19:04:35 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
估计你今天情绪上很难接受,为此还专门另开一个贴抱怨下,以你现在对网络 IO 的认知,我很难跟你一一讲明白你的错误在哪里,但是明眼人都看得出来,真心建议你静下心来好好找点资料看看网络基础,各种多路复用方案到底在解决什么问题,等你看完了如果还是不服气,欢迎你回来对线
你可以先带着这个问题去看
“当客户端没有发数据时,你的程序在干什么,而其他正确实现了的网络框架,包括( BIO 、NIO 、netty 等)又在干什么”

到时候你就会明白,你这个东西称之为“一个异步非阻塞的网络编程包”,是多么的荒谬
2021-04-20 13:19:19 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
你开心就好
2021-04-20 13:11:27 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
@Joker123456789
这个包结构本身就是另一个槽点了,证明了你对 tcp/http/websocket 三者关系的理解都是混乱的,这个暂且不表
你只需要回答一个问题,“在一个循环里依次对一堆 channel 进行 read”这个做法,相比起被你否定掉的 NIO,优势在哪里?
2021-04-20 12:09:27 +08:00
回复了 Joker123456789 创建的主题 推广 网络编程包 - Magician 的原理 与 使用
折腾的精神可嘉,但是。。。容我给估计还沉浸在造完轮子的喜悦中的你泼点冷水,相信我,再过几年,你会希望自己没有留下过这段黑历史的
首先,你所谓的 Selector 的“缺点”,证明你并没有真正理解 Selector 的意义,并不是说 configureBlocking(false)就可以称为 NIO 了,恰恰相反,Selector 才是 NIO 的灵魂,让我们来看下你是怎么做的吧,用一个每 100ms 执行的定时任务,循环对每一个 channel 进行 read,这点实在是槽点太多无从吐起,建议你自己先去看看 select 、epoll 、iocp 等的基本原理,如果看完还有疑问的话欢迎回来讨论
至于“如果业务比较耗时,会造成整个循环被堵住”,这个跟是否用 Selector 根本没关系,而是使用者自己应该保证不在 IO 线程里处理耗时业务,再看看你是怎么解决这个所谓的“问题”的,我猜这个 BlockingQueue 应该是你最引以为傲的一部分了吧,恭喜你,你重新发明了线程池……的雏形版
总的来说,这个东西作为一个关于 NIO 以及线程池的概念验证的课后实验还是可以的,但是实际应用的话,价值为零
1 ... 5  6  7  8  9  10  11  12  13  14 ... 20  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2651 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 20ms · UTC 06:23 · PVG 14:23 · LAX 22:23 · JFK 01:23
Developed with CodeLauncher
♥ Do have faith in what you're doing.