社区平台核心工程挑战:fan-out on write vs fan-out on read的选择、Redis预计算feed、通知管道等分布式系统设计决策。
在过去的几年里,我花了大量时间构建社区和社交功能——信息流、聊天、审核系统、通知管道——我一直在观察开发者(包括过去的我自己)犯同样的错误。不是因为他们水平不行,而是因为一个社区平台看起来像个 CRUD 应用,实际上却是一个带有情感的分布式系统。
这篇文章是我在第一次动手构建之前希望有人能塞给我的。没有框架推销,没有产品哲学——只有那些会悄悄决定你的平台能否经受住真实用户考验的工程决策。
信息流不是一个查询。在第一天就决定你的分发策略。
每个社区平台都有一个信息流,每个幼稚的实现都从同样的方式开始:
SELECT * FROM posts WHERE author_id IN (SELECT followed_id FROM follows WHERE follower_id = ?) ORDER BY created_at DESC LIMIT 20;
这在演示中表现得非常漂亮。在 1,000 用户时也还可以。当你的关注量最大的成员有 5 万粉丝、follows 表有数千万行数据时,这个查询就成了值班的噩梦。
真正的决策是:写时分发 vs 读时分发:
写时分发:当有人发帖时,把帖子 ID 推入每个粉丝的预计算信息流(通常是 Redis)。读取是 O(1) 的,即时的。对于热门账号,写入会爆炸——一个拥有 10 万粉丝的成员发一条帖子,意味着 10 万次列表插入。
读时分发:在请求时计算信息流(上面的查询,加上大量缓存)。写入便宜;读取越来越贵。
大多数人会选择的混合方案:普通账号写时分发,"大 V"账号(超过某个粉丝阈值)读时分发,读取时合并。
发布日你不需要这个混合方案。但你需要把代码结构做好,让切换策略不需要重写——从一开始就用一个接口把信息流生成封装起来。在一个六个功能直接查询帖子的系统里后补这个,是一次重写。
信任等级是你能构建的最省力的审核系统
这是我交付过的 effort-to-impact 比最高的审核功能,里面没有任何 ML:基于账号年龄和参与度的分级权限。
Level 0 (新人):阅读、反应。不许发链接、图片、私信。
Level 1 (基础):发帖/评论,有频率限制。链接需审核。
Level 2 (成员):正常发帖,可以发图片和私信。
Level 3 (常驻):创建主题/圈子,举报权重增加。
Level 4 (老用户):编辑标题、移动帖子、审核队列。
垃圾机器人和路过型喷子有一个共同点:他们不会花三周的真实参与来获得发帖权限。信任等级在结构层面把他们过滤掉,比你的举报队列或毒性分类器更早一步看到他们。Discourse 用这个模型运行了十年,不是没有原因的。
来自踩坑实操的笔记:
异步计算等级(每晚跑一次任务就够了),不要在请求时内联计算。阈值要做成配置,不是代码。你会调优它们的。为每次自动限制操作记录日志并标注原因。当一个合法的新用户链接被拦截时,客服需要看到具体原因。在第一天就做好手动覆盖功能。某个 CEO 会注册,然后需要立即获得 Level 2。这不是假设。
你的通知系统是留存基础设施。照着这个思路构建。
错误做法:把通知当作 sendPush(userId, text) 调用,散落在代码库里任何事件发生的地方。
六个月后你发现:没有按用户的频率上限、没有免打扰时段、没有办法把"17 人反应了"批成一条通知、没有办法让用户静音某项内容而不静音所有内容。用户理性应对:他们在系统层面关掉通知,你永远失去了唯一的重新激活渠道。
把它构建成一个管道:
事件总线 → 资格判断(偏好、静音、限额)→ 聚合窗口(合并相似事件)→ 调度(免打扰时段、时区)→ 投递(推送/邮件/站内)→ 回执
每个可通知事件都进入总线;一个独立服务拥有其余所有逻辑。这可能多花一周的前期投入,但省下了四分之一痛苦的重构工作量。
一个值得借鉴的产品工程细节:新用户的第一个回复优先级几乎高于一切。一个在第一篇帖子发出后 24 小时内得到真实回复的成员,留存率远高于在沉默中发帖的成员。有些平台专门把第一篇帖子路由到一个志愿者接待队列里。这不是增长黑客;这是理解这个系统的使命所在。
实时是一个光谱。买底部,建顶部。
"我们需要聊天"是预算的黑洞。持久 WebSocket 连接、在线状态、正在输入提示、消息排序、手机在渣网络下的重连、离线队列——这是好几个月的基础架构工作,做出来的东西用户还觉得是理所当然的。
在两种方式都构建过之后,我真诚的建议:
买或者用开源来解决传输层。托管聊天 SDK 或自托管引擎处理连接管理、排序和同步——这些问题已经解决了,没有差异化,重新实现好是非常痛苦的。自己构建它上面的一切:聊天如何与你的权限系统、信任等级、审核队列和通知管道挂钩。那个集成层才是你的产品;原始消息管道不是。
质疑在线状态指示器。"12 位成员在线"在一个热闹的社区里是激励,在一个刚起步的社区显示"1 位成员在线"(那是访客,孤零零的)则是毁灭性的。把在线状态做成一个配置标志,可以按圈子开关。
设计空房间
工程师用充满活动的种子数据库测试。真实的社区上线时是空的,而空状态才是你最早——最重要——的用户真正体验到的状态。
具体来说,这意味着:信息流需要一个设计好的零内容状态,配上常青内容,而不是空白滚动。圈子需要最低活跃密度逻辑——与其用 12 个看起来被遗弃的频道,不如用 2 个看起来充满活力的频道上线(把创建频道做成管理员操作,而不是默认行为)。你的排序算法需要一个冷启动模式:每日帖子数低于某个阈值时,按时间序加置顶就够了;基于参与度排序的信息流需要先有参与度才能排序。
我现在把"这个页面在 30 用户和 4 篇帖子时显示什么"当作标准的设计审查问题,和加载状态、错误状态一样。
那些能救你的无聊清单
快速列举一些让我付出过代价的教训:
软删除一切。审核争议、GDPR 请求、"我不小心删了"都需要 deleted_at,而不是 DELETE。把审核操作存成只追加的日志。谁对谁做了什么、为什么、是否可逆。社区死于感知的不公平比死于真正的喷子更快,审计日志是你唯一的防御。从第一天起就按信任等级限制写操作频率。在第一波垃圾内容之后加限速意味着在事件进行中做这件事。媒体上传需要可恢复性。你的用户在手机上、在移动数据下、在电梯里。搜索是那沉默的 90%——只看不发帖的人——的功能。潜水者通过搜索和摘要来体验你的社区。为他们构建;他们才是大多数用户。
不舒服的总结
社区平台的所有难点在一张截图里都不可见,而补上它们的体验都是痛苦的。分发策略、信任等级、通知管道、审核审计日志——这些都是第一天就需要做的架构决策,穿着"我们以后再加"的外衣。
你不需要一开始就构建一切。但你需要在一开始就决定一切,在架构里留下缝隙,让后面要加的东西能落地。这就是全部的诀窍。能存活下来的社区运行的不是更聪明的算法——而是有人在用户到来之前就打好了地基。
如果你在这个领域构建过,碰到过不同的墙,我真的很想在评论区听听——尤其是那些在大规模下处理过信息流分发的做法。