惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
酷 壳 – CoolShell
酷 壳 – CoolShell
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
J
Java Code Geeks
Jina AI
Jina AI
雷峰网
雷峰网
M
MIT News - Artificial intelligence
小众软件
小众软件
H
Help Net Security
The Register - Security
The Register - Security
T
Tailwind CSS Blog
D
DataBreaches.Net
大猫的无限游戏
大猫的无限游戏
有赞技术团队
有赞技术团队
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
Google DeepMind News
Google DeepMind News
月光博客
月光博客
Project Zero
Project Zero
P
Proofpoint News Feed
S
Security @ Cisco Blogs
L
LINUX DO - 最新话题
I
InfoQ
Vercel News
Vercel News
V
Vulnerabilities – Threatpost
S
Schneier on Security
Spread Privacy
Spread Privacy
Hugging Face - Blog
Hugging Face - Blog
D
Docker
博客园 - 【当耐特】
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
The Blog of Author Tim Ferriss
博客园 - 聂微东
宝玉的分享
宝玉的分享
Recorded Future
Recorded Future
K
Kaspersky official blog
L
LINUX DO - 热门话题
Stack Overflow Blog
Stack Overflow Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
腾讯CDC
A
About on SuperTechFans
D
Darknet – Hacking Tools, Hacker News & Cyber Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
GbyAI
GbyAI
Schneier on Security
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
B
Blog RSS Feed

博客园 - buguge

最差实践(bad-practice):开发者在方法里直接实例化线程池对象,然后...(应用gg了) 【HttpClient最差实践(bad-practice)】开发者在 http 工具方法中直接实例化 HttpClient,然后…… Crypto、Cipher与Password:Java加密开发的三个核心概念 知识VS技能:如何优雅判空? 20260604SR超时问题排查 推敲见文章:从 `try..catch` 看异常日志打印的正确姿势 #解决问题要彻底# 慢SQL治理完成后,如何防止同类问题“死灰复燃”? 从合同甲方是荒谬的“JD”谈起:软件开发不应遗忘的“常识” 别留小尾巴/尽快剪掉小尾巴:从一次“ABA”字段重命名,谈谈“解决问题要彻底” 常见的OOM错误 ( OutOfMemoryError全类型详解) 开发者暴露了一个无需授权访问的裸接口,我问:如果有人暴力请求怎么办? 【SQL性能优化篇】有了!治理慢SQL“WHERE create_time ORDER BY id”的良药---规避“Using filesort”性能杀手 高效查询商户日终余额:一个SQL的优化实践 Hutool 的 `TimedCache` 到期会自动清理吗? ——————hutool cache的"惰性清理"和"定期清理" Fastjson枚举反序列化:当字符串不是枚举常量名时,会发生什么? fastjson-EnumDeserializer类及源码分析 随笔20260309:我们都是围城里的人 `UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only` 异常解析 认识2个单词:goal/target —————— 为什么Maven是 "goal" 而不是 "target"? 聚合系统设计:策略模式(Strategy Pattern)在银行通道对接场景中的应用 这样构建对象,太帅了!—— 阶梯式Builder模式与代码整洁之道 注意!字段数据类型不匹配,这个sql会很慢 还在用ArrayList?用HashSet吧!--性能对比 分页查询还在用create_time去做降序? 从 synchronized 到 ConcurrentHashMap:一个小小的并发控制策略升级优化,证明我还是初级程序员 面试题沟通整理 【研发笔记20260120】值得记录:靠谱程序员的回聘 研发笔记:如何消除长字符串的秘钥数据对RPC负荷、日志量、系统安全所带来的伤害? 未给entity的主属性赋值,Mybatisplus却抛出了type mismatch异常。——————分享一下Mybatisplus主键填充机制 在java中实现c#的int.TryParse方法 灵活用工平台-连续劳务所得税-计算器-工具类,拿走不谢 Apollo场景建议配置指南:充分发挥分布式配置中心优势
从 `int` 到 `Duration`:一个缓存 API 的三次演进教会我的事
buguge · 2026-07-21 · via 博客园 - buguge

image

1) 一个让我熬夜排查的 Bug

某天凌晨两点,线上告警:用户 Token 频繁过期,大量请求被踢回登录页。

查了一圈,发现 Redis 里 Token 的 TTL 设置有问题——本该存活 1 小时的 Token,实际只活了 1 分钟。顺着调用链找到罪魁祸首:

CacheUtils.set("token:" + userId, tokenJson, 60); // 调用方以为是60秒

再看方法签名:

public static String set(String key, String value, int cacheSeconds)

