260320笔记
bwmarrin/snowflake 库在生产环境中会遇到的问题
Worker ID硬编码——单机没问题,K8s重启后ID冲突
Pod 是无状态的,重启后 IP 会变,但如果你把 Worker ID 配置成固定值(比如从 IP 计算),同一时刻不同 Pod 可能算出相同的 Worker ID
或者你把 Worker ID 写死在 ConfigMap 里,所有 Pod 都用同一个 ID
🎯 本质问题
Snowflake 要求每个节点必须有全局唯一的 Worker ID,但 bwmarrin/snowflake 库本身不提供 Worker ID 的分配机制,需要你自己保证唯一性。
✅ 更好的方案
自动注册:用 Redis/ZooKeeper/Etcd 动态分配 Worker ID
K8s StatefulSet:利用 Pod 序号(pod-0, pod-1)作为 Worker ID
IP 映射:把 Pod IP 的最后几位映射成 Worker ID(但要注意 CIDR 范围)
时钟回拨只抛异常 —— 没有等待策略
1 | // 其他库(如 sonyflake 或改进版)的做法: |
或者使用 “雪花漂移算法”(IdGenerator 库),可以自适应处理时间回拨
雪花漂移算法
雪花漂移算法 = 传统 Snowflake 的”魔改版”
这是由国内开发者 yitter 开源的优化版本,核心目标是:让 ID 更短、更快、更鲁棒(鲁棒性指的是一个系统、模型、算法或方法在面对内部参数变化、外部扰动、输入不确定性、噪声、故障或异常情况时,保持其原有功能和性能稳定性的能力。)
想象传统 Snowflake 是一辆严格按时刻表运行的火车:
时间必须一直向前开
一旦时间”倒车”(时钟回拨),火车就抛锚
每毫秒固定发 4096 张票,发完就关门
雪花漂移算法 是一辆可以”漂移”的赛车:
时间回拨?没关系,我可以用预留的”过去时间槽”继续发号
并发太高?我提前”漂移”到未来时间借额度用
序列号用完?我不硬等,而是平滑过渡
| 特性 | 传统 Snowflake | 雪花漂移算法 |
|---|---|---|
| ID 长度 | 64 位(19 位数字) | 更短(可配置位数) |
| 瞬时并发 | ~4096/ms | 50W/0.1s(约 50 万/100毫秒) |
| 时钟回拨 | 抛异常或等待 | 用过去预留序数继续生成 |
| Worker ID | 手动配置 | 支持 K8s 自动注册 |
| 序列号耗尽 | 阻塞等下一毫秒 | 漂移借用相邻时间槽 |
| 后补生成 | 不支持 | 支持生成过去时间的 ID |
1 | 概念:我提前在过去的时间槽里"藏"了一些序列号 |
序列号用完直接阻塞 —— 没有抖动优化
每毫秒最多生成 4096 个 ID(12 位序列号)。如果这一毫秒内你已经生成了 4096 个,下一毫秒才能继续。
1 | // bwmarrin/snowflake 的做法(简化) |
高并发时,大量请求堆积,延迟飙升
没有 “抖动”(Jitter)来平滑流量
🎯 什么是”抖动优化”?
想象你在发号,前面排队的人太多了:
阻塞策略:所有人都站着干等,门一开蜂拥而上
抖动策略:让后面的人稍微分散一下,别都挤在同一毫秒(避免周期性拥堵)
更好的做法:
忙等待(Busy-wait):CPU 空转检查时间,延迟低但耗 CPU
指数退避:等的时间越来越长,避免惊群效应
预生成:提前批量生成一批 ID 缓存起来
借用未来额度:提前在未来时间槽里”藏”了一些序列号,专门用来应对时钟回拨时的紧急需求
雪花飘移算法的实现:
1 | 核心思想:序列号不是严格绑定当前毫秒,可以"漂移"到相邻时间槽 |
JWT双Token鉴权,Refresh Token存在哪里?Redis?那用户注销时怎么让Refresh Token失效
| 存储位置 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Redis ⭐推荐 | 键值对存储,设置TTL过期时间 | 天然支持过期、查询快、易删除 | 增加基础设施依赖 | 大多数生产环境 |
| 数据库 (MySQL/PostgreSQL) | 持久化表存储 | 数据持久化、易审计 | 查询慢、无自动过期 | 小型应用、审计要求高 |
| 内存缓存 (Caffeine/Guava) | 应用本地缓存 | 极速访问 | 分布式失效难、重启丢失 | 单节点应用 |
| 客户端 (Cookie/LocalStorage) | 不存储服务端 | 服务端无状态 | 不安全、无法强制失效 | ❌不推荐 |
用户注销时怎么让Access Token失效
维护黑名单
Refresh Token 版本号
Refresh Token 版本号(Version Number / Token Version)是一种安全增强机制,用于在用户状态变更时使旧的 Refresh Token 失效,而无需等待其自然过期
这歌不错 [宇宙计时器]https://www.bilibili.com/video/BV1vVPezjEcg/?spm_id_from=333.1245.0.0&vd_source=818f94ff798aa7fbb006f84b2b970091
用户登录
└── 生成 RT (version=1),存储到 DB/Redis正常使用
└── 每次刷新 Access Token 时验证 version=1 ✓用户修改密码
└── DB: version 1 → 2
└── 所有携带 version=1 的 RT 失效攻击者使用偷取的旧 RT
└── 验证 version=1 ≠ DB.version=2 ✗
└── 拒绝刷新,要求重新登录
这和直接弃用旧的refreshtoken生成个新的有何区别呢
换新结果:同样让所有 Token 失效,但需要维护黑名单。
用户有 3 台设备登录,Token 有效期 7 天
版本号方案:
├── 存储: {user123: version=3} → 几个字节
└── 7天后: 自动清理,无需操作
黑名单方案:
├── 存储:
│ ├── blacklist:token_a → 7天
│ ├── blacklist:token_b → 7天
│ └── blacklist:token_c → 7天
└── 7天后: 需要定时任务清理过期 key
版本号查询的是热数据(用户表,常驻内存)
黑名单查询的是冷数据(需要额外存储层)
RAG
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 文档加载 │ -> │ 文本分块 │ -> │ 向量化存储 │ -> │ 检索召回 │
│ (Load) │ │ (Split) │ │ (Embed) │ │ (Retrieve) │
└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘
│
┌─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ 上下文拼接 │ -> │ LLM生成 │
│ (Prompt) │ │ (Generate) │
└─────────────┘ └─────────────┘
| 存储方案 | 代表产品 | 适用场景 |
|---|---|---|
| 向量数据库 | Milvus、Pinecone、Weaviate、Qdrant | 大规模生产环境 |
| 内存向量库 | FAISS、Annoy、Hnswlib | 中小规模、原型开发 |
| 传统数据库扩展 | pgvector(PostgreSQL)、Redis Vector | 已有DB基础设施 |
| 云托管服务 | AWS OpenSearch、Azure AI Search、阿里云向量检索 | 免运维、快速上线 |
Embedding 模型选择
Embedding 模型选择
| 模型 | 维度 | 语言支持 | 特点 |
|---|---|---|---|
| BGE-large-zh | 1024 | 中英 | 中文SOTA,MTEB榜单前列 |
| BCEmbedding | 768 | 中英 | 有道开源,双语优化 |
| GTE-large | 1024 | 多语言 | 阿里达摩院,效果稳定 |
| E5-mistral-7b | 4096 | 多语言 | 大模型级Embedding,效果最强 |
| m3e-base/large | 768 | 中文 | 社区常用,轻量快速 |
召回策略
基础召回:向量相似度搜索
1 | 核心:余弦相似度或点积 |
进阶召回策略
① 多路召回(Multi-Channel)
┌─────────────────┐
│ 用户Query │
└────────┬────────┘
│
┌────┼────┐
▼ ▼ ▼
┌─────┐┌─────┐┌─────────┐
│向量召回││关键词召回││稀疏向量召回│
│(Dense)││(BM25) ││ (SPLADE) │
└──┬──┘└──┬──┘└────┬────┘
└──────┼───────┘
▼
┌─────────────┐
│ 重排序(Rerank) │
│ (Cross-Encoder) │
└─────────────┘
② 重排序优化(Rerank)
使用 Cross-Encoder 模型(如 bge-reranker-large)
对召回的Top-K重新打分排序
准确率提升 10-20%
③ Query 改写/扩展
HyDE(Hypothetical Document Embedding):生成假答案再检索(Hypothetical Document Embedding,假设文档嵌入)
1 | 传统RAG: Query ──▶ 向量检索 ──▶ 结果 |
Query Expansion:扩展同义词、相关词
子查询分解:复杂问题拆分为多个子问题
④ 分层索引
摘要索引:先检索文档摘要,再深入章节
树状索引:文档 -> 章节 -> 段落 层级检索
用户Query
│
▼
┌─────────────┐
│ API Gateway │
└──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Query理解 │────▶│ 缓存层(Redis)│
│ (意图/改写) │ └─────────────┘
└──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ 向量检索服务 │◀───▶│ Milvus集群 │
│ (多路召回) │ │ (向量存储) │
└──────┬──────┘ └─────────────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Rerank服务 │◀───▶│ GPU推理节点 │
└──────┬──────┘ └─────────────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Prompt组装 │────▶│ LLM服务 │
└─────────────┘ │ (vLLM/TGI) │
└─────────────┘
1 | 用户Query: "如何优化深度学习模型训练速度?" |
MySQL和Redis数据一致性
| 策略 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Cache Aside | 先更新DB,再删缓存 | 简单可靠 | 存在短暂不一致 | 读多写少,推荐首选 |
| Read Through | 缓存未命中自动加载 | 对应用透明 | 实现复杂 | 需要缓存中间件支持 |
| Write Through | 同步更新DB和缓存 | 强一致性 | 写入延迟高 | 强一致性要求 |
| Write Behind | 先写缓存,异步刷DB | 写入性能高 | 可能丢数据 | 高吞吐,容忍短暂不一致 |
1 | # 写操作 |
ES和MySQL的数据同步
1. Logstash + JDBC 插件(官方推荐)
这是 Elastic 官方提供的方案,适合增量同步。
原理
Logstash 通过 JDBC 连接 MySQL
使用 SQL 查询数据
将数据写入 Elasticsearch
| 优点 | 缺点 |
|---|---|
| 配置简单,官方支持 | 实时性较差(依赖定时调度) |
| 支持增量同步 | 对 MySQL 有一定压力 |
| 无需修改业务代码 | 不支持 DELETE 操作同步 |
Logstash
Logstash 是 Elastic Stack(ELK Stack) 的核心组件之一,主要功能是数据收集、处理和转发。
核心作用
数据管道(Data Pipeline):从各种来源收集数据,进行转换,然后发送到目标存储
典型的 ELK 架构:Logstash → Elasticsearch → Kibana
| 特性 | 说明 |
|---|---|
| Input(输入) | 支持文件、数据库、消息队列(Kafka)、API 等多种数据源 |
| Filter(过滤) | 使用 Grok 解析非结构化数据、添加字段、删除敏感信息等 |
| Output(输出) | 发送到 Elasticsearch、文件、数据库、消息队列等 |
JDBC
JDBC = Java Database Connectivity(Java 数据库连接)
核心作用
Java 语言访问数据库的标准 API
提供统一的接口,让 Java 程序能与各种关系型数据库交互
2. Canal + Kafka + 消费者(实时同步)
阿里巴巴开源的 Canal 是业界最常用的实时同步方案。
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ MySQL │───▶│ Canal │───▶│ Kafka │───▶│ Consumer│───▶│ ES │
│ (Binlog)│ │ Server │ │ │ │ │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
| 优点 | 缺点 |
|---|---|
| 实时性强(秒级延迟) | 架构复杂,组件多 |
| 支持 INSERT/UPDATE/DELETE | 需要维护 Canal 和 Kafka |
| 对 MySQL 压力小(读 Binlog) | 需要处理消息顺序和幂等性 |
Canal
Canal 是阿里巴巴开源的一个数据同步工具,主要用于数据库变更数据捕获(CDC, Change Data Capture)。
核心功能
实时监听 MySQL 数据库的变更
伪装成 MySQL 的从库(Slave),读取主库(Master)的 Binlog 日志
解析 Binlog 中的增删改操作(INSERT/UPDATE/DELETE)
将变更数据实时推送给订阅方
3. Debezium(CDC 方案)
Red Hat 开源的分布式 CDC 平台,支持 Kafka Connect
4. 业务代码双写(简单场景)
直接在业务代码中同时写入 MySQL 和 ES。
GMP
GMP调度模型是Go语言的运行时调度器,其中P代表Processor(逻辑处理器)。
P的数量默认等于机器的CPU核心数(GOMAXPROCS)。
G(Goroutine):Go协程,轻量级线程
M(Machine):系统线程,由操作系统管理
P(Processor):逻辑处理器,G和M之间的中间层,维护本地可运行G队列
什么场景下需要调整GOMAXPROCS?
- CPU密集型应用优化
场景:纯计算任务,需要榨干CPU性能
调整:保持默认(等于CPU核心数),或根据NUMA拓扑微调
原因:避免过多的P导致上下文切换开销 - I/O密集型应用
场景:大量网络请求、文件操作、数据库查询
调整:可以适当增加P的数量(超过CPU核心数)
原因:Goroutine在I/O阻塞时会释放P,更多的P可以让其他Goroutine继续执行 - 容器化环境(Docker/K8s)
场景:容器被限制了CPU配额(如只分配0.5核)
调整:手动设置为容器的CPU限制值
原因:默认读取宿主机的CPU核心数,会导致过多的P争抢有限的CPU资源,增加调度开销
上下文压缩
| 方法 | 原理 | 适用场景 |
|---|---|---|
| 摘要提取 | 用轻量模型提取关键信息 | 长文档、会议记录 |
| 语义压缩 | 去除冗余表达,保留语义骨架 | 重复性内容多的文本 |
| 结构化提取 | 转为表格/JSON/图谱 | 信息密度高的专业文档 |
| 关键词向量化 | 只保留核心实体和关系 | 快速检索场景 |
| 实际案例:Kimi 的长上下文处理 | ||
| ├── 稀疏注意力(Sparse Attention):不计算所有token间的关系 | ||
| ├── 滑动窗口:只关注局部上下文 + 全局摘要 | ||
| ├── 分层记忆:远端内容压缩为”记忆摘要”,近端保持完整 | ||
| └── 动态淘汰:根据注意力权重丢弃低价值token |
概念示意:多级压缩流程
原始文档(100万字)
→ 分块(1000字/块)
→ 向量化索引
→ 检索Top-K相关块
→ 重排序(Rerank)
→ 送入上下文(实际只用1-2万字)
超大上下文的问题
- 注意力稀释(Attention Dilution)
当上下文超过 128K tokens 时,模型对每个具体细节的”关注度”会指数级下降
类比:你在嘈杂的万人会场找一个人说话,难度远高于在小会议室 - 检索干扰(Lost in the Middle)
研究表明:长上下文中间部分的信息被召回率最低
关键信息放开头(System Prompt)或结尾(总结性指令)更有效上下文长度 首Token延迟 成本倍数 4K 0.5s 1x 32K 2s 8x 200K 8s+ 50x+ - 噪声累积
杂乱信息会”污染”模型的推理路径,导致:
回答偏离用户真实意图
产生幻觉(Hallucination)
逻辑链条断裂
示例:
Step 1: 意图识别
用户问:”那个红色的方案后来怎么样了?”
↓ 解析出实体:【红色】【方案】【后续进展】
Step 2: 多路召回
├── 关键词匹配:找含”红色/Red”的段落
├── 语义搜索:找”方案/计划/提案”相关内容
├── 时序过滤:找”后来/结果/最终”的时间线
└── 对话历史:检查前文是否定义过”红色方案”
Step 3: 重排序(Reranking)
用轻量模型给候选段落打分,选出Top-5最相关的
Step 4: 上下文重构
把选中的片段按逻辑顺序重组,添加连接词
elasticsearch相关
Elasticsearch 使用 dense_vector 字段类型来存储向量。



