Agent Token Routing: 本地匹配、缓存与模型分层

问题 聊天类 Agent 里,不少问答是相对固定的模式:营业时间、退款入口、帮助说明、菜单选项等。若一律调用大模型,既费 token,又容易把标准话术写飘。 常见做法有两种: 正则或规则匹配用户输入,命中则返回固定回答 在模型前加缓存,对特定提示词返回已有内容 这两层有用,但不够。省 token 的核心不是「再找一种缓存技巧」,而是做分层路由:能确定性处理的绝不进模型。 分层路由 用户输入 → L0 本地确定性匹配(规则 / FAQ / 按钮 / 命令) → L1 轻量意图分类(小模型 / embedding / 关键词) → L2 提示词 / 响应缓存(相同或近似请求) → L3 完整 LLM(复杂推理、开放对话、工具编排) 每一层命中就短路返回;只有前面都 miss 才往下走。 层 手段 适用场景 省什么 L0 本地匹配 正则、精确字符串、按钮 payload、slash 命令、状态机固定选项 话术固定、歧义低、可穷举 零 LLM 成本 L1 轻量分类 小分类模型、embedding 相似度、关键词 + 规则 说法多变但意图有限 用便宜模型代替大模型 L2 缓存 精确 cache key、semantic cache、prompt prefix cache 输入或 system prompt 重复率高 省重复推理 L3 LLM 完整对话 / function calling 开放问答、多步推理、需工具、强个性化 正确性优先 L0:本地匹配 适合: ...

Created 23236-07-23 · Updated 23236-07-23 · 2 min · 305 words · -

缓存一致性问题

概述 应用读多写少时,常在 PostgreSQL 与 Redis 之间加一层缓存:热数据放 Redis,持久化仍由 PostgreSQL 负责。难点不在「能不能缓存」,而在写路径与读路径如何协调——数据库已更新,缓存仍是旧值;或缓存已失效,并发读把脏数据又写回缓存。 下文以 Redis 作缓存、PostgreSQL 作权威数据源,梳理几种主流缓存模式、各自的一致性特征与适用场景。示例以伪代码和常见实践为主,便于对照落地。 模式总览 模式 谁加载缓存 谁写缓存 一致性 复杂度 典型场景 Cache-Aside 应用 应用 最终一致,需自行处理竞态 低 通用业务,最常用 Read-Through 缓存层 缓存层 同 Cache-Aside 中 统一缓存 SDK Write-Through 应用 缓存层同步写 DB 较强一致 中 读多、可接受写延迟 Write-Back 应用 缓存层异步刷盘 弱一致,有丢数据风险 高 写密集、可容忍短暂不一致 Binlog 同步 中间件 CDC 消费 binlog 更新缓存 最终一致,解耦业务 高 多服务共享缓存、不想侵入业务 分布式事务 应用 + 协调者 2PC / TCC / Saga 强一致或业务级一致 很高 资金、库存等强一致域 graph LR App[应用] Redis[(Redis)] PG[(PostgreSQL)] App -->|Cache-Aside / Read-Through / Write-Through| Redis App -->|直接写或经缓存层| PG Redis -->|miss 时回源| PG PG -->|Binlog / WAL| CDC[CDC 消费者] CDC -->|更新/失效| Redis Cache-Aside(旁路缓存) 也叫 Lazy Loading。应用自己管缓存:读先查 Redis,miss 再查 PostgreSQL 并回填;写先更新 PostgreSQL,再删除(推荐)或更新 Redis。 ...

Created 19196-06-19 · Updated 19196-06-19 · 6 min · 1080 words · -

Redis

