如何快速向文件中写入 1 亿个 ip?

2022-04-09 15:49:53 +08:00
 lsk569937453

背景:需要向一个文件中写入 1 亿个 ip ,最快的方法是什么?机器配置是 mac book,双核 cpu,8GB 内存。 我用 java 实现了一个多线程的写入,发现速度慢的要死(写入时间 5 分钟以上)。有没有人推荐一下速度写入大文件的方法,或者其他语言快速写入大文件的方案。

7842 次点击
所在节点    程序员
66 条回复
0o0O0o0O0o
2022-04-09 22:15:25 +08:00
回复好像不支持格式
none
2022-04-09 22:23:49 +08:00
1 亿个 IP ,就是 4 亿个字节,直接申请 4 亿 byte 内存,往里面写入随机 uint32 数字,然后一次性写入文件,我本机测试结果大概 2000ms 左右。
Golang 代码:

package main

import (
"encoding/binary"
"math/rand"
"os"
"time"
)

func main() {

start := time.Now().UnixMilli()

size := int(4e8)
buf := make([]byte, size)
for i := 0; i < size; i += 4 {
binary.BigEndian.PutUint32(buf[i:], rand.Uint32())
}

err := os.WriteFile("iptest.bin", buf, os.ModePerm)
if err != nil {
panic(err)
}
println("elapsed:", time.Now().UnixMilli()-start)
}
lsk569937453
2022-04-09 22:24:12 +08:00
@ZE3kr 当然是可以保存啊,问题你保存 Ip 肯定是为了看的是吧,或者为了查询。我有个疑问,究竟是保存数字更容易查询呢还是报错字符串更容易查询呢?因为我接触的项目比较少,一般都是保存为字符串。所以比如我想查询 192 字段的 ip ,可能前缀匹配就行了。如果保存数字的话,也支持这种查询吗?
lsk569937453
2022-04-09 22:31:19 +08:00
0o0O0o0O0o
2022-04-09 22:36:13 +08:00
@lsk569937453 #43 我也没有项目经验,就说点自己的感受。ip 字符串是给人看的,写少量日志文件我倾向于用字符串,但需要放数据库我会用数字。至于这个数量级的查询还要匹配 ip range ,那我觉得还是适合用数据库,mysql 支持 INET_ATON ,匹配 192.0.0.0 到 192.255.255.255 对应的数字范围即可。
lsk569937453
2022-04-09 22:37:11 +08:00
此贴可以结了。Lz 原来的想法只是向文件写入 ip 字符串。最终采用的是写入 byte 数组,每 4 位表示一个 ip 。最终也没用使用 MMAP 。获益匪浅!
ZE3kr
2022-04-09 22:38:22 +08:00
@lsk569937453 我觉得数字无论从 IP 段匹配还是 IP 段搜索都更容易,32bit 和 IPv4 是完全的一一对应关系,但 String 和 IP 并不是一一对应,因为并不是所有 String 都是 IP ; IPv6 就更复杂了,还会有多个 String 对应一个 IPv6 的问题(如果硬要杠的话 IPv4 也有多对一问题,比如 1.1 、1.0.0.1 、001.0.0.001 都是一个 IP )。你要用 String 存,数据库读写是不是得做校验?

数字因为是二进制,IPv4 可以划分为四组每组 8 个 bit 。

一般只有日志、以及代码中的临时变量存 String ,要入数据库还是存数字稳妥
ZE3kr
2022-04-09 22:39:01 +08:00
https://1.1
lsk569937453
2022-04-09 22:48:00 +08:00
@0o0O0o0O0o 使用 INET_ATON 运算会不会在高流量下对数据库造成压力?我记得在去哪的 mysql 数据库设计规范上明确指出,要尽可能的少使用函数运算。我找了下文档,结果出现了令我疑惑的一点,设计规范如下:
1.禁止在 MySQL 中进行数学运算和函数运算。

