知乎 Feed 流架构演进
姚钢强 2013 年年加 入知乎, 知乎 Feed 流技术负责 人, 负责期间 Server 端 P95 响应时间从 1.6S 降低到 700Ms, 稳定性由 99.9% 提升到 99.995%
提纲 A. Feed 流的需求和特点 B. 老老 Feed 流的构架遇到的问题 C. 新构架 Redis module 技术 方案 D. Redis module 方案遇到的问题 E. 新的问题与挑战
知乎 Feed 流的需求和特点 A. DAU 2600 万 (2017.9) B. 个性化推荐, 每次请求返回内容不不同 ( 与搜索不不同, cache 难做 ) C. 用户动态准实时分发
无个性化 ( 热 门榜单 )
用户个性化 push
push 存在的问题 A. 资源消耗严重, 计算量量 大, 存储量量 大 B. 智能推荐, 排序难以实时调整 C. 过滤 比较难做 ( 关注 or 被删除 ) D. 动态准实时分发难以达到 ( 高粉丝 用户 )
实时 pull
pull 计算流程 ( 慢 ) client member 推荐的相关源 拼装 meta 从指定的源拉取条 目 N 过滤条 目 选前 10 条 条 目够 用 Y 算法排序
提前计算, 做缓存 client member 相关的源 N 缓存 Y 拼装 meta 从指定的源拉取条 目 前 10 条 后 10 条 缓存 N 过滤条 目 选前 20 条 条 目够 用 Y 算法排序
提前计算的问题 存在冗余计算, 占 用资源多 冷启动 P95 响应时间 长 用户 行行为分发延迟, 体验差 离线计算策略略复杂, 难以维护 推荐算法难以实时调整
如何优化, 难点 儿在哪 儿? 依赖服务响应慢 redis cache + local cache gevent 并发 超时做降级 Python 计算太慢 Cython 模块替换 由于条 目不不够, 反复访问底层源的存储 feesource
可能的解决 方案 拉取出更更多的条 目, 防 止被过滤掉? 拉取更更多的条 目也会浪费时间, 过滤压 力力 大 根据算法拉取出更更精准的条 目? 算法期望召回池越 大越好
计算下推, 接近存储
新 feed 计算逻辑
Redis Module Redis modules make possible to extend Redis functionality using external modules, implementing new Redis commands at a speed and with features similar to what can be done inside the core itself.
Redis Module 加载定制命令 (MODULE LOAD module load lib_path/xx.so) 执 行行定制命令 卸载定制命令 (MODULE UNLOAD mymodule)
加载定制命令 Client module load lib_path/xx.so START handle = dlopen("lib_path/xx.so") dlsym(handle,"redismodule_onload") RedisModule_Init 初始化模块 redisserver attributes + modules: dict + commands: dict operations RedisModule_CreateCommand 注册定制命令... RedisModule_CreateCommand 注册定制命令 END
Client 执 行行定制命令 AxxCmd argv1 argv2 START 查找命令 RedisModuleCommandDispatcher 执 行行命令 redisserver attributes + modules: dict + commands: dict 模块 callback modulehandlepropagationaftercommandcallback operations 回收内存 modulefreecontext END
Client 卸载定制命令 module unload ModuleName START 清空注册符号 dictdelete(server.commands,cmdname) dlclose 卸载模块 modulefreemodulestructure redisserver attributes + modules: dict + commands: dict operations END
定制的命令 open_core, close_core Redis module 更更新时打开 core dump, 如果 crash 方便便分析 ts_query Redis 内部的过滤, 归并流程
ts_query 接 口设计 Request [source_type, source_id]: 需要拉取 source black_items: 需要过滤的 黑名单条 目 merge_strategy: 合并策略略 required_number: 需要返回的条 目数量量 Response [item_type, item_id, item_action, item_score]: 返回需要数 目的 feed 条 目
带来的收益 响应时间 P95 降低 300ms 去掉了了离线计算, 节省 60% 的计算资源 内容候选池更更 大, 为算法提供了了更更 大的空间 用户动态实时分发, 算法实时调整
遇到的问题 Module 更更新不不 生效, 继续调 用 老老 Module Redis 单线程 CPU 瓶颈, 高可 用 Redis 的内存浪费
Module 更更新不不 生效 加载新的 so 后, 发现调 用的还是 老老逻辑 GDB 发现存在符号被标记 DF_1_NODELETE dlcose 仅仅声明 so 不不在被系统使 用,so 内存占 用依旧存在 gcc 编译使 用 STB_GNU_UNIQUE, 防 止符号被标记为 DF_1_NODELETE 按照依赖路路径加载 so, 不不直接加载定制的 so
Redis 单线程 CPU 瓶颈, 高可 用 采 用 Redis Sentinel 部署集群, 保证 高可 用 一致性哈希 Redis shard, 每个 shard 采 用 master slave 的 方式部署
Redis 内存浪费 采 用 protobuf 和 Redis ziplist 数据压缩, 减少 shard 有多个 slave, 还是浪费内存, 没有根本解决问题 Redis 仅仅作为任务队列列, 任务分派给其他进程处理理? 同 一台机器器伴随 Redis 部署计算节点?
Feed 架构的历程 Feed 都 一样 Feed 个性化, 推模型 Feed 个性化, 拉模型 + 离线计算 Feed 个性化, 拉模型, 采 用 Redis Module, 计算接近存储
参考资料料 Serving Facebook Multifeed: Efficiency, performance gains through redesign dlclose - close a symbol table handle dlclose doesn't really unload shared object, no matter how many times it is called Redis Modules: an introduction to the API Redis Loadable Modules System