V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  kuanat  ›  全部回复第 7 页 / 共 9 页
回复总数  180
1  2  3  4  5  6  7  8  9  
164 天前
回复了 hkhk366 创建的主题 程序员 everything 索引原理探讨
其实你这个思路偏了啊,当你意识到 5ms 连文件 IO 都完成不了的时候,应该考虑这不是个算法问题而是个策略问题。



官方页面上已经说得很清楚了,因为关键内容不多,我就直接复制过来:

来自 https://www.voidtools.com/support/everything/searching/

Content Searching

Warning: content searching is extremely slow.

File content is not indexed.

Please combine content: functions with other filters for the best performance.

Content search functions:
content:<text> Search file content using the associated iFilter. If no iFilter exists UTF-8 content is used.
ansicontent:<text> File contents are treated as ANSI text.
utf8content:<text> File contents are treated as UTF-8 text.
utf16content:<text> File contents are treated as UTF-16 (Unicode) text.
utf16becontent:<text> File contents are treated as UTF-16 (Big Endian) text.

另外 https://www.voidtools.com/forum/viewtopic.php?t=9793 这里有更详细的说明。

Everything will keep content in memory.
Content indexing is intended for indexing user documents only.
I do not recommend indexing over 1GB of text.
For the best performance, set an include only folder.

By default, Everything will index the following file types for content:
*.doc;*.docx;*.pdf;*.txt;*.xls;*.xlsx



这 5ms 就做了一个内存数据库的检索,而且数量级并不是你想象中的 100 万。你在没有验证的情况下,就认为它扫描了文件,所以思路跑偏了。即使没有一点逆向基础,也有很简单的办法知道一个程序运行的时候打开读取了哪些文件,如果你验证了这一点,估计已经找到答案了。

所有的“索引”类算法,核心思想并不在“算”上面,而在于将原始数据结构以一种易于被检索的数据格式存储。换句话说,之所以索引可以加速搜索,是因为提前生成了要查询的结果。

Windows 自身有个索引服务,但是绝大多数文件格式都是二进制的,在不了解它的文件结构的情况下,是没有办法进行文本搜索的。所以 Windows 提供了一个 IFilter 机制,由文件格式的创建者,主动暴露可以被索引的信息。然后再通过后台服务对于相关的文件格式进行索引,首次全盘索引完成之后,只有文件内容发生变化,才会触发索引更新。几大操作系统的索引都是相同的机制。

Everything 的说明就是这样一个意思,它会在内存索引少数路径下的特定格式的文件,为其建立文本内容索引。后续的搜索只是内存搜索引。至于全文匹配到底用了什么算法,是不是有指令集优化,是另外的事情,到了这个层面,只要思路对,算法是不是最优化已经不重要了。( Everything 是 C 写的,但是没有什么混淆加密,稍微逆向一下大概就能弄清楚。)
165 天前
回复了 happmaoo 创建的主题 Linux 终于可以登陆网页微信了
165 天前
回复了 happmaoo 创建的主题 Linux 终于可以登陆网页微信了
@pakro888 #56

我又重新看了一下,实际上只有登录过程有校验,实际使用的时候是没有的。所以只需要匹配 /cgi-bin/mmwebxxxx-bin/webxxxxnewloginpage 这个路径做修改就可以了。

估计腾讯现在比较后悔,当初就不该做 web 版本,做了 web 版不说,失误就失误在又套壳 web 做了 UOS 版,这下彻底摆脱不掉了。结果就是现在所有的微信机器人都是走的 UOS 这个协议。

腾讯不想让人用 web 版,就从账号上做限制,明明有 web 版就是不给用。但是腾讯没法限制 UOS 这个客户端,好在用 UOS 的人还是比较少的,只是这样还是管不住机器人。顺便一说,UOS 这个版本还是 MIT 协议发布的。

UOS 版本是有发行版校验的,这也就是 extspam 这个字段的来源。

所以去年的时候,有人把 UOS 版本重新打包给其他发行版用,就把 /etc/lsb-release 给改了。之前 V2EX 上有过讨论帖子 https://v2ex.com/t/858659