调用方传的 60 确实是 60 秒,但问题出在另一个地方——有人传了 TimeUnit.HOURS.toMillis(1)(结果是 3600000),被当作秒存进去了,导致 TTL 变成 3600000 秒 ≈ 41 天,而其他人传的正常值反而显得异常。

排查过程极其痛苦,因为 int 参数无法区分单位。那一刻我意识到:程序设计不注意细节的话,也许会成为整个团队的隐患。


2) 第一代:int cacheSeconds——简单,但脆弱

public static String set(String key, String value, int cacheSeconds)

优点: 参数少,调用简单,靠参数名来约定调用方。

缺陷:

  • 单位全靠参数名约定,编译器不帮忙,IDE 不提醒。
  • 魔法数字泛滥set("key", val, 7200) 谁知道 7200 是两小时还是两毫秒?
  • 容易误传TimeUnit.HOURS.toMillis(1) 这种错误,只要团队里有一个人犯,就够所有人喝一壶。

这个版本的代码就像“手写 SQL 拼接”——能跑,但随时可能炸。


3) 第二代:long + TimeUnit——类型安全,但调用繁琐

痛定思痛,我们加了 TimeUnit 参数:

public static String set(String key, String value, long cacheTTL, TimeUnit timeUnit)

进步之处:

  • 单位显式指定,set(k, v, 1, TimeUnit.HOURS) 一眼可知是 1 小时。
  • 类型不同(long vs TimeUnit),顺序写反会编译报错,不会留到运行时。
  • long 避免了 int 溢出的问题(虽然 Redis TTL 很少超过 int 范围,但更严谨)。

依然存在的问题:

  • 调用方每次都要写两个参数,略显啰嗦。
  • long cacheTTL 这个数值本身没有语义——1 代表 1 个单位,但单位是 TimeUnit 决定的,调用方需要理解“TTL 数值”的含义。
  • 与主流框架不一致:Spring 的 RedisTemplate 早已用 Duration,我们的自定义工具类却还在用“数值+枚举”的组合。

这个版本像是“用安全带代替了徒手攀岩”——安全了,但还不够优雅。


4) 第三代:Duration——优雅且安全

在我们的 Java 8 版本中,有更好的方案:

public static String set(String key, String value, Duration cacheDuration)

这才是正确的姿态:

// 调用方代码即文档
CacheUtils.set("token", token, Duration.ofHours(1));
CacheUtils.set("code", code, Duration.ofMinutes(5));
CacheUtils.set("temp", temp, Duration.ofSeconds(30));
CacheUtils.set("config", config, Duration.ZERO); // 永不过期

相比前两代的碾压性优势:

维度 第一代 int 第二代 long+TimeUnit 第三代 Duration
单位明确性 靠参数名约定 显式指定,但数值与单位分离 类型自带单位,语义合一
编译期检查 顺序写反会报错,但数值本身无约束 类型安全,传错类型直接编译失败
可读性 魔法数字,需换算 set(k,v,1,HOURS) 可读,但略繁琐 Duration.ofHours(1) 自然语言
与生态集成 手动转换 手动转换 与 Spring/JDK 原生 API 无缝对接
扩展性 只能秒 支持多种单位,但需额外枚举 纳秒到天,任意精度,且支持运算

内部实现同样简洁:

public static String set(String key, String value, Duration cacheDuration) {
    if (cacheDuration.isNegative()) {
        throw new IllegalArgumentException("TTL must not be negative");
    }
    long seconds = cacheDuration.getSeconds(); // 底层 Redis 需要秒
    // ... 执行 Redis SETEX 命令
}

5) 三次演进教会我的事

教训0️⃣:定义清晰的参数名,仅仅是一个基础

int cacheSeconds指明让调用者传“秒”。

教训一:类型是最好的文档

int cacheSeconds 写了一百遍“单位是秒”,不如 Duration 一个类型来得可靠。编译器能替你检查的,就不要留给人类去记。

教训二:API 设计要考虑调用方的犯错成本

第一代 API 的设计者可能觉得“传个 int 多简单”,但他没想过调用方可能会传毫秒、传分钟、传魔法数字。一个好的 API 应该让正确用法显而易见,让错误用法难以编译通过。


6) 结语:高质量代码是从每一个参数开始的

经过这次 Bug,不妨定义如下这条团队规约:

所有表示“时间段”的参数,一律使用 java.time.Duration,禁止使用 intlong

回头看,从 intlong+TimeUnit 再到 Duration,不仅仅是 API 签名变了,更是对代码质量理解的深化——高质量代码不是靠“约定”和“自觉”,而是靠类型系统和编译器来保障。

下一次你写一个接收时间参数的方法时,不妨问问自己:我能让调用方犯错的可能性降到零吗?