Redis REmote DIctionary Server(Redis) 是一个由SalvatoreSanfilippo写的key-value存储系统。 Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash (哈希类型) 。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave (主从)同步。 Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。 Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。 redis的官网地址,非常好记,是redis.io。 (特意查了一下,域名后缀io属于国家域名,是british Indian Ocean territory,即英属印度洋领地) 目前,Vmware在资助着redis项目的开发和维护。redis[2] 的作者,叫Salvatore Sanfilippo,来自意大利的西西里岛,现在居住在卡塔尼亚。目前供职于Pivotal公司。他使用的网名是antirez。 Redis 4.0 之后的版本,情况就有了一些变动,新版的 Redis 服务在执行一些命令时就会使用『主处理线程』之外的其他线程,例如 UNLINK、FLUSHALL ASYNC, FLUSHDB ASYNC 等非阻塞的删除操作。 Redis 在较新的版本中引入了多线程,不过是在部分命令上引入的,其中包括非阻塞的删除操作,在整体的架构设计上,主处理程序还是单线程模型的 线程模型 使用单线程模型能带来更好的可维护性,方便开发和调试; 使用单线程模型也能并发的处理客户端的请求; Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU 6.0 Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂 I/O 多路复用的主要作用是让我们可以使用一个线程来监控多个连接是否可读或者可写,但是从网络另一头发的数据包需要先解序列化成 Redis 内部其他模块可以理解的命令,这个过程就是 Redis 6.0 引入多线程来并发处理的。 I/O 多路复用模块收到数据包之后将其丢给后面多个 I/O 线程进行解析,I/O 线程处理结束后,主线程会负责串行的执行这些命令,由于向客户端发回数据包的过程也是比较耗时的,所以执行之后的结果也会交给多个 I/O 线程发送回客户端。 AOF AOF 是 Redis 的一种持久化机制,它会在每次收到来自客户端的写请求时,将其记录到日志中,每次 Redis 服务器启动时都会重放 AOF 日志构建原始的数据集,保证数据的持久性。 Keyspace Notifications, 键空间通知 在Redis2.8.0版本的时候,推出 Keyspace Notifications future。 Keyspace Notifications 此特性允许客户端可以以 订阅/发布 (Sub/Pub) 模式,接收那些对数据库中的键和值有影响的操作事件。这些操作事件具体来说,就是 hash , del, expire , set , lpop 等。 那么你可能会问,redis keyspace 到底有啥用处? 简单说,对于我个人主要关注keyspace几个扩展场景: ...

Created 13136-07-13 · Updated 13136-07-13 · 2 min · 357 words · -

Redis, Memcache, Guava, Ehcache 中的算法

