跳至主要内容

博文

Redis数据结构的实现原理

Redis 五种基本数据类型的底层实现 Redis 对外暴露的是 String、Hash、List、Set、Sorted Set 五种类型,但内部每种类型往往有 两种(甚至多种)底层编码 ,根据数据量大小自动切换。核心设计哲学是: 小数据用紧凑、连续内存的结构省内存;数据量大了再切换成哈希表/跳表这类效率更高但更耗内存的结构 。 1. String —— SDS(Simple Dynamic String) Redis 没有直接用 C 语言原生字符串,而是自己实现了 SDS。 结构(以 sdshdr8 为例): len : 已使用长度 alloc : 总分配空间 flags : 类型标记 buf[] : 实际字节数组(以 \ 0 结尾,但不依赖它判断长度) 根据字符串长度不同,还细分为 sdshdr5/8/16/32/64 ,用最小的头部类型存放,进一步省内存。 为什么这样设计: - C 字符串要遍历到 \0 才能拿到长度,SDS 直接读 len 字段, O(1) 。 - C 字符串不是二进制安全的(遇到 \0 就截断),SDS 靠 len 判断边界,可以存图片、序列化数据等任意二进制内容。 - 拼接前会检查剩余空间,杜绝了缓冲区溢出。 - 空间预分配 :字符串变长时,若新长度 < 1MB,分配双倍空间;若 ≥ 1MB,每次只多分配 1MB,避免频繁 realloc 。 - 惰性空间释放 :字符串变短时不立即释放内存,留着以后用。 优缺点: 换来了安全性、O(1) 长度获取、减少内存重分配次数,代价是比原生 C 字符串多占一点元数据空间。 2. Hash —— listpack / hashtable 小对象 :用 listpack (Redis 7.0 之前叫 ziplist ),本质是一段连续内存,紧凑存放 key1 value1 key2 value2... ,没有指针开销。 大对象 :转为 hashtable (Redis 自己实现的 dict ),本质是数组 + 链地址法解决哈希冲突,支持 渐进式 rehash 。 触发转换的阈值(可配置): - hash-max-listpack-entries (默认 128) - hash-max-listpack-value (默认 64 字节) 任一超限...
最新博文

使用kafka作为消息队列,有哪些容易踩的坑

使用 Kafka 作为消息队列时,有些坑是因为它设计初衷是"分布式日志系统"而非传统消息队列(如 RabbitMQ),如果照搬传统 MQ 的思维方式,很容易踩坑。以下是常见的几类问题: 1. 消息顺序问题 分区内有序,分区间无序 :Kafka 只保证同一分区内的消息顺序,如果你的业务需要严格顺序(比如同一用户的操作按顺序处理),必须保证相关消息落到同一分区(合理设计 key)。 重试导致乱序 :生产者开启重试(retries)且 max.in.flight.requests.per.connection > 1 时,重试的消息可能"插队",导致同一分区内乱序。如果对顺序敏感,建议设置为 1,或使用 enable.idempotence=true (幂等生产者会自动处理这个问题)。 2. 重复消费 / 消息丢失 at-least-once 是默认语义 :消费者处理完消息后才提交 offset,如果提交前挂掉,重启后会重复消费。业务逻辑必须做到 幂等 ,否则会出问题(比如重复扣款)。 自动提交的陷阱 : enable.auto.commit=true 时,offset 是按时间间隔自动提交的,可能在消息还没处理完就提交了(消息丢失),也可能处理完了还没提交就挂了(重复消费)。生产环境通常建议手动提交,并且在业务处理成功后再提交。 acks 设置不当 : acks=0 或 acks=1 在某些故障场景下会丢消息,需要金融级可靠性的场景建议 acks=all 配合 min.insync.replicas 。 3. 消费者组与 Rebalance 风暴 Rebalance 影响吞吐 :消费者数量变化、消费者处理超时(超过 max.poll.interval.ms )都会触发 rebalance,期间整个消费者组会短暂停止消费。如果单条消息处理耗时很长,容易频繁触发 rebalance。 消费者数量 > 分区数 :多余的消费者会一直空闲,分区数决定了消费并行度的上限,扩容消费者之前要先看分区数够不够。 4. 把 Kafka 当成传统 MQ 用 没有真正的"消息确认/拒绝/重新入队"机制 :不像 RabbitMQ 那样可以针对单条消息 ack/nack,Kafk...

介绍一下k8s的功能,包括服务注册、服务发现、负载均衡、配置、存储管理、健康检查、自动扩缩容、RBAC、多命名空间隔离。讲解它们的原理、怎么用、有哪些使用场景

