千万条数据,1万QPS的实现方案

需求:每秒1万个http请求,响应时间500毫秒以内。

1万QPS的缓存实现方案

分3个场景:

场景1:单ID详情查询

按ID存储到Redis分片,可以用3台Redis分片。

场景2:列表分面产,多条件筛选(千万条数据)

方案1:使用ReidsSearch,支持按多条件、分页筛选数据,使用3个Redis分片节点。

方案2:使用ElasticSearch,支持安多条件、分页筛选数据,使用3个ElasticSearch分片节点。

场景3:树形数据,需要递归查询(10万条数据)

这种情况不能使用Redis或ElasticSearch做缓存了。因为要10万条数据做递归查询一个树型,不能单条读Redis或ES,也不能把一个10万条数据的json字符串存到Redis。这种情况只能使用内存缓存了,把10万条数据存到内存。

有三个方案:

方案1:把整条List存到MemoryCache,在内存里递归查询,部署3台api节点;

方案2:不用递归,用本地词典,部署3台api节点,性能最高。

扁平化预计算树形关系,内存中直接建立索引,无需递归查询。

不存原始树形嵌套对象,内存维护 3 套字典索引,O (1) 全量遍历、父子 / 子孙查询:

复制代码
// 1. 全节点主键字典:ID → 节点完整实体
Dictionary<long, TreeItem> AllNodeDict;
// 2. 父级索引:父ID → 子节点ID列表(查直接子节点O(1))
Dictionary<long, List<long>> ChildIndex;
// 3. 祖先链路索引:节点ID → 根到自身全路径ID数组(递归向上查询直接取)
Dictionary<long, List<long>> ParentChainIndex;
// 4. 后代全量索引:节点ID → 所有子孙ID(一次性查整棵子树,无需循环递归)
Dictionary<long, HashSet<long>> DescendantIndex;

优势

全部数据驻留应用进程内存,无任何网络 IO,单次树形查询微秒级,单实例轻松承载 2~5 万 QPS,满足 1 万 QPS 需求。

提前预计算子孙、祖先关系,业务代码完全不用递归循环查询缓存 / DB。

10 万条树形数据内存占用极低,普通服务器内存无压力。

单实例重启:仅冷启动瞬间拉一次全量数据。

方案3:用RockDB,嵌入式混合冷热缓存(几十万条数据,内存放不下)

关键矛盾:

10 万~几十万树形数据,纯全量驻内存会占用过高,但完全放远程 Redis 递归查询网络 IO 爆炸;

嵌入式 RocksDB 采用「内存 BlockCache + 磁盘持久层」冷热分离,自动热点放内存、冷节点落本地磁盘,不用一次性把几十万条全塞进内存,完美平衡内存占用与查询性能。