实习项目分析

小米

2023年6月至10月在小米实习。产品线是游戏媒体后端,主要负责的是游戏媒体后端的社区业务。

移库问题

讲讲细节?

当时的移库任务并不复杂。因为业务需求,需要原来放在两个表里面的配置项合并。因为数据库被包装好了,不太方便直接向数据库输入sql,通过 SpringBoot 应用,相当于实现一个脚本。这个工作并不复杂。

数据库里面存储的是一些基于规则引擎的配置文件,或许是json文件?业务合并后,也需要将业务的规则引擎配置文件合并。

如何处理大型表的数据库移库需求?

首先要明确迁移的数据库的状态和迁移时的可用性要求,是同构的还是异构的?数据量的大小?数据库在迁移的时候必须保障原有服务吗?允许服务降级吗?数据库的QPS是怎样的?

迁移方案有两个思路:一种是“停机迁移”,简单粗暴但风险高,需要和业务方反复确认停机时间窗口是否可接受;另一种是“在线迁移”,通过全量+增量的方式逐步同步数据,最后只切少量停机时间。比如先用工具全量复制历史数据,再通过监听Binlog实时同步增量变更,最终在业务低峰期停写旧库、追平增量后切换流量。但这里有个陷阱:如果业务有长事务或者大表,增量追平可能会耗费远超预期的时间。

还要注意Null值问题。

在迁移时,需要注意性能优化,迁移时禁用外键和索引可以提速,但完成后必须记得重建,否则查询性能可能断崖式下跌,要尽量保障数据库迁移时间的贷款和物理机性能,尽量减少迁移需要的时间,尽量选择QPS不繁忙的时间进行数据迁移。

迁移后的验证和切换。应当通过抽样或者其他方式校验新库和老库中的数据一致性,比如md5校验值或者抽样检测。应当灰度地把流量切换到新库,观察慢查询和错误日志,再逐步放开写操作。保留好回滚的手段。

工具的选择往往事半功倍。比如同构迁移可以用数据库自带的工具链(如MySQL的mysqldump+Binlog),而跨云或异构迁移(比如Oracle到PostgreSQL)可能需要借助Alibaba Cloud DTS这类服务。没有银弹,所以迁移过程不能掉以轻心,应当时刻监控数据,结合人工检查。多方校验。

什么是Null值问题

Null值导致迁移失败的直接原因

问题场景 说明 示例
目标表字段不允许Null 目标数据库的字段定义为NOT NULL,但源数据中存在Null值,导致插入失败。 迁移时抛出错误:ERROR: null value in column "user_id" violates not-null constraint
数据类型不兼容 源数据库允许Null的字段类型在目标数据库中可能不支持。 Oracle的VARCHAR2允许Null,但迁移到某些NoSQL数据库时可能无法隐式处理。
默认值冲突 目标表字段定义了默认值,但Null值可能覆盖默认逻辑,导致数据不一致。 源数据中status为Null,目标表定义status INT DEFAULT 1,迁移后status变为Null而非1。

Null值引发的逻辑隐式问题

问题场景 说明
索引失效 目标数据库中Null值可能导致索引不包含这些记录(如唯一索引允许多个Null)。
聚合函数偏差 SUM()AVG()等函数忽略Null值,可能导致迁移后统计结果与源库不一致。
应用逻辑异常 应用程序可能未处理目标库中Null值的特殊含义(如空字符串与Null的混淆)。
外键约束失效 外键字段为Null时,可能绕过约束检查,破坏数据完整性。

跨数据库系统的Null值差异

不同数据库对Null值的处理规则可能不同,迁移时需特别注意:

数据库 Null值特性
Oracle 空字符串''被存储为Null。
PostgreSQL 空字符串''与Null是两种不同的值。
MySQL 严格模式下禁止插入Null到NOT NULL字段,非严格模式会尝试转换(如Null转默认值)。
SQL Server 空字符串与Null分离,但比较时NULL = NULL返回False

什么是规则引擎

规则引擎(Rule Engine)是一种将业务规则与应用程序代码解耦的技术,通过预定义的逻辑模型(如条件-动作规则)动态执行决策流程。它允许非技术人员(如业务人员)直接管理规则,而无需修改底层代码,适用于需要频繁调整业务策略的场景(如风控、营销活动、流程审批等)。

组成:

  1. 规则定义:用特定语法或可视化界面描述条件(Condition)和动作(Action)。
  2. 规则存储:持久化规则(如数据库、文件、配置中心)。
  3. 规则编译:将规则转换为可执行的内部结构(如决策树、Rete网络)。
  4. 推理执行:根据输入数据匹配规则并触发动作。

规则引擎算法

  1. Rete算法
  2. PHREAK 算法
  3. Leaps
  • 先收集所有可能的规则匹配,再按优先级批量触发动作。
  • 将规则分解为可并行处理的子网络,提升多核利用率。
  • 根据规则条件概率排序事实,优先匹配高概率命中条件。
  • 将无依赖的规则分组并行执行(如按业务模块分组)。

Viewpoint 社区项目缓存不一致问题

原始方案核心缺陷分析

  1. 数据持久化不可靠
    • Write-Behind模式依赖缓存层完成数据持久化,在缓存宕机时未持久化的内存数据会完全丢失
    • 缺乏持久化队列保护,无法应对节点故障、网络分区等异常场景
    • 前端调用点赞接口不校验或者校验错误用户是否已经点赞导致重复点赞
  2. 缓存-数据库同步机制缺陷
    • 采用简单TTL过期策略,在缓存重建时可能覆盖其他未同步的修改(写覆盖问题)
    • 缺乏版本控制机制,无法处理并发场景下的操作时序问题
  3. 异常处理机制缺失
    • 没有定义明确的故障恢复流程(如缓存击穿后的数据重建策略)
    • 缺乏异步操作的状态监控与补偿机制
    • 未考虑分布式场景下的脑裂问题应对方案

优化方案设计原则

  1. 可靠性优先

    • 所有写操作必须至少持久化到磁盘日志(WAL)后才能响应成功
    • 采用异步批处理代替实时双写,降低数据库压力
  2. 最终一致性模型

    • 允许前台展示延迟数据(如点赞数±100内随机波动)
    • 关键操作(如实际点赞关系)保持强一致性
  3. 分层容错设计

    mermaid

    复制

    1
    2
    3
    4
    5
    6
    7
    8
    graph TD
    A[客户端] --> B[本地内存缓存]
    B --> C{缓存命中?}
    C -->|Yes| D[返回缓存值]
    C -->|No| E[Redis集群]
    E --> F{Redis可用?}
    F -->|Yes| G[返回并更新本地缓存]
    F -->|No| H[降级读数据库+本地限流]

关键改进措施

  1. 持久化层重构

    • 引入消息队列作为持久化日志层,写操作分布式事务,只需要更新了缓存之后就返回,更新数据库的操作在后台运行
    • 设计幂等消费者,确保至少一次投递语义
  2. 缓存更新策略优化

    • 采用多级TTL策略:

      • 基础TTL(5分钟):常规过期时间
      • 动态TTL(1-30分钟):根据写频率自动调整
      • 强制TTL(1小时):绝对最长存活时间
    • 实现延迟双删模式:

      复制

      1
      2
      3
      4
      更新流程:
      1. 删除缓存
      2. 更新数据库
      3. 延时500ms再次删除缓存
  3. 异常处理增强

    • 设计熔断降级策略:
      • 当Redis错误率>30%时,自动切换为直连数据库模式
      • 对非核心操作(如点赞数展示)实施随机降级(30%请求直接返回默认值)
    • 建立补偿任务:
      • 每小时执行缓存与数据库的差异校验
      • 对偏差超过阈值的热点数据触发主动预热

如何记录用户和帖子之间的点赞关系

  1. 索引策略

    • 主索引:创建(user_id, post_id)联合唯一索引(防止重复点赞)
    • 覆盖索引:对查询频率高的场景建立(post_id, user_id)倒排索引
  2. 分库分表

    • 对帖子进行hash 分库存储
  3. Redis 使用 Set 存储热点帖子的点赞信息

方案对比分析

维度 原始方案 优化方案
数据安全性 内存数据易丢失 通过WAL日志保障持久化
写吞吐量 最高5k QPS 可达50k QPS
一致性延迟 分钟级不一致 秒级最终一致性
故障恢复时间 依赖人工介入(小时级) 自动恢复(分钟级)
复杂度 中高(需维护消息队列)

适用场景建议

  1. 推荐使用优化方案场景
    • 社交媒体点赞/阅读数统计
    • 商品库存的准实时展示
    • 新闻资讯类PV/UV统计
  2. 不建议使用场景
    • 金融账户余额变更
    • 医疗设备实时监控
    • 需要ACID事务保证的核心业务

该方案在保证最终一致性的前提下,通过架构分层和异步化设计实现了性能与可靠性的平衡,适用于对数据实时性要求不苛刻但需要高吞吐的场景。对于关键业务数据仍需采用同步写+分布式事务的强一致性方案。

Write Behind Caching 最适合以下场景

✅ 高并发写入且允许最终一致性

✅ 数据库写入性能不足需削峰填谷

✅ 非关键数据需快速响应(如统计类、日志类)

帖子推荐

需求背景

为提升平台内容分发效率,需实现一套基于运营策略的帖子推荐系统。系统需提供帖子预览列表页帖子详情阅读页两个核心接口,支持未来灵活扩展推荐算法(如大模型、大数据分析等)。当前首期采用人工运营配置的固定推荐策略,需保障架构的可扩展性,同时优化接口性能与数据加载效率。


核心设计方案

  1. 推荐策略模块(策略模式落地)
    • 抽象统一的推荐策略接口 RecommendationStrategy,定义排序算法、过滤规则等方法。
    • 一期实现:通过运营配置的固定优先级规则(如置顶帖、人工加权、时间衰减)生成推荐列表。
    • 扩展预留:后续可通过新增策略实现类(如 AIModelStrategyUserBehaviorStrategy)无缝接入大模型或用户行为分析算法,无需修改主流程代码。
  2. 接口与数据聚合设计
    • 帖子预览接口:返回推荐列表的概要信息(标题、作者、缩略图、点赞数等),按策略排序。
    • 帖子详情接口:提供帖子完整内容,并一次性返回关联的前10条热门评论(按点赞数排序),减少客户端多次请求。
    • 数据打包优化:通过DTO(Data Transfer Object)聚合帖子基础信息、用户基础信息、评论数据,采用嵌套结构避免前端拼接逻辑。
  3. 缓存与性能优化
    • 热点数据缓存:对运营配置的固定推荐位帖子(如置顶帖、轮播帖),采用Redis缓存并设置较长过期时间(如12小时),通过定时任务预热更新。
    • 评论缓存:为每个帖子缓存前10条热门评论,采用旁路缓存策略(Cache-Aside),优先读取缓存,失效时异步回源数据库。
    • 降级兜底:缓存失效或数据库压力过大时,自动降级返回基础运营配置列表,保障服务可用性。

技术亮点

  • 扩展性保障:策略模式隔离算法与业务逻辑,未来新增推荐策略仅需实现接口,符合开闭原则。
  • 性能友好:接口数据聚合减少HTTP请求次数,缓存设计降低数据库QPS压力(预估减少70%以上读请求)。
  • 业务解耦:运营配置规则(如权重、生效时间)独立存储于配置中心,支持动态调整无需发版。

补充说明

  • 数据一致性:采用双删策略(更新DB后先删缓存再延迟二次删除),避免极端场景下的缓存脏数据。
  • 监控报警:对缓存命中率、接口响应时间、推荐策略执行耗时等指标埋点监控,异常时触发告警。

通过上述设计,系统在满足当前运营需求的同时,为未来技术演进预留充足空间,兼顾性能、可维护性与架构弹性。

相关帖子

标签体系构建(运营侧)

  1. 建立标签知识库
  • 使用图数据库(Neo4j)构建多级标签体系
  • 包含标签属性:名称、权重、层级关系、同义词库
  • 提供可视化运营后台管理界面

基于TF-IDF 实现标签分析

计算标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def extract_tags(text):
# 预处理
cleaned_text = preprocess(text)

# TF-IDF关键词提取
tfidf_keywords = tfidf_vectorizer.transform([cleaned_text])

# BERT语义嵌入
bert_embedding = bert_model.encode([cleaned_text])

# 标签匹配
candidate_tags = hybrid_match(tfidf_keywords, bert_embedding)

# 置信度过滤
final_tags = [tag for tag in candidate_tags if tag.confidence > threshold]

return final_tags

计算相似度

def calculate_similarity(post_a, post_b):
tags_a = get_post_tags(post_a)
tags_b = get_post_tags(post_b)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def calculate_similarity(post_a, post_b):
tags_a = get_post_tags(post_a)
tags_b = get_post_tags(post_b)

# 计算基础相似度(Jaccard)
base_sim = len(set(tags_a) & set(tags_b)) / len(set(tags_a) | set(tags_b))

# 层级权重调整
hierarchy_sim = adjust_by_hierarchy(tags_a, tags_b)

# 语义相似度(使用标签向量)
semantic_sim = cosine_similarity(tag_vectors[tags_a], tag_vectors[tags_b])

return 0.4*base_sim + 0.3*hierarchy_sim + 0.3*semantic_sim

存储设计

数据库存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE post_tags (
post_id BIGINT,
tag_id INT,
confidence FLOAT,
PRIMARY KEY (post_id, tag_id)
);

CREATE TABLE tag_relations (
tag_id INT,
related_tag_id INT,
relevance_score FLOAT,
PRIMARY KEY (tag_id, related_tag_id)
);

倒排索引

1
2
3
4
5
6
7
8
9
10
11
{
"index": "posts",
"body": {
"mappings": {
"properties": {
"tags": { "type": "nested" },
"vector": { "type": "dense_vector" }
}
}
}
}

收获

主要的成长点在于:

  1. 这个是我第一次完整地接触工业实践,包括 RPC 在学校里比较少用到的技术,设计模式在实际实践里的真实应用;
  2. 技术规范的过程,主要是设计文档和接口文档的编写,学校里的项目产生的团队合作都比较小规模,对文档的要求没那么高。这次实践让我真实认识到文档的重要性,把老师课堂上讲的内容映射到实践中来。
  3. 完整的流程,从需求分析,到设计,开发,测试和上线的软件工程全过程实践。

校园实践项目

secareer@nju

数据库设计

核心在于四张表:

  1. 用户表,用于记录各类型的用户信息,包括导师,学生,辅导员和实验室管理员
  2. 学生导师关系表,用于记录学生的实际导师和挂名导师,单独一张表方便导出导入。主键索引是student_id,添加联合索引(student_id, mentor_id)
  3. 申请记录:用于记录每一位学生的实习申请记录。主键索引application_id,辅助索引 student_id
  4. 操作记录:用于记录在某个时间节点,某个人员对某个记录做了何种操作。添加辅助索引application_id

操作记录表和申请记录表记录了主要的业务逻辑。通过操作记录表记录人员的操作信息,结合系统日志作为后续责任划分的依据。后续考虑增加机器码等信息。

使用事务保障对两张表的修改的原子性。

minio

MinIO作为一款基于Golang 编程语言开发的一款高性能的分布式式存储方案的开源项目,有十分完善的官方文档。

支持多种客户端,兼容AWS 的API

因为无法使用云服务商的对象存储服务而选择。

docker

Docker的优势应该包括环境一致性、快速部署、资源高效和易于扩展。这些都是用户可能关心的点,特别是对于开发、测试和部署流程中的问题。

Docker 通过容器化技术,将应用与环境“打包”,解决了开发到部署的协作难题。它是现代 DevOps、云原生和微服务的基础设施核心,几乎成为软件开发的标配工具

nginx

作为网关和前端服务器。

ci-cd

传统开发中,多人协作时频繁合并代码到主分支会导致大量冲突和兼容性问题。CI(持续集成)通过高频次代码提交与自动化构建/测试,确保每次变更都能快速发现冲突和错误,避免长期分支带来的集成难题。传统瀑布模型下,版本发布周期长(如每月/季度),无法快速响应需求。CD(持续交付/部署)通过自动化将代码快速推向生产环境,实现按需发布,缩短迭代周期。开发、测试、生产环境差异常导致“在我机器上能运行”的问题。CI/CD 结合容器化(如 Docker)和基础设施即代码(IaaC),确保环境一致性。

Tai-e

tai-e 是静态程序分析课程的课程实验。

静态分析是一种在不实际运行程序的情况下,通过分析源代码或中间表示(IR)来推断程序行为的技术。其主要目标包括:

  • 代码优化:如删除冗余代码、内联函数等。
  • 缺陷检测:如空指针解引用、内存泄漏等。
  • 安全性验证:如信息流分析、权限控制等73。

静态分析通常基于抽象解释(Abstract Interpretation)理论,通过近似(Over-approximation 或 Under-approximation)在精度与效率之间权衡。

主要是实现一个静态程序分析的工具,用于进行常量分析,活跃变量分析,上下文敏感和不敏感的指针分析等。

指针分析是静态分析中的关键技术,用于确定程序中的指针(变量或字段)可能指向哪些对象。它在编译优化、漏洞检测和调用图构建中起基础作用。

比如说,一个代码里面,经过非运行时的推断,无论经过怎样的运行路径,一个变量的值都是一个常量,那么我们就可以认为这个变量是一个常量。那我可以在编译期间就进行优化。基于常量分析,我们可以做到死代码分析,知道哪些分支的判断条件是不变的,哪些代码是永远不会执行的。

指针分析相对来说比较复杂,因为指针的引用关系是动态的,我们需要在静态分析的时候,模拟出动态的引用关系。如对于非上下文敏感的指针分析中,将同一个指针指向的对象抽象化为一个对象;而在上下文敏感的指针分析中则是会研究在不同情况下,对该指针指向的对象进行细粒度的分析,比如从哪一行开始创建的。基于指针分析,可以实现逃逸分析,分析一个变量的引用是否会逃逸到函数外部。从而可以进行栈上分配,减少堆上内存的分配。获得优化。

这门课程和我们的研究方向有很大的类似性。我们对分布式协议的验证,并不能在运行时验证,因为分布式系统的状态空间太大,有些状态转移的路径,访问的概率很低。我们需要在静态分析的时候,通过工具,例如tla+,将分布式协议的各个状态抽象出来,然后对每个可达的然后进行验证,确保总体状态符合安全属性。