@@ -108,3 +108,102 @@ getUserFeed(api_dev_key, user_id, since_id, count, max_id, exclude_replies)
108
108
下图展示了系统的高层架构,其中用户 B 和 C 关注了用户 A。
109
109
110
110
![ 图13-2] ( /grokking/f13-2.png )
111
+
112
+ ## 7. 详细组件设计
113
+
114
+ 我们来详细讨论系统的各个组件。
115
+
116
+ ** a. 动态生成**
117
+
118
+ 以新闻动态生成服务为例,假设该服务需要获取 Jane 关注的所有用户和实体的最新帖子,其查询可能如下:
119
+
120
+ ```
121
+ (SELECT FeedItemID FROM FeedItem WHERE UserID in (
122
+ SELECT EntityOrFriendID FROM UserFollow WHERE UserID = <current_user_id> and type = 0(user)
123
+ ))
124
+ UNION
125
+ (SELECT FeedItemID FROM FeedItem WHERE EntityID in (
126
+ SELECT EntityOrFriendID FROM UserFollow WHERE UserID = <current_user_id> and type = 1(entity)
127
+ ))
128
+ ORDER BY CreationDate DESC
129
+ LIMIT 100
130
+ ```
131
+
132
+ ** 该动态生成服务设计存在的问题:**
133
+
134
+ 1 . ** 速度极慢** :对于关注大量好友/实体的用户,需要对海量帖子进行排序、合并和排名,导致查询速度极慢。
135
+ 2 . ** 高延迟** :在用户加载页面时才生成动态,响应速度慢,延迟较高。
136
+ 3 . ** 实时更新的积压** :每次状态更新都会触发所有关注者的动态更新,可能导致新闻动态生成服务积压严重。
137
+ 4 . ** 推送负载过高** :服务器向用户推送或通知新帖子时,尤其是对于拥有大量关注者的用户或页面,可能会产生极高的负载。
138
+
139
+ ** 改进方案:预生成动态**
140
+ 为提高效率,我们可以预生成用户的动态并存储在内存中。
141
+
142
+ ** 离线生成新闻动态**
143
+ 可以使用专门的服务器持续生成用户的新闻动态,并将其存储在内存中。这样,用户请求动态时,不需要即时计算,而是直接从预存的结果中获取。
144
+
145
+ 当服务器需要为某个用户生成动态时,会先查询该用户上次生成动态的时间点,然后从该时间点开始计算新的动态数据。该数据可以存储在哈希表中,** 键(Key)** 为用户ID,** 值(Value)** 为如下结构体(STRUCT):
146
+
147
+ ```
148
+ Struct {
149
+ LinkedHashMap<FeedItemID, FeedItem> feedItems;
150
+ DateTime lastGenerated;
151
+ }
152
+ ```
153
+
154
+ 我们可以使用类似 ** LinkedHashMap** 或 ** TreeMap** 的数据结构来存储 ** FeedItemID** ,这样不仅可以快速跳转到任意动态项,还能方便地遍历整个映射。
155
+
156
+ 当用户请求更多动态时,他们可以发送当前新闻动态中最后一个 ** FeedItemID** ,然后我们可以在哈希映射中找到该 ** FeedItemID** ,并从该位置返回下一批动态数据。
157
+
158
+ ** 内存中应存储多少条动态?**
159
+ 初始方案是为每个用户存储 ** 500 条动态** ,但可以根据使用模式调整。例如:
160
+ - 如果一页动态包含 ** 20 篇帖子** ,且大多数用户不会浏览超过 ** 10 页** ,那么我们可以将存储量减少至 ** 200 条动态** 。
161
+ - 对于希望查看更多动态的用户,可以向后端服务器查询额外的帖子数据。
162
+
163
+ ** 是否应该为所有用户生成并缓存新闻动态?**
164
+ 并非所有用户都会频繁登录,因此需要优化存储策略:
165
+ 1 . ** LRU 缓存淘汰策略** :使用 ** LRU(Least Recently Used)** 缓存机制,移除长时间未访问新闻动态的用户数据,以节省内存。
166
+ 2 . ** 智能预测用户登录模式** :分析用户的访问习惯,预测他们活跃的时间段,并在适当时间点预生成新闻动态,例如:
167
+ - 用户一天中活跃的时间
168
+ - 用户每周访问新闻动态的频率
169
+
170
+ 在接下来的部分,我们将探讨如何优化 ** 实时更新** 方案。
171
+
172
+ ** b. 动态发布**
173
+
174
+ 将帖子推送给所有关注者的过程称为 ** Fanout(广播)** 。
175
+ - ** 推送(Push)模式** 称为 ** Fanout-on-write** (写时广播)。
176
+ - ** 拉取(Pull)模式** 称为 ** Fanout-on-load** (加载时广播)。
177
+
178
+ 我们来探讨不同的动态发布方案。
179
+
180
+ ** 1. 拉取模式(Pull)——Fanout-on-load**
181
+ 该方法会将所有最新的动态数据存储在内存中,用户需要时可从服务器拉取。客户端可以定期拉取动态,或者手动刷新以获取最新内容。
182
+
183
+ ** 缺点** :
184
+ - ** 延迟问题** :新内容不会立即显示,用户必须主动请求才能看到新动态。
185
+ - ** 资源浪费** :如果拉取请求没有新数据,服务器会返回空响应,导致资源浪费。
186
+
187
+ ** 2. 推送模式(Push)——Fanout-on-write**
188
+ 当用户发布新帖子时,服务器会立即将其推送给所有关注者。
189
+
190
+ ** 优点** :
191
+ - 取动态时无需遍历好友列表,大大减少 ** 读操作** ,提高访问效率。
192
+
193
+ ** 缺点** :
194
+ - 需要维护一个 ** 长轮询(Long Polling)** 连接,以便服务器向客户端发送更新。
195
+ - 对于拥有 ** 数百万关注者的明星用户** ,服务器需要同时向大量用户推送数据,可能造成 ** 巨大负载** 。
196
+
197
+ ** 3. 混合模式(Hybrid)**
198
+ 混合方案结合了 ** 写时广播(Fanout-on-write)** 和 ** 加载时广播(Fanout-on-load)** ,可以优化性能:
199
+ - ** 针对普通用户** :继续使用 ** 推送(Push)模式** ,确保关注者能及时收到更新。
200
+ - ** 针对明星用户(大量关注者)** :** 禁用推送** ,让关注者自行拉取更新,以减少服务器开销。
201
+ - ** 优化策略** :
202
+ - 只向 ** 在线好友** 推送动态,减少无效推送。
203
+ - 结合 ** 推送通知(Push to Notify)** 和 ** 拉取获取(Pull to Serve)** ,确保系统既高效又灵活。
204
+
205
+ ** 请求时返回多少条动态?**
206
+ 每次请求应设置最大返回条数(例如 ** 20 条** ),但允许客户端根据设备类型(** 移动端 vs. 桌面端** )自行调整请求数量。
207
+
208
+ ** 是否始终通知用户有新动态?**
209
+ ** 桌面端** 可以开启通知,而 ** 移动端** 由于流量成本较高,可以采用 ** “下拉刷新”(Pull to Refresh)** 机制,让用户主动获取新动态,减少不必要的流量消耗。
0 commit comments