Java 序列化与反序列化:原理、Jackson 实战与避坑指南 一、序列化与反序列化基础 1.1 定义 序列化(Serialization):将内存中的对象转换为可存储或可传输的数据格式(字节流、JSON、XML 等)的过程。 反序列化(Deserialization):将存储或传输的数据格式还原为内存对象的逆过程。 TEXT 序列化 Object ──────► byte[] / JSON / XML / Protobuf ... 反序列化 Object ◄────── byte[] / JSON / XML / Protobuf ... 1.2 核心目的 场景 说明 持久化 将对象状态保存到磁盘、数据库 网络传输 RPC、HTTP API、消息队列中传递对象 进程间通信 跨 JVM 数据交换 深拷贝 通过序列化/反序列化实现对象深复制 缓存 Redis、Memcached 等存储 Java 对象 1.3 常见序列化方案对比 方案 格式 可读性 性能 跨语言 典型场景 JDK Serializable 二进制 ✗ 低 ✗ 遗留系统 JSON (Jackson/Gson) 文本 ✓ 中 ✓ REST API、配置 Protobuf 二进制 ✗ 高 ✓ gRPC、高性能 RPC Kryo 二进制 ✗ 高 ✗ Spark、Flink 内部 Hessian 二进制 ✗ 中 ✓ Dubbo Avro 二进制 ✗ 高 ✓ Kafka、大数据 1.4 序列化的本质问题 序列化不仅仅是"转格式",需要解决以下核心问题: ...
Regex Guide
Regex 1. 字符类 (Character Classes) 语法 说明 等价于 . 匹配换行符外的任意单字符 [^\n\r] \d 匹配数字 [0-9] \D 匹配非数字 [^0-9] \w 匹配字母、数字或下划线 [A-Za-z0-9_] \W 匹配非单词字符 [^A-Za-z0-9_] \s 匹配空白符 [ \t\n\r\f\v] \S 匹配非空白符 [^ \t\n\r\f\v] [ABC] 匹配括号内任意单字符 - [^ABC] 匹配括号内字符外的任意单字符 - 2. 量词 (Quantifiers) 语法 说明 * 匹配 0 次或多次 + 匹配 1 次或多次 ? 匹配 0 次或 1 次 {n} 匹配确切 n 次 {n,} 匹配至少 n 次 {n,m} 匹配 n 到 m 次 注:默认贪婪匹配(匹配最大长度)。在量词后追加 ?(如 *?、+?)切换为懒惰匹配(匹配最小长度)。 ...
微服务稳定性兜底:深入解析 go-kratos/aegis 的核心设计
微服务稳定性兜底:深入解析 go-kratos/aegis 的核心设计 在高并发微服务体系中,单点故障、流量洪峰、缓存热点都可能引发雪崩。aegis 是 Kratos 框架中的稳定性组件库,集成了多种常见的保护机制,以较低开销提供多层防护。本文围绕其核心模块展开,分析背后的算法原理与工程权衡。 一、项目全景 TEXTgo-kratos/aegis ├── ratelimit/bbr # 自适应限流(BBR 算法) ├── circuitbreaker/sre # 熔断器(Google SRE 自适应节流) ├── topk/heavykeeper # Top-K 热点 key 检测(HeavyKeeper 算法) ├── hotkey # 热点 key 自动本地缓存 ├── subset # 一致性哈希子集路由 └── internal ├── window # 滑动窗口(环形数组) ├── consistent # 一致性哈希 ├── minheap # 最小堆 └── cpu # CPU 使用率采样 核心哲学:用概率和统计代替精确计数,以极低的内存和计算开销换取足够准确的系统保护。 二、BBR 自适应限流 2.1 问题背景 传统限流通常是硬编码一个 QPS 上限(如"最多 1000 RPS")。问题在于: ...
复制滞后与多端同步
前言 主从复制可以显著提升系统的读能力,但复制滞后会直接破坏多端同步体验。 本文聚焦三个最常见的一致性问题:读自己写、单调读、前缀一致读,以及在即时通讯场景中的对应治理手段。 读自己的写 读自己写失效:客户端提交了写入,但随后的读取请求被路由到了尚未完成同步的从节点,导致客户端读不到自己刚刚写入的数据。 用户读自己写数据,强制走主节点。如果大部分数据都修改,会给主库造成巨大压力 用户记录最近更新时间戳,可以是逻辑时间和系统时钟(时钟不可靠)。多设备不适用,并不知道记录的时间戳。 单调读 单调读失效:用户的多次读取请求打到了同步进度不一致的多个副本上,导致用户先看到了较新的数据,随后又看到了较旧的数据,出现了“时光倒流”。 始终从同一副本读取,例如基于用户id哈希。但是如果副本失效,必须重新路由到另一个副本 前缀一致读 前缀/因果一致性失效:具有因果关系(先后顺序)的写入,由于底层分布式组件的处理速率不一致,导致在第三方的视角中,事件发生的顺序被颠倒。 将具有因果顺序的都交由一个分区处理。效率低 即时通讯场景下的主从滞后问题 读自己写失效(Read-Your-Writes Anomaly) 多端同步丢失: 用户在手机端发了一条消息,成功写入服务端主库。用户立刻打开电脑端(PC 版)查看,PC 端的拉取请求恰好打到了一个由于网络抖动而延迟了 500 毫秒的 MySQL 从库。结果 PC 端界面上一片空白,用户以为消息没发出去。 解决方案:纯内存 Push 模型跑赢物理复制 放弃让其他在线端去“查”数据库的传统做法。 手机端消息先进入 Kafka,由消息处理服务按会话维度分配 Seq 并完成落库。 后端的 msg_transfer 服务消费 Kafka,定位到 PC 端的 WebSocket 长连接,直接将消息从内存推(Push)过去。 核心逻辑: 内存与网络的流转速度远快于磁盘 IO 和数据库 Binlog 复制。客户端直接利用 Push 过来的数据渲染上屏,从物理架构上彻底绕开了从库延迟的陷阱。 单调读失效(Monotonic Reads Anomaly) 漫游消息凭空消失: 用户断网重连,触发历史消息拉取(Pull)。第一次拉取,网关路由到了延迟极低的从库 A,用户看到了最新的 10 条消息。用户立刻下拉刷新,第二次请求被路由到了卡顿的从库 B,从库 B 还没这 10 条消息。于是,用户屏幕上刚刚还在的 10 条最新聊天记录突然集体消失。 解决方案:客户端主导的严格 Seq 游标 在 OpenIM 中,拉取漫游消息的“游标控制权”在客户端手里,而不是服务端盲查。 客户端本地 SQLite 记录着自己当前看到的最后一条连续消息的 MaxLocalSeq(如 Seq=100)。 发起 Pull 请求时,客户端携带极其明确的条件:“只拉取 Seq > 100 的增量消息”。 核心逻辑: 服务端接收到游标后,会和 Redis 中维护的 ServerMaxSeq 对比。如果查到的从库最新数据只有 Seq=95,服务端立刻判定该副本滞后,可以选择等待、报错或强制回源主库。这保证了客户端拉取的数据永远是向前递增的,彻底封杀时光倒流。 前缀/因果一致性失效(Causal Consistency Anomaly) 旁观者视角的逻辑错乱: 在百人群聊中,用户 A 问:“去不去吃饭?”,用户 B 看到后秒回:“去”。(A 绝对发生在 B 之前)。然而,A 和 B 的消息并发打入服务端,处理 B 的线程极快,处理 A 的线程卡顿,导致 B 的消息先同步到了部分从库。此时,旁观者 C 刷新群聊,竟然先看到了 B 说“去”,过了几秒才看到 A 问“去不去吃饭”。因果逻辑彻底崩塌。 解决方案:Kafka 分区串行化 + 会话内 Seq 发号 步骤 1(先入队): 消息以 ConversationID 为 Hash Key 投递到 Kafka,确保同一会话的消息进入同一个 Partition。 步骤 2(再发号): 消息处理服务按 Partition 顺序消费,并为该会话分配严格递增的 Seq,然后落库。 步骤 3(客户端连续性校验): 就算网络抖动导致 Seq=11 的消息先推给旁观者 C,客户端发现本地缺少 Seq=10 时,会先放入重排缓冲区,待 Pull 补齐后再按序展示。 核心逻辑: 先利用 Kafka 分区保证同会话处理顺序,再用会话内递增 Seq 做最终顺序锚点,最后由客户端状态机兜底连续性。 分布式架构设计:不与底层的物理不确定性(网络抖动、磁盘延迟)死磕,而是通过高层的应用逻辑(Seq、Push/Pull、分区 Hash)来建立确定的秩序 ...
LSM-Tree 原理与消息存储选型
1. 概述 LSM-Tree(Log-Structured Merge-Tree)是一种典型的写优化存储结构。它的核心思路是:不追求每次写入都直接落到最终有序位置,而是将写入拆成**“前台快速落地 + 后台异步整理”**两个阶段。 设计目标可以归纳为: 顺序化写入:前台写入以追加为主,避免随机写。 后台整理:异步 Compaction 控制查询路径复杂度。 可调节的权衡:在写吞吐、读延迟和空间占用之间提供调优空间。 2. 整体架构与核心组件 LSM-Tree 的运行依赖以下组件协同工作: 组件 位置 职责 WAL(Write-Ahead Log) 磁盘 写前日志,保证崩溃恢复 MemTable 内存 有序表,接收实时写入 Immutable MemTable 内存 写满后冻结,等待刷盘 SSTable 磁盘 有序、不可变的持久化文件 Compaction 后台任务 合并 SSTable,版本收敛与空间回收 数据在组件间的流转路径如下: flowchart LR W[Write Request] --> L[Append WAL] L --> M[Insert MemTable] M -->|Threshold reached| IM[Immutable MemTable] IM --> F[Flush to SSTable] F --> C[Compaction] C --> S[(Merged SSTables)] 3. 写路径 写入的关键原则是 “先确认可恢复,再确认可查询”。具体步骤: 追加 WAL——保证持久化,崩溃后可重放。 更新 MemTable——对外可查。 冻结 MemTable——达到阈值后转为 Immutable。 Flush 为 SSTable——后台将 Immutable MemTable 写入磁盘。 这条路径带来的关键特性: ...
Agent_practice
1. 客服agent 客户问题种类: 事实类问题、诊断类问题、模糊类问题、其他问题等 诊断类问题 解释问题 VS 解决问题 React Agent 从Agent的最开始,LLM先思考(Thought),然后触发动作(Action)和输入(Action Input),之后执行并观察工具执行结果(Observation),如果观察的效果不满足需求,会重回到思考阶段,最后生成最终回答(Final Answer) 选择API、反问、提取入参、执行API的准确度 “问题识别” -> “查询SOP工具” -> “反问客户、获取信息” -> “根据信息查询工具” -> “查询到工具执行结果” -> “根据执行结果来回复客户” -> “客户继续沟通” -> … -> “解决问题” TEXT用户提问 -> 判断意图 -> 模糊 -> 追问 -> 清楚 - 需要进入agent? -> api检索 -> api选择 -> 参数判断 -> 参数组装 -> 动作执行 -> 观察结果 -> 生成答案 -> 不足 -> 追问 问题与优化 在实际业务落地的情况下,需要考虑几个因素:执行效果、整体耗时、大模型生成成本、API调用成本等等诸多因素,如果效果好,但是耗时太久,或者大模型的生成成本(token、qps)、API的调用成本(qps)等都太高,那么也未必有好的用户体验。 多步调用耗时,将api做到开箱即用,支持多种传参方式,减少对用户的询问。 慢api作异步处理,前端显示卡片展示进度。 构造高质量的训练数据集来对模型进行Finetune训练,让大模型在选择API、反问、提取入参、执行API的准确度上都尽可能的高 2. why agent? agent: 让大模型“代理/模拟”「人」的行为,使用某些“工具/功能”来完成某些“任务”的能力。 ...
分布式 IM 网关路由架构:从集中式 Redis 到连接即路由
分布式 IM 网关路由架构分析与设计 单机 IM 网关阶段,用户与 WebSocket 连接关系维护在本地内存即可,例如使用 sync.Map 或 map[int64]*websocket.Conn + RWMutex。该模式实现简单、链路短、时延低。 当在线规模持续增长后,单机的 TCP 连接数、CPU 核数与网络带宽会触达上限,系统必须演进为多实例网关集群。此时核心问题变为: 用户分散在不同网关实例后,A 给 B 发消息时,如何在毫秒级准确定位 B 所在节点? 本文围绕该问题展开,重点覆盖接入层方案、路由演进路径、集中式 Redis 架构在高规模下的瓶颈,以及“连接即路由”的去中心化方向。 一、接入层:客户端先连到谁 在内部路由之前,需要先确定客户端第一跳接入方式。主流做法通常分为两类。 1. 前置负载均衡接入(Nginx/LVS/SLB) 在网关集群前加反向代理,客户端连接统一入口,再由代理层分发到后端网关。 特点:接入结构直观,客户端侧配置简单。 问题:代理层同样承担长连接状态,在高并发七层代理场景下会带来明显内存与成本压力。 2. Dispatch 服务引流(服务发现 + 直连) 客户端先请求无状态的 HTTP Dispatch 接口。Dispatch 根据网关健康度、负载指标或一致性 Hash 返回目标地址,例如 192.168.1.100:8080,然后客户端直接连目标网关。 优势:去掉代理层代持长连接成本,网络拓扑更扁平。 代价:对调度策略、健康探测和故障转移能力要求更高。 二、路由策略:A 如何找到 B 接入路径确定后,核心问题回到路由:A 发给 B,A 所在网关如何将消息准确投递到 B 所在网关。 演进一:全局广播(Broadcast) A 所在网关把消息广播给所有网关实例。每个实例检查本地连接表,命中就投递,未命中就丢弃。 优点:实现成本低,网关可保持近似无状态。 缺点:单聊被放大为 $N$ 份网络与计算开销($N$ 为网关节点数),规模增大后资源浪费显著。 演进二:Redis 集中式路由(Centralized Routing) 为避免广播风暴,可将 Redis 作为全局路由目录:用户登录时写入“用户-网关”绑定,发消息前查询 Redis 后再定向投递。 ...
用基础数学看懂LLM的本质
函数的表达能力,决定了计算机能在多大程度上逼近对自然语言的理解。 本文尝试用直觉性的方式,从"计算机如何理解语言"这个根本问题出发,逐步推导出大语言模型背后的核心数学原理。 假设词典中有若干单词,单词之间存在各种语义关系——近义、反义、上下位等。如何让计算机捕捉这些关系? 如何让计算机理解单词? 可以借助一种数学工具——向量。 两个向量的夹角余弦可以衡量它们的相似度: $$\cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|}$$$\cos(\theta)$ 越接近 1,两个向量越相似;越接近 -1,越相反。 因此,可以用向量来表示单词,向量之间的余弦相似度就反映了两个词之间的语义关系。这种表示方式称为 Token Embedding:每个单词对应一个 d 维向量 $\mathbf{x} = (x_1, x_2, \dots, x_d)$。 如何让计算机理解句子? 同一个词在不同句子中含义不同。Token Embedding 只能表示孤立单词的语义,还需要一种方式来编码单词在上下文中的含义。 为此引入位置向量(Positional Embedding):假设句子由 m 个单词组成,为每个位置分配一个 d 维向量 $\mathbf{p}_j$。将两者相加得到最终表示: $$\mathbf{x}_j = \text{token\_embedding}_j + \text{pos\_embedding}_j$$直觉上理解:向量加法会让结果同时保留两个加数的特征。就像把"红色"和"大"两个标签贴在同一个物体上,加法后的向量 $\mathbf{x}_j$ 就同时携带了"这个词是什么"和"它在第几个位置"两层信息。 这样,一个由 m 个单词组成的句子就可以用 m 个向量 $(\mathbf{x}_1, \mathbf{x}_2, \dots, \mathbf{x}_m)$ 来表示,计算机便能通过向量运算"理解"句子的含义。 如何训练模型生成文本? GPT 类 LLM 的核心目标是预测下一个词——给定前面的所有词,预测最可能的下一个词: $$P(x_{t+1} \mid x_1, x_2, \dots, x_t)$$例如输入"今天天气真",模型应输出"好"的概率最高。生成长文本时,只需不断把预测出的词拼接到输入末尾,重复这个过程即可。 完整的流程是: 将输入文本通过 Embedding 转换为向量序列 向量序列经过模型的函数映射,得到输出向量 输出向量与词表中所有词的向量计算相似度,通过 softmax 得到每个词的概率分布 从概率分布中选出下一个词 其中 Embedding 映射表(词 → 向量)也是函数的一部分,无法手工指定,需要和整个模型一起学习。 ...
Agent
系统梳理 LLM Agent 的经典架构(Planning、Memory、Tools)及演进方向(Multi-Agent、MCP、A2A、Skills)。
Id Seq Generation
分布式 ID / 序列号生成方案技术选型 1. 方案概述 1.1 UUID 128 位随机数(V4)或基于时间+MAC(V1)。无需中心节点,生成即唯一。 完全无序、不连续,无法用于排序或范围查询 36 字节字符串存储开销大,随机值导致 B+Tree 页分裂,写入性能差 1.2 数据库自增 ID MySQL AUTO_INCREMENT 主键,单表内严格递增且连续。 每次发号伴随磁盘写(INSERT/UPDATE),单机上限约 1000-3000 TPS 强依赖单点数据库,宕机即停;分库后各库独立递增,全局不连续 1.3 Snowflake(雪花算法) 64 位 = 1 bit 符号 + 41 bit 时间戳 + 10 bit 机器 ID + 12 bit 序列号。纯内存计算,单机 400 万+/秒。 趋势递增但不连续,两个 ID 的差值无业务含义 依赖机器时钟,时钟回拨可能导致 ID 重复 1.4 Leaf-Segment(美团 Leaf 号段模式) 从数据库批量预取号段(如 1000 个),应用内存中顺序分发。当前号段消耗到阈值时异步预加载下一号段,避免切换时阻塞。 号段内连续,但进程重启时未消费的号段被浪费,产生空洞 多实例部署时各进程持有不同号段,同一业务维度内 ID 交叉,无法保证连续 1.5 Leaf-Snowflake Snowflake 变体,用 ZooKeeper 管理 workerID 并解决时钟回拨。本质仍是 Snowflake,不连续的缺陷不变。 ...