Kubernetes 核心功能学习文档 涵盖:服务注册、服务发现、负载均衡、配置管理、存储管理、健康检查、自动扩缩容、RBAC、多命名空间隔离 每节包含:原理讲解、使用方式(含 YAML 示例)、典型场景/案例 0. 全局背景:为什么需要这些机制 Kubernetes(简称 k8s)是一个容器编排平台,核心目标是把一堆"随时可能死掉、随时可能漂移到别的机器上"的容器(Pod),管理成一个稳定、可自愈、可伸缩的分布式系统。 它要解决的根本问题是: Pod 是临时的、IP 会变、数量会变,但访问它的人不希望关心这些细节 。下面的每一个功能,几乎都是围绕这个核心矛盾展开的。 1. 服务注册(Service Registration) 原理 在 k8s 里,"服务注册"不是一个独立动作,而是 自动完成 的: 每个 Pod 启动后,kubelet 会向 API Server 上报自己的状态(IP、所在节点、是否 Ready)。 API Server 把这些信息写入 etcd (k8s 的分布式键值存储,相当于整个集群的"数据库")。 当你创建一个 Service 资源,并用 selector (标签选择器)指定它要代理哪些 Pod 时,控制平面里的 Endpoint Controller 会持续监听(watch)匹配该 selector 的 Pod,把它们的 IP:Port 写入一个叫 Endpoints (或新版的 EndpointSlice )的对象里。 也就是说: Pod 通过打标签"自动注册"到 Service 上 ,不需要像传统微服务架构(如 Eureka、Consul)那样,应用代码里手写"启动时向注册中心汇报"的逻辑。 怎么用 apiVersion: apps/v1 kind: Deployment metadata: name: order-service spec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: ...

为懂js html css基础的人写一个vue的入门文档,以便学后可以使用claude code开发vue单页应用 因此也不要有过多代码细节 着重原理概念以及用法的讲解

Vue 入门指南(面向 Claude Code 开发者) 写给已经掌握 JS / HTML / CSS 基础、但没接触过任何前端框架的你。这篇文档会先带你把一个项目"跑起来",再逐步讲概念,尽量少跳步骤。 一、为什么需要 Vue:从一个具体问题说起 假设你要用原生 JS 做一个计数器:一个数字,一个"+1"按钮,点一下数字加一。原生写法大致是这样的思路: 用 document.getElementById 或类似方法,先"抓到"页面上显示数字的那个元素; 定义一个变量存当前的数字; 给按钮绑定点击事件,点击时变量加一; 再手动把这个变量的新值,写回刚才抓到的那个元素里 (比如 .innerText = 新值 )。 问题出在第 4 步: 数据变了,你必须自己记得去更新对应的 DOM 。页面简单时还好,一旦页面上有几十个地方的内容依赖同一批数据,你就要在每个数据变化的地方,手动更新一堆 DOM 元素,非常容易漏改、改错。 Vue 做的事情就是把第 4 步接管过去:你只要告诉 Vue "这个数字应该显示在这里",之后每次数字变化, Vue 自动帮你把页面对应的部分更新掉 ,你不用再手写这一步。这就是"响应式"和"声明式渲染"两个词想表达的意思——你只声明"结果应该是什么样",不用管"怎么一步步改出来"。 记住这个对比,后面所有概念都是围绕"如何声明数据、如何声明界面"展开的。 二、开发环境与项目是怎么跑起来的 在讲语法之前,先搞清楚一个 Vue 项目从零到能在浏览器里看到效果,中间发生了什么。这一步很多教程会跳过,但恰恰是最容易让人懵的地方。 1. 需要先装好的东西 Node.js :一个能让 JS 脱离浏览器、在电脑上直接运行的环境。前端的各种工具(包括创建项目的脚手架)本质上都是用 JS 写的小程序,需要靠 Node.js 来运行。装好后,终端里可以运行 node -v 查看版本确认装好了。 npm :随 Node.js 一起安装的"包管理工具",作用是下载别人写好的代码库("包"),并帮你管理项目依赖了...

架构学习之路

先啃 DDIA(第 1–6 章),同时拿手头项目做数据层笔记;然后读 Clean Architecture 重构自己的 Go repo 结构;接着 Building Microservices + Learning DDD 两本交叉读;最后 Cloud Native Go 和 Production Kubernetes 一起对着 K8s 集群实操。 几个实践建议: 读 DDIA 时,找项目里用的数据库(PostgreSQL/Redis/Kafka 等)对应章节,立刻去看官方文档验证书里的描述,会记忆深刻得多。 读 Building Microservices 时,不要急着拆服务,而是先用 DDD 的 bounded context 把现有 monolith 的边界画出来,再决定要不要拆。 Cloud Native Go 的代码示例质量很高,建议 fork 下来在本地跑,比只读要有效得多。 有没有哪个方向想进一步深入?比如具体的分布式模式、Go 并发架构,或者 K8s operator 开发?