Redis, Memcache, Guava, Ehcache 中的算法 缓存那些事,一是内存爆了要用LRU(最近最少使用, Least Recently Used)、LFU(最少访问次数, Least Frequently Used)、FIFO的算法清理一些;二是设置了超时时间的键过期便要删除,用主动或惰性的方法。 在看所有的细节之前,可以看一篇相当专业的《缓存算法》,世界真宽阔,算法真奇妙。 LRU 简单粗暴的Redis 今天看Redis3.0的发行通告里说,LRU算法大幅提升了,就翻开源码来八卦一下,结果哭笑不得,这旧版的"近似LRU"算法,实在太简单,太偷懒,太Redis了。 在Github的Redis项目里搜索lru,找到代码在redis.c的freeMemoryIfNeeded()函数里。 先看2.6版的代码: 竟然就是随机找三条记录出来,比较哪条空闲时间最长就删哪条,然后再随机三条出来,一直删到内存足够放下新记录为止…….可怜我看配置文档后的想象,一直以为它会帮我在整个Redis里找空闲时间最长的,哪想到我有一百万条记录的时候,它随便找三条就开始删了。 好,收拾心情再看3.0版的改进: 现在每次随机五条记录出来,插入到一个长度为十六的按空闲时间排序的队列里,然后把排头的那条删掉,然后再找五条出来,继续尝试插入队列………嗯,好了一点点吧,起码每次随机多了两条,起码不只在一次随机的五条里面找最久那条,会连同之前的一起做比较…… 中规中矩的Memcached 相比之下,Memcached实现的是再标准不过的LRU算法,专门使用了一个教科书式的双向链表来存储slab内的LRU关系,代码在item.c里,详见memcached源码分析–LRU队列与item结构体,元素插入时把自己放到列头,删除时把自己的前后两个元素对接起来,更新时先做删除再做插入。 分配内存超限时,很自然就会从LRU的队尾开始清理。 同样中规中矩的Guava Cache Guava Cache同样做了一个双向的Queue,见LocalCache中的AccessQueue类,也会在超限时从Queue的队尾清理,见evictEntries()函数。 和Redis旧版一样的Ehcache/Hazelcast 看文档,居然和Redis2.6一样,直接随机8条记录,找出最旧那条,刷到磁盘里,再看代码,Eviction类 和 OnHeapStore的evict()函数。 再看Hazelcast,几乎一样,随机取25条。 这种算法,切换到LFU也非常简单。 小结 不过后来再想想,也许Redis本来就不是主打做Cache的,这种内存爆了需要通过LRU删掉一些元素不是它的主要功能,默认设置都是noeviction——内存不够直接报错的,所以就懒得建个双向链表,而且每次访问时都要更新它了,看Google Group里长长的讨论,新版算法也是社区智慧的结晶。何况,Ehcache和Hazelcast也是和它的旧版一样的算法,Redis的新版还比这两者强了。 后来,根据@刘少壮同学的提示,JBoss的InfiniSpan里还实现了比LRU更高级的LIRS算法,可以避免一些冷数据因为某个原因被大量访问后,把热数据挤占掉。 过期键删除 如果能为每一个设置了过期的元素启动一个Timer,一到时间就触发把它删掉,那无疑是能最快删除过期键最省空间的,在Java里用一条DeplayQueue存着,开条线程不断的读取就能做到。但因为该线程消耗CPU较多,在内存不紧张时有点浪费,似乎大家都不用这个方法。 所以有了惰性检查,就是每次元素被访问时,才去检查它是否已经超时了,这个各家都一样。但如果那个元素后来都没再被访问呢,会永远占着位子吗?所以各家都再提供了一个定期主动删除的方式。 Redis 代码在redis.c的activeExpireCycle()里,看过文档的人都知道,它会在主线程里,每100毫秒执行一次,每次随机抽20条Key检查,如果有1/4的键过期了,证明此时过期的键可能比较多,就不等100毫秒,立刻开始下一轮的检查。不过为免把CPU时间都占了,又限定每轮的总执行时间不超过1毫秒。 Memcached Memcached里有个文不对题的LRU爬虫线程,利用了之前那条LRU的队列,可以设置多久跑一次(默认也是100毫秒),沿着列尾一直检查过去,每次检查LRU队列中的N条数据。虽然每条Key设置的过期时间可能不一样,但怎么说命中率也比Redis的随机选择N条数据好一点,但它没有Redis那种过期的多了立马展开下一轮检查的功能,所以每秒最多只能检查10N条数据,需要自己自己权衡N的设置。 Guava Cache 在Guava Cache里,同一个Cache里所有元素的过期时间是一样的,所以它比Memached更方便,顺着之前那条LRU的Queue检查超时,不限定个数,直到不超时为止。而且它这个检查的调用时机并不是100毫秒什么的,而是每次各种写入数据时的preWriteCleanup()方法中都会调用。 吐槽一句,Guava的Localcache类里面已经4872行了,一点都不轻量了。 Ehcache Ehcache更乱,首先它的内存存储中只有惰性检查,没有主动检查过期的,只会在内存超限时不断用近似LRU算法(见上)把内存中的元素刷到磁盘中,在文件存储中才有超时检查的线程,FAQ里专门解释了原因。 然后磁盘存储那有一条8小时左右跑一次的线程,每次遍历所有元素…..见DiskStorageFactory里的DiskExpiryTask。 一圈看下来,Ehcache的实现最弱。 文章持续修改,转载时请保留原链接: http://calvin1978.blogcn.com/articles/lru.html

Created 28286-06-28 · Updated 28286-06-28 · 1 min · 56 words · -