<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Webook-One on GzzhM</title>
        <link>https://ddlgitgzzhm.github.io/tags/webook-one/</link>
        <description>Recent content in Webook-One on GzzhM</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <copyright>GzzhM</copyright>
        <lastBuildDate>Sat, 08 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://ddlgitgzzhm.github.io/tags/webook-one/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>关注 one</title>
        <link>https://ddlgitgzzhm.github.io/p/contract-0x02/</link>
        <pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/contract-0x02/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 : 实现了一个完整的用户关系功能 支持 关注、取消关注、获取关注列表&lt;/p&gt;
&lt;h2 id=&#34;流程设计&#34;&gt;流程设计
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;需求分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;要求支持 用户的关注，取消关注，获取关注列表，获取关注数量的功能&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;模块分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;完全独立的一个业务，单独拆分成一个模块&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;关注模块&#34;&gt;关注模块
&lt;/h3&gt;&lt;h4 id=&#34;支持的功能&#34;&gt;支持的功能
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;关注某人&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;取消关注&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看用户粉丝列表&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看用户关注列表&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看用户关注数量&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;表结构设计&#34;&gt;表结构设计
&lt;/h4&gt;&lt;p&gt;表结构维护 Follower 和 Followee 以及 status 进行维护关系之间的展示&lt;/p&gt;
&lt;p&gt;并且创建 唯一索引 &amp;lt;follower_followee&amp;gt; 和 普通索引 &amp;lt;followee_follower&amp;gt; 来优化 查询用户的关注列表和粉丝列表&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FollowRelation&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;primaryKey,autoIncrement,column:id&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Follower&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;type:int(11);not null;uniqueIndex:follower_followee,priority:1;index:followee_follower,priority:2&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Followee&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;type:int(11);not null;uniqueIndex:follower_followee,priority:2;index:followee_follower,priority:1&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 这里你可以根据自己的业务来增加字段，比如说&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 关系类型，可以搞些什么普通关注，特殊关注&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Type int64 `gorm:&amp;#34;column:type;type:int(11);comment:关注类型 0-普通关注&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 备注&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Note string `gorm:&amp;#34;column:remark;type:varchar(255);&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 创建时间&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Ctime&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Utime&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id=&#34;缓存设计&#34;&gt;缓存设计
&lt;/h4&gt;&lt;p&gt;使用 Hash 结构维护一个 &lt;code&gt;follow&lt;/code&gt; 和 &lt;code&gt;followee&lt;/code&gt; 的关注和被关注数量&lt;/p&gt;
&lt;p&gt;使用 TxPiepline 进行一致性控制&lt;/p&gt;
&lt;p&gt;在关注和取消关注的时候 进行同步的 +1 和 -1 操作&lt;/p&gt;
&lt;h4 id=&#34;其他细节&#34;&gt;其他细节
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;拉黑，屏蔽&lt;/strong&gt; 的功能不额外创建表进行存放 。 考虑这个操作是一个低频操作，因此使用 status 直接存放到 关系表里面&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于 关系表 ， 一个用户关注100个人，那么行数就会增加100倍。 使用 阿里的 &lt;code&gt;tablestore&lt;/code&gt; 进行类似分库分表的优化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取用户关注数量和粉丝数量的时候 。 优先去获取 Redis 的数量 然后再通过 Mysql Count 进行统计&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取关注列表不进行缓存 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;考虑是一个低频操作 缓存命中率低&lt;/li&gt;
&lt;li&gt;并且是一个分页查询 不好缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://ddlgitgzzhm.github.io/p/contract-0x02/contract_struct.png&#34;
	width=&#34;995&#34;
	height=&#34;798&#34;
	srcset=&#34;https://ddlgitgzzhm.github.io/p/contract-0x02/contract_struct_hu_2fad5b1bac0432cb.png 480w, https://ddlgitgzzhm.github.io/p/contract-0x02/contract_struct_hu_f690c8122189d791.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;contract_struct.png&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;124&#34;
		data-flex-basis=&#34;299px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;难点--亮点&#34;&gt;难点 &amp;amp; 亮点
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;问题 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;维护关注数量 , 我们可以采用单独创一张表进行实现。 但是为关注服务直接创一张表太重了&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;方案  :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;因此使用 Redis 进行直接统计 计数 在关注和取消关注的时候分别操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在获取关注数量的时候 使用 Cache-Aside快慢路径 ， 优先获取 Redis 的数据 然后再根据 Count 进行计数 。 因为 我们关注表的索引设计，保证了能够命中索引&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;问题 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建了唯一索引后 &amp;lt;followee_follower&amp;gt; 。这个只能解决查询粉丝数量，但是对于关注数量 并不能很好的命中索引&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;方案 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;反向创建一个普通索引，不需要额外创建唯一索引 因为原有的索引就能控制  。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;亮点 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code&gt;tablestore&lt;/code&gt; 进行维护大数据量级的 关系信息 。 避免自身系统的复杂性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 TxPiePline 进行 控制 同时写入 粉丝和关注 的计数  。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取关注和粉丝列表是一个分页接口 考虑 用户使用频率低 缓存命中率差。不进行额外缓存&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;取消关注 通过 表设计的 status 进行处理 更新 status 就可以完成取消关注，软删除 。 并且在关注功能的时候 ， 使用 Upsert 保证语意&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;缺点&#34;&gt;缺点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;对于关系维护的大数据量级处理 只是引入了三方的 tablestore 进行处理比较简单&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果 Redis 崩溃，那么会有大部分 Count操作进入到 我们的Mysql 中 并没有很好的容错机制&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;总分68--100&#34;&gt;总分：&lt;strong&gt;68 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;对「2 年经验、讲关注关系模块」的面试准备来说：&lt;strong&gt;骨架够用，能过一轮筛选，但扛不住有经验的面试官追问。&lt;/strong&gt; 更像「能把做过的事串起来」，还没到「能把设计讲透、把坑讲清楚」。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;优点&#34;&gt;优点
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;需求边界清晰&lt;/strong&gt;：关注 / 取消关注 / 列表 / 数量，模块独立，简历表述和正文能对上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表设计基本正确&lt;/strong&gt;：&lt;code&gt;Follower + Followee&lt;/code&gt; + 唯一索引防重复关注，反向普通索引服务粉丝查询，这是面试里常见的加分点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有取舍意识&lt;/strong&gt;：列表不缓存（低频 + 分页）、拉黑用 &lt;code&gt;status&lt;/code&gt; 软状态、取消关注用 status + Upsert，说明不是只会「全量加 Redis」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有自我批评&lt;/strong&gt;：缺点里提到 Redis 挂了打爆 MySQL、tablestore 引入偏简单，态度加分。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;缺点面试里容易被打穿&#34;&gt;缺点（面试里容易被打穿）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;一致性讲错/讲浅了&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;TxPipeline&lt;/code&gt; 只保证 &lt;strong&gt;Redis 内部多命令原子&lt;/strong&gt;，不保证 &lt;strong&gt;MySQL ↔ Redis&lt;/strong&gt; 一致。面试官一问「DB 成功、Redis 失败怎么办？」现在的稿子接不住。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;索引表述前后矛盾&lt;/strong&gt;&lt;br&gt;
前文唯一索引是 &lt;code&gt;follower_followee&lt;/code&gt;，难点里写成「唯一索引 &lt;code&gt;&amp;lt;followee_follower&amp;gt;&lt;/code&gt;」——细节错了会显得没真正想清楚。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;计数方案深度不够&lt;/strong&gt;&lt;br&gt;
Hash + 同步 ±1 + Cache-Aside，缺：key 设计、并发丢更新、缓存击穿/雪崩、冷启动回源、与 DB 对账。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;亮点偏「名词」&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;tablestore&lt;/code&gt;、Pipeline 写得很空，2 年经验候选人若只背名词，追问「为什么不用分表 / 为什么不用计数表」会立刻露馅。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;业务边界漏洞&lt;/strong&gt;&lt;br&gt;
未关注却要拉黑、自己关注自己、重复关注并发、软删后重新关注的语义、分页游标 vs offset，基本没准备。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;几乎没有 Golang 工程视角&lt;/strong&gt;&lt;br&gt;
接口分层、Repo、事务边界、幂等、错误码、超时降级——对「Golang 岗」这是硬伤。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文档完成度差&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;xxxx&lt;/code&gt;、&lt;code&gt;TxPiepline&lt;/code&gt; 拼写、序号重复（两个「2.」），会给面试官「准备潦草」的印象。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;建议按优先级&#34;&gt;建议（按优先级）
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;优先级&lt;/th&gt;
					&lt;th&gt;做什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;P0&lt;/td&gt;
					&lt;td&gt;把一致性故事补全：写路径（先 DB 再 Redis / Outbox / 对账）、读路径降级、Redis 不可用时的限流与熔断&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;P0&lt;/td&gt;
					&lt;td&gt;画一张「关注 / 取消关注」时序图，标清事务、Upsert、status、计数更新顺序&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;P1&lt;/td&gt;
					&lt;td&gt;准备 3 个追问标准答：并发重复关注、未关注拉黑、百万粉丝列表分页&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;P1&lt;/td&gt;
					&lt;td&gt;把 tablestore 改成「可选扩展」：先讲单机/单库上限与分片思路，再提引入外部存储的原因与代价&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;P2&lt;/td&gt;
					&lt;td&gt;补一层 Go 实现：&lt;code&gt;FollowService&lt;/code&gt; 接口、幂等、GORM 事务片段，能对着代码讲 2 分钟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;P2&lt;/td&gt;
					&lt;td&gt;修正文案与术语：Pipeline 拼写、索引名统一、标题 description 写完整&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&#34;分项参考面试官视角&#34;&gt;分项参考（面试官视角）
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;需求与模块拆分&lt;/td&gt;
					&lt;td&gt;78&lt;/td&gt;
					&lt;td&gt;清楚，但偏浅&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;存储与索引&lt;/td&gt;
					&lt;td&gt;72&lt;/td&gt;
					&lt;td&gt;方向对，表述有错&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缓存与一致性&lt;/td&gt;
					&lt;td&gt;55&lt;/td&gt;
					&lt;td&gt;最大短板&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;难点/亮点说服力&lt;/td&gt;
					&lt;td&gt;60&lt;/td&gt;
					&lt;td&gt;有点，深度不够&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自我认知与表达&lt;/td&gt;
					&lt;td&gt;70&lt;/td&gt;
					&lt;td&gt;有缺点意识，文档粗糙&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;对 2 年岗的匹配度&lt;/td&gt;
					&lt;td&gt;68&lt;/td&gt;
					&lt;td&gt;能讲项目，难打深水区&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;一句话&lt;/strong&gt;：当成「项目介绍提纲」大约 70+；当成「能过中高级追问的面试稿」现在大概 60 出头。把 &lt;strong&gt;DB–缓存一致性&lt;/strong&gt; 和 &lt;strong&gt;3～5 个边界追问&lt;/strong&gt; 补扎实，整体可以冲到 &lt;strong&gt;80+&lt;/strong&gt;。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>feed one</title>
        <link>https://ddlgitgzzhm.github.io/p/feed-0x02/</link>
        <pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/feed-0x02/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 : 实现 关注,点赞, 收藏, 发布文章 Feed 流的实现&lt;/p&gt;
