爬虫的任务队列和页面缓存都放在 Redis 里,内存是钱。任务体该用什么格式,压不压缩,打包多大,缓存的页面要不要压缩?这篇在本机独立的 Redis 上逐项量内存、吞吐和网络流量。
任务数 100000;紧凑 JSON 平均 376 字节
任务里很多内容是每个任务都相同的(UA、语言、referrer)。直接把常量字段拿出任务体,JSON 就从 392.9 降到 183.1 字节/任务;换 Protobuf 是 265.8;JSON 加 zstd 字典是 52.8。字典能把这些重复统统吃掉。
编码 平均载荷 Redis 内存增量 每任务内存 其中 Redis 自身开销 网络入流量 JSON 376.1 37.5MiB 392.9B 16.8B 38.7MiB JSON(常量字段不进任务体) 176.1 17.5MiB 183.1B 7.1B 19.7MiB MessagePack 332.1 32.7MiB 343.3B 11.3B 34.5MiB Protobuf 258.3 25.4MiB 265.8B 7.5B 27.5MiB pickle(只在可信环境用) 389.1 39.3MiB 412.0B 22.9B 40.0MiB JSON + zstd-3 258.2 25.4MiB 265.8B 7.6B 27.5MiB JSON + zstd-3 + 字典 50.3 5.0MiB 52.8B 2.6B 7.6MiB Protobuf + zstd-3 + 字典 51.2 5.1MiB 53.8B 2.6B 7.7MiB JSON + lz4 326.3 32.7MiB 343.3B 17.0B 34.0MiB
MessagePack 和 Protobuf 的编码解码比 JSON 快;压缩加字典的 JSON 入队 128789 个/秒,和纯 JSON(138308)接近。在 Python 里瓶颈是编码解码和 Redis 往返,不是压缩本身。
JSON 入队 138308 个/秒;出队 + 解码 153853 个/秒 MessagePack 入队 214950 个/秒;出队 + 解码 199171 个/秒 Protobuf 入队 178133 个/秒;出队 + 解码 238389 个/秒 pickle(只在可信环境用) 入队 195311 个/秒;出队 + 解码 194645 个/秒 JSON + zstd-3 入队 94576 个/秒;出队 + 解码 126820 个/秒 JSON + zstd-3 + 字典 入队 128789 个/秒;出队 + 解码 151041 个/秒 Protobuf + zstd-3 + 字典 入队 154402 个/秒;出队 + 解码 216982 个/秒 JSON + lz4 入队 125481 个/秒;出队 + 解码 144472 个/秒
把 N 个任务打包成一个元素再压缩,每任务内存随 N 增大迅速下降(1 个 265.8,10 个 63.4,50 个 36.1,200 个 31.0 字节)。但租约、重试、去重都以元素为粒度,崩溃时要重做整包(见第 66 篇)。
每元素任务数 元素数 Redis 内存增量 每任务内存
1 100000 25.4MiB 265.8B
10 10000 6.0MiB 63.4B
50 2000 3.4MiB 36.1B
200 500 3.0MiB 31.0B
(元素里装的任务越多,每个任务的内存越小,但一个 worker 取走一个元素就是取走 N 个任务,崩溃时要重新入队 N 个;租约、重试、去重都以元素为粒度)13 KiB 的页面原样存 68.7 MiB(5000 页),lz4 29.6 MiB,zstd-3 20.5 MiB,zstd-3 加字典 14.9 MiB;读取加解压的吞吐 lz4 最快,zstd 约 46000/秒,brotli q5 只有 24219/秒。缓存适合 zstd-3 或加字典,生成一次读很多次。
存储方式 平均值大小 Redis 内存增量 写入 读取 + 解压 原样 13907 68.7MiB 41961/秒 59061/秒 (解压校验通过) lz4 5862 29.6MiB 73115/秒 73856/秒 (解压校验通过) zstd-3 4013 20.5MiB 75343/秒 46750/秒 (解压校验通过) zstd-3 + 字典 2903 14.9MiB 91079/秒 46016/秒 (解压校验通过) brotli q5 3891 19.8MiB 83252/秒 24219/秒 (解压校验通过)
同样 300 字节,列表元素额外占 16.9 字节,单独的 key 67.3,哈希 field 71.3。把小对象放进列表比单独的 key 省内存。
列表元素 RPUSH q 每个元素共占 316.9 字节(载荷 300 字节,额外 16.9 字节) 单独的 key SET k:i 每个元素共占 367.3 字节(载荷 300 字节,额外 67.3 字节) 哈希 field HSET h 每个元素共占 371.3 字节(载荷 300 字节,额外 71.3 字节) 集合成员 SADD s 每个元素共占 341.1 字节(载荷 300 字节,额外 41.1 字节)
任务体已经很小(比如只有 url),压缩的收益有限,而每个任务加字典后要保证 worker 和队列里的字典 ID 一致,版本升级时队列里还有旧字典压的任务,要继续能解(见第 17 篇)。