perf(playback): 观看进度上报异步化(Redis write-behind 批量落库)
- POST /app/watch/progress 只写 Redis 缓冲立即返回(p95 75ms→2ms), WatchProgressFlusher 每 2s 把 watch:pg:dirty 队列按身份合并批量 UPSERT, Redis 异常自动回退同步写库,接口契约不变 - 读接口用缓冲覆盖 DB 行保证读己之写;Caffeine 缓存剧集校验免去上报路径两条 DB 查询 - 落库按「新增进度/新增流水/更新行」分组攒批(MyBatis BATCH 只合并连续同形状语句), 配合 reWriteBatchedInserts 多值 INSERT,12.4 万积压 45 秒消化完, 1650 QPS 持续压测队列稳态归零
This commit is contained in:
1 parent
fc9e429187
commit
3e2d6a06d9
9 files changed
+837
-54
No files matched your search
@@ -23,6 +23,13 @@ fruitshorts:
|
||||
cors:
|
||||
# 生产只允许显式配置的来源;没配就等于空,浏览器侧会被拦住(比"*"安全)
|
||||
allowed-origins: ${CORS_ALLOWED_ORIGINS:}
|
||||
watch-progress:
|
||||
# 观看进度写缓冲:POST 先写 Redis 立即返回,后台任务批量合并进 watch_progress。
|
||||
# 消化能力 = batch-size / interval ≈ 5000 条/秒,覆盖 30 万 DAU 峰值 ~3700 条/秒上报。
|
||||
# 关掉(false)即回退同步写库,接口契约不变。
|
||||
buffer: true
|
||||
flush-interval-ms: 2000
|
||||
flush-batch-size: 10000
|
||||
|
||||
springdoc:
|
||||
# 生产只留机器可读契约,关掉 UI(doc.html 不对外开放)
|
||||
|
||||
@@ -11,8 +11,10 @@ spring:
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
# 地址必须外置:不同环境(本机 / 测试 / 生产)连的库不一样,
|
||||
# 生产请用 DB_URL 注入(prod profile 会校验它非空)
|
||||
url: ${DB_URL:jdbc:postgresql://127.0.0.1:5432/fruitshorts}
|
||||
# 生产请用 DB_URL 注入(prod profile 会校验它非空)。
|
||||
# reWriteBatchedInserts:pgjdbc 把批内多条 INSERT 重写成多值 INSERT,
|
||||
# 观看进度缓冲落库依赖它才能跑满批量吞吐。
|
||||
url: ${DB_URL:jdbc:postgresql://127.0.0.1:5432/fruitshorts?reWriteBatchedInserts=true}
|
||||
username: ${DB_USER:fruitshorts}
|
||||
password: ${DB_PASSWORD:fruitshorts}
|
||||
jackson:
|
||||
|
||||
+12
-1
@@ -20,10 +20,14 @@ import com.fruitshorts.playback.vo.ProgressRespVO;
|
||||
import com.fruitshorts.playback.vo.ProgressSaveReqVO;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -58,11 +62,18 @@ class WatchProgressServiceTest {
|
||||
historyMapper = mock(WatchHistoryMapper.class);
|
||||
dramaService = mock(DramaService.class);
|
||||
episodeService = mock(EpisodeService.class);
|
||||
// 测试统一走缓冲关闭(同步写库)路径:getIfAvailable 返回 null
|
||||
@SuppressWarnings("unchecked")
|
||||
ObjectProvider<WatchProgressBuffer> bufferProvider = mock(ObjectProvider.class);
|
||||
given(bufferProvider.getIfAvailable()).willReturn(null);
|
||||
progressService = new WatchProgressService(
|
||||
progressMapper,
|
||||
historyMapper,
|
||||
dramaService,
|
||||
episodeService);
|
||||
episodeService,
|
||||
bufferProvider,
|
||||
mock(SqlSessionFactory.class),
|
||||
new TransactionTemplate(mock(PlatformTransactionManager.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user