&lt;h2 id=&#34;流程设计&#34;&gt;流程设计
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;需求分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;对于用户关注的博主, 希望能够在博主发布文章的第一时间进行通知&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于点赞 , 关注,收藏 操作希望能够第一时间进行通知&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;模块分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;提供一个 Feed 服务 供给业务方 读取和写入&lt;/p&gt;
&lt;h3 id=&#34;feed-模块&#34;&gt;Feed 模块
&lt;/h3&gt;&lt;h3 id=&#34;支持的功能&#34;&gt;支持的功能
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;通用的 写入 收件箱&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;业务定制化的 写入收件箱 并且根据 业务定制 考虑优先写入到发件箱&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通用的 查询 用户 Feed 流&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;业务定制化的查询 用户 Feed 流&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;表结构设计&#34;&gt;表结构设计
&lt;/h3&gt;&lt;p&gt;对于 收件箱 和 发件箱 维护一个 大JSON列进行处理&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;uid&lt;/code&gt;和 &lt;code&gt;ctime&lt;/code&gt; 的索引&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FeedPushEvent&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;primaryKey,autoIncrement&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 收件人&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;UID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;index&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Type 用来标记是什么类型的事件&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 这边决定了 Content 怎么解读&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 大的 json 串&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Content&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Ctime&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;index&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 这个表理论上来说，是没有 Update 操作的&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Utime&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;缓存设计&#34;&gt;缓存设计
&lt;/h3&gt;&lt;p&gt;无 。因为需要保证实时性 并不考虑设计缓存&lt;/p&gt;
&lt;p&gt;但是引入 kafka 进行削峰 。 因为在业务上能够容忍 1s -10s 的响应误差&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ddlgitgzzhm.github.io/p/feed-0x02/feed_process.png&#34;
	width=&#34;1445&#34;
	height=&#34;650&#34;
	srcset=&#34;https://ddlgitgzzhm.github.io/p/feed-0x02/feed_process_hu_55382b766c4bccec.png 480w, https://ddlgitgzzhm.github.io/p/feed-0x02/feed_process_hu_3f99a565db6e2150.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;feed_process.png&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;222&#34;
		data-flex-basis=&#34;533px&#34;
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;其他细节&#34;&gt;其他细节
&lt;/h3&gt;&lt;p&gt;在 文章服务 写入收件箱的时候考虑定制化服务&lt;/p&gt;
&lt;p&gt;对于大V 关注的人很多, 写扩散来说会产生很多内容。 因此根据 用户粉丝数量来判断写入 发件箱还是收件箱&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;同样的 查询用户 Feed 流的时候 同样在文章服务做定制化服务&lt;/p&gt;
&lt;p&gt;因为大V 可能发送了文章之后又设置了隐藏，因此会额外根据 status 进行一层过滤&lt;/p&gt;
&lt;h2 id=&#34;难点--亮点&#34;&gt;难点 &amp;amp; 亮点
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;难点 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;问题 ：&lt;/p&gt;
&lt;p&gt;对于文章服务, 如果是一个 大V 采用写扩散会一时间产生很多数据 ，但是普通用户无所谓 。 但是对于一个 普通用户 使用读扩散 会导致请求多次数据库&lt;/p&gt;
&lt;p&gt;方案 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设计 文章服务 混合使用 推拉模型 ，而点赞/关注服务只进行写扩散&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;难点 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;问题 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读路径 可能存在业务方定制 和 非业务方定制 , 并且还需要考虑数据聚合&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;方案 :&lt;/p&gt;
&lt;p&gt;考虑优先读取业务定制的内容,通过每个handle 自己的实现进行读取。 然后再通过通用逻辑进行读取 。 另外对于 第三方业务方 如果降级的情况 会考虑 不进行读取 。 全局limit查询之后 再聚合内容进行排序&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;亮点 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用 Kafka 进行异步的处理 Feed 流&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设计了一个通用的和业务定制化的 Feed 流 。 关联之前开发的 点赞、收藏、文章服务&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;缺点&#34;&gt;缺点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Feed 流的存储比较简单 ，非常粗暴的使用 大JSON 进行存储 并且 数据表并没有做定期归档&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;写入服务还可以再优化，根据判断用户是否是活跃用户，判断是否要使用写扩散保证活跃用户的使用体验&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;作为面试官，按「2 年经验、能讲清一个完整 Feed 项目」来打分。&lt;/p&gt;
&lt;h2 id=&#34;总分62--100&#34;&gt;总分：&lt;strong&gt;62 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;得分&lt;/th&gt;
					&lt;th&gt;满分&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;需求与业务理解&lt;/td&gt;
					&lt;td&gt;12&lt;/td&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;关注/点赞/收藏/发文通知说清楚了&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;架构与方案设计&lt;/td&gt;
					&lt;td&gt;14&lt;/td&gt;
					&lt;td&gt;25&lt;/td&gt;
					&lt;td&gt;有推拉混合，但细节和取舍不够&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;数据与存储&lt;/td&gt;
					&lt;td&gt;8&lt;/td&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;表结构过简，缺容量与演进&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;工程实现（Kafka/异步）&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;有削峰思路，缺可靠性细节&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;难点与亮点表达&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;方向对，面试官追问容易露怯&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自我反思与边界&lt;/td&gt;
					&lt;td&gt;8&lt;/td&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;有缺点意识，但深度不够&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;优点&#34;&gt;优点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;业务闭环清晰&lt;/strong&gt;：从「谁要被通知」到「Feed 读写服务」再到「文章大 V 定制」，简历点能对上故事。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推拉混合有意识&lt;/strong&gt;：大 V 写发件箱、普通人写收件箱，这是 Feed 面试的核心考点，方向正确。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;知道自己的短板&lt;/strong&gt;：大 JSON、无归档、可按活跃用户优化写扩散——自我认知加分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工程上有基本取舍&lt;/strong&gt;：用 Kafka 换 1–10s 延迟，比「全都要实时」更真实。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;缺点面试里容易被打穿&#34;&gt;缺点（面试里容易被打穿）
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;「无缓存」说得太绝对&lt;/strong&gt;&lt;br&gt;
实时性 ≠ 不能缓存。常见做法是：收件箱最近 N 条 Redis ZSet、发件箱热 Key、读路径短 TTL。直接说「无」会被追问「QPS 上去怎么办」。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;表结构过于简陋&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单表 &lt;code&gt;Content&lt;/code&gt; 大 JSON：如何按 Type 过滤、分页、清理？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uid + ctime&lt;/code&gt; 索引是否联合？大 V 粉丝千万级写入怎么分库分表？&lt;/li&gt;
&lt;li&gt;发件箱表结构没写出来，推拉混合缺一半。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;推拉切换规则模糊&lt;/strong&gt;&lt;br&gt;
「按粉丝数判断」——阈值多少？阈值变化怎么办？粉丝从 999 涨到 1001 历史数据怎么处理？面试官一追问就虚。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;读路径描述含糊&lt;/strong&gt;&lt;br&gt;
「业务定制优先 → 通用逻辑 → 降级跳过 → limit 再聚合」——&lt;br&gt;
多 Handler 如何保证全局时间序？每个源 limit 多少？聚合复杂度？降级后一致性？这些才是 2 年候选人该补的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kafka 只停在「削峰」&lt;/strong&gt;&lt;br&gt;
缺：幂等、顺序（按 uid 分区？）、失败重试/死信、重复投递、消费者并发与乱序。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;亮点偏「做过什么」而非「解决了什么」&lt;/strong&gt;&lt;br&gt;
「用了 Kafka」「接了点赞收藏」——面试官更想听量化效果或具体坑（延迟、重复、空洞 Feed）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文档结构略乱&lt;/strong&gt;&lt;br&gt;
功能列表编号重复、发件箱模型缺失、流程图依赖图片但文字没补关键路径。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;建议按优先级&#34;&gt;建议（按优先级）
&lt;/h2&gt;&lt;h3 id=&#34;立刻补齐面试可讲-3-分钟版&#34;&gt;立刻补齐，面试可讲 3 分钟版
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;画清两条写路径 + 两条读路径&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写：普通用户 → 写扩散进粉丝收件箱；大 V → 只写发件箱。&lt;/li&gt;
&lt;li&gt;读：收件箱时间线 ∪ 关注列表里大 V 发件箱 → merge 按时间排序 → 分页。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;把「粉丝阈值」说死&lt;/strong&gt;&lt;br&gt;
例：粉丝 ≥ 1万走拉模式；并说明阈值变更时只影响新内容、或异步回填策略。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缓存改成「有边界的有」&lt;/strong&gt;&lt;br&gt;
例：用户 Feed 首屏 200 条 ZSet；未命中再查 DB；写路径异步更新。强调与「可容忍秒级延迟」一致。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kafka 准备 4 个追问答案&lt;/strong&gt;&lt;br&gt;
幂等（业务唯一键）、分区键（authorId/uid）、至少一次语义下的去重、消费失败策略。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;亮点改成「问题 → 方案 → 结果」&lt;/strong&gt;&lt;br&gt;
例：「大 V 发文写扩散导致写放大 → 粉丝阈值切拉模式 → 峰值写 QPS 从 X 降到 Y」（没有真实数字就说「预期/压测方向」）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;建议你能背下来的追问清单&#34;&gt;建议你能背下来的追问清单
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;分页用 offset 还是 cursor？空洞/重复怎么处理？&lt;/li&gt;
&lt;li&gt;取消关注、删文、隐藏后 Feed 如何失效？&lt;/li&gt;
&lt;li&gt;点赞/收藏也写 Feed，和文章 Feed 如何统一 Type？&lt;/li&gt;
&lt;li&gt;单用户收件箱无限增长怎么办（归档/TTL）？&lt;/li&gt;
&lt;li&gt;降级某个业务 Handler 后，用户看到的 Feed 如何保证「还能刷」？&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;面试官结论&#34;&gt;面试官结论
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;能过初级/中级偏初的筛选，但撑不起「独立设计过 Feed」的强叙事。&lt;/strong&gt;&lt;br&gt;
架构骨架有了（推拉混合 + 异步削峰 + 业务定制 Handler），但存储、缓存、一致性、容量、读聚合这几块偏薄，和 2 年「做过但未深挖」的画像吻合。&lt;/p&gt;
&lt;p&gt;把推拉边界、读合并算法、Kafka 可靠性、缓存策略补成可口述的闭环后，有机会提到 &lt;strong&gt;75–80&lt;/strong&gt;；再补上容量估算和一次真实故障/压测故事，才更像能打的中级候选人。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>评论 one</title>
        <link>https://ddlgitgzzhm.github.io/p/comment-0x02/</link>
        <pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/comment-0x02/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 : 设计并实现了 用户的 评论功能, 支持多层级的评论回复&lt;/p&gt;
&lt;h2 id=&#34;流程设计&#34;&gt;流程设计
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;需求分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;需要支持 用户 某篇文章进行评论&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;需要支持 用户 给评论 进行评论&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;需要支持 获取评论，以及获取子评论，创建评论，删除评论&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;模块分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户 可以给文章进行 评论 也可以给 用户的评论 进行评论 ， 可以抽象为 用户给某个资源进行评论 因此将评论 单独抽象成一个服务&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;评论服务模块&#34;&gt;评论服务模块
&lt;/h3&gt;&lt;h4 id=&#34;支持的功能&#34;&gt;支持的功能
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;获取第一页的评论&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除评论&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建评论&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取单条评论&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取子评论&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;表结构设计&#34;&gt;表结构设计
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;设计 &amp;lt;PID, id&amp;gt; 外键, 因为我们不允许子评论出现在一个不存在的父评论上 。 并且我们希望在删除父评论的时候 同步删除子评论。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;引入 RootID, 因为是通过 邻接表创建的 评论结构 。额外创建一个 RootId 列 能够优化查询顶级评论下全部评论的时间&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PID 和 RootID 的类型是 Sql.NullInt64 因为顶级节点的父节点是空 。 这里考虑优化成 -1&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Comment&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Uid&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Biz&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;index:biz_type_id&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;BizID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;index:biz_type_id&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;NullInt64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;index&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 外键指向的也是同一张表&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ParentComment&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Comment&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;ForeignKey:PID;AssociationForeignKey:ID;constraint:OnDelete:CASCADE&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RootID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;NullInt64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;index:root_ID_ctime&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Ctime&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`gorm:&amp;#34;index:root_ID_ctime&amp;#34;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 评论的内容&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Content&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Utime&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id=&#34;获取评论列表功能设计&#34;&gt;获取评论列表功能设计
&lt;/h4&gt;&lt;p&gt;我们需要获取对应业务的 第一页所有评论，并且返回前3条子评论&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;直接根据 biz 和 bizID 进行查询&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;考虑到并发问题 使用 minID 进行偏移控制 。 如果使用 offset 可能会频繁变化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在降级到时候 考虑不去读取子评论 。 遍历所有顶级节点 然后并发的去查询子评论&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;其他细节&#34;&gt;其他细节
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;创建和删除 的时候 通过外键进行约束&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查询顶级评论的时候 根据 RootId 进行优化&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://ddlgitgzzhm.github.io/p/comment-0x02/comment.png&#34;
	width=&#34;964&#34;
	height=&#34;551&#34;
	srcset=&#34;https://ddlgitgzzhm.github.io/p/comment-0x02/comment_hu_aaa190d859283d1c.png 480w, https://ddlgitgzzhm.github.io/p/comment-0x02/comment_hu_f10ddf38046ffb89.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;comment.png&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;174&#34;
		data-flex-basis=&#34;419px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;难点--亮点&#34;&gt;难点 &amp;amp; 亮点
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;问题 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于多级评论的表结构设计&lt;/p&gt;
&lt;p&gt;方案 :&lt;/p&gt;
&lt;p&gt;使用邻接表的方案进行设计评论表 。设计 外键 &amp;lt;pid, id&amp;gt; 控制 插入子评论的时候 不应该插入到不存在的父评论 。 以及删除父评论的时候应该同步删除子评论&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;亮点 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在查找第一页评论的时候 需要返回对应的前3条子评论 。 考虑在服务降级的时候，不走这部分慢查询 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;考虑 用户可以评论 别人的评论 和文章 将评论服务单独拆成一个服务 并且使用 biz + biz_id 进行通用化处理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 minId 进行第一次分页查询，避免 offset 在高并发插入下 会漏&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;表结构设计 冗余 RootID 列，在查询顶级评论的时候能够直接查询全部回复 ，而不需要递归一整颗树&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;缺点&#34;&gt;缺点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;没有较好的缓存方案，例如加载第一页的时候可以考虑 维护一个热榜评论，并且缓存第一页 定时刷新&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;总分68--100&#34;&gt;总分：&lt;strong&gt;68 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;对标 &lt;strong&gt;2 年 Golang&lt;/strong&gt;、简历点「多层级评论」：能讲清主干，但深度、完整性和面试表达还不够稳，容易被追问卡住。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;分项满分&#34;&gt;分项（满分）
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;分&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;需求分析 / 边界&lt;/td&gt;
					&lt;td&gt;14/20&lt;/td&gt;
					&lt;td&gt;功能列全了，缺非功能（QPS、延迟、一致性、审核）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;表结构 / 建模&lt;/td&gt;
					&lt;td&gt;16/20&lt;/td&gt;
					&lt;td&gt;邻接表 + RootID + FK 方向对，细节经不起深挖&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;查询 / 分页&lt;/td&gt;
					&lt;td&gt;12/20&lt;/td&gt;
					&lt;td&gt;知道 minID、降级，论证不严谨，实现路径模糊&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;亮点可讲性&lt;/td&gt;
					&lt;td&gt;14/20&lt;/td&gt;
					&lt;td&gt;有点，但缺「为什么 / 怎么权衡 / 踩过什么坑」&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;表达与材料质量&lt;/td&gt;
					&lt;td&gt;12/20&lt;/td&gt;
					&lt;td&gt;结构尚可，笔误多、描述空、像草稿&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&#34;优点&#34;&gt;优点
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;服务抽象合理&lt;/strong&gt;：&lt;code&gt;biz + biz_id&lt;/code&gt; 把「评文章 / 评评论」统一成资源评论，符合可扩展拆分，面试官一般认可。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;树存储选型正确&lt;/strong&gt;：邻接表 + 冗余 &lt;code&gt;RootID&lt;/code&gt; 是常见方案；外键约束父子存在性、级联删也说得通。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有工程意识&lt;/strong&gt;：第一页带前 3 条子评、降级不查子评、&lt;code&gt;minID&lt;/code&gt; 代替 &lt;code&gt;offset&lt;/code&gt;，说明想过热路径和并发插入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有自省&lt;/strong&gt;：主动写「缺缓存」比硬吹完整方案更加分。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;缺点面试里会被追的&#34;&gt;缺点（面试里会被追的）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;「多层级」与实现深度不匹配&lt;/strong&gt;&lt;br&gt;
简历写多层级，正文几乎是「顶级 + 若干子评」。要明确：产品是无限嵌套还是只展示两级；邻接表如何保证插入时 &lt;code&gt;PID/RootID&lt;/code&gt; 一致。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分页说法不严谨&lt;/strong&gt;&lt;br&gt;
高并发下 &lt;code&gt;offset&lt;/code&gt; 的问题主要是&lt;strong&gt;重复/跳过、深分页成本&lt;/strong&gt;，不是简单「漏数据」。&lt;code&gt;minID&lt;/code&gt;（游标）要讲清排序键（&lt;code&gt;id&lt;/code&gt; 还是 &lt;code&gt;ctime&lt;/code&gt;）、同时间戳、方向（上滑/下滑）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;级联硬删很脆&lt;/strong&gt;&lt;br&gt;
真实产品多为软删、子评保留并标「父评已删」。只说 &lt;code&gt;OnDelete:CASCADE&lt;/code&gt; 会被问：误删恢复、审核下架、计数怎么维护。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;「前 3 条子评」实现空洞&lt;/strong&gt;&lt;br&gt;
按时间还是热度？一次 SQL 还是 N+1？并发查子评的限流、错误、超时？降级开关与指标？这些不补，亮点站不住。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;空值方案半吊子&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;sql.NullInt64&lt;/code&gt; vs &lt;code&gt;-1&lt;/code&gt; 只提「考虑」，没有索引、查询条件、GORM 写入的结论。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺面试常问块&lt;/strong&gt;&lt;br&gt;
创建时事务与 RootID 赋值、内容长度/敏感词、计数（回复数）、鉴权（谁可删）、幂等、缓存一致性——几乎空白。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;材料完成度低&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;description: xxxx&lt;/code&gt;、错别字、口语化、无接口草图/时序，显得准备不充分。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;建议按优先级&#34;&gt;建议（按优先级）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;准备 3 分钟口述稿&lt;/strong&gt;：需求 → 为何独立评论服务 → 表（PID/RootID）→ 第一页查询（含前 3 条子评）→ 降级 → 已知不足（缓存）。控制在 2～3 分钟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;画清两种删除语义&lt;/strong&gt;：硬删级联 vs 软删；选一种并说清对列表、计数、子评展示的影响。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把分页讲死&lt;/strong&gt;：排序字段、&lt;code&gt;WHERE id &amp;lt; ? ORDER BY id DESC LIMIT&lt;/code&gt;、为何不用 &lt;code&gt;offset&lt;/code&gt;、边界 case。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;补「前 3 条」实现&lt;/strong&gt;：例如按 &lt;code&gt;root_id&lt;/code&gt; 批量取 + 应用层截断，或窗口函数；说明为何不用「每条顶级再查一次」做默认路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;补一条缓存方案（哪怕未做）&lt;/strong&gt;：第一页热评 ZSet / 本地+Redis、失效（写删、TTL）、穿透与击穿——对应你自己写的缺点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对齐简历用词&lt;/strong&gt;：若只做两级展示，简历改成「支持回复与两级展示」；若真支持 N 级，补插入校验与查询深度限制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;准备 5 个追问答案&lt;/strong&gt;：并发插评、父评不存在、删父留子、深分页、评论热点文章打爆 DB。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;一句话结论&#34;&gt;一句话结论
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;68 分&lt;/strong&gt;：方向和关键词对，够「做过评论」的入门讲述；要过认真的 Golang 一面/二面，需要把分页、删除语义、子评批量查询和缓存权衡讲扎实，并把材料从草稿修到能直接口述的版本。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>热榜 one</title>
        <link>https://ddlgitgzzhm.github.io/p/rank-0x02/</link>
        <pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/rank-0x02/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 : 实现多实例部署下的 热榜 异步计算和查询&lt;/p&gt;
&lt;h2 id=&#34;流程设计&#34;&gt;流程设计
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;需求分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;需要能够获取到最近7天 前100点赞的数据&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;模块分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设计一个 RankService 支持获取 TopN 的操作&lt;/p&gt;
&lt;h3 id=&#34;模块设计&#34;&gt;模块设计
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;提供一个基于 本地缓存 + Zset 实现的热榜设计 并且通过 分key 热点信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提供一个离线计算的热榜服务, 使用小根堆进行维护数据 。使用分布式锁 启动时固定实例进行计算热榜&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://ddlgitgzzhm.github.io/p/rank-0x02/rank.png&#34;
	width=&#34;1776&#34;
	height=&#34;1498&#34;
	srcset=&#34;https://ddlgitgzzhm.github.io/p/rank-0x02/rank_hu_308e84b830efd7e2.png 480w, https://ddlgitgzzhm.github.io/p/rank-0x02/rank_hu_3d33baea75324321.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;rank.png&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;118&#34;
		data-flex-basis=&#34;284px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;难点--亮点&#34;&gt;难点 &amp;amp; 亮点
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;问题 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;热榜服务要求高可用 高性能 以及处理较大的数据 怎么做到 ?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用 本地缓存 + Redis 的方式进行 处理高可用 。 如果本地缓存和redis 缓存都实效 加载本地已过期的进行兜底&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;较大的数据 可以采用 分key 的方式进行处理 。 或者通过业务折中的方式，例如 只考虑最近7天的 top 数据，最近 7 天可能最多也就100w条&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;问题 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Zset 不可能进行较大数据量大计算, 这会给机器带来很大的压力&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;采用定时任务 离线计算 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 优先队列维护一个小根堆，固定 TopN 的数量。 每次分批进行获取数据加载计算&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;亮点 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;考虑在多实例部署的情况下, 可能会存在 多个实例同时计算一个热榜，并且每个热榜都会有细微的偏差。 使用分布式锁+自动续约的机制 控制同一时间只有一个 节点 在进行计算&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 Hacknews 模型 &lt;code&gt;Score = (p - 1) / (T + 2)^G&lt;/code&gt; 公式进行计算热点信息。防止点赞数量被老贴市场霸占&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Redis 存 TopN 的时候晴空 Content 避免占用较大的内存&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;缺点&#34;&gt;缺点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;缓存方式比较简单&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;强一致性不足&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;总分62--100&#34;&gt;总分：&lt;strong&gt;62 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;按「2 年 Golang 后端」面试标准看：方向对、有架构意识，但&lt;strong&gt;深度不够、表述不严谨、经不起追问&lt;/strong&gt;。能过初筛讲清故事，很难稳住深挖。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
					&lt;th&gt;简评&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;问题定义与业务抽象&lt;/td&gt;
					&lt;td&gt;70&lt;/td&gt;
					&lt;td&gt;TopN + 近 7 天边界清楚&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;架构设计完整性&lt;/td&gt;
					&lt;td&gt;65&lt;/td&gt;
					&lt;td&gt;多级缓存 + 离线计算主链路合理&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;技术细节与落地&lt;/td&gt;
					&lt;td&gt;50&lt;/td&gt;
					&lt;td&gt;缺关键实现、参数、边界条件&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;难点亮点说服力&lt;/td&gt;
					&lt;td&gt;68&lt;/td&gt;
					&lt;td&gt;锁、堆、HN 公式有亮点，但讲不透&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;表达与专业度&lt;/td&gt;
					&lt;td&gt;55&lt;/td&gt;
					&lt;td&gt;错别字、占位符多，像草稿&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;面试可追问准备&lt;/td&gt;
					&lt;td&gt;48&lt;/td&gt;
					&lt;td&gt;几乎没有「被问住」时的备用料&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;优点&#34;&gt;优点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;主路径合理&lt;/strong&gt;：查询走本地缓存 → Redis → 兜底；计算走离线 TopN + 小根堆，符合热榜常见做法，不是乱堆关键词。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有业务折中意识&lt;/strong&gt;：用「近 7 天 / 约 100 万」控数据量，比硬扛全量排序更像有生产经验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;亮点选得对&lt;/strong&gt;：多实例分布式锁、Hacker News 时间衰减、ZSet 不存 Content——这些正是面试官能追问的点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主动暴露缺点&lt;/strong&gt;：缓存简单、强一致不足，至少说明你知道系统边界，比只会吹「高可用高性能」好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有架构图&lt;/strong&gt;：比纯文字更容易在面试里讲清读写两条链路。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;缺点面试会被卡住的地方&#34;&gt;缺点（面试会被卡住的地方）
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;简历描述太空&lt;/strong&gt;：「多实例部署下的热榜异步计算和查询」没有规模、指标、职责边界，面试官第一问就会要数字。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方案停在名词层&lt;/strong&gt;：分 key、小根堆、分布式锁、自动续约都点到了，但缺：
&lt;ul&gt;
&lt;li&gt;分 key 规则（按天？按互动类型？怎么合并 TopN？）&lt;/li&gt;
&lt;li&gt;堆的维护时机与批大小&lt;/li&gt;
&lt;li&gt;锁用 Redis/etcd？续约失败怎么办？持锁实例挂了呢？&lt;/li&gt;
&lt;li&gt;本地缓存过期后仍兜底，跨实例一致性怎么说？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公式只写了不解释&lt;/strong&gt;：&lt;code&gt;Score = (p - 1) / (T + 2)^G&lt;/code&gt; 里 p/T/G 含义、G 怎么选、和「点赞数排序」差在哪，不讲等于没亮点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图文对不齐&lt;/strong&gt;：图里有并发 Queue、分区、&lt;code&gt;forceGetLocal&lt;/code&gt;，正文几乎没展开，面试官对着图一问，你会空。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;「高可用」论证弱&lt;/strong&gt;：本地 + Redis 失效后用过期数据，是降级不是高可用；没有说明降级 SLA、空榜、穿透、击穿。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表达不专业&lt;/strong&gt;：&lt;code&gt;xxxx&lt;/code&gt;、&lt;code&gt;实效→失效&lt;/code&gt;、&lt;code&gt;晴空→清空&lt;/code&gt;、&lt;code&gt;Hacknews&lt;/code&gt;，会直接拉低可信度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点写完就停了&lt;/strong&gt;：「强一致性不足」没有影响面和为何可接受，像没想完。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;建议按面试准备优先级&#34;&gt;建议（按面试准备优先级）
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;补一组可说的数字&lt;/strong&gt;：数据量、TopN、计算周期、单次计算耗时、QPS、缓存命中率、锁超时/续约周期。没有真实数也要给合理量级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;准备 3 层回答&lt;/strong&gt;：一句话结论 → 架构步骤 → 细节/权衡。例如「为何小根堆」：只要 Top100，堆复杂度 O(n log k)，比全量排序或大 ZSet 更合适。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把追问清单写进稿子&lt;/strong&gt;，至少覆盖：
&lt;ul&gt;
&lt;li&gt;多实例本地缓存不一致怎么办？&lt;/li&gt;
&lt;li&gt;计算中途失败，半成品会不会写进 Redis？&lt;/li&gt;
&lt;li&gt;分 key 后如何得到全局 Top100？&lt;/li&gt;
&lt;li&gt;锁续约失败 / 时钟漂移 / 脑裂&lt;/li&gt;
&lt;li&gt;缓存击穿（热榜 key 过期瞬间）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;讲清 HN 公式&lt;/strong&gt;：老帖高赞为何会被新帖追上；G 调大/调小的业务含义。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把图里的并发 Queue 讲成 Go 实现&lt;/strong&gt;：channel + worker pool、batch size、背压，对应 2 年经验该有的落地感。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;润色表述&lt;/strong&gt;：去掉占位符和错别字；简历改成「近 7 天 Top100 热榜：离线小根堆计算 + Redis ZSet + 本地缓存，分布式锁保证单实例计算」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点改成「已知取舍」&lt;/strong&gt;：例如强一致不必要，因为热榜允许分钟级延迟；用计算周期 + TTL 把最终一致说清楚。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;面试官视角一句话&#34;&gt;面试官视角一句话
&lt;/h2&gt;&lt;p&gt;作为 2 年经验候选人，这份材料证明你&lt;strong&gt;做过热榜、懂分层和折中&lt;/strong&gt;，但目前更像设计草稿，不像能扛 20–30 分钟深挖的面试稿。把「分 key / 锁续约 / 公式参数 / 并发队列 / 降级语义」补实，并清掉错别字，分数大概能到 &lt;strong&gt;75–80&lt;/strong&gt;；再补上指标和失败场景，才比较稳。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>搜索 one</title>
        <link>https://ddlgitgzzhm.github.io/p/select-0x02/</link>
        <pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/select-0x02/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 : 设计并实现了 搜索&lt;/p&gt;
&lt;h2 id=&#34;流程设计&#34;&gt;流程设计
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;需求分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;支持用户创建 标签, 查看已有标签, 删除标签&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持用户给指定文章打标签&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持用户查询 对应关键字的文章 并且把 打过标签的文章优先返回&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;模块分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;考虑标签应该是一个通用的功能 所以将标签单独拆分一个模块&lt;/p&gt;
&lt;p&gt;并且不希望业务直接写入ES，单独设计一层 搜索和插入服务。 将读和写拆成两个服务&lt;/p&gt;
&lt;h3 id=&#34;标签服务-模块&#34;&gt;标签服务 模块
&lt;/h3&gt;&lt;h4 id=&#34;支持功能&#34;&gt;支持功能
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;覆盖标签&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建标签&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取用户的全部标签&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取用户给某篇文章打的全部标签&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;表结构设计&#34;&gt;表结构设计
&lt;/h4&gt;&lt;p&gt;设计 Tag 表, 和 TagBiz 表 用于统计用户创建的 tag 和 用户给文章标记的 Tag&lt;/p&gt;
&lt;p&gt;索引设计 :&lt;/p&gt;
&lt;p&gt;在 Tag 表上设计了 uid 为 索引，保证 获取用户的全部标签的时候能够命中&lt;/p&gt;
&lt;p&gt;在TagBiz表设计了 &lt;code&gt;biz_id,biz&lt;/code&gt; 和 &lt;code&gt;uid&lt;/code&gt; 设计了索引, 同时tagBiz 设置 &lt;code&gt;target_id&lt;/code&gt; 外键连接 &lt;code&gt;tag&lt;/code&gt; 表&lt;/p&gt;
&lt;p&gt;考虑 uid 的不可变性, 设计 tagBiz 表的时候冗余了  uid 的设计，从而在查寻用户给某篇文章打的标签的时候 不需要去Join Tag表 加速查询&lt;/p&gt;
&lt;h4 id=&#34;缓存设计&#34;&gt;缓存设计
&lt;/h4&gt;&lt;p&gt;在获取用户的全部标签的时候, 考虑使用缓存预加载进行优化&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在程序启动的时候，提前全量扫描一次数据库&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将数据分key 加载到 redis-list 中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在获取全部标签的时候 预先读取缓存再去操作数据库&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;插入搜索模块&#34;&gt;插入搜索模块
&lt;/h3&gt;&lt;p&gt;支持&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;通用的插入逻辑 &lt;code&gt;content_id,content&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;业务定制化的 user 和 article 的插入逻辑&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;es-索引设计&#34;&gt;Es 索引设计
&lt;/h4&gt;&lt;p&gt;文章表结构设计 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;title , content , id ,status . 这里引入 status 主要是为了后续 用户可以查看到仅自己可以见的文章处理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用户表设计 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;nickename, email(text), phone, id .  email设计称为 text 而不是keyword是因为正常人很难记住邮箱&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;数据插入处理&#34;&gt;数据插入处理
&lt;/h4&gt;&lt;p&gt;使用 Kafka 开启 3个 Consumer 分别监听 文章, User, Any 的插入&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;因为我们在索引中引入了 id 字段, 保证了在并发场景下是 upsert 语意&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;查询搜索模块&#34;&gt;查询搜索模块
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;支持查询有关用户的信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持查询有关文章的信息&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;并且将用户打过标签文章的内容优先返回&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;联合-tag-查询&#34;&gt;联合 Tag 查询
&lt;/h4&gt;&lt;p&gt;Es 支持通过 父子关系 和 内嵌文章进行联合查询 但是性能过差 。 我们这里采用二次查询的方式实现联合查询&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;先查询 Tag 里面的内容， 查询当前 用户已经打过标签的 文章 id&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;再去查询有关keywords的文章信息，并且使用 TermQuery Boost 增加在第一次查询出来的权重&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://ddlgitgzzhm.github.io/p/select-0x02/es.png&#34;
	width=&#34;1236&#34;
	height=&#34;703&#34;
	srcset=&#34;https://ddlgitgzzhm.github.io/p/select-0x02/es_hu_e068f0ac8412cddd.png 480w, https://ddlgitgzzhm.github.io/p/select-0x02/es_hu_77a8f5c3b845798d.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;es.png&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;175&#34;
		data-flex-basis=&#34;421px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;难点--亮点&#34;&gt;难点 &amp;amp; 亮点
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;问题 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户在给某个文章打标签的时候 都会加载一次自己已有的全部标签, 预计这里会是一个高频的全量扫表过程&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;方案 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用 Redis-list 预加载一次全部用户的全部标签内容&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每次查询优先查询缓存 再查询数据库,对于未命中的内容 再次更新缓存&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;问题 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在获取用户给某篇文章的所有标签内容处理时 都需要去 Join 一次 Tag 表 . Join操作会变慢&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;方案 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;因为 Uid 的不可变性。我们设计的 tag 是基于 Uid 的 。不存在 用户A给 文章 A打了标签。 突然这个标签变成用户 B 的情况 。 所以在 TagBiz 冗余了 Uid 字段加速查询&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;问题 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户对一篇文章重复打标签可能会有并发问题 。 因为我们使用 kafka 进行发送Es信息&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;方案 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在对应 producer 的时候, 使用 key 绑定对应的uid 保证同一个uid 的内容只会发送到同一个分区&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;亮点 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;实现了 两个 Tag 内容之前的查询 , 使用二次查询的方法 实现 用户查询有关文章的时候, 优先返回已打标签的文章&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;亮点 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;搜索服务拆分成 &lt;code&gt;sync&lt;/code&gt; 和 &lt;code&gt;search&lt;/code&gt; ,读写分离 。 业务方面不直连 Es . 并且 支持 &lt;code&gt;InputAny&lt;/code&gt; 方便业务快速上线&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;亮点 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;预加载的时候 使用 pipeline 进行打包发送 redis 指令&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;缺点&#34;&gt;缺点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;仅实现了 文章和标签的相关服务 。 实现内容过少，并没有聚合点赞收藏等业务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kafka 部分少了兜底机制&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;总分61--100&#34;&gt;总分：&lt;strong&gt;61 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;定位：2 年经验、项目深挖准备材料。能讲清主流程和模块边界，但多处方案经不起追问，Golang 工程深度偏弱。面试中「能讲」可以，&lt;strong&gt;扛不住连环追问&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;需求与模块拆分&lt;/td&gt;
					&lt;td&gt;72&lt;/td&gt;
					&lt;td&gt;标签独立、读写拆分思路清楚&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;表结构 / 索引&lt;/td&gt;
					&lt;td&gt;65&lt;/td&gt;
					&lt;td&gt;有索引与冗余意识，外键与命名易被挑&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缓存设计&lt;/td&gt;
					&lt;td&gt;42&lt;/td&gt;
					&lt;td&gt;启动全量预加载是明显硬伤&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ES / 搜索&lt;/td&gt;
					&lt;td&gt;60&lt;/td&gt;
					&lt;td&gt;二次查询 + Boost 合理，缺量化与一致性&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Kafka / 并发&lt;/td&gt;
					&lt;td&gt;48&lt;/td&gt;
					&lt;td&gt;问题与解法错位&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;难点亮点表达&lt;/td&gt;
					&lt;td&gt;68&lt;/td&gt;
					&lt;td&gt;有结构，但方案可信度不足&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自我认知&lt;/td&gt;
					&lt;td&gt;70&lt;/td&gt;
					&lt;td&gt;知道功能少、缺兜底&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Golang 工程深度&lt;/td&gt;
					&lt;td&gt;35&lt;/td&gt;
					&lt;td&gt;几乎看不到语言/并发/落地细节&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&#34;优点&#34;&gt;优点
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;需求 → 模块 → 表 → 缓存 → MQ → ES&lt;/strong&gt; 叙事完整，面试官能跟着走。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签独立模块 + sync/search 读写分离 + 业务不直连 ES&lt;/strong&gt;，符合中级对边界的基本要求。&lt;/li&gt;
&lt;li&gt;TagBiz &lt;strong&gt;冗余 uid 避免 Join&lt;/strong&gt;、ES &lt;strong&gt;二次查询 + Term Boost&lt;/strong&gt;（相对 nested/父子文档）是可讲的取舍。&lt;/li&gt;
&lt;li&gt;有&lt;strong&gt;难点/亮点/缺点&lt;/strong&gt;框架，并提到 Kafka 缺兜底、功能未聚合，比只会吹亮点强。&lt;/li&gt;
&lt;li&gt;提到 &lt;strong&gt;pipeline 批量写 Redis&lt;/strong&gt;、&lt;strong&gt;id upsert&lt;/strong&gt;，说明接触过落地细节。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;缺点面试里最容易被打穿的点&#34;&gt;缺点（面试里最容易被打穿的点）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缓存方案危险&lt;/strong&gt;&lt;br&gt;
启动全量扫库、把「全部用户全部标签」塞进 Redis List：冷启动、内存、扩缩容、局部失效都没讲。2 年经验里这是减分点，不是加分点。List 也不适合标签的增删改；一致性（写标签后如何更新缓存）几乎空白。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;并发问题与解法错位&lt;/strong&gt;&lt;br&gt;
「重复打标签」应落到：&lt;strong&gt;DB 唯一约束 / 事务 / 幂等&lt;/strong&gt;。Kafka 按 uid 分区只保证&lt;strong&gt;同 uid 有序&lt;/strong&gt;，解决不了重复打标、更解决不了跨服务幂等。面试官一追问就会露馅。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ES 细节经不起问&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;email 用 text：理由偏弱，应讲分词、模糊、精确匹配、keyword 多字段。&lt;/li&gt;
&lt;li&gt;status 字段只点到，未见权限/过滤怎么做。&lt;/li&gt;
&lt;li&gt;upsert、同步延迟、最终一致性、失败重试几乎没准备。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;表设计表述不严谨&lt;/strong&gt;&lt;br&gt;
TagBiz &lt;code&gt;target_id&lt;/code&gt; 外键、索引 &lt;code&gt;(biz_id, biz)&lt;/code&gt; + &lt;code&gt;uid&lt;/code&gt; 缺查询路径说明；「覆盖标签」语义不清。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺量化与 Golang 深度&lt;/strong&gt;&lt;br&gt;
无 QPS、延迟、数据量、缓存命中率。无接口抽象、超时取消、consumer 并发、错误处理、压测——对 Golang 岗这是硬伤。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;材料完成度&lt;/strong&gt;&lt;br&gt;
「其他细节」空、序号重复、resume「搜索」与正文「标签+搜索」焦点略散。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;建议按优先级&#34;&gt;建议（按优先级）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 重写缓存故事（必改）&lt;/strong&gt;&lt;br&gt;
改成：按 uid 懒加载 / 热点预热；Hash 或 String+JSON；打标/删标时删缓存或局部更新；讲穿透、击穿、雪崩之一即可。删掉「启动全量预加载所有用户」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 对齐「重复打标」叙事&lt;/strong&gt;&lt;br&gt;
主答：唯一索引 &lt;code&gt;(uid, biz, biz_id, tag_id)&lt;/code&gt; + 幂等；Kafka 分区 key 只作为「同步有序/同用户串行」的补充，不要当并发正解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 补 3 个可量化数字&lt;/strong&gt;&lt;br&gt;
例：标签人均条数、搜索 P99、sync 延迟、日增量。没有真实数据就给设计目标量级，比没有强。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 准备 5 个追问答案&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;标签写后搜索多久可见？&lt;/li&gt;
&lt;li&gt;sync 失败怎么办？（重试、DLQ、对账）&lt;/li&gt;
&lt;li&gt;缓存与 DB 不一致怎么办？&lt;/li&gt;
&lt;li&gt;Boost 权重怎么定、如何验证效果？&lt;/li&gt;
&lt;li&gt;为何不用 nested / join / 宽表？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. 补 Golang 落地半页&lt;/strong&gt;&lt;br&gt;
Consumer 并发模型、context 超时、接口分层、ES client 错误处理、本地单测怎么 mock——面试官常从这里区分 1 年和 2 年。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. 收束简历表述&lt;/strong&gt;&lt;br&gt;
简历写「标签 + 搜索（读写分离）」或主讲一条线，避免「实现了搜索」却大段讲标签预热。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;面试官一句话结论&#34;&gt;面试官一句话结论
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;能过初筛讲项目，过不了深挖。&lt;/strong&gt; 模块拆分和二次查询+Boost 可以撑住 10–15 分钟；缓存全量预加载和「Kafka 解决重复打标」会把印象从「还行」拉到「基础不扎实」。把这两处改掉，材料有机会到 &lt;strong&gt;75+&lt;/strong&gt;；再补量化与 Golang 细节，更接近 2 年合格线。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>打赏 one</title>
        <link>https://ddlgitgzzhm.github.io/p/payment-0x02/</link>
        <pubDate>Wed, 05 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/payment-0x02/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 : 支持用户使用微信支付进行文章的打赏功能 以及 用户对账流程&lt;/p&gt;
&lt;h2 id=&#34;流程设计&#34;&gt;流程设计
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;需求分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;用户能够选择一篇文章 通过 微信支付 进行打赏&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支付成功后 用户能够在自己的余额里面看到扣款 以及 被打赏的金额，同时可以看到对应的流水&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;模块设计 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;考虑支付是一个通用的模块, 并且支付可以额外对接审计，账号，反洗钱等模块 所以把支付模块单独拆分出来&lt;/p&gt;
&lt;p&gt;设计 &lt;code&gt;支付、用户资产、打赏&lt;/code&gt; 三个模块&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;功能分析 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用户模块支持 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;查看业务流水&lt;/li&gt;
&lt;li&gt;更新用户资产&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;打赏模块支持 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;查看文章打赏记录&lt;/li&gt;
&lt;li&gt;发起支付请求&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;支付模块支持 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;向第三方发起支付请求&lt;/li&gt;
&lt;li&gt;接受微信支付的calback&lt;/li&gt;
&lt;li&gt;查询发起的支付信息&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://ddlgitgzzhm.github.io/p/payment-0x02/all.png&#34;
	width=&#34;1059&#34;
	height=&#34;682&#34;
	srcset=&#34;https://ddlgitgzzhm.github.io/p/payment-0x02/all_hu_a10998516597b8fa.png 480w, https://ddlgitgzzhm.github.io/p/payment-0x02/all_hu_1e71cb306e291f57.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;all.png&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;155&#34;
		data-flex-basis=&#34;372px&#34;
	
&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;难点--亮点&#34;&gt;难点 &amp;amp; 亮点
&lt;/h2&gt;&lt;p&gt;问题 :&lt;/p&gt;
&lt;p&gt;微信支付回调后如果成功那么不会在此推送消息, 我们使用 MQ进行通知下游,如果MQ发送失败那么将丢失该信息&lt;/p&gt;
&lt;p&gt;方案 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;引入 本地消息表&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 本地事务 开启 支付表 和 本地消息表的写入 。 并且设置 消息表 init&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只有当MQ发送成功之后才会设置 本地表 success 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;定时任务 定时轮询 init 的任务进行补偿&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;问题 :&lt;/p&gt;
&lt;p&gt;由于本地消息表的引入, 可能带来重复消费的问题&lt;/p&gt;
&lt;p&gt;方案 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在下游账户处理模块， 设置了 &lt;code&gt;biz_id + biz + account + account_type&lt;/code&gt; 的联合唯一索引&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;并且引入 redis 进行查询优化, 预先判断 redis 的状态&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;问题 :&lt;/p&gt;
&lt;p&gt;可能存在 微信回调消息发送失败, 微信回调只会默认会在 30分钟之后不再回调 不管成功或是失败&lt;/p&gt;
&lt;p&gt;方案 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;定时任务 定期扫描 超过约30分钟的未完结订单 主动查询微信状态 并写回&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;问题 :&lt;/p&gt;
&lt;p&gt;获取打赏信息的时候 ,我们会先去查询 打赏表的数据,如果打赏表数据还未完成 。会通过慢路径查询支付表的信息进行更新 用于加快状态的轮询。&lt;/p&gt;
&lt;p&gt;方案 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;考虑限流时候降级, 对于限流的时候不走慢路径&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 账户资产更新 MQ 的时候 会一并更新对应的 状态&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;亮点 :&lt;/p&gt;
&lt;p&gt;模块拆分 与 边界  + gRPC&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;支付只关心第三方与支付单状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打赏只关心 业务单和分账意图&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;账户只关心余额与流水&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;缺点&#34;&gt;缺点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;缺少定时自动对账的机制。 涉及 &lt;code&gt;打赏-支付-记帐&lt;/code&gt; 三个流程&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 MQ 更新 账户资产 和  打赏表的时候 。 并不是强一致的, 我们优先更新 打赏表, 如果之后调用 rpc 处理 账户资产失败了 。 那么会发送告警 由人工手动修复&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;其他细节&#34;&gt;其他细节
&lt;/h2&gt;&lt;h3 id=&#34;支付消息必然发送成功&#34;&gt;支付消息必然发送成功
&lt;/h3&gt;&lt;p&gt;支付的一个核心步骤是由 微信来回调的 . 我们原先的处理是&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;微信回调&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;更新支付表状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发送消息给 MQ 通知账户变更&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果这里 MQ 发送失败,那么将会丢失这笔消息 。但是支付消息可以认为是一个非常关键的功能 。&lt;/p&gt;
&lt;p&gt;所以考虑优化引入了 本地消息表&lt;/p&gt;
&lt;p&gt;表结构设计&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;主要字段 &lt;code&gt;content&lt;/code&gt; 和 &lt;code&gt;status&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我们通过 content 记录原有的 &lt;code&gt;MQ&lt;/code&gt; 信息 使用 &lt;code&gt;status&lt;/code&gt; 维护 MQ 的状态 分为 &lt;code&gt;init,success,failed&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;id:&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;stauts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;Ctime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;Utime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;https://ddlgitgzzhm.github.io/p/payment-0x02/must_produce.png&#34;
	width=&#34;1048&#34;
	height=&#34;623&#34;
	srcset=&#34;https://ddlgitgzzhm.github.io/p/payment-0x02/must_produce_hu_55f31dc902d37ca8.png 480w, https://ddlgitgzzhm.github.io/p/payment-0x02/must_produce_hu_4f9d44917a88dbc4.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;must_produce.png&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;168&#34;
		data-flex-basis=&#34;403px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;流程优化设计 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用 事务控制 更新 支付表状态 和 写入本地事务表 并且初始化 init&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只有当我们成功发送 MQ 之后才把 本地事务表设置 success&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里可能会造成重复发送&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;开启一个定时任务，批次获取 init 的状态, 然后发送 MQ 消息 。 如果半小时内都发送失败 设置 failed . 发送成功之后设置 success&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;接口幂等性分析&#34;&gt;接口幂等性分析
&lt;/h3&gt;&lt;p&gt;由于改造了本地事务之后，可能会产生重复消费数据 . 我们在下游 账户更新的时候&lt;/p&gt;
&lt;p&gt;通过 redis+ 本地索引来处理 。&lt;/p&gt;
&lt;p&gt;我们设置 账户流水表的 唯一索引 &lt;code&gt;biz,biz_id,account,account_type&lt;/code&gt; 进行过滤&lt;/p&gt;
&lt;p&gt;&lt;code&gt;biz_id&lt;/code&gt; 就是 微信回调里面带的 &lt;code&gt;tradeNoId&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;使用 redis 进行初步过滤 使用 唯一索引进行兜底&lt;/p&gt;
&lt;h3 id=&#34;打赏详情降级处理&#34;&gt;打赏详情降级处理
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;我们打赏的时候 会先查询我们打赏表 如果打赏已经是支付状态 会直接返回&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;否则会去查询一遍支付表&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于限速的状态,我们会抛弃这个慢路径。 这条慢路径，由 MQ 进行处理, MQ 会较慢一点时间 更新到支付表的状态 。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;44
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;45
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;46
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;47
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;48
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;49
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;50
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;51
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;52
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;WechatNativeRewardService&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;GetReward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;rid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;uid&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Reward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 快路径&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;repo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;GetReward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;rid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Reward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Uid&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;uid&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 说明是非法查询&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Reward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;查询的打赏记录和打赏人对不上&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 有可能，我的打赏记录，还是 Init 状态&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 已经是完结状态&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Completed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;limited&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 我已经知道你的支付结果了&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 这个时候，考虑到支付到查询结果，我们搞一个慢路径&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 你有可能支付了，但是我 reward 本身没有收到通知&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 我直接查询 payment，&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 只能解决，支付收到了，但是 reward 没收到&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 降级状态，限流状态，熔断状态，不要走慢路径&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;GetPayment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pmtv1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GetPaymentRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;BizTradeNo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;bizTradeNO&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 这边我们直接返回从数据库查询的数据&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;慢路径查询支付结果失败&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;			&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Int64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;rid&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 更新状态&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;switch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pmtv1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PaymentStatus_PaymentStatusFailed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RewardStatusFailed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pmtv1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PaymentStatus_PaymentStatusInit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RewardStatusInit&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pmtv1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PaymentStatus_PaymentStatusSuccess&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RewardStatusPayed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pmtv1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PaymentStatus_PaymentStatusRefund&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 理论上来说不可能出现这个，直接设置为失败&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;domain&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RewardStatusFailed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;repo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;UpdateStatus&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;rid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;更新本地打赏状态失败&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;			&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Int64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;rid&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;		&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;总分72--100&#34;&gt;总分：&lt;strong&gt;72 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;对应 &lt;strong&gt;2 年经验中级偏上&lt;/strong&gt;：系统设计与可靠性意识不错，能讲清主链路和几个关键问题；但深度、闭环和对面试追问的准备还不够稳。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;评分拆解&#34;&gt;评分拆解
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;需求与业务理解&lt;/td&gt;
					&lt;td&gt;14/20&lt;/td&gt;
					&lt;td&gt;主流程清楚，但对账、分账、退款几乎空缺&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;模块拆分与边界&lt;/td&gt;
					&lt;td&gt;16/20&lt;/td&gt;
					&lt;td&gt;支付 / 打赏 / 账户划分合理，亮点明确&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可靠性设计（消息必达、补偿）&lt;/td&gt;
					&lt;td&gt;18/20&lt;/td&gt;
					&lt;td&gt;本地消息表 + 定时补偿是加分项&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;幂等与一致性&lt;/td&gt;
					&lt;td&gt;12/20&lt;/td&gt;
					&lt;td&gt;有唯一索引思路，但一致性模型讲得偏浅&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;降级与工程细节&lt;/td&gt;
					&lt;td&gt;8/10&lt;/td&gt;
					&lt;td&gt;慢路径 + 限流降级有工程味道&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;表达与面试可讲性&lt;/td&gt;
					&lt;td&gt;4/10&lt;/td&gt;
					&lt;td&gt;结构散、术语错误多，容易被追问打穿&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&#34;优点&#34;&gt;优点
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;边界意识好&lt;/strong&gt;&lt;br&gt;
「支付只关心第三方与支付单」「打赏关心业务单」「账户关心余额流水」——这是面试里很吃香的表述，说明做过拆分，不是堆接口。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;抓住了支付域真难点&lt;/strong&gt;&lt;br&gt;
微信回调只推一次成功、MQ 可能丢、回调有时效——都点到了，再配本地消息表 + 定时扫 init 补偿，对 2 年经验来说已经超过很多只会 CRUD 的候选人。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;幂等有兜底思路&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;biz + biz_id + account + account_type&lt;/code&gt; 唯一索引 + Redis 前置过滤，主路径对、兜底也对。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;会主动说缺点&lt;/strong&gt;&lt;br&gt;
缺自动对账、账户与打赏非强一致靠告警人工修——面试官通常加分，显得务实。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;有可落地的代码片段&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;GetReward&lt;/code&gt; 快慢路径 + &lt;code&gt;limited&lt;/code&gt; 降级，能证明不是纯理论。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;缺点面试里容易被打穿的点&#34;&gt;缺点（面试里容易被打穿的点）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;「对账」名不副实&lt;/strong&gt;&lt;br&gt;
简历写「对账流程」，正文却写「缺少定时自动对账」。2 年经验可以不做完整对账，但简历与口述必须对齐，否则会被认定夸大。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;一致性模型说不清&lt;/strong&gt;&lt;br&gt;
「优先更新打赏表，RPC 失败靠人工」——面试官会问：为何不先账后业务 / 同事务 / Outbox 统一出口？最终一致还是人工一致？超时重试与幂等如何配合？这里准备不足。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;本地消息表细节偏薄&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;failed&lt;/code&gt; 后怎么办？（死信、告警、人工重放）&lt;/li&gt;
&lt;li&gt;发送成功但改 status 失败 → 重复投递，下游如何保证？&lt;/li&gt;
&lt;li&gt;定时任务并发扫同一批 init 如何防重？&lt;/li&gt;
&lt;li&gt;content 序列化格式、版本兼容？&lt;br&gt;
这些是「本地消息表」题的标配追问。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redis 幂等设计不严谨&lt;/strong&gt;&lt;br&gt;
「预先判断 Redis 状态」太模糊：key 是什么？TTL？先 Redis 还是先 DB？Redis 挂了怎么办？正确叙事应是：&lt;strong&gt;DB 唯一约束是真相，Redis 只是加速，不能单独当幂等源&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Golang / 工程深度弱&lt;/strong&gt;&lt;br&gt;
全文几乎没有：gRPC 超时与重试、context 传递、回调验签、并发安全、事务边界、错误码。2 年 Golang 岗会被追问这些。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;表达与硬伤&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;calback&lt;/code&gt;、&lt;code&gt;stauts&lt;/code&gt;、&lt;code&gt;ctx.Value(&amp;quot;limited&amp;quot;)&lt;/code&gt;（类型不安全、难测）、JSON 伪结构不规范——细节会拉印象分。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;退款 / 部分失败 / 重复打赏&lt;/strong&gt; 几乎未覆盖&lt;br&gt;
支付面试常问：重复回调、金额篡改校验、订单超时关单、退款与冲正。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;建议按优先级&#34;&gt;建议（按优先级）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 先改简历表述（立刻做）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;改为：「实现微信支付打赏；用本地消息表保证支付结果可靠投递；账户侧幂等入账」&lt;/li&gt;
&lt;li&gt;不要写「完整对账」，除非补一版：按日对 &lt;code&gt;支付成功单 vs 账户流水 vs 打赏状态&lt;/code&gt; 的差异报表 + 告警。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 准备一张 2 分钟口述主链路&lt;/strong&gt;&lt;br&gt;
发起打赏 → 建业务单/支付单 → 微信下单 → 回调验签 → 本地事务写支付状态+Outbox → 发 MQ → 账户幂等入账 → 更新打赏状态；失败靠定时补偿 + 主动查单。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 把三个「追问答案」写死&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为何本地消息表而不是直接依赖微信重试？&lt;/li&gt;
&lt;li&gt;重复消息如何保证只加一次钱？&lt;/li&gt;
&lt;li&gt;打赏已成功、账户失败如何发现与修复？（最好从「人工」升级到「对账任务 + 可重放」）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 补齐技术细节清单（半页纸即可）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;回调验签、金额与 &lt;code&gt;out_trade_no&lt;/code&gt; 校验&lt;/li&gt;
&lt;li&gt;Outbox 状态机：&lt;code&gt;init → sending → success / failed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;定时任务：分页、&lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; 或租约，避免多实例抢同一批&lt;/li&gt;
&lt;li&gt;幂等：先插流水（唯一索引）再改余额；冲突当成功返回&lt;/li&gt;
&lt;li&gt;主动查单：状态机只允许 Init→Success/Failed，禁止回退&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. 代码展示再打磨&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;限流标记不要用 &lt;code&gt;ctx.Value(&amp;quot;limited&amp;quot;)&lt;/code&gt;，改成明确参数或中间件注入的 &lt;code&gt;Limiter&lt;/code&gt;/&lt;code&gt;Degrade&lt;/code&gt; 标志&lt;/li&gt;
&lt;li&gt;慢路径更新打赏状态时说明：是否只更新、是否再触发入账（避免查询接口变成写副作用）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;6. 面试话术升级一句&lt;/strong&gt;&lt;br&gt;
不要只说「模块拆了」；说：「支付可复用到会员/订单，打赏是支付的一种业务形态，账户是账本，三者用 MQ 最终一致，用唯一键兜底。」&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;面试官结论&#34;&gt;面试官结论
&lt;/h3&gt;&lt;p&gt;这份材料说明候选人&lt;strong&gt;做过真实支付链路，懂拆分和消息可靠性&lt;/strong&gt;，对 2 年经验够用；但若现场只能复述文档、答不上一致性与对账追问，分数会掉到 60 左右。把「对账诚实化 + Outbox/幂等细节 + 2 分钟口述」补上，冲击 &lt;strong&gt;80+&lt;/strong&gt; 更稳。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>不停机数据迁移 one</title>
        <link>https://ddlgitgzzhm.github.io/p/migrate-0x02/</link>
        <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/migrate-0x02/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 : 支持一套通用的同构数据不停机迁移方案, 支持全量和增量校验 使用 kafka 进行减轻数据库压力 并且动态的根据数据库状态进行切换挂载和运行&lt;/p&gt;
&lt;p&gt;方案描述 :&lt;/p&gt;
&lt;p&gt;由于我在拆分了 点赞、阅读、收藏相关的微服务, 考虑 微服务中 一个服务一个数据库进行数据隔离。从而实现了一套数据迁移方案 。 因为不修改原有数据存储架构，所以采用同构的方案 , 并且考虑这个服务属于关键服务，因此选用不停机迁移方案&lt;/p&gt;
&lt;p&gt;考虑在 100w条数据的情况下,如果进行 校验后立即进行修复会给数据库带来很大的负担因此使用 kafka 进行削峰的处理&lt;/p&gt;
&lt;p&gt;另外通过额外重写 gorm 中 connPool 的exec、prepare、query Func 实现 双写&lt;/p&gt;
&lt;p&gt;并且通过重写 Commit 和 Rollback 实现双写的事务控制&lt;/p&gt;
&lt;p&gt;困难点和改进 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;引入 kafka 进行异步的优化 全量校验与修复的过程 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优化单条校验的逻辑 采用批量校验的逻辑。 并且考虑 硬删除 的业务场景，在校验 原表和目标表的 条件下，同步进行 目标表和原表的 差异校验 防止硬删除产生的不一致问题&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持使用者动态切换 数据迁移 &lt;code&gt;原表only,读原表写目标表，读目标表写原表，目标表only&lt;/code&gt; 的配置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持根据数据库状态，判断是否高负载从而判断是否要挂载校验任务 。 并且通过 context cancel() 实现，尽可能只有同一个 全量/增量 校验程序在运行&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;缺点 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;因为 ConnPool 返回值 sql.Result 并不暴露 Error 的设置，所以对于双写的错误并不能很好的暴露出来，只能进行打log和监控&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;事务控制的实现并不能保证一致性，只能做到主表最大可控，但是对于目标表如果失败了 只能等待全量校验与修复&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;不停机迁移数据&#34;&gt;不停机迁移数据
&lt;/h3&gt;&lt;p&gt;相比较于 停机迁移数据， 主要的困难点在于 一边迁移 一边有新数据产生&lt;/p&gt;
&lt;p&gt;并且因为我们切换 原表和目标表 的时候，如果运气不好会导致出现一些数据不一致。 不停机数据迁移很难做到 最终数据一致性&lt;/p&gt;
&lt;h3 id=&#34;数据校验&#34;&gt;数据校验
&lt;/h3&gt;&lt;p&gt;为了防止重复写一些 dirty code, 放弃在 dao层维护两个数据库的思路&lt;/p&gt;
&lt;p&gt;重建了一套通用的双写逻辑。 由业务方提供 equal 的方法 ， 对于某些校验不严格的业务方可以考虑&lt;/p&gt;
&lt;p&gt;不需要 time 就可以认为 equal 以及自定义精度误差等等&lt;/p&gt;
&lt;h3 id=&#34;流量控制-和-性能优化&#34;&gt;流量控制 和 性能优化
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在数据库高负载的情况下会自动挂起全量校验 只有当数据库低负载的时候才会进行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;尽可能多的进行 异步处理 和 并发处理 。 对于全量数据与校验 采用异步处理， 对于 目标表和原表的校验 以及 目标表和原表的差异校验 采用异步处理的方式进行&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;面试题&#34;&gt;面试题
&lt;/h2&gt;&lt;p&gt;Q: 不停机迁移的基本步骤是什么&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;数据初始化，完成首轮数据的同步 和 数据库表的建立 。 对于 mysql 可以使用 mysqldump 进行同步，如果是异构数据的话，可以考虑内部批量轮询的方式进行首次同步&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这时候可以考虑启用全量校验进行一次数据的校验与修复&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;读取并写入原表 同时 写入目标表 。 同时进行 全量校验与修复&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;写入并读取目标表 同时写入原表 。 同时进行全量校验与修复 如果业务稳定可以考虑只运行增量校验&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完全使用目标表&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Q : 你的数据校验方案是什么&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;支持 全量的数据校验和增量的数据方案 。 通过用户是否传递 utime + sleepTime 考虑走的是全量校验还是增强校验&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同步的进行 原表和目标表的校验 以及 目标表和原表的差异 检查 。 批量获取原表的数据 并且批量的获取目标表的数据，使用业务方提供的 equal method 进行比对 ，如果存在不想等的 在轮询完成之后批量通过 kafka 发送给下游修复 。   批量获取目标表的数据，并且检查是否在原表存在，如果不存在同时发送对应的 event 给下游&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Q : 你的数据修复方案是什么 ？ 如何保证数据正确性？ 怎么解决并发问题 ？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用 kafka 进行异步的通知修复 。 kafka 仅仅只是做触发器，并不存放detail信息用于实际修复 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于数据修复情况只会有3种场景，target 不存在 那么需要insert，not equal 需要进行 update, base_missing 则需要删除 。 考虑 insert &amp;amp; update 的并发问题，使用 upsert 进行控制&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;并不能够在单词修复保证数据的正确性，数据的正确性最终交由多次校验与修复来控制&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Q : 在数据迁移的每个阶段，你是怎么考虑保护着数据库的&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;尽可能的进行 批量操作 。 批量的查询数据库，本地机器进行比对&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 kafka 进行异步的校验与修复&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;考虑 调度时机进行 校验任务的运行 ， 对于数据库高负载的情况 挂载正在运行的任务&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Q : 如果 kafka 瓶颈了怎么办，消息积压了怎么办？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;优化消费者。 尽可能改成批量消费&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;并行处理数据库修复流程&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Q : 为什么 你要使用 Kafka 直接校验之后修复数据不行吗&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;直接进行修复数据 , 会给数据库带来很大的负担 。 Mysql 单库的 读写有瓶颈，尽可能保证 大量的读和写操作不同时进行&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Q: 增量校验和修复、业务写数据、全量校验和修复同时进行，有什么并发问题 ？ 怎么解决？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;同时进行会带来更多的数据库操作，带来负担 。 并且我们在事务以及双写操作上必然会有并发问题，在我们切换 目标表和原表的时候 最终总会有不一致的情况 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;采用超时控制, 优化任务调度时间 。所有的不一致问题，最终都是由反复校验反复修复解决的&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Q :  怎么保证在数据迁移的时候 不影响业务&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;调度时间的控制 。 在低负载的情况下进行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以考虑在内部数据跑完几次全量校验之后 只开启增量校验，每次仅校验一周之内的数据&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;总分62--100&#34;&gt;总分：&lt;strong&gt;62 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;按「2 年经验、能讲清一个核心项目」来评：能过初筛，但扛不住深挖。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;评分拆解&#34;&gt;评分拆解
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;项目背景与动机&lt;/td&gt;
					&lt;td&gt;12/15&lt;/td&gt;
					&lt;td&gt;微服务拆库、同构不停机，动机清楚&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;方案完整性&lt;/td&gt;
					&lt;td&gt;14/25&lt;/td&gt;
					&lt;td&gt;四阶段迁移有框架，关键细节和边界含糊&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;技术深度&lt;/td&gt;
					&lt;td&gt;10/20&lt;/td&gt;
					&lt;td&gt;提到 ConnPool 双写、Kafka 削峰，但原理讲不透&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;问题意识与权衡&lt;/td&gt;
					&lt;td&gt;12/15&lt;/td&gt;
					&lt;td&gt;能主动说缺点，这点加分&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;面试表达与结构化&lt;/td&gt;
					&lt;td&gt;8/15&lt;/td&gt;
					&lt;td&gt;有 Q&amp;amp;A，但表述乱、有笔误、答不到位&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;与 2 年经验匹配度&lt;/td&gt;
					&lt;td&gt;6/10&lt;/td&gt;
					&lt;td&gt;选题够大，但「最终靠反复校验」像在回避设计&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&#34;优点&#34;&gt;优点
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;选题合适&lt;/strong&gt;：不停机迁移 + 双写 + 校验修复，是面试官愿意追问的「有难度」项目。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有诚实的缺点&lt;/strong&gt;：ConnPool 错误难暴露、事务只能保主表——比空谈「强一致」强很多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有业务抽象意识&lt;/strong&gt;：业务方提供 &lt;code&gt;Equal&lt;/code&gt;，比硬编码字段对比更像工程实践。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有保护库的意识&lt;/strong&gt;：批量、Kafka 削峰、高负载挂起任务，方向对。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;准备了面试题清单&lt;/strong&gt;：说明有意识在「被问什么」。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;缺点面试里会被打穿的点&#34;&gt;缺点（面试里会被打穿的点）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;核心一致性说不清&lt;/strong&gt;&lt;br&gt;
「切换时总会不一致」「最终靠反复校验」可以当结论，但不能当唯一答案。面试官会问：双写失败怎么回滚？阶段切换的窗口期如何缩小？源表为准 / 目标表为准时，修复方向怎么定？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kafka 用法站不住脚&lt;/strong&gt;&lt;br&gt;
「只做触发器、不带 detail」——那消费者靠什么修？再查一次库？查到的是哪一侧？积压时只说「批量消费 / 并行」，没有分区键、幂等、重试、死信、顺序。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;并发问题答得空&lt;/strong&gt;&lt;br&gt;
业务写、全量校验、增量修复同时跑，只提「超时 + 调度」不够。应有：同一主键的冲突策略、upsert 的版本/utime 条件、校验批次与写路径的可见性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;双写实现太薄&lt;/strong&gt;&lt;br&gt;
重写 ConnPool / Commit / Rollback 是亮点，但文档几乎没展开：嵌套事务？只读语句？Prepare 缓存？目标库延迟？失败是「打日志继续」还是「业务失败」？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;表达与严谨性差&lt;/strong&gt;&lt;br&gt;
「挂载」应为「挂起」；「增强校验」应为「增量」；步骤编号重复；句子碎、逻辑跳。面试口述时会显得准备乱。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺可量化结果&lt;/strong&gt;&lt;br&gt;
100 万量级提了，但没有：迁移耗时、校验一轮多久、峰值 QPS、不一致率下降、切换窗口多长。2 年经验项目，数据是加分项。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;建议按优先级&#34;&gt;建议（按优先级）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 把故事讲成一条线（30 秒版）&lt;/strong&gt;&lt;br&gt;
背景（拆库）→ 约束（不停机、同构）→ 四阶段 → 双写怎么做 → 校验/修复怎么兜底 → 已知局限。每阶段说清「读谁、写谁、以谁为准」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 补齐 5 个必追问答案（写到能口述）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;双写失败：主成功副失败怎么办？是否影响接口返回？&lt;/li&gt;
&lt;li&gt;事务：主提交、目标失败时数据状态与补偿路径&lt;/li&gt;
&lt;li&gt;Kafka：消息体设计、幂等键、消费失败重试、为何不直接同步修&lt;/li&gt;
&lt;li&gt;硬删除：双向 diff 的具体算法（按主键批量 in-query）&lt;/li&gt;
&lt;li&gt;切换：如何判断可以进下一阶段（不一致率、连续 N 轮全量通过）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 准备一组数字&lt;/strong&gt;&lt;br&gt;
例如：数据量、双写延迟、全量一轮时间、Kafka 吞吐、挂起阈值、最终不一致量级。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 把「反复校验」升级成「有界收敛」&lt;/strong&gt;&lt;br&gt;
说明：在「源为准」阶段修复方向固定；切换前要求不一致 &amp;lt; 阈值；切换后短窗口只开增量；为何认为会收敛而不是越修越乱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 润色简历与口述&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简历：一句话写清「四阶段双写 + 全量/增量校验 + Kafka 异步修复 + 负载感知调度」&lt;/li&gt;
&lt;li&gt;去掉口语和错别字；每个 Q 控制在「结论 → 做法 → 权衡」三层&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;6. 区分「你做了什么」和「方案通用知识」&lt;/strong&gt;&lt;br&gt;
mysqldump 初始化、四阶段迁移是通识；面试官更想听你改 ConnPool、Equal 抽象、双向校验、挂起任务这些「你亲手做的」。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;面试官结论&#34;&gt;面试官结论
&lt;/h3&gt;&lt;p&gt;材料证明你做过这件事，也知道难点在一致性和库压力，&lt;strong&gt;作为 2 年经验的项目素材够用&lt;/strong&gt;。但当前深度停在「知道概念 + 知道会有问题」，&lt;strong&gt;还不足以在中高级追问里稳住&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;把一致性边界、Kafka 契约、双写失败语义、切换判定补全后，这份准备可以冲到 &lt;strong&gt;75–80&lt;/strong&gt;；再加可量化结果和一次完整口述演练，才更接近「能让面试官点头」的水平。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>阅读、点赞、收藏 one</title>
        <link>https://ddlgitgzzhm.github.io/p/interactive-0x02/</link>
        <pubDate>Wed, 29 Jul 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/interactive-0x02/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 :&lt;/p&gt;
&lt;p&gt;实现了一个通用的  阅读、点赞、收藏 服务。使用 Kafka 进行解耦&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据库表设计 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通用表设计 :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;id,biz,bizid,readCnt,CollectCnt,LikeCnt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;为 &lt;code&gt;bizid 和 biz&lt;/code&gt; 创建一个联合唯一索引&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;点赞表设计 :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;id,biz,bizid,uid,status&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;为 &lt;code&gt;bizid 和 biz&lt;/code&gt;和&lt;code&gt;uid&lt;/code&gt; 创建一个联合唯一索引, 用 &lt;code&gt;status&lt;/code&gt;进行软删除&lt;/p&gt;
&lt;h3 id=&#34;数据库处理&#34;&gt;数据库处理
&lt;/h3&gt;&lt;p&gt;对于 阅读、点赞、收藏 操作都会涉及到一个 &lt;code&gt;check-dothing&lt;/code&gt; 的并发问题&lt;/p&gt;
&lt;p&gt;即 : 当用户点赞的时候 文章是否已经点赞过了，如果点赞过需要+1，否则创建 ，这里统一采用 &lt;code&gt;do on conflict update&lt;/code&gt; 处理&lt;/p&gt;
&lt;p&gt;不过需要注意的是 : &lt;code&gt;update&lt;/code&gt; 的时候我们需要保证自增 所以需要使用 &lt;code&gt;gorm.Expr&lt;/code&gt; 来获取数据库运行的值，而不是直接 &lt;code&gt;select-update&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&#34;缓存处理&#34;&gt;缓存处理
&lt;/h3&gt;&lt;p&gt;对于写入方，优先写入数据库，后写入redis&lt;/p&gt;
&lt;p&gt;对于查询方，优选查redis 然后再查数据库&lt;/p&gt;
&lt;p&gt;对于写入的过程，因为我们使用的 redis key 是一次缓存了 3个 like,read,collect 三个字段&lt;/p&gt;
&lt;p&gt;所以我们自增的时候使用的是 HINCRBY 指令，对于 key 不存在的情况下会有歧义 。所以使用 lua 控制了一下 仅只有 key 存在的时候才会自增&lt;/p&gt;
&lt;h3 id=&#34;消息队列&#34;&gt;消息队列
&lt;/h3&gt;&lt;p&gt;新增 统计事件 引入 kafka 给 阅读、点赞服务解耦&lt;/p&gt;
&lt;p&gt;在阅读文章服务，使用 kafka 异步发送，设置 10次每批，1s超时进行聚合发送消息&lt;/p&gt;
&lt;p&gt;消费端同样设置 10条每批，1s超时来进行组合消费&lt;/p&gt;
&lt;p&gt;每次消费 使用一个 map 进行聚合 同个&lt;code&gt;biz+biz_id&lt;/code&gt;的内容，优化之前一次点赞就需要一次 &lt;code&gt;insert&lt;/code&gt; 的 操作&lt;/p&gt;
&lt;p&gt;并且通过开启事务进行消费，防止 MVCC 中的 刷盘操作，将原先要进行10次的刷盘降低到1次。&lt;/p&gt;
&lt;h3 id=&#34;问题&#34;&gt;问题
&lt;/h3&gt;&lt;h4 id=&#34;一致性问题的处理&#34;&gt;一致性问题的处理
&lt;/h4&gt;&lt;p&gt;不处理&lt;/p&gt;
&lt;p&gt;从业务上来说，没有必要要求阅读数、点赞数或者收藏数量一定是严格准确的，因为即便不准确，也不会对用户
产生什么不好的影响。&lt;/p&gt;
&lt;p&gt;更进一步说，只有高并发的文章才会有并发问题。而高并发的文章，阅读数、点赞数或者收藏数本身就很多，你
少一点点无所谓。&lt;/p&gt;
&lt;h3 id=&#34;亮点&#34;&gt;亮点
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;大量使用了事务&lt;/strong&gt;，来保证在同时操作两个表的时候，保持住 ACID 语义。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;大量使用了 Upsert 语义&lt;/strong&gt; , 因为很多情况下我们都不知道数据库中是否已经存在数据&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;大量使用 lua 脚本来保证缓存中的数据是正确的&lt;/strong&gt;, 但是没有彻底解决缓存一致性的问题&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在查询接口中，只缓存总数数据，&lt;/strong&gt;, 并不会缓存个人是否收藏或者点赞的数据。&lt;/p&gt;
&lt;h2 id=&#34;总分62--100&#34;&gt;总分：&lt;strong&gt;62 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;按「2 年 Go 后端」面试标准看：方向对、有工程意识，但表达粗糙、关键点浅、经不起追问。面试里能撑 15–20 分钟开场，深入一追容易露怯。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;分项打分&#34;&gt;分项打分
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;业务建模与表设计&lt;/td&gt;
					&lt;td&gt;14/20&lt;/td&gt;
					&lt;td&gt;通用计数表 + 点赞明细表思路正确，索引方向对&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;并发与数据正确性&lt;/td&gt;
					&lt;td&gt;12/20&lt;/td&gt;
					&lt;td&gt;知道 Upsert / Expr，但 check-then-act、事务边界讲不清&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缓存设计&lt;/td&gt;
					&lt;td&gt;12/20&lt;/td&gt;
					&lt;td&gt;Cache-Aside + Lua 防空 key 自增有亮点，一致性论证弱&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;消息队列与解耦&lt;/td&gt;
					&lt;td&gt;13/20&lt;/td&gt;
					&lt;td&gt;Kafka 批量、聚合、减刷盘有工程味道&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;表达与面试叙事&lt;/td&gt;
					&lt;td&gt;6/10&lt;/td&gt;
					&lt;td&gt;口语化、错别字多，「大量使用」空洞，像笔记不像口述稿&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;亮点与深度&lt;/td&gt;
					&lt;td&gt;5/10&lt;/td&gt;
					&lt;td&gt;亮点空泛，缺少权衡、失败场景、可观测性&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&#34;优点&#34;&gt;优点
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;表设计合理&lt;/strong&gt;：&lt;code&gt;biz + biz_id&lt;/code&gt; 抽象通用互动能力，点赞用 &lt;code&gt;(biz, biz_id, uid)&lt;/code&gt; 唯一 + &lt;code&gt;status&lt;/code&gt; 软删除，符合常见业务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;踩过真实坑&lt;/strong&gt;：&lt;code&gt;ON CONFLICT&lt;/code&gt; + &lt;code&gt;gorm.Expr&lt;/code&gt; 自增、Redis &lt;code&gt;HINCRBY&lt;/code&gt; 空 key 用 Lua 护栏，说明写过代码而不只是背概念。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有吞吐意识&lt;/strong&gt;：Kafka 批量生产/消费、按 &lt;code&gt;biz+biz_id&lt;/code&gt; 聚合、事务合并刷盘，对 2 年经验来说是加分项。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一致性取舍有业务理由&lt;/strong&gt;：承认最终一致、高热内容允许误差，比死磕「强一致」更成熟——前提是能讲清边界。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;缺点面试官会盯的&#34;&gt;缺点（面试官会盯的）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;「不处理一致性」太绝对&lt;/strong&gt;：面试官会问：DB 成功 Redis 失败？Kafka 重复消费？取消点赞与计数对不上？需要有策略（补偿、对账、幂等），不能只说「少一点点无所谓」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;亮点写法很伤&lt;/strong&gt;：连续「大量使用事务/Upsert/Lua」像模板，没有场景、没有代价。事务多 ≠ 好，还可能拖慢吞吐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技术细节易被打穿&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;写库再写缓存：失败怎么回滚/补偿？&lt;/li&gt;
&lt;li&gt;Lua「仅 key 存在才 incr」：key 不存在时计数会丢吗？靠查询回源还是异步重建？&lt;/li&gt;
&lt;li&gt;Kafka 聚合：幂等键、乱序、部分失败重试怎么做？&lt;/li&gt;
&lt;li&gt;「防止 MVCC 刷盘」表述不严谨，容易被纠正。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺个人态查询设计&lt;/strong&gt;：只说不缓存「是否点赞」，但列表页如何批量查个人态？N+1？位图/本地缓存？没写。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表达与文档质量差&lt;/strong&gt;：&lt;code&gt;优选/check-dothing&lt;/code&gt;、front matter 的 &lt;code&gt;xxxx&lt;/code&gt;、标点混乱，会降低「能讲清楚」的印象。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;建议按面试口述改&#34;&gt;建议（按面试口述改）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;改成 STAR 口述结构&lt;/strong&gt;：背景（互动服务压力）→ 方案（表 + Kafka + Redis）→ 难点（空 key、重复消费）→ 结果（QPS/延迟/错误率，哪怕是压测数字）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;准备 3 个追问标准答&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;DB/Redis 不一致：异步对账 + 定时校准热点 key&lt;/li&gt;
&lt;li&gt;Kafka 至少一次：消费幂等（唯一键 + Upsert）&lt;/li&gt;
&lt;li&gt;取消点赞：&lt;code&gt;status&lt;/code&gt; 翻转 + 计数 &lt;code&gt;GREATEST(cnt-1,0)&lt;/code&gt;，防负值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删掉「大量使用」&lt;/strong&gt;，改成「在点赞写明细+改计数时用事务；计数更新用 Upsert；缓存用 Lua 保证 key 存在才 HINCRBY」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;补一张口述链路&lt;/strong&gt;：读请求 → Redis Hash → miss 回源 DB 回填；写请求 → DB Upsert →（可选）发 Kafka → 消费者聚合写计数 → 更新缓存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;亮点换成可量化的 2–3 条&lt;/strong&gt;：例如「批量聚合把热点文章写放大降到约 1/N」「Lua 避免幽灵 key」「个人态与总数分离降低缓存体积」。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;面试官一句话结论&#34;&gt;面试官一句话结论
&lt;/h3&gt;&lt;p&gt;材料有「做过互动域」的骨架，适合当提纲；按现在写法直接上面试，深度和表达大概卡在 &lt;strong&gt;及格偏上&lt;/strong&gt;。把一致性策略、幂等、失败路径和口述结构补齐，冲到 &lt;strong&gt;75–80&lt;/strong&gt; 比较现实。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>发帖服务 one</title>
        <link>https://ddlgitgzzhm.github.io/p/publish-service-0x01/</link>
        <pubDate>Sat, 25 Jul 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/publish-service-0x01/</guid>
        <description>&lt;h2 id=&#34;简历-preview&#34;&gt;简历 preview
&lt;/h2&gt;&lt;p&gt;简历上 : 设计并实现了一个 &lt;strong&gt;文章&lt;/strong&gt; 服务&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;考虑文章的业务属性, 创作者支持保存草稿和发布文章。 设计了 线上库 和 制作库 两个库表设计 。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;采用 ESR 原则，考虑会有 用户查看自己的文章，用户查看某篇文章详情,以及热榜查看对应时间的文章 。设定过了 _create,auth_id . 这两个索引&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;文章服务支持&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;制作库 保存草稿, 设置隐私文章, 发布制作库内容, 按照 ID 查看文章,或许作者文章列表, 获取发布文章列表&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;
&lt;p&gt;支持 MongoDB 和 OSS 的扩展&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设计合理的缓存服务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;考虑并发场景&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;细节&#34;&gt;细节
&lt;/h2&gt;&lt;h3 id=&#34;数据库索引设计&#34;&gt;数据库索引设计
&lt;/h3&gt;&lt;p&gt;主要涉及到的业务有 : 作者查看当前文章，和最近一周的热榜设计&lt;/p&gt;
&lt;p&gt;对于作者查看当前文章预计查询是 &lt;code&gt;select from where auth_id = &#39;&#39; and statuts = &#39;&#39;&lt;/code&gt; 因此设计了 &lt;code&gt;auth_id&lt;/code&gt; 的索引&lt;/p&gt;
&lt;p&gt;对于最近一周的热榜设计，因为热榜计算是一个频繁的操作，预计查询是 &lt;code&gt;select from where _create &amp;gt;= now-7week&lt;/code&gt; 因此设计了 &lt;code&gt;_create&lt;/code&gt; 的索引&lt;/p&gt;
&lt;h3 id=&#34;数据库并发处理&#34;&gt;数据库并发处理
&lt;/h3&gt;&lt;p&gt;隐私文章功能 :&lt;/p&gt;
&lt;p&gt;需要同步改动 制作库和 线上库的 状态 。&lt;/p&gt;
&lt;p&gt;系统设计的时候选择的是同库不同表的流程因此采用数据库事务进行一致性处理，对于更新失败的操作进行回滚 。从业务角度考虑，就算更新失败了也只是让用户多重试几次。&lt;/p&gt;
&lt;p&gt;如果是不同库的情况，可以考虑在接口层面增加多次重试。&lt;/p&gt;
&lt;p&gt;发布功能 :&lt;/p&gt;
&lt;p&gt;对于发布功能, 线上库存在两个状态,线上没有文章需要create,线上有文章需要 update&lt;/p&gt;
&lt;p&gt;防止数据库层面的同一篇文章多次竞态问题使用&lt;code&gt;upsert&lt;/code&gt;进行操作。&lt;/p&gt;
&lt;h3 id=&#34;缓存设计&#34;&gt;缓存设计
&lt;/h3&gt;&lt;p&gt;对于列表接口 :&lt;/p&gt;
&lt;p&gt;根据习惯习惯缓存第一页内容,大部分用户只会访问第一页的内容。 细节: 缓存之前需要清空文章的content 节省内存资源。&lt;/p&gt;
&lt;p&gt;在更新，删除，隐私的接口增加对应的删除缓存的操作 。&lt;/p&gt;
&lt;p&gt;对于查看detail接口 :&lt;/p&gt;
&lt;p&gt;根据用户习惯，往往只会查看列表第一篇文章的内容。 因此考虑在拉取列表后，缓存第一篇文章的内容。 仅在 content &amp;lt; 1M 的时候进行缓存。 缓存设置了一个很短的过期时间，防止浪费。&lt;/p&gt;
&lt;p&gt;对于发布接口 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;考虑作者发布文章后就会有人阅读，因此在发布的时候就设置缓存。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;其他细节&#34;&gt;其他细节
&lt;/h2&gt;&lt;h3 id=&#34;mongodb&#34;&gt;MongoDB
&lt;/h3&gt;&lt;p&gt;尝试接入 MongoDB 进行存储文章数据。 接入 MongoDB 之后发现，MongoDB 返回的 ID 是 String 类型 。破坏了原先接口的定义。 因此引入了 雪花算法 进行对其接口&lt;/p&gt;
&lt;h3 id=&#34;oss&#34;&gt;OSS
&lt;/h3&gt;&lt;p&gt;接入 OSS，在发布文章之后，对于线上库的文章内容进行优化。线上库不保存文章内容，文章内容全部存储在 OSS 中&lt;/p&gt;
&lt;h3 id=&#34;对接阅读统计服务&#34;&gt;对接阅读统计服务
&lt;/h3&gt;&lt;p&gt;使用 channel + ctxWithTimeout 进行批量发送阅读统计给 MQ。 供下游消费&lt;/p&gt;
&lt;p&gt;作为 Golang 面试官，我按「2 年经验、能独立负责一个业务模块」的标准，对这份 &lt;strong&gt;发帖/文章服务&lt;/strong&gt; 面试准备稿打分如下。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;综合评分68--100&#34;&gt;综合评分：&lt;strong&gt;68 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;得分&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;业务理解与架构设计&lt;/td&gt;
					&lt;td&gt;18/25&lt;/td&gt;
					&lt;td&gt;制作库/线上库分离思路正确，有业务驱动意识&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;数据库与索引&lt;/td&gt;
					&lt;td&gt;12/20&lt;/td&gt;
					&lt;td&gt;有索引意识，但 ESR 理解偏浅，热榜查询设计不完整&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;并发与一致性&lt;/td&gt;
					&lt;td&gt;13/20&lt;/td&gt;
					&lt;td&gt;提到事务、upsert、重试，但缺少边界与失败场景&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缓存设计&lt;/td&gt;
					&lt;td&gt;14/20&lt;/td&gt;
					&lt;td&gt;第一页缓存、去 content、失效策略有亮点，缺 key 设计与一致性细节&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;工程化与 Golang 表达&lt;/td&gt;
					&lt;td&gt;6/15&lt;/td&gt;
					&lt;td&gt;几乎无 Go 实现细节，MQ 只一笔带过&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;面试表达与完整性&lt;/td&gt;
					&lt;td&gt;5/10&lt;/td&gt;
					&lt;td&gt;结构尚可，错别字多，placeholder 未填，缺 Q&amp;amp;A 演练&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：作为 2 年经验候选人，&lt;strong&gt;项目经历是真实的、方向也对&lt;/strong&gt;，但这份准备稿更像「项目笔记摘要」，还不足以支撑 45–60 分钟的深度技术面。能过初筛，若在索引、一致性、缓存、Go 并发等点被追问，容易失分。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;优点&#34;&gt;优点
&lt;/h2&gt;&lt;h3 id=&#34;1-业务建模有思考&#34;&gt;1. 业务建模有思考
&lt;/h3&gt;&lt;p&gt;「制作库 + 线上库」分离是内容类系统的常见做法，能解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;草稿编辑 vs 线上可读&lt;/li&gt;
&lt;li&gt;发布时从制作库同步到线上库&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这比只会说「CRUD 增删改查」高一档。&lt;/p&gt;
&lt;h3 id=&#34;2-缓存策略贴近真实访问&#34;&gt;2. 缓存策略贴近真实访问
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;只缓存第一页 —— 符合大部分用户行为&lt;/li&gt;
&lt;li&gt;列表缓存去掉 &lt;code&gt;content&lt;/code&gt; —— 说明考虑过内存成本&lt;/li&gt;
&lt;li&gt;写操作删缓存 —— 知道 Cache Aside 的基本思路&lt;/li&gt;
&lt;li&gt;详情预缓存第一条 + 短 TTL + &lt;code&gt;&amp;lt; 1M&lt;/code&gt; 限制 —— 有「预测性缓存」意识&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;3-并发场景有触点&#34;&gt;3. 并发场景有触点
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;发布用 &lt;code&gt;upsert&lt;/code&gt; 防竞态&lt;/li&gt;
&lt;li&gt;隐私状态双库同步用事务&lt;/li&gt;
&lt;li&gt;不同库场景考虑接口层重试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;说明不是只写 happy path。&lt;/p&gt;
&lt;h3 id=&#34;4-扩展能力有落地&#34;&gt;4. 扩展能力有落地
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;MongoDB 接入 + 雪花 ID 对齐 &lt;code&gt;int64&lt;/code&gt; 接口&lt;/li&gt;
&lt;li&gt;OSS 存正文、DB 存元数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;channel + ctxWithTimeout&lt;/code&gt; 批量发 MQ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有「存储分层 + 异步解耦」的工程感。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;缺点&#34;&gt;缺点
&lt;/h2&gt;&lt;h3 id=&#34;1-esr-原则理解不准确高风险扣分点&#34;&gt;1. ESR 原则理解不准确（高风险扣分点）
&lt;/h3&gt;&lt;p&gt;文档写「采用 ESR 原则，设计了 &lt;code&gt;_create&lt;/code&gt; 和 &lt;code&gt;auth_id&lt;/code&gt; 两个索引」。&lt;/p&gt;
&lt;p&gt;ESR（Equality / Sort / Range）说的是 &lt;strong&gt;复合索引字段顺序&lt;/strong&gt;，不是两个单列索引。面试官很可能追问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作者查文章：&lt;code&gt;auth_id = ? AND status = ?&lt;/code&gt; → 更合理的是 &lt;code&gt;(auth_id, status)&lt;/code&gt; 或 &lt;code&gt;(auth_id, status, ctime DESC)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;热榜：&lt;code&gt;ctime &amp;gt;= now-7d&lt;/code&gt; 且可能要 &lt;code&gt;status = published&lt;/code&gt; → 单列 &lt;code&gt;_create&lt;/code&gt; 往往不够，还要考虑排序字段（阅读量、score）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;若被问到「为什么不用复合索引」，目前文档撑不住。&lt;/p&gt;
&lt;h3 id=&#34;2-热榜设计过于粗糙&#34;&gt;2. 热榜设计过于粗糙
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;now-7week&lt;/code&gt; 应为 7 天（笔误会减印象分）&lt;/li&gt;
&lt;li&gt;没说明热榜指标（UV、阅读量、点赞、时间衰减）&lt;/li&gt;
&lt;li&gt;没说明是实时算还是定时任务 + 缓存&lt;/li&gt;
&lt;li&gt;频繁计算却只提 &lt;code&gt;_create&lt;/code&gt; 索引，逻辑链不完整&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;3-一致性问题讲不深&#34;&gt;3. 一致性问题讲不深
&lt;/h3&gt;&lt;p&gt;隐私文章「制作库 + 线上库同步」只说了事务和重试，缺：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同库不同表 vs 跨库：最终一致性怎么做&lt;/li&gt;
&lt;li&gt;事务失败后用户看到什么状态&lt;/li&gt;
&lt;li&gt;OSS 与 DB 不一致怎么办（DB 更新了，OSS 上传失败）&lt;/li&gt;
&lt;li&gt;是否需要 Saga / 补偿 / 幂等键&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2 年经验不要求精通分布式事务，但要能讲清 &lt;strong&gt;自己的方案边界&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;4-几乎没有-golang-相关内容&#34;&gt;4. 几乎没有 Golang 相关内容
&lt;/h3&gt;&lt;p&gt;这是 &lt;strong&gt;Golang 面试准备&lt;/strong&gt;，但全文没有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;context&lt;/code&gt; 怎么用、超时怎么传&lt;/li&gt;
&lt;li&gt;goroutine 泄漏怎么防&lt;/li&gt;
&lt;li&gt;&lt;code&gt;errgroup&lt;/code&gt; / worker pool 怎么做批量 MQ&lt;/li&gt;
&lt;li&gt;接口设计、依赖注入、分层（handler/service/repo）&lt;/li&gt;
&lt;li&gt;单元测试、压测数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;channel + ctxWithTimeout&lt;/code&gt; 只一句，深度不够。&lt;/p&gt;
&lt;h3 id=&#34;5-表达与细节问题&#34;&gt;5. 表达与细节问题
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;statuts&lt;/code&gt;、&lt;code&gt;auth_id&lt;/code&gt;/&lt;code&gt;author_id&lt;/code&gt; 混用、&lt;code&gt;_create&lt;/code&gt; 命名不规范&lt;/li&gt;
&lt;li&gt;「习惯习惯」重复&lt;/li&gt;
&lt;li&gt;description 仍是 &lt;code&gt;xxxx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;相比同系列的 &lt;code&gt;register_login tidy&lt;/code&gt;，这篇 &lt;strong&gt;没有 Q&amp;amp;A 脱稿演练&lt;/strong&gt;，面试时容易散&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;6-缺少可量化的结果&#34;&gt;6. 缺少可量化的结果
&lt;/h3&gt;&lt;p&gt;没有 QPS、P99 延迟、缓存命中率、发布失败率等，「设计合理」缺少说服力。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;改进建议&#34;&gt;改进建议
&lt;/h2&gt;&lt;h3 id=&#34;一把每个技术点补成star--追问应答&#34;&gt;一、把每个技术点补成「STAR + 追问应答」
&lt;/h3&gt;&lt;p&gt;建议每个模块固定四句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;背景&lt;/strong&gt;：为什么这么做&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方案&lt;/strong&gt;：具体怎么实现&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取舍&lt;/strong&gt;：为什么不用别的方案&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：数据或线上表现&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;示例（索引）&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;作者列表查询是 &lt;code&gt;author_id + status + 按创建时间倒序分页&lt;/code&gt;。我用复合索引 &lt;code&gt;(author_id, status, ctime DESC)&lt;/code&gt;，遵循 ESR：等值条件在前，排序字段在中间。热榜是近 7 天已发布文章按 score 排序，定时任务每 5 分钟算一次，结果放 Redis ZSet，读路径不走 DB。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;二重点补强-5-个高频追问&#34;&gt;二、重点补强 5 个高频追问
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;追问&lt;/th&gt;
					&lt;th&gt;建议准备内容&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;为什么制作库/线上库要分开？&lt;/td&gt;
					&lt;td&gt;读写隔离、草稿不影响线上、发布可审核&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;发布接口如何保证幂等？&lt;/td&gt;
					&lt;td&gt;幂等键、upsert 条件、版本号 / status 机&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缓存一致性怎么保证？&lt;/td&gt;
					&lt;td&gt;key 设计、先更 DB 再删缓存、延迟双删要不要&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;为什么用 MongoDB？MySQL 行不行？&lt;/td&gt;
					&lt;td&gt;文档结构、字段变化、大正文；MySQL 可以但 OSS 后 MySQL 更合适&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MQ 批量发送失败怎么办？&lt;/td&gt;
					&lt;td&gt;batch 大小、重试、dead letter、ctx 超时&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;三补-golang-实现细节至少-3-点&#34;&gt;三、补 Golang 实现细节（至少 3 点）
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;发布流程&lt;/strong&gt;：&lt;code&gt;ctx&lt;/code&gt; 超时、&lt;code&gt;errgroup&lt;/code&gt; 并行写 DB + 上传 OSS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阅读统计&lt;/strong&gt;：带 buffer 的 worker，&lt;code&gt;select&lt;/code&gt; + &lt;code&gt;ctx.Done()&lt;/code&gt; 优雅退出&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口层&lt;/strong&gt;：统一错误码、&lt;code&gt;middleware&lt;/code&gt; 鉴权、作者只能改自己的文章&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;能画一张流程图或贴 20 行核心代码，比纯文字强很多。&lt;/p&gt;
&lt;h3 id=&#34;四oss--db-一致性单独成段&#34;&gt;四、OSS + DB 一致性单独成段
&lt;/h3&gt;&lt;p&gt;建议明确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先发 OSS 还是先写 DB？&lt;/li&gt;
&lt;li&gt;发布失败如何回滚 / 清理孤儿文件？&lt;/li&gt;
&lt;li&gt;读路径：DB 存 &lt;code&gt;content_url&lt;/code&gt;，CDN /cache 怎么处理？&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;五参考-register_login-tidy-的结构改版&#34;&gt;五、参考 &lt;code&gt;register_login tidy&lt;/code&gt; 的结构改版
&lt;/h3&gt;&lt;p&gt;那篇有「脱讲稿 + 问题列表 + 标准答法」，这篇建议对齐：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## 30 秒 elevator pitch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## 架构图（制作库/线上库/OSS/缓存/MQ）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## 核心接口（Publish / SaveDraft / SetPrivate / List / Detail）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## 5 个技术亮点（每个 1 分钟）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## 10 个高频面试题 + 参考答案
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## 踩坑（MongoDB ObjectId、缓存大对象、双库不一致）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;六修正文档硬伤&#34;&gt;六、修正文档硬伤
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;统一术语：&lt;code&gt;author_id&lt;/code&gt;、&lt;code&gt;status&lt;/code&gt;、&lt;code&gt;ctime&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;修正「7 week」→「7 day」&lt;/li&gt;
&lt;li&gt;填满 front matter 的 &lt;code&gt;description&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通读去掉错别字&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;面试官视角的一句话评价&#34;&gt;面试官视角的一句话评价
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;「候选人做过真实的文章发布模块，有制作库/线上库、缓存、OSS、MQ 等完整链路意识；但对索引、一致性、Go 并发和面试表达深度不足。若能在 ESR、发布幂等、缓存 key 设计、双库一致性边界上准备充分，可从『大概做过』提升到『能独立负责』，分数有望到 80+。」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;如果你愿意，我可以按面试官口吻，直接把这篇 &lt;code&gt;tidy&lt;/code&gt; 文档改成「可背诵版」（含 10 道追问 + 参考答案），或对照 &lt;code&gt;register_login tidy&lt;/code&gt; 的结构帮你重写一版。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>高可用 高扩展 高安全 的 登陆与注册的设计 one</title>
        <link>https://ddlgitgzzhm.github.io/p/register-login-desin-0x01/</link>
        <pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate>
        
        <guid>https://ddlgitgzzhm.github.io/p/register-login-desin-0x01/</guid>
        <description>&lt;h2 id=&#34;脱讲稿&#34;&gt;脱讲稿
&lt;/h2&gt;&lt;p&gt;简介 : 设计并实现了 完整的 高安全性,高可用,高扩展的 登陆与注册 服务&lt;/p&gt;
&lt;p&gt;自述 :&lt;/p&gt;
&lt;p&gt;我给系统设计了一个支持 手机号码、微信登陆、邮箱注册 的登陆与注册服务。 使用 长短JWT + Session 控制用户的登陆登出。 处理了跨域和限流问题,并且对于手机登陆支持切换云服务商 支持 failover 策略。功能拥有完整的链路控制，并且使用 wrk 进行压测。&lt;/p&gt;
&lt;p&gt;高安全性 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;做了限流, 手机验证, 设备验证&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;高可用 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持切换多个云服务商&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;高扩展的 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基于面向接口编程. 并且采用 DDD 设计 支持 repo 层切换多个存储方案，支持 SMS 和 微信登陆切换各种服务&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;问题-1&#34;&gt;问题 1
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;什么是 &lt;code&gt;Gin&lt;/code&gt; 的 &lt;code&gt;middle-ware&lt;/code&gt; ？ 能解决什么问题 ？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Gin 框架提供的一种 AOP 编程机制, 支持实际业务逻辑前后进行插入额外处理 。 能够解决以下几类问题&lt;/p&gt;
&lt;p&gt;Web治理 : 实现 http 层面的熔断、限流、降级&lt;/p&gt;
&lt;p&gt;可观测性 : 可以聚合日志，metrics，tracing&lt;/p&gt;
&lt;p&gt;身份的认证与鉴权&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;什么是跨域问题，怎么解决 ？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;跨域的问题本质是 请求发送方和接收方 host 和 端口不匹配的问题 。 对于跨域问题，浏览器会预先发送一个 preflight option方法，询问允许访问的范围 。&lt;/p&gt;
&lt;p&gt;Gin 框架支持使用 Cros 中间件进行处理跨域问题，设置对应的 &lt;code&gt;allow-origins,allow-headers,allow-method&lt;/code&gt;即可&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;跨域问题需要设置哪些头部 ？  上面的已经回答&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;什么是 cookie，什么是 session ?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;http 本身是无状态协议，为了保护用户的登陆状态，浏览器支持本地存储一个 kv 数据, 这个数据可以理解为 cookie&lt;/p&gt;
&lt;p&gt;由于 cookie 的安全性问题，隐私信息一般不存放在 cookie中，因此引入 session 机制&lt;/p&gt;
&lt;p&gt;session 机制允许 绑定一个 session_id 给前端，具体身份校验逻辑可以存放在后端避免隐私信息泄漏&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;5&#34;&gt;
&lt;li&gt;cookie 和 session 比起来有什么缺点 ？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;cookie 不依赖后端存储，并且本身可以设置一些安全性配置 例如 &lt;code&gt;domain&lt;/code&gt;,&lt;code&gt;expires&lt;/code&gt;,&lt;code&gt;secure&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;缺点的话就是 不允许存放敏感信息，容易丢失&lt;/p&gt;
&lt;p&gt;&amp;ndash;&lt;/p&gt;
&lt;ol start=&#34;6&#34;&gt;
&lt;li&gt;Session ID 可以放在哪里？ 如果Cookie禁用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;cookie, header, redis,本地缓存,sql 等各种地方&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;7&#34;&gt;
&lt;li&gt;用户密码加密算法的选取&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;尽可能选择高安全系数的,不宜破解的 。 技术选型的时候考虑过 md5,pbkdf2,bcrypt&lt;/p&gt;
&lt;p&gt;因为 md5 需要在数据库中额外存储盐值的原因最终选择 bcrypt&lt;/p&gt;
&lt;p&gt;使用 wrk 压测的时候，发现 密码加密和不加密的情况下 性能相差10倍, 但是这是不可避免的&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;8&#34;&gt;
&lt;li&gt;怎么做登陆校验 ?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;正确性校验 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在登陆的时候 获取对应的账号和密码，去数据库中查找，并且使用 bcrypt 进行比对 正确的话 则返回对应的 jwt 用于保护用户状体啊&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;状态维护:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每次获取对应的 短token进行 jwt解析，解析失败则登陆失败&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;安全性处理:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在 JWT 中维护一个 user-agent字段，每次比对该字段是否相同&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 Redis 限流控制 每次的登陆&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;ol start=&#34;9&#34;&gt;
&lt;li&gt;
&lt;p&gt;刷新 Session 过期时间的几种方案&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;快要过期的时候刷新 ; 这个可能会有 gap&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每次访问都刷新 。会频繁的读redis&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;固定时间刷新 ， 增加 updateAt字段&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;怎么保护 session_id ?主要还是启用 https协议，设置 cookie 的 secure&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;怎么做到 在session_id 和 jwt-token泄漏之后保护着客户 ？ 记录登陆的额外信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如何保护 Web 服务 ？ 针对 IP限流、整个集群限流&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几个问题主要在问题 8 会引申&lt;/p&gt;
&lt;h2 id=&#34;问题-2&#34;&gt;问题 2
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;你用 Redis 解决过什么问题？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;业务上 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;用来标记某些 handle 是否完成。 例如某些需要在全环境修复的任务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分布式锁 控制针对于 IP 限流的一些账户创建&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stream 实现的轻量化 mq 队列 用于实现异步的消费&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储一些实时性不高的 例如快照的信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 Zset 进行热榜功能的排序&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 Redis 单机 线程安全的特性 实现限流&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;你知道 Redis 支持哪些数据结构，用过哪些 ？ 用来解决什么问题？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;string,list,hash,set,zset . 问题 1 的扩展&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;各个数据结构的底层实现 ？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不知道 ，有对应的 memo 。 预计会单独开辟一个文章&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;当你更新数数据的时候 先更新数据库还是先更新缓存，有没有一致性问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;先更新数据库，然后删除缓存。 对于更新操作 采用 Cahce-Aside 的方式 会有一致性问题&lt;/p&gt;
&lt;p&gt;我们只有在 Get 操作的时候才会重新写入缓存&lt;/p&gt;
&lt;p&gt;(这里说实话，忘记了开发的时候的想法了，创建一个memo处理一下)&lt;/p&gt;
&lt;ol start=&#34;5&#34;&gt;
&lt;li&gt;如何解决一致性问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里主要是 check-do thing 的模型吧 。 尽可能使用原子操作 ，例如 lua 或者是 atomic&lt;/p&gt;
&lt;h2 id=&#34;同样单独使用-memo-进行处理&#34;&gt;（同样单独使用 memo 进行处理）
&lt;/h2&gt;&lt;h2 id=&#34;问题-3&#34;&gt;问题 3
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;什么是依赖注入，如何在 Go 实现 依赖注入&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;依赖注入，就是 A 要调用 B 上面的方法, A 在构造的时候要求传入构造好的 B 而不是自己初始化一个 B 。&lt;/p&gt;
&lt;p&gt;通过引入 &lt;code&gt;wire&lt;/code&gt;包进行依赖注入&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;什么是面向接口编程 ？ 为什么要面向接口编程 ？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;业务之间 组件和组件的通信必须依赖接口 。 即 如果 A 调用 B，那么 B 必须是一个接口 。&lt;/p&gt;
&lt;p&gt;面向接口编程最主要的一点就是提升扩展性，避免代码堆积 。&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;什么是长短 token ？ 为什么要用长短两个 token ？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;长 token 用于控制 短 token 的刷新&lt;/p&gt;
&lt;p&gt;短 token 用于进行登陆的校验&lt;/p&gt;
&lt;p&gt;长短token的设计是为了安全性， 如果只有一个 token 有效期设置的很短 用户频繁登陆，很长的话会有安全问题&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;长短 token 的过期时间应该怎么设置 ？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根据业务需求和产品经理来判断，长 token 一般会很长 例如半年，一个月 。 短token 可以是 2小时 或者是 1天&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;5&#34;&gt;
&lt;li&gt;怎么保证长token的安全性，万一泄漏了怎么办 ？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;携带一些设备信息，例如 &lt;code&gt;user-agent&lt;/code&gt; &lt;code&gt;手机设备号&lt;/code&gt;，在对于敏感操作的时候，增加手机号验证&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&#34;6&#34;&gt;
&lt;li&gt;使用 JWT token 怎么退出登陆,使用 长短 token 之后怎么退出登陆&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;增加一个 session_id 进行维护，在 Redis 中维护一个黑名单&lt;/p&gt;
&lt;h2 id=&#34;扩展问题&#34;&gt;扩展问题
&lt;/h2&gt;&lt;p&gt;Q : 对于系统支持 微信、手机号、邮箱注册，数据库表是怎么设计的&lt;/p&gt;
&lt;p&gt;A : 对于这三个字段单独设计了唯一索引,我们认为任意一个渠道注册进来就是单独的一个账号&lt;/p&gt;
&lt;p&gt;Q : 那么如果我后续想要实现 手机号和微信和邮箱 是一个账号怎么办&lt;/p&gt;
&lt;p&gt;A :&lt;/p&gt;
&lt;p&gt;看一下业务层面是否要迁移历史数据&lt;/p&gt;
&lt;p&gt;如果不迁移历史数据的话 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;创建部分联合索引 加上 _create 限制，保证就新的数据需要遵守该索引&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;业务层面强制 这三类业务注册的时候 就一起绑定 手机号 或者是 邮箱&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果要迁移历史数据的话&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;优先保证内部数据迁移跑完&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;并且合并多个账户之间的数据。 这里可能复杂点 预计需要强制用户绑定手机号才可以进行功能操作 xxx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建索引&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;改善业务层面的登陆与注册服务&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Q : Gin 实现了哪些限流 降级 熔断&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;遗留&#34;&gt;遗留
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;尝试把 SMS 服务单独在拆一个场景出来，增加异步的内容&lt;/p&gt;
&lt;p&gt;可以设置一个 同步转异步的 容错机制 。 当第三方不可用的时候，将当前请求存储到数据库中，后续再单独启动一个 gouroutine 异步的发送出去&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;ai-judge&#34;&gt;AI Judge
&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;你现在是 Golang 面试官，下面是一个拥有2年经验的求职者的一个面试准备 。 请你结合现有系统实现 和 xxx.md 给其中的内容进行打分 满分100分，给出 优缺点和建议 。 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;对照 &lt;code&gt;webook&lt;/code&gt; 实现后，这份准备材料按 &lt;strong&gt;2 年经验后端面试&lt;/strong&gt; 标准评分如下。&lt;/p&gt;
&lt;h2 id=&#34;总分62--100&#34;&gt;总分：&lt;strong&gt;62 / 100&lt;/strong&gt;
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;分数&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;项目叙事与亮点提炼&lt;/td&gt;
					&lt;td&gt;14/20&lt;/td&gt;
					&lt;td&gt;有多通道登录、长短 Token、限流、failover，但「三高」略夸大&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;与代码一致性&lt;/td&gt;
					&lt;td&gt;10/15&lt;/td&gt;
					&lt;td&gt;大方向对，细节与代码不一致，易被追问穿帮&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;安全与鉴权深度&lt;/td&gt;
					&lt;td&gt;12/20&lt;/td&gt;
					&lt;td&gt;知道 bcrypt / JWT / ssid，但机制说不清、有概念混用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Go 工程能力（DI/接口/分层）&lt;/td&gt;
					&lt;td&gt;12/15&lt;/td&gt;
					&lt;td&gt;这块相对扎实，和代码匹配&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Redis / 中间件深度&lt;/td&gt;
					&lt;td&gt;7/15&lt;/td&gt;
					&lt;td&gt;用过什么能说，底层与一致性大量空白&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;表达与完整度&lt;/td&gt;
					&lt;td&gt;7/15&lt;/td&gt;
					&lt;td&gt;错别字多、多题空答、遗留太多&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;优点&#34;&gt;优点
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;项目选型对得上代码&lt;/strong&gt;：邮箱密码、短信、微信；长短 JWT + &lt;code&gt;ssid&lt;/code&gt; 黑名单；&lt;code&gt;User-Agent&lt;/code&gt; 校验；SMS failover；Wire + 接口；Redis 滑动窗口限流；CORS；&lt;code&gt;wrk&lt;/code&gt; 压测——这些在仓库里都能找到对应实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能讲清楚「为什么做」&lt;/strong&gt;：长短 Token 的安全/体验权衡、接口便于换 SMS/Repo、bcrypt 有压测体感——比只会背概念强。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扩展题有业务意识&lt;/strong&gt;：多渠道账号合并、历史数据迁移，这是面试官爱听的「产品 + 工程」题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工程分层叙事清晰&lt;/strong&gt;：依赖注入、面向接口，和 &lt;code&gt;wire.go&lt;/code&gt;、&lt;code&gt;UserCache&lt;/code&gt; 注释里的约束一致。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;缺点面试里会扣分的点&#34;&gt;缺点（面试里会扣分的点）
&lt;/h2&gt;&lt;h3 id=&#34;1-概念混用容易被打穿&#34;&gt;1. 概念混用，容易被打穿
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;实际是 &lt;strong&gt;JWT 放 Header&lt;/strong&gt;（&lt;code&gt;x-jwt-token&lt;/code&gt; / &lt;code&gt;x-refresh-token&lt;/code&gt;）+ Redis &lt;code&gt;users:ssid&lt;/code&gt; 黑名单，不是经典 Cookie Session。&lt;/li&gt;
&lt;li&gt;Cookie / Session 答得偏教材，和项目实现脱节；面试官一问「你们项目 Session 存在哪」，容易答乱。&lt;/li&gt;
&lt;li&gt;「Session ID 可放 redis/本地缓存/sql」把 &lt;strong&gt;服务端存储&lt;/strong&gt; 和 &lt;strong&gt;客户端携带位置&lt;/strong&gt; 混在一起了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;2-与代码不一致&#34;&gt;2. 与代码不一致
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;讲稿说法&lt;/th&gt;
					&lt;th&gt;代码现实&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;短 Token 2h / 1 天，长 Token 月/半年&lt;/td&gt;
					&lt;td&gt;短 &lt;strong&gt;30 分钟&lt;/strong&gt;，长 &lt;strong&gt;7 天&lt;/strong&gt;（&lt;code&gt;redis_jwt.go&lt;/code&gt;）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;设备验证&lt;/td&gt;
					&lt;td&gt;仅比对 &lt;code&gt;User-Agent&lt;/code&gt;，不是设备指纹&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;完整高可用 SMS&lt;/td&gt;
					&lt;td&gt;有 failover，但 &lt;code&gt;SendV1&lt;/code&gt; 有逻辑问题；真实多厂商未必接好&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;DDD&lt;/td&gt;
					&lt;td&gt;更接近分层 + 接口，不是完整 DDD&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;短信登录流程本身也不完整：&lt;code&gt;LoginSMS&lt;/code&gt; 校验成功后&lt;strong&gt;没发 Token&lt;/strong&gt;，Token 却在 &lt;code&gt;SendLoginSMSCode&lt;/code&gt; 里设置——若被问「短信登录时序」，会很难圆。&lt;/p&gt;
&lt;h3 id=&#34;3-关键答案有硬伤&#34;&gt;3. 关键答案有硬伤
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;bcrypt vs md5&lt;/strong&gt;：选 bcrypt 主要因为自适应哈希/抗暴力破解，不是「md5 要额外存盐」。bcrypt 自带盐，理由说偏了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨域&lt;/strong&gt;：不只是 host/port，还有 &lt;strong&gt;scheme&lt;/strong&gt;；还缺 &lt;code&gt;Credentials&lt;/code&gt;、&lt;code&gt;ExposeHeaders&lt;/code&gt;（你们代码里刚好有 &lt;code&gt;ExposeHeaders&lt;/code&gt; 暴露 JWT Header）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存一致性&lt;/strong&gt;：写了「忘了」——2 年经验被问 Cache-Aside / 双写，这是硬伤。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gin 限流/降级/熔断&lt;/strong&gt;：空题；项目有限流，没有完整熔断降级，却标题写了「高可用」。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;4-准备完整度不够&#34;&gt;4. 准备完整度不够
&lt;/h3&gt;&lt;p&gt;Redis 底层、一致性、Gin 治理、遗留 SMS 异步都标了 memo/空答。面试不是写博客进度，空题 = 扣分。&lt;/p&gt;
&lt;h3 id=&#34;5-自我包装过满&#34;&gt;5. 自我包装过满
&lt;/h3&gt;&lt;p&gt;「完整的高安全性、高可用、高扩展」对学习项目偏重。2 年经验更稳妥的说法是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;练手项目，覆盖多登录方式、长短 Token、限流与 SMS 容错，重点验证接口化扩展。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;建议按优先级&#34;&gt;建议（按优先级）
&lt;/h2&gt;&lt;h3 id=&#34;立刻改讲稿&#34;&gt;立刻改讲稿
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;用代码里的真实数字讲&lt;/strong&gt;：AT 30min、RT 7 天；退出时 &lt;code&gt;ssid&lt;/code&gt; 写入 Redis，TTL 与 RT 对齐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一术语&lt;/strong&gt;：说「JWT + 会话标识（ssid）黑名单」，少说「Session 存在 Cookie」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删掉或降级「设备验证」&lt;/strong&gt;：改成「绑定 User-Agent，防简单 Token 盗用，不能防 UA 伪造」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;补齐空题&lt;/strong&gt;，至少能答 2 分钟：
&lt;ul&gt;
&lt;li&gt;Cache-Aside：先写 DB 再删缓存；并发下的短暂不一致&lt;/li&gt;
&lt;li&gt;Redis String/Hash/ZSet 各举一个本项目例子（验证码、用户信息、热榜）&lt;/li&gt;
&lt;li&gt;限流：滑动窗口；熔断降级诚实说「项目未做，可讲思路」&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;对着代码能画一张时序图&#34;&gt;对着代码能画一张时序图
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;邮箱登录 → 发 AT/RT → 中间件校验 AT + UA + ssid&lt;/li&gt;
&lt;li&gt;Refresh：校验 RT → 查黑名单 → 发新 AT&lt;/li&gt;
&lt;li&gt;Logout：ssid 入黑名单&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面试官最爱顺着这条链路追问。&lt;/p&gt;
&lt;h3 id=&#34;主动暴露已知坑加分&#34;&gt;主动暴露已知坑（加分）
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;短信登录时序问题&lt;/li&gt;
&lt;li&gt;Failover 主备不均 / &lt;code&gt;SendV1&lt;/code&gt; 错误处理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FindOrCreate&lt;/code&gt; 主从延迟注释&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「我知道问题在哪、怎么改」比「系统很完美」更像有 2 年经验。&lt;/p&gt;
&lt;h3 id=&#34;表达&#34;&gt;表达
&lt;/h3&gt;&lt;p&gt;错别字（Cros、状体啊、Cahce）和口语化「不知道有 memo」直接改掉；面试可以诚实说「这块我回去补过」，但讲稿里不该留白。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;面试官视角的一句话&#34;&gt;面试官视角的一句话
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;项目素材够用（约 70 分的料），讲解与掌握大约 55–60 分；合在一起 62。&lt;/strong&gt;&lt;br&gt;
能过初筛讲项目，但深挖安全、缓存一致性、短信登录细节时风险大。把术语对齐代码、补空题、主动讲缺陷，冲到 &lt;strong&gt;75+&lt;/strong&gt; 比较现实。&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