2.建议使用 INT UNSIGNED 存储 IPV4 。
UNSINGED INT 存储 IP 地址占用 4 字节,CHAR(15)则占用 15 字节。另外,计算机处理整数类型比字符串类型快。使用 INT UNSIGNED 而不是 CHAR(15)来存储 IPV4 地址,通过 MySQL 函数 inet_ntoa 和 inet_aton 来进行转化。IPv6 地址目前没有转化函数,需要使用 DECIMAL 或两个 BIGINT 来存储。

难道 inet_aton 不属于函数运算吗?
jetyang
2022-04-09 22:52:20 +08:00
关键是先尽量放在内存,批量 flush 到硬盘,这时候就要看硬盘 IO 如何了
0o0O0o0O0o
2022-04-09 22:56:19 +08:00
@lsk569937453 #49 上面所说是在需要手动查询日志时保持查询语句的可读性,“高流量”那我肯定是把 inet_aton 放在程序里而不是放在 sql 查询语句里
lsk569937453
2022-04-09 22:58:23 +08:00
@0o0O0o0O0o 有道理!我刚才也想到了这一点!
zhoujinjing09
2022-04-10 01:40:28 +08:00
这个任务肯定是被硬盘的顺序写性能 bound 住啊……就是你把要写的东西一段段 buffer 准备好然后直接 flush 就能达到最大值啊……如果是 SSD 可能还有 io depth 可以优化但是速度应该也差别不大……
levelworm
2022-04-10 01:50:55 +08:00
@GeruzoniAnsasu 多谢分享,求问这些知识哪里能系统学习到?比方说文件系统 api ,ip 的数据结构,以及 mmap 和之后说的各种优化?
GeruzoniAnsasu
2022-04-10 02:28:22 +08:00
@levelworm 好多都是学校里的了,因为学校里你有时间,可以很纯粹地为了学习而学习。你可以 TCP/IP 详解一二三卷硬看,虽然最后可能没记住多少,但看过了哪些目录一定是有印象的,这跟从目的出发一点一点零碎地搜就完全不一样了。

在学校你也有机会看一整本自制 OS 教程,一整本 Linux 教程,一整本 windows 核心编程,你如果说「系统地」怎么学的,那就是书籍目录,没有比这个更系统的了。

有了这些印象作为框架,即便是搜索新东西也会更有头绪一些。比方说 OP 这个例子,假设他其实想解决的问题其实是「要对大量 IP 进行匹配」,你就能想到协议栈,想到 win 和 nix 的内核,就会联想能不能手写逻辑简化数据结构的处理,以及联想到能不能别用内核的协议栈——复制数据和切换内核态都很耗时间。然后你就能用 high performance 「 USER SPACE 」 tcp 「 STACK 」搜到 DPDK (虽然我是从前公司项目知道它的),而搜 high performance tcp 就很难找到这种方案



我想大多数人都不缺书单,缺的是时间和精力真的去看一遍…… 工作之后自己都有点开始对「为了学习而学习」不屑一顾了
jorneyr
2022-04-10 08:32:51 +08:00
每个 IP 最大长度 15 Bytes ,一亿个 IP 的容量为: 100000000 * 15Bytes ,即 1.39G ,现在的硬盘写入速度几个 G/S 的很多,机械硬盘也是 100+M/S ,可以一个线程分段生成,交给另一个异步线程写入 (不要多个线程同时写入)。
cloverzrg2
2022-04-10 11:31:54 +08:00
多线程写磁盘。。。
levelworm
2022-04-10 11:57:48 +08:00
@GeruzoniAnsasu 多谢,果然基础知识重要
northernlights0
2022-04-10 16:10:47 +08:00
前面有说不要多线程写入的???除非你用的是机械硬盘才这样,ssd 一直都是并发写效率最高。
kalluwa
2022-04-10 17:28:28 +08:00
“你们的真实场景中有将 ip 保存为 32 位整数的吗?”
这个一般下意识都会选择存 4bytes 的,存字符串的方式按道理说除非有明文要求,都不会去考虑的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/845892

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX