漫谈AI上下文长度

flowchart TD A[2018–2021512–2K Token金鱼记忆时代] -->|只能处理短文、短句、单轮问答| B[2022–20234K–128K Token中上下文普及时代] B -->|可处理论文、合同、小说、中型代码库RAG 成为企业标准方案| C[2024–2025200K–1M Token百万上下文实验期] C -->|纸面支持 1M,但遗忘、成本、速度问题明显仅限量 Beta| D[2026–20271M–2M Token1M 量产普惠时代] D -->|1M 成为旗舰标配可处理整卷宗、整代码库、全年业务日志| E[2028–20292M–10M Token超百万窗口分化时代] E -->|2M 以上面向行业定制长视频、大型代码库、多年日志联合分析| F[2030+10M+ 弹性记忆原生长记忆智能体时代] 一、大模型上下文完整进化时间线(2018–2026,分4个时代) 1. 初代Transformer时代:512–2K(2018–2021,金鱼记忆) 2018 GPT-1 / BERT:512 token,仅短文、短句分类,多轮对话必丢前文 2019 GPT-2:1024 token,能写短篇故事,连续对话3–5轮就遗忘历史 2020 GPT-3:2048 token(2K),行业通用标准,仅支持短提示词、少量示例,长文档必须分段/摘要处理 2021 早期国产(文心一言初代、GLM-1):统一2K上限,无长文本能力 时代特征:没有原生长文本能力,所有长内容必须靠人工拆分、外部摘要,RAG雏形出现。 2. 中上下文普及时代:4K–128K(2022–2023,工业化可用) 2022 ChatGPT(GPT-3.5):4K;年末升级16K,日常聊天够用,长文档仍吃力 2023.3 GPT-4 首发:8K,专业文档、代码单文件处理门槛打开 2023.7 Claude 2:100K,首个量产十万级窗口,法律、长篇论文场景爆发 2023.11 GPT-4 Turbo:128K,全球主流商用标配门槛,单本小说完整载入 2023年底 国产跟进:通义、GLM、混元开放32K/64K;开源Llama 2、Qwen 7B固定4K–32K 2023.12 Gemini 1.0 Ultra 纸面宣称1M,但仅实验室封闭测试,无法商用落地 时代里程碑:128K成为专业AI标配;RAG成为行业标准方案,弥补窗口不足。 3. 百万上下文实验期:200K–2M纸面(2024–2025,纸面强、实际弱) 2024.2 Gemini 1.5 Pro:原生2M token,全球首个公开百万级模型,但存在严重「中间遗忘Lost in the Middle」,后半段文档召回暴跌,仅适合摘要,不适合精细推理,仅限开发者限量Beta 2024 Claude 3 Opus:200K,稳定可靠,法律行业主力,无严重遗忘问题 2025 上半年:各大厂商放出1M Beta(Claude Sonnet 4、Gemini 2.0),长上下文计费溢价极高、响应慢、显存开销巨大,企业少量试用,普通用户无法接触 2025 国产开源:Qwen、DeepSeek推出64K–128K底座,少量实验版支持256K 时代特征:1M只是技术噱头,标称≠有效;硬件成本、遗忘问题、价格三重门槛,无法大规模落地。 ...

2026-07-09 08:58:34 PM · 2 分钟
AI

一个程序员应该知道的各种锁

1. 悲观锁 vs 乐观锁(心态问题) 悲观锁:觉得肯定有人抢厕所,进去就反锁门(加锁),用完再开。 → Java里的 synchronized 和 ReentrantLock 都是这种。安全,但慢。 乐观锁:觉得没人抢,不锁门,但上厕所时盯着门把手(版本号/时间戳),如果发现被人动过(冲突),就重试。 → Java里的 CAS(比较并交换) 就是这种,比如 AtomicInteger。快,但冲突多时会反复重试。 2. 公平锁 vs 非公平锁(排队问题) 公平锁:先来后到,乖乖排队。 → new ReentrantLock(true)。公平,但效率低(大家都得排队)。 非公平锁:新来的可以插队,如果锁刚好释放,它就直接抢。 → synchronized 和默认的 ReentrantLock 都是非公平。效率高,但可能导致某些线程饿死(一直抢不到)。 3. 可重入锁(同一个人的多次进入) 你进了厕所A,发现里面还有个小隔间B,你能直接进B,不用再掏钥匙。 → synchronized 和 ReentrantLock 都支持。同一个线程可以多次获取同一把锁,防止自己把自己卡死。 4. 读写锁(分情况管理) 厕所分蹲位(写锁)和洗手池(读锁)。 多人同时洗手(读)没问题。 但有人蹲坑(写)时,别人既不能蹲也不能洗手(全阻塞)。 → ReentrantReadWriteLock。适合读多写少的场景。 5. 共享锁 vs 排他锁(权限级别) 排他锁(写锁):厕所门一锁,谁也别进。 共享锁(读锁):可以多人同时看同一份文件(但不能改)。 → 读写锁就是共享/排他的典型实现。 6. 偏向锁 → 轻量级锁 → 重量级锁(锁升级,JVM自动优化) 这是 synchronized 的底层升级过程(为了性能): 偏向锁:厕所只认你一个人,你每次来都不用掏钥匙(无竞争)。 轻量级锁:偶尔有别人来,你们用“自旋”方式(原地转圈等)抢,不挂起线程(省资源)。 重量级锁:竞争激烈,排队挂起(操作系统介入,慢)。 → 这是JVM自动做的,你不用管,但知道它存在即可。 7. 自旋锁(不睡觉,死等) 厕所被占,你不去排队睡觉,而是在门口原地转圈(循环检查),等它释放。 → 适合持有锁时间很短的情况,避免线程挂起/唤醒的开销。Java里的 CAS 就是自旋思想。 8. 分段锁(分块管理) 一个大厕所分成多个小隔间,锁只锁其中一间,不影响其他间。 → ConcurrentHashMap 早期就是用分段锁,提升并发度(现在改用CAS+细粒度锁了)。 一张图总结(按使用场景选): 场景 推荐锁 简单同步,代码少 synchronized 需要可中断、超时、公平等灵活功能 ReentrantLock 读多写少(如缓存) ReentrantReadWriteLock 计数器、自增等简单操作 AtomicXXX(乐观锁) 追求极致性能,竞争不激烈 偏向锁/轻量级锁(JVM自动)

2026-07-09 11:15:34 AM · 1 分钟

Docker 常用配置

Kafka Kafdrop services: kafdrop: image: obsidiandynamics/kafdrop container_name: kafdrop ports: - "9000:9000" environment: KAFKA_BROKERCONNECT: "localhost:9092" SERVER_SERVLET_CONTEXTPATH: "/" restart: unless-stopped KRaft 模式 services: kafka: image: apache/kafka:3.9.0 container_name: kafka hostname: kafka ports: - "9092:9092" environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 # 注意:这里不要换行,不要逗号 KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 # 外部访问地址 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk volumes: - ./data:/var/lib/kafka/data restart: unless-stopped

2026-07-09 11:04:37 AM · 1 分钟
Docker

Java GC 是如何驱赶“老年群体”的

在 Java 的世界里,“分代年龄”(Age) 是专门为 JVM 垃圾回收(GC)设计的一个计数器。 简单来说,它记录了一个 Java 对象在垃圾回收中活过了多少轮。就像打游戏刷副本一样,对象每在垃圾回收的“清洗”中幸存下来一次,它的分代年龄就会 +1。 1. 为什么需要“分代年龄”? 这源于 Java 垃圾回收领域著名的 弱分代假说(Weak Generational Hypothesis): 绝大多数的 Java 对象都是“朝生夕死”的。 比如你在一个方法里 new 了一个局部变量,方法结束了,这个对象就没用了。但也有极少数对象(如 Spring 的 Bean、数据库连接池)会一直存活。 为了高效管理内存,JVM 把堆内存分成了两大区域: 新生代(Young Generation): 存放刚出生、寿命短的对象。 老年代(Old Generation): 存放寿命长、常驻内存的对象。 “分代年龄”就是对象从“新生代”晋升到“老年代”的资格证和进度条。 2. 它的工作流程是怎样的? 我们可以把新生代想象成一个“新手村”,老年代想象成“满级主城”: 出生(0岁): 绝大多数新对象在新生代的 Eden(伊甸园)区 出生。 第一次渡劫(1岁): 发生了一次 Minor GC(新生代垃圾回收)。如果这个对象没被回收,它会幸存下来,被移动到 Survivor(幸存者)区,同时它的分代年龄变成 1 岁。 继续熬资历(每活过一轮 +1岁): 以后每发生一次 Minor GC,只要它还在幸存者区折腾且没死掉,它的年龄就会加 1 岁。 晋升老年代: 当它的年龄达到一定阈值(默认是 15 岁,由 JVM 参数 -XX:MaxTenuringThreshold 决定)时,JVM 就会认为:“这小伙子挺能活,应该是个核心常驻对象。” 于是把它晋升(Promote)到老年代。 3. 它存在对象的什么地方? 正如我们在讨论“锁”时提到的,分代年龄存在于每个 Java 对象的 对象头(Object Header)的 Mark Word 里面。 ...

2026-07-09 10:19:20 AM · 1 分钟

为什么每个java对象都有锁的属性?这样不是浪费内存吗

1. 为什么当初要这样设计?(设计初衷) Java 诞生于 20 世纪 90 年代中期,当时多线程编程(并发)正在兴起。Java 的设计者高斯林(James Gosling)希望 Java 成为一门简单易用的多线程语言。 简化并发编程 如果对象没有内置锁,你每次想给一段代码加锁,都必须显式地创建一个 Lock 对象: // 如果没有内置锁,你得这么写: Lock myLock = new ReentrantLock(); void doSomething() { myLock.lock(); try { // 业务逻辑 } finally { myLock.unlock(); } } 而 Java 设计者引入了 synchronized 关键字,直接让对象充当锁,把复杂的同步简化成了: // 极简的同步写法 synchronized(this) { // 业务逻辑 } “万物皆对象,万物皆可为锁”的设计,让开发者不需要管理一堆乱七八糟的锁对象,只要拿到目标对象,就能直接进行同步控制。 2. 这样不浪费内存吗?JVM 是怎么“偷懒”的? 你担心的内存浪费,JVM 工程师早就想到了。他们绝对不会傻到给每个刚 new 出来的对象都分配一个沉重的操作系统级别的锁(Monitor)。 JVM 解决这个问题的核心思路是:按需分配,动态升级。 秘密武器:对象头(Object Header) 在 Java 中,每个对象的内存结构里都有一个叫 Mark Word(标记字) 的区域(在 64 位虚拟机上占 8 个字节 / 64 位)。 ...

2026-07-09 10:11:43 AM · 1 分钟

H mod C = H & (C - 1) ?

在 Java 后端开发面试或源码阅读中,HashMap 永远是避不开的核心。很多人能熟练地背出它的扩容阈值、红黑树化条件,但当深入到源码底层,看到诸如 e.hash & (newCap - 1) 和 e.hash & oldCap 这样的位运算时,往往会陷入沉思。 一、 起源:为什么数组长度必须是 2 的次幂? 在散列表中,为了让数据均匀分布,最直观的想法是对哈希值进行取模(求余数): $$\text{索引位置} = \text{hash} \pmod{\text{Capacity}}$$然而,在 CPU 的底层底层执行中,除法和取模(%)是非常昂贵的算术操作(可能需要几十个时钟周期)。为了追求极致的性能,底层的数论定理为我们提供了一个完美的替代方案: 数学定理:当容量 $C$ 是 $2$ 的次幂($2^n$)时,对于任意整数 $H$,满足: $$H \pmod C \equiv H \ \& \ (C - 1)$$ 位运算(&)在 CPU 中只需要 1 个时钟周期!为了享受到这个性能红利,HashMap 在源码中将容量死死限制为 2 的次幂: // 默认初始容量 16 (2的4次方) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 裁剪器的魔术:hash & (newCap - 1) 以容量 16 为例,16 - 1 = 15(二进制:0000 1111)。任何 Hash 值与 15 进行 & 运算,高位都会被无情“抹零”,只有最后 4 位被保留下来: ...

2026-07-08 05:38:40 PM · 2 分钟
代码原理

限流降级研究笔记 更新中 

主流算法 固定窗口计数器 每秒一个计数器,+1超过阈值就拒绝 缺点 0.51s 和 1.51s 之间产生2倍流量 滑动窗口 把时间切成更细的小格,滑动统计 缺点 解决临界问题,但是实现复杂 漏桶 请求进桶,匀速流出,桶满则弃 缺点流出速度恒定,扛不了正常的突发 令牌桶 匀速往桶里放令牌,来请求拿令牌,没令牌被拒绝 缺点允许一定突发

2026-07-07 04:05:10 PM · 1 分钟

Java 从Random理解 CAS自旋模式 更新中 

你理解这段代码吗? do { oldseed = seed.get(); newseed = (oldseed * ...) & mask; } while (!seed.compareAndSet(oldseed, newseed)); // ← CAS 第〇层:先搞懂"变量"在多线程里的危险 假设有一个变量 seed = 100,两个线程同时想改它: 线程A:读到 seed=100 → 算出新值 200 → 写回 seed=200 线程B:读到 seed=100 → 算出新值 300 → 写回 seed=300 问题来了——两个人都读到了 100,各算各的,最后谁写得晚谁赢。A 的结果被 B 覆盖了,A 白干了,而且程序根本不知道出错了。 这就是经典的 线程安全问题。 第一层:解决办法有两条路 路线一:悲观锁(synchronized) → "我先把门锁上,别人别进来" → 安全但慢(别人要排队) 路线二:乐观锁(CAS) → "门不锁,但我写之前验一下,被人改了我就重来" → 快但可能要重试 你这段代码走的就是 路线二。 第二层:什么是 CAS? CAS = Compare And Set(比较并设置),三个参数: CAS(内存地址, 我期望的旧值, 我想写入的新值) 它做的事用大白话说: “我记得这个值是 42,如果现在还是 42,就帮我改成 77。如果不是 42 了,说明有人动过了,啥也别干,告诉我失败了。” ...

2026-06-06 09:40:20 PM · 2 分钟

常见缓存策略精析 更新中 

缓存三兄弟 缓存穿透 是什么:大量请求查询数据库不存在的数据。缓存没有,持续击打数据库 防范方法: 缓存空值 数据库查不到也往缓存放一个空标记(TTL短,2分钟,该TTL过期了才能进行再次数据库查询) 下一次同样的id来了直接被挡住 优点:有效拦截大量穿透请求 缺点: 恶意攻击造成大量不同的不存在的key,缓存堆满无效数据,浪费内存 延迟了数据一致性,新数据必须等缓存过期 布隆过滤器(推荐) 启动时把所有合法id放入布隆过滤器; 布隆过滤器不给进就真的没有; 优点: 没有高内存占用风险 没有数据一致性风险 缺点: 有极小误判率 增删要维护。数据更新时,要同步更新布隆过滤器 接口层限流和校验 API网关或Controller层做拦截 参数校验、限流降级 互斥锁重建 互斥锁主要解决缓存击穿(热点key过期),但是在缓存空对象场景下,如果有大量并发请求一个不存在的key,可以使用锁 每次访问让一个线程去查DB,其他线程等待 大厂的实践 网关层 校验参数格式,IP限流(挡掉脚本攻击) 过滤器层 布隆过滤器 缓存层 缓存击穿 是什么:某个被疯狂访问的热点key,因为TTL一到,失效的一瞬间海量并发请求同时miss,同时到DB重建同一个key 防范方法: 互斥锁 只让第1个miss的线程去查DB 其他线程等一下再读缓存 高一致性:线程傻傻等待 低一致性:先给旧数据,下一次来查就是新的 逻辑过期 key永不物理过期,把过期时间存在value里面 如果发现逻辑上过期了,异步开个线程重建 当前请求先返回旧值 互斥锁的实现 如何搭建出正确的锁? v1:SETNX key 1 抢锁,DEL key 释放 SETNX=“不存在才设置成功”,天然互斥。但问题:抢到锁的线程崩了、DEL 没执行 → 锁永远不释放 → 死锁。 v2:给锁加过期间 崩了也能自动过期。但关键:必须用一条原子命令 SET key value NX EX 30。 陷阱:别写成 SETNX + 再 EXPIRE 两条——如果刚 SETNX 成功、还没 EXPIRE 就崩了,又变回 v1 的死锁。“抢锁"和"设过期"必须一步做完。 ...

2026-05-05 10:46:44 PM · 1 分钟
Redis 缓存策略

构建一个聊天室需要的基础知识 更新中 

基础知识 TCP 三次握手 客户端 - 服务器 三次握手目的:客户端的收发没问题,服务器的收发也没问题。 第一次握手:客户端发 SYN(同步请求) 客户端:我啥也不知道,我发个 SYN 消息出去,看看有没有人接收到。 如果没人回复,我再发几次(超时重传)。 这个时刻,客户端知道的事实是: 我发出去了,但不知道有没有人收到 ❌ 我能不能收消息?不知道 ❌ 对方存不存在?不知道 ❌ 第二次握手:服务器回 SYN+ACK(同步+确认) 服务器收到了客户端的 SYN。 服务器回复 SYN+ACK:“我收到你的消息了,我也发一条试试,看看你能不能收到。” 这个时刻,服务器知道的事实是: 客户端发消息是没有问题的 ✅(因为我收到了) 服务器收消息是没有问题的 ✅(因为我收到了) 服务器不知道服务器发消息有没有问题 ❌(我发出去了,但不知道对方收到没) 服务器不知道客户端收消息有没有问题 ❌(我发出去了,但不知道对方收到没) 服务器需要发送一条 SYN+ACK,等对方回复 ACK 来确认“我能发、对方能收” 当客户端收到 SYN+ACK 后,客户端可以确定以下事实: 我发消息是没有问题的 ✅(我发的 SYN 被收到了) 我收消息也是没有问题的 ✅(我收到了对方的回复) 服务器收消息是没有问题的 ✅(对方收到了我的 SYN) 服务器发消息也是没有问题的 ✅(我收到了对方的 SYN+ACK) ✅ 客户端这边已经确认全双工通信没问题了! 但是服务器他还有顾虑(不知道自己能不能发、不知道我能不能收),我再发个 ACK 给他吧 第三次握手:客户端发 ACK(确认) 客户端收到 SYN+ACK 后,回复一个 ACK:“我收到你的 SYN+ACK 了,我这边收发都没问题,你也可以放心了。” 当服务器收到这个 ACK 后,服务器可以确定以下事实: ...

2026-04-03 11:45:26 AM · 9 分钟
后端技术