LLM缓存详解

 可以把“大模型缓存”理解成: 把已经算过的结果(或中间结果)存下来,下次尽量复用 。但这里面其实分几层,不只是简单的“问题→答案”缓存。 1️⃣ 常见的几种缓存类型 (1)KV Cache(推理内部缓存) Transformer 在生成时,会把前面 token 的 Key/Value 向量 缓存下来。 本质:避免重复计算 attention 作用: 同一请求内部加速 特点: 👉 只对“同一上下文继续生成”有效 👉 不跨用户、不跨请求 这类缓存是你体感“流式输出越来越快”的原因之一。 (2)Prompt Cache(提示词缓存) 缓存的是: 相同(或高度相似)的 prompt → 对应的中间表示 / 输出 典型场景: 系统提示词(system prompt)很长 多轮对话里前文基本不变 👉 这里能省掉 前缀计算成本(prefill) (3)Embedding / 语义缓存(Semantic Cache) 这个才是你问题的关键 👇 不是按“字符串完全一致”,而是: 把问题转成向量 → 找“语义相似”的历史问题 → 直接复用答案 2️⃣ 为什么命中缓存成本低很多? 因为大模型推理成本主要在两块: (1)Prefill(吃 prompt) 复杂度 ~ O(n²) 很贵(尤其长 prompt) (2)Decode(逐 token 生成) 每个 token 都要算一遍模型 而缓存命中后: KV cache:不用重复 attention Prompt cache:不用重新 encode 语义缓存: 直接跳过模型推理 👉 相当于从: 几十~几百毫秒 + GPU算力 变成: 一次向量检索(毫秒级)+ 直接返回 所以成本差一个数量级是正常的。 3️⃣ “每个人问法不同,怎么命中缓存?” 这是核心难点,也是工程重点👇 ❌ 不能靠字符串匹配 比如: “今天天气怎么样” “今天外面热不热” 字符串完全不同 → 必须 miss ✅ 用语义相似度(Embedding) 流程一般是: 把问题转 embedding(向量) 在向量数据库里找 TopK 相似问题 如果相似度 > 阈值(比如 0.9) 直接返回缓存答案 一个简单示意 Q1: 北京天气怎么样 → embedding A Q2: 北京今天热吗 → embedding B cosine(A, B) ≈ 0.95...

temperature, top_p , frequency_penalty, max_tokens 概念详解

1️⃣ temperature(温度)——控制“概率分布的平滑程度” 模型本来会输出一个概率分布: tokenA: 0.7 tokenB: 0.2 tokenC: 0.1 temperature 会对这个分布做“拉伸/压缩”: 低温(0~0.3) → 分布更尖锐 → 更接近“选概率最高的词” → 更确定、稳定 高温(0.7~1.2) → 分布更平滑 → 小概率词更容易被选中 → 更发散、有创造性 👉 数学上其实是: [ P_i = \frac{e^{logits_i / T}}{\sum e^{logits / T}} ] 👉 直觉一句话: temperature = “敢不敢冒险” 2️⃣ top_p(nucleus sampling)——控制“参与抽奖的候选池” 不是调整概率,而是 裁剪候选集合 。 步骤: 按概率排序 token 从高到低累加,直到总概率 ≥ p 只在这个子集里采样 举个例子: 原始分布: A: 0.5 B: 0.2 C: 0.15 D: 0.1 E: 0.05 top_p = 0.7: 选: A (0.5) + B (0.2) = 0.7 👉 只在 {A, B} 里选 👉 直觉一句话: top_p = “允许多少概率质量参与竞争” 和 temperature 的区别 参数 作用本质 temperature 改概率分布形状 top_p 截断候选集合 👉 组合效果: temperature 决定“洗牌程度” top_p 决定“参与人数” 3️⃣ frequency_penalty ——“你刚说过,就少说点” 这个参数是 对已经生成过的 token 做惩罚 👉 逻辑: 出现次数越多 → 概率被压低 举个例子: 生成中: the cat and the cat and the ... 如果 frequency_penalty > 0: “the”、“cat” 被重复使用 下一次出现概率会下降 👉 数学近似: [ logits_i = logits_i - \lambda \cdot count(i) ] 👉 直觉一句话: frequency_penalty = “别复读” 使用场景 场景 建议 写文章 0.3 ~ 0.8 代码生成 0 列表生成 0.2 4️⃣ max_tokens ——“最多说多少” 这个最简单,但很多...