研究一下就会发现,这个特殊的 extspam 竟然是静态的……这个失误就比较离谱了,也就是说根本起不到防滥用的作用。所以现在用简单的网页扩展,修改一下请求头就能一直用。

估计是当初负责 UOS 版本的人没有什么网络开发经验,缺少基本的校验要动态化的常识。另外本地防逆向的水平也很弱,生成逻辑都在一个 so 库里,只做了字符串混淆,混淆还是最简单的 xor 。
@zhwguest #4

可能之前没表达清楚,这里应该不存在冲突的。


比如说你一开始用的 /old/dep 依赖,那么在 go.mod 里面就会有

require github.com/old/dep v1.2.3

只要你不升级依赖,那么整个项目一直会用 /old/dep v1.2.3

然后你 fork 了 /new/dep 出来,并且给 /new/dep 打了版本 v1.2.3
这个时候增加一句

replace github.com/old/dep v1.2.3 => github.com/new/dep v1.2.3

就可以了。

假如 /old/dep 更新过后,你发现没有必要用 /new/dep 了,那就把上面的删掉,然后更新依赖到比如说 /new/dep v1.2.4 ,不管是 go get -u 还是手动修改 go.mod ,最终 go.mod 都会是

require github.com/old/dep v1.2.4

假如你还是要用 /new/dep ,不管你是继续在 /new/dep 上修改,还是合并了 /old/dep 的上游更新,然后打了 v1.2.4 的版本,只需要修改 replace 这一句变成

replace github.com/old/dep v1.2.3 => github.com/new/dep v1.2.4

但是 require 那一句还保持

require github.com/old/dep v1.2.3



也就是说,只要你不主动升级 /old/dep 的版本,那就只需要修改 replace 的目标版本。至于 /new/dep 怎么打版本 tag 是随意的。

之前说保持版本号一致只是应对两种场景,一种是 /new/dep 作为临时开发测试分支,有可能被 /old/dep 合并,当 /old/dep 更新之后又会回到 /old/dep 上面去。另一种是 /new/dep 是因为某些原因不可能被 /old/dep 合并,但是修改可以通过 patch 的方式自动化,上游 /old/dep 发布一个版本,下游 /new/dep 就发布一个对应的修改版。



默认情况下,github 的 fork 功能,是不带 tag 的,所以你可以在 fork 里面重新任意标记版本。假如你不想处理 tag 相关的事情,也可以完全不理会。这种情况下,replace 后面改成 /new/dep 就行了,go.sum 会用 v0.0.0-timestamp-commit_hash 的方式来唯一确定。
166 天前
回复了 happmaoo 创建的主题 Linux 终于可以登陆网页微信了
@BaiLinfeng #41

前面是手机回复的,开电脑看了一下。

登录链接用:

https://wx.qq.com/?&lang=zh_CN&target=t

然后用个可以改 header 的扩展,匹配一下 https://wx.qq.com/* 的请求,添加两个字段

client-version = 2.0.0

extspam =
Go8FCIkFEokFCggwMDAwMDAwMRAGGvAESySibk50w5Wb3uTl2c2h64jVVrV7gNs06GFlWplHQbY/5FfiO++1yH4ykCyNPWKXmco+wfQzK5R98D3so7rJ5LmGFvBLjGceleySrc3SOf2Pc1gVehzJgODeS0lDL3/I/0S2SSE98YgKleq6Uqx6ndTy9yaL9qFxJL7eiA/R3SEfTaW1SBoSITIu+EEkXff+Pv8NHOk7N57rcGk1w0ZzRrQDkXTOXFN2iHYIzAAZPIOY45Lsh+A4slpgnDiaOvRtlQYCt97nmPLuTipOJ8Qc5pM7ZsOsAPPrCQL7nK0I7aPrFDF0q4ziUUKettzW8MrAaiVfmbD1/VkmLNVqqZVvBCtRblXb5FHmtS8FxnqCzYP4WFvz3T0TcrOqwLX1M/DQvcHaGGw0B0y4bZMs7lVScGBFxMj3vbFi2SRKbKhaitxHfYHAOAa0X7/MSS0RNAjdwoyGHeOepXOKY+h3iHeqCvgOH6LOifdHf/1aaZNwSkGotYnYScW8Yx63LnSwba7+hESrtPa/huRmB9KWvMCKbDThL/nne14hnL277EDCSocPu3rOSYjuB9gKSOdVmWsj9Dxb/iZIe+S6AiG29Esm+/eUacSba0k8wn5HhHg9d4tIcixrxveflc8vi2/wNQGVFNsGO6tB5WF0xf/plngOvQ1/ivGV/C1Qpdhzznh0ExAVJ6dwzNg7qIEBaw+BzTJTUuRcPk92Sn6QDn2Pu3mpONaEumacjW4w6ipPnPw+g2TfywJjeEcpSZaP4Q3YV5HG8D6UjWA4GSkBKculWpdCMadx0usMomsSS/74QgpYqcPkmamB4nVv1JxczYITIqItIKjD35IGKAUwAA==

