【Feed 高并发架构实战】:雪花 ID + 三级缓存 + 计数旁路设计详解

🔥你好我是fengxin_rou这是我的个人主页 fengxin_rou的主页

❄️欢迎查看我的专栏我的专栏

《Java后端学习》《JAVASE基础》《JUC并发》《redis》《JVM虚拟机》《MYSQL》《黑马点评》《rabbitmq》《JavaWeb+AI的talis学习系统》《苍穹外卖》

目录

前言

[一、雪花算法 ID 生成器:分布式唯一 ID 设计与实现](#一、雪花算法 ID 生成器:分布式唯一 ID 设计与实现)

[1.1 ID 结构设计](#1.1 ID 结构设计)

[1.2 核心代码实现](#1.2 核心代码实现)

[1.3 关键特性](#1.3 关键特性)

[二、Feed 流三级缓存架构:高并发读性能优化](#二、Feed 流三级缓存架构:高并发读性能优化)

[2.1 缓存层级定义](#2.1 缓存层级定义)

[2.2 缓存读取流程](#2.2 缓存读取流程)

[2.3 hasMore 软缓存设计](#2.3 hasMore 软缓存设计)

[2.4 核心优势](#2.4 核心优势)

三、计数旁路更新与反向索引:实时计数精准失效

[3.1 核心设计思路](#3.1 核心设计思路)

[3.2 计数监听核心代码](#3.2 计数监听核心代码)

[3.3 反向索引与清理机制](#3.3 反向索引与清理机制)

四、工程化实践要点与架构价值

[4.1 关键实践原则](#4.1 关键实践原则)

[4.2 架构整体价值](#4.2 架构整体价值)

结语


前言

在高并发内容平台中,分布式 ID 生成、多级缓存架构、实时计数更新 是支撑海量请求的核心技术。本文基于知文业务实战,深度解析雪花算法 ID 生成器、Feed 流三级缓存设计、计数旁路更新与反向索引精准失效方案,覆盖原理、代码实现与工程化实践,可直接用于分布式内容系统架构设计。补充可以观看我前一篇feed文Feed 三级缓存架构详解:分层设计、缓存一致性与高性能实战

一、雪花算法 ID 生成器:分布式唯一 ID 设计与实现

在分布式系统中,数据库自增 ID 存在性能瓶颈、易暴露业务量、不支持多机房 等缺陷,雪花算法通过内存生成 ID,单机 TPS 可达千万级,完美解决上述问题。

1.1 ID 结构设计

雪花 ID 为64 位长整型,结构固定:

  • 1 位符号位:固定为 0,保证 ID 为正数
  • 41 位时间戳:相对自定义纪元,支持约 69 年
  • 5 位数据中心 ID:支持 32 个机房
  • 5 位工作节点 ID:单机房支持 32 台服务器
  • 12 位序列号:单毫秒支持 4096 个 ID

1.2 核心代码实现

复制代码
@Component
public class SnowflakeIdGenerator {
    // 自定义纪元:2024-01-01 00:00:00 UTC
    private static final long EPOCH = 1704067200000L;
    // 各部分位数定义
    private static final long WORKER_ID_BITS = 5L;
    private static final long DATACENTER_ID_BITS = 5L;
    private static final long SEQUENCE_BITS = 12L;
    // 最大值计算
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
    // 位移偏移量
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

    private final long datacenterId;
    private final long workerId;
    private long lastTimestamp = -1L;
    private long sequence = 0L;

    // 构造器与参数校验
    public SnowflakeIdGenerator(long datacenterId, long workerId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("workerId越界");
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId越界");
        }
        this.datacenterId = datacenterId;
        this.workerId = workerId;
    }

    // 线程安全生成ID
    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();
        // 时钟回拨处理
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                // 小幅度回拨:等待时钟追回
                try { Thread.sleep(offset); } 
                catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new IllegalStateException("线程中断");
                }
                timestamp = System.currentTimeMillis();
                if (timestamp < lastTimestamp) {
                    throw new IllegalStateException("时钟仍回拨");
                }
            } else {
                // 大幅度回拨:拒绝生成
                throw new IllegalStateException("时钟回拨过大");
            }
        }
        // 序列号自增
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                timestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        // 组装ID
        return ((timestamp - EPOCH) << TIMESTAMP_LEFT_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }

    private long waitNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}

1.3 关键特性

  • 线程安全:nextId () 加 synchronized,保证并发安全
  • 时钟回拨防护:≤5ms 等待追回,>5ms 直接抛异常,避免 ID 重复
  • 高并发:单毫秒支持 4096 个 ID,内存计算无 IO 开销

二、Feed 流三级缓存架构:高并发读性能优化

知文 Feed 采用L1 本地缓存 + L2 Redis 分片缓存 + L3 数据库三级架构,解决高并发下缓存击穿、命中率低、更新不及时问题。

2.1 缓存层级定义

  • L1 Caffeine 缓存:本地内存,存储整页 Feed 数据,访问速度 0.001ms
  • L2 Redis 分片缓存:存储文章 ID 列表、单篇文章详情、hasMore 标记,支持批量读取
  • L3 MySQL 数据库:数据源头,仅缓存未命中时查询

2.2 缓存读取流程

  1. 请求进入,优先查询 L1,命中直接返回
  2. L1 未命中,查询 L2,数据完整则组装并回填 L1
  3. L2 数据不完整,触发 L3 查询,全量覆盖 L2 后回填 L1
  4. 前端展示并结束流程

2.3 hasMore 软缓存设计

hasMore 标记是否有下一页,采用软缓存 + 兜底逻辑

  • 优先使用缓存中的 hasMore 值
  • 缓存缺失时,按当前页数量==页大小判断
  • 兜底不影响最终准确性,下一页请求会修正为真实值

2.4 核心优势

  • 防缓存击穿:单航班锁保证同一页只查一次数据库
  • 高命中率:L1+L2 组合命中率可达 99%+
  • 状态分离:用户态与公共态分离,避免缓存污染

三、计数旁路更新与反向索引:实时计数精准失效

点赞、收藏等计数变化,需不侵入主流程、精准更新所有相关缓存,采用计数旁路 + 反向索引方案实现。

3.1 核心设计思路

  • 旁路更新:计数变更通过事件异步触发,不阻塞主接口
  • 反向索引:记录文章 ID→被哪些 Feed 页引用,实现精准失效
  • 双缓存更新:同步更新 L1 本地缓存与 L2 Redis 缓存

3.2 计数监听核心代码

复制代码
@EventListener
public void onCounterChanged(CounterEvent event) {
    // 只处理知文点赞/收藏事件
    if (!"knowpost".equals(event.getEntityType())) return;
    String metric = event.getMetric();
    if (!"like".equals(metric) && !"fav".equals(metric)) return;
    String eid = event.getEntityId();
    int delta = event.getDelta();
    // 更新创作者总计数
    try {
        KnowPost post = knowPostMapper.findById(Long.valueOf(eid));
        if (post != null && post.getCreatorId() != null) {
            long owner = post.getCreatorId();
            if ("like".equals(metric)) {
                userCounterService.incrementLikesReceived(owner, delta);
            }
            if ("fav".equals(metric)) {
                userCounterService.incrementFavsReceived(owner, delta);
            }
        }
    } catch (Exception ignored) {}
    // 获取最近两小时反向索引
    long hourSlot = System.currentTimeMillis() / 3600000L;
    Set<String> keys = new LinkedHashSet<>();
    Set<String> cur = redis.opsForSet().members("feed:public:index:" + eid + ":" + hourSlot);
    Set<String> prev = redis.opsForSet().members("feed:public:index:" + eid + ":" + (hourSlot - 1));
    if (cur != null) keys.addAll(cur);
    if (prev != null) keys.addAll(prev);
    // 遍历更新缓存
    for (String key : keys) {
        // 更新L1本地缓存
        FeedPageResponse local = feedPublicCache.getIfPresent(key);
        if (local != null) {
            FeedPageResponse updated = adjustPageCounts(local, eid, metric, delta, true);
            feedPublicCache.put(key, updated);
        }
        // 更新L2 Redis缓存
        String cached = redis.opsForValue().get(key);
        if (cached != null) {
            try {
                FeedPageResponse resp = objectMapper.readValue(cached, FeedPageResponse.class);
                FeedPageResponse updated = adjustPageCounts(resp, eid, metric, delta, false);
                writePageJsonKeepingTtl(key, updated);
            } catch (Exception ignored) {}
        } else {
            // 清理失效索引
            redis.opsForSet().remove("feed:public:index:" + eid + ":" + hourSlot, key);
        }
    }
}

3.3 反向索引与清理机制

  • 正向索引:页面 Key→包含的文章 ID
  • 反向索引:文章 ID→被哪些页面引用
  • 自动清理:缓存过期时,监听器自动移除无效索引,避免内存浪费

四、工程化实践要点与架构价值

4.1 关键实践原则

  1. 参数安全化:接口参数校验,防止恶意请求
  2. 防御性编程:空值判断、异常捕获,保证系统健壮
  3. 状态隔离:用户态不写入公共缓存,避免数据污染
  4. 软缓存兜底:关键标记用软缓存 + 逻辑兜底,提升可用性

4.2 架构整体价值

  • 性能提升:接口响应从百毫秒降至毫秒级
  • 高可用:缓存 + 数据库降级,支持流量洪峰
  • 易扩展:支持多机房、多节点水平扩容
  • 低耦合:计数、缓存、业务逻辑分离,便于维护

结语

本文完整呈现知文 Feed 高并发架构的三大核心:雪花 ID 保证分布式唯一三级缓存支撑高并发读反向索引 + 旁路更新实现实时计数。整套方案兼顾性能、可靠性与可扩展性,适用于内容 Feed、社交动态、商品流等高并发场景。实际落地中,可根据业务调整节点位数、缓存过期时间、回拨容忍阈值,进一步优化架构适配性。

相关推荐
廿一夏10 小时前
MySql存储引擎与索引
数据库·sql·mysql
Mahir0810 小时前
Spring 循环依赖深度解密:从问题本质到三级缓存源码级解析
java·后端·spring·缓存·面试·循环依赖·三级缓存
曲幽10 小时前
我用了FastApiAdmin后,连夜把踩过的坑都整理出来了
redis·python·postgresql·vue3·fastapi·web·sqlalchemy·admin·fastapiadmin
lzhdim12 小时前
SQL 入门 15:SQL 事务:从 ACID 到四种常见的并发问题
数据库·sql
瀚高PG实验室12 小时前
瀚高企业版V9.1.1在pg_restore还原备份文件时提示extract函数语法问题
数据库·瀚高数据库
TDengine (老段)12 小时前
TDengine Tag 设计哲学与 Schema 变更机制
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
YOU OU13 小时前
Spring IoC&DI
java·数据库·spring
Muscleheng14 小时前
Navicat连接postgresql时出现‘datlastsysoid does not exist‘报错
数据库·postgresql
春天花会开13115 小时前
Kubernetes 高可用架构实战指南
架构