估计搜一下“微信 extspam”就能找到,基于 UOS 的都应该是这个方法。
166 天前
回复了 happmaoo 创建的主题 Linux 终于可以登陆网页微信了
我一直在用模拟 UOS 那个 electron 版本的方式,大概一年了没有问题。

登录请求抓下来印象是三个特殊 header 字段,然后登录链接加个 &target=t 就行。
166 天前
回复了 abcfreedom 创建的主题 Android note12turbo 刷哪个系统比较靠谱
@justincnn #11

厉害的是做 rom 的人啊,其他机型上 xda 看一下,基本上找到一个就能全找到了。

我这是更新一批开发测试机,特意挑的刷机最方便的 n12t ,其他机型可能没这么多选择。
167 天前
回复了 abcfreedom 创建的主题 Android note12turbo 刷哪个系统比较靠谱
t.me/Poco_F5Official

这里面基本上有所有的第三方。
167 天前
回复了 suqiuluck 创建的主题 程序员 有没有自己电脑上跑大模型的大佬啊
@shuiguomayi #18

是 RTX 4060 Mobile ,笔记本上用的,8GB 显存。说的是开发验证这种需求,你需要训练一个模型,先在本地写个小规模的验证程序,然后放到服务器上去跑大数据集。并不是常见的用模型来推理,推理这个需求还是 12GB 起步吧,8GB 只能跑一些简化或者降低精度的模型,速度也不太理想。

每一代 60 显卡都会有个显存略大的版本,可以理解为 nvidia 推广 cuda 生态用的,因为这个级别上加显存对游戏性能几乎没什么影响。说移动版 4060 是因为它相对 3060 加了显存,而且能耗比很好,市面上的笔记本能做到 5000 块,比起台式机性价比可以的。
169 天前
回复了 0xTSO 创建的主题 Android 安卓 app 同步功能实现原理
先需要弄清楚几个概念:

- jetpack compose 重点是 ui 和本地数据状态的同步
- 安卓 app 的同步指的是多客户端以及服务器之间数据副本的同步
- 断网可用这个特性一般用 local first 来描述

你的问题是 local first 实现方式,和用不用 jetpack compose 与是不是安卓没有关系。

这个问题的答案和业务逻辑强相关,没有普适的模式。楼上提到的 OT/CRDT 适合于多人同时编辑文档的场景,并不适合 local first 意义上的同步。

另外你举例的 app 可能在多端同步实现上没有想象中那么好。
169 天前
回复了 kuanat 创建的主题 Go 编程语言 分享一些 Go 在全栈开发中的经验
@ila #36

你的问题比较笼统,建议开个帖子详细描述一下,这里可能会有不少人愿意解答。

我不确定你说的配置是哪方面的。如果是说 gocv 依赖这些,既然目标平台是 windows ,那我建议 MSYS2 搞一套 MinGW 环境,按官方指引构建 opencv 然后直接在 windows 平台开发。

如果有跨平台需求,比如 linux 开发部署到 windows 这样,和一般交叉编译没什么区别。需要 linux 安装 MinGW 交叉工具链,然后指定 GOOS/GOARCH/CGO 编译就行。主要是 CC/CXX 指定交叉工具链,CGO_CPPFLAGS/CGO_LDFLAGS 指定 opencv 依赖。这个方法对于 pc 开发 arm 平台目标也一样。

也可以考虑预编译好的 opencv 二进制库,动态链接。

如果你说的是 windows 调用摄像头获得视频流,这个事情比较复杂,opencv 虽然有非常简单的抽象,但远远不够用。

主要看摄像头设备是不是足够标准,一般的 ip camera 走网络数据最简单;如果能把自己注册成摄像头的要走 avicap32 这套 Win32 API ,然后用消息机制来通信;如果设备比较特殊,那就只能硬怼厂家 sdk 了。

理论上 WinRT 的 MediaCaputre API 也能用,只是我不确定有没有人做相关的 bindings 。这个思路有一些曲线救国的方式,比如打包 Electron ,然后调用 Electron API 。它内部就是调用的系统 API ,但不需要你自己封装转换了。

剩下应该都是 opencv 自己就能解决的。建议有条件还是 C# 开发吧,估计很可能还是要做界面的,全用 go 写可能不如 C# 效率高。当然如果目标平台是 linux 那用 go 就简单多了,走 v4l2 能解决大部分问题。
用你方法一的思路,replace 是支持版本号映射的,比如

require github.com/old/dep v1.2.3 => github.com/new/dep v1.2.4

另外也可以 github.com/new/dep@dev 指定分支。

你说的方法二有个问题:就像你说的,如果它包含子模块,这些子模块还是会引用 /old/dep ,那 fork 出来需要修改的地方就非常多。

具体怎么做取决于 /new/dep 这个 fork 的定位。仅仅打个补丁,需要定期合并上游 /old/dep 那就用方法一。如果是要大刀阔斧另起炉灶,那就方法二吧。

关于版本号,我的建议是保持一致。就是你在 v1.2.3 上的改版,就用 v1.2.3 发布,特别是你仅仅要做个小改动的时候。不过这个事情不重要,你清楚对应的版本就可以了,而且几乎做不到一一对应。

另外这里要注意 go sum 机制,改版 /new/dep 发布之后 github.com/new/dep v1.2.3 校验信息会被 google 缓存。这个时候如果对 /new/dep 做了多次修改,就只能升版本或者换分支了。
@LemonLeon #36

我做了个简单推理,可能需要你实际测试一下。

既然加 cpu 和内存不影响 rps 说明大概率瓶颈应该是在 IO 了。我大致看了一下处理流程,涉及到 IO 的操作就是日志相关的。

https://github.com/songquanpeng/one-api/blob/366b82128f89a328f096da6951cbafebb6b0060f/controller/relay-text.go#L410

这段代码主要功能是在请求结束后结算用量,然后记录。记录的内容本地文件有一份,数据库有一份。本地那一份肯定比数据库快,所以不考虑了。

数据库 IO 涉及三个操作:

https://imgur.com/a/OAPZXsl

如果是默认配置( common.LogConsumeEnabled=true ),RecordConsumeLog 会产生一次插入操作;

默认 BATCH_UPDATE_ENABLED=false ,那么 UpdateUserUsedQuotaAndRequestCount 和 UpdateChannelUsedQuota 各自都会产生一次查找更新操作。

等于说每个请求都伴随三次数据库写操作。默认 SQL_MAX_OPEN_CONNS = 1000 ,理论并发在 333 左右,和实测比较接近。

当然前提是这三个数据库操作能够在 1s 之内完成(大概率是的)。这个事情不太容易确定,因为 locust 记录的是完成请求的总时间,并不知道中继请求和数据库操作的时间占比。

如果做个粗略估计的话,观察第响应时间的中位数,在测试刚刚开始的时候是 2s ,之后略低于 3s 。猜测当数据库 IO 达到瓶颈的时候,平均要多花接近 1s 等待。

要验证这个猜想,简单的方式是调整 BATCH_UPDATE_ENABLED 启用批量更新,单请求的数据库写入会降至 1 次。或者再提高 SQL_MAX_OPEN_CONNS 的数值。同时可以查看本地日志和 sql 服务器的日志,辅助确认 sql 服务器不是瓶颈。

假如上面的方法无效,那就要考虑 pprof 之类的方式来定位瓶颈了。
信息有点少,最好能把部署环境、压测方式都贴上来,这样便于分析。

主要是 4c8G -> 6c8G 就能提升并发,这个现象很难解释。表面上只能判定说,内存不是瓶颈,与 GC 或者内存泄漏无关。

而且 2000/5000 rpm 的尺度是分钟,每秒也就是 30~80 的水平。这个数值过于低了,一般是业务逻辑造成的,而不是技术栈造成的。

另外关于 #19 讨论的锁,相关代码是 oneapi 向外发起链接的逻辑。虽然 net/transport 内部维护的连接池确实是有互斥锁的,但这里 race condition 非常弱。最差的情况等价于是退化到 openai 服务器不支持 keepalive ,每次都需要建立连接。而且每秒不到 100 的并发,基本不可能触发死锁。
我大概试了一下网站,没感觉查询有什么特别慢的地方。如果要定位慢查询的问题,最好贴上语句和配置信息什么的看一下。

在不改变技术框架的情况下,优化方案就是加索引。再具体一点,这个索引是要能通过用户输入的关键词,快速定位到特定字段包含关键词的数据集。

这里涉及到两个主要的技术点,一个是对文本字段进行分词,提取出关键词。另一个是通过关键词反查数据集,这个要构建的索引叫倒排索引( inverted index )。剩下的事情就是更改查询逻辑,前端查询请求走索引,后端直接取索引筛选出的数据集。(这个索引可以做到后端应用程序里,也可以保存到数据库里)

考虑到楼主非专业开发,我是建议手动做上面的事情。用其他的成熟方案,需要学习的东西其实更多,原理理解不到位的话很可能达不到效果,而且没有必要。


如果是我来做这个事情的话,可能会采取完全不同的方案,楼主可以参考一下。

因为这个数据库基本上是只读不写的,写场景基本只发生在数据修正或者批量更新的时候。所以优化的核心思路就是数据集(数据库)为查询优化。

虽然楼主没有透露这个数据库的表结构,考虑到万智牌大概就几万张,而数据集大概有 10 万条,这个数据库应该是冗余很低,符合范式的设计方式。

这一类数据库是为写操作和存储优化的,冗余很低,提取数据需要对数据表做组合查询。为读操作和查询优化的数据库正好相反,冗余很高。既然最终查询结果是卡牌集合,那就以某种数据结构来记录每张卡牌包含的所有信息,一次读取即可获得完整数据,省去了数据组合的过程。

即便有大量的冗余,内存中以这种数据结构存储卡牌信息,可能不会超过 4GB ,即使再翻个倍也不是问题。那对于查询系统来说,有没有 sql 数据库都不重要了。

当然 sql 对于数据录入还是很有意义的,一方面可能本身数据来源导入比较方便,另一方面修改低冗余数据远比高冗余数据方便。所以这里需要编写个脚本,在每次 sql 数据库更新之后,重新生成一份通用格式的(比如 csv 或者 json )的数据集,后端应用程序反序列化之后即可获得上述的内存数据库。

上面这些如果用技术语言描述,就是 sql 转 kv/nosql ,没有多少技术难度,就是个经验问题。

剩下的事情都差不多,分词、建索引。主要是一些经验层面的优化,这里就随便列举几个。

比如请求是可以缓存的,一般用在高频查询和分页上。再比如具体到这个查询场景说,一个请求如果需要返回几百个数据,要么不合理要么没必要。(人一般不需要这么多,返回太多都是在给爬虫服务了。)
楼主这个是非常简单的 cwd 的问题。

也许是现在大家写代码都过于依赖自动化工具,基础的东西反倒不知道了。不是说楼主,这种情况非常常见。我举几个例子:

- 即便大厂的项目,搞不清外部依赖的比比皆是,特别是 python 类的机器学习项目,鲜有能做好工程化的

- 各种所谓的跨平台工具,处理不好路径的正反斜杠,即使用心处理了,很大概率也是硬编码而不是用系统级的 path 方法

- 外表绚丽的 i18n 结果内部实现竟然不是 rune

反正大家都是草台班子……
174 天前
回复了 yzqdm 创建的主题 Java 请教一下 Java 写物联网项目监控设备上下线方案
我觉得这个事情是个工程问题而非技术问题。


“监控设备掉线是用定时任务来做的”:
说明设备本身就没有心跳机制,只有数据上报功能。工程上这个数据上报行为如果是固定间隔的就能当心跳来用,如果不是,那上面的判定逻辑是没问题的。
后者情况下,不管设备是不是真下线,超过一定时间没有数据上报都认为设备已经下线。对于判定在线的设备,你也只能说截至最后一条消息的时刻,该设备还在线。再换句话,判断是否在线这个事情不需要那么准确。


“隔一段时大批量更改设备状态,并且实时性不高”:
理论上监控系统的实时性不可能强于数据上报的最小间隔,只要定时任务周期和数据上报间隔保持一致就可以了。


“mqtt 那边就推送这个设备掉线的信息到 java 程序”:
主动轮训变事件监听,很标准的做法。技术上说,数据上报消息处理流程不仅写数据库,同时转发给 java 程序让它自己维护一份在线状态表就可以了。


“EMQX 的遗嘱消息存在内存”:
这个消息丢了很重要吗,进程重启很频繁吗?


以我的经验来说,物联网业务里对于单设备状态监控的实时性和准确性要求不高的,你能根据这个数据,给出判定“设备损坏”或者“区域网络失效”之类的经验参数才是真正的目的。这个事情可以容忍非常高的 false positive ,没有必要纠结微观细节。
我不写 java 但我经常需要阅读 java 代码,这个真的挺好用的。
177 天前
回复了 tracymcladdy 创建的主题 程序员 用脚本帮朋友抢个专家号居然没抢到
@tracymcladdy #72

如果服务方不愿意改变抢模式,这个事情就会演变成脚本大战。

脚本大战一旦有人不讲武德,大量暴力发请求,结果就会变成另一个意义上的抽签。最终所有人的最优策略就是加资源提高中签率,尽管大家都知道这么做边际收益很低,但不得不这么做。

另一方面你可以看到提供这种服务的主要模式都变成了“代抢”,甚至承诺抢不到加倍退款。对于商家来说,投入达到某个阈值之后中签率是比较稳定的,成本也是稳定的,只要代抢客户足够多就能赚。因为他们可以保证总有账号可以抢到,但没法保证某个特定账号能抢到。
177 天前
回复了 tracymcladdy 创建的主题 程序员 用脚本帮朋友抢个专家号居然没抢到
鉴于楼上说的手动 tcp 方案太扯淡了,本来想简单吐槽一句,结果没忍住还是把这个事情说清楚吧。

我这么说是有理论和实践经验做支撑的,tcp 手动发包几乎没有统计学上的有效性。这类需求前些年几乎是外包市场仅次于网站开发的业务,钱多门槛低。我自己做得很少,基本上是给别人指导一下,不做的原因主要是业务容易擦边,再就是现在多数大平台的都从抢的逻辑变成抽签逻辑了。

先解释一下为什么有人认为“手动发包”是个有意义的方案。

因为按照一般的“抢”的逻辑,以某个时刻为分界线,在此时刻之后某个特定资源就生效了。在此时刻之前到达服务器的请求是无效的,你希望你的请求恰好在这个时刻之后到达服务器。

那么我们有能力确定服务器的时间吗?绝大多数时间不能。(少数情况下可以通过触发服务器错误等等获得一个相对时间差,然后加上网络延迟做一个粗略估计。)即使能够确定服务器时间,实际应用里也没有太大意义。“上线”这个事情 99% 不是自动化的而是人为的。

因此所有人的策略都做出调整了,用大量链接去覆盖资源上线的可能时段。只要保证资源上线之后有一部分请求以尽量低的延迟到达服务器即可。

这个时候 tcp 手动发包好像就体现出优势了?提前建立好(大量)连接,然后看情况开始发数据。注意这里的潜台词是,网站卡的原因在于 tcp 连接数不够,而且新建立连接会多 1RTT 的延时。

但是这个思路是不是有点眼熟?如果很多人都是这样做,或者某个人控制机器人程序这样做,在服务器看来,客户端只连接不发数据,这连接还不会主动关闭,那它是不是就是 DoS 攻击?(这一类攻击有个专门的名字叫 Slowloris )

这里设定个非常简单也非常普遍的模型,来看看这个做法有没有可行性。或者说“连接数不够”这个假设是不是真的符合现实。

就非常普通的单服务器 nginx 做 ssl offloading 和反代,配置近似默认,upstream 大概是某种动态语言的服务端。网络请求在闲时 10 QPS 忙时 10000 QPS 这样。(意思就是,规模不大,用户量有限。服务器网络带宽不是瓶颈,nginx 也大概率不是瓶颈,瓶颈在后端应用,挂号服务基本上就是这个水平。)

这里套不套应用层防火墙差别非常大。一旦套一层 cf 之类的服务,手动 tcp 发包就没意义了。因为 cf 之类的服务会缓存请求然后回源,不可能在这个层面预先抢占 tcp 连接。

如果退一步讲,服务器裸跑。这个时候你通过手动 tcp 发包,那能够获得的时间窗口有多长?

在 tcp 层面,理论上 keepalive 是无限长的。但是实际上以国内的网络环境来说,经过 NAT 之后中间设备的映射缓存大概就是 150 秒这个水平。实践里可以找公网服务器、代理来绕开这个限制。提这个限制的意思是,利用 keepalive 机制几乎不需要你在程序层面上做很大的改动。

但是在 nginx 服务器层面上,时间窗口就小得多。这类服务器默认对慢速攻击有应对,tcp 连接建立后,headers/body 的默认容忍延迟基本在 60 秒。(但凡看过一点点所谓的优化经验,这个值应该不会超过 10 秒)

那就又回到之前的问题上了,你本来打算预先抢占 tcp 连接,结果时间窗口太小,当所有人的策略都是提前覆盖可能的时段,你的连接会被淹没在海量连接里面。

这里有个非常极端的情况,这个 tcp 手动发包,利用 tcp 的机制,强行把 headers 分成多个包发送,然后每个发送间隔卡在目标服务器的限制之内。(这里就不提这个程序的复杂性了,要么魔改底层网络库,要么纯手写然后一直处理到应用层协议)

但问题是 nginx 这类服务器的反代,一样会先缓存请求,在收到完整请求之后,再转发给 upstream 服务器。也就是说,你抢占了本地到 nginx 的连接,而没有能力抢占 nginx 的 upstream 的连接。

现在回到核心的问题上,服务器在高并发的时候变卡的核心原因是什么?

核心是服务端应用要对特定资源加锁,业务逻辑在高并发的情况下只能等。

由于一般网站的瓶颈都在后端应用服务器上,nginx 能承载的客户端连接数远大于 nginx 到 upstream 的连接数。

当应用服务器不需要处理锁事务的时候,平均响应可以做到很低。但是一旦遇到加锁资源的时候,这个响应时间会成倍增加,理论 QPS 就大幅下降。而此时所有人会更加疯狂地反复刷新,这时候 nginx 到 upstream 的连接就会长期占满。

所以体感上就是,网站先变卡,因为 nginx 在等 upstream 释放出新的连接。等 nginx 撑不住了才会 502 错误。而从客户端来说,没有任何手段能保证你优先获得到 upstream 的连接。

所以说 tcp 手动发包这个事情……别挣扎了。
1  2  3  4  5  6  7  8  9  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5485 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 44ms · UTC 02:29 · PVG 10:29 · LAX 19:29 · JFK 22:29
Developed with CodeLauncher
♥ Do have faith in what you're doing.