月度记录-2025-5月

98 阅读10分钟

1. 第三方接口校验工具

调用第三方接口,来实现一些功能,比如开发票,一些返回值是必须要有的,但是供应商某些情况下可能并不会返回。是不是可以做个通用工具,提前设置一下哪些参数必须返回,没有的话就抛异常记录日志。这也就避免了写很多次相同的判断逻辑。接口返回值可以按业务要求分为三类:

  1. 必须有返回值(强依赖,无补偿),没有就无法继续处理,必须立即报错或中断。
  2. 可以没有返回值(非关键字段),有最好,没有也可以用默认值处理或忽略。
  3. 必须有返回值(弱依赖,可补偿),没有就触发补偿机制,如重试、兜底调用、通知人工。

此外,还要支持嵌套的返回值,有些返回值是嵌套起来的,要做的校验更复杂。

最后,这个没座!!!,为甚?因为TMD那个补偿接口调用之后还是补偿不到,气死我了,没心情做。

2. DDD Service们

Application service层,只负责编排业务逻辑。最好就是写的跟自然语言一样,方法名体现业务含义,不应该带技术实现细节,例如sendMessageAndSaveToDb就不是一个好名字,而registerNewUser就很高明。要干什么用方法名描述清楚。将具体的实现,下沉到domain service和Infrastructure Service。

Application service不应包含if/else流程逻辑过多,应该委托domain service或domain model内部封装。domian service封装业务中稳定且核心的领域逻辑,不应该涉及基础设施。

Application service,负责组织逻辑、协调各服务,最好做到是个正常人就能一眼看明白大概流程。而domain service,承载纯粹的领域逻辑,不是技术服务。技术服务调用Infrastructure Service实现。

其实就是分离业务逻辑和具体实现,代码可维护。DDD的价值在于“建模领域”,通过分层解耦,让系统具备高内聚、低耦合,强表达力的结构。

3. 为什么 null 不适合作为 Redis 缓存值

如果返回 null,你无法区分“真的设置了一个 null”还是“key 根本不存在”。

应该用"1",标识我确实设置过这个值,表示该 key 有效且存在。

4. Redisson看门狗

当你通过 Redisson 获取分布式锁时,如果没有手动指定锁的超时时间,Redisson 会默认启用看门狗机制,自动帮你延长锁的持有时间,直到你显式释放锁为止。

每隔:10秒(可配置),Redisson 会自动给锁续命一次(再延长 30 秒)

5. Redisson锁可重入

schedule依赖了admin和web,这就导致这两个的IdUtil2,用的会是相同的workId。但是吧,也没啥问题。保证admin和admin不重复,web和web不重复就ok。admin和web重了也问题不大。

Redisson 的分布式锁是可重入的,允许同一个线程多次加锁而不阻塞。

  • 锁重入次数必须匹配 unlock 调用次数,否则锁不会真正释放,会导致死锁。

  • 不同线程重复加锁会被阻塞,即使是同一个类或方法,只要线程 ID 不一样就不能重入。

  • 锁的唯一性依赖线程 + UUID 标识,所以跨线程重入不支持(也不安全)。

6. Optional orElse() 和 orElseGet()

optional.map(a::getId()).orElse(method());

// 相当于以下代码:method() 在 orElse 调用之前就被执行了
T value = method();
result = optional.map(a::getId).orElse(value);

如果你希望只有在 Optional 为空时才执行 method(),应该用 orElseGet(),orElseGet() 接收的是 Supplier,是惰性求值,method() 只会在 Optional 为空的时候才执行。

optional.map(a::getId).orElseGet(() -> method());

7. id生成器

之前的id生成器,datacenterId和workId是随机出来的,且两者相同...,导致出问题的概率贼大。我优化了两版,最终选择了第二版。

共性:新IdUtil的id生成逻辑和原IdUtil一致,只是将datacenterId和workId改为传参设置的,除此之外不做任何改动。两个方案都依赖redis保证不重复,取值0-1023,拿到后再取高5位和低5位作为datacenterId和workId,传入IdUtil。

8. 最近工作涉及多个系统之间的数据同步,开发此类系统,有哪些方法论吗?

要考虑一致性,又要兼顾性能、解耦与演进。用事件驱动思维解耦业务系统;用可靠消息机制保障最终一致性;用幂等 + 补偿 + 可观测,托底分布式的复杂性。

一、顶层设计哲学:先明确“同步的目标是什么”

1. 是强一致性,还是最终一致性?

2. 是实时同步,还是定时同步?

3. 是单向同步,还是双向同步?

二、常见数据同步架构模式(推荐你熟悉这4种)

1. 事件驱动(Event-Driven Architecture,推荐)

适合:最终一致性、解耦、可扩展同步

2. 可靠消息最终一致性(Recommended for 业务同步)

适合:注册、订单、状态类同步(保证可靠 + 可重试)

3. 数据抓取(CDC / 变更订阅)

适合:历史系统改造、BI 分析同步、缓存构建等

4. ETL / 定时同步

适合:离线分析、次要系统同步

三、同步系统的关键机制设计

1. 幂等机制

2. 补偿机制

3. 链路追踪

4. 配置中心 / 数据订阅

9. 数据库三态问题

你有个字段是true或者false,但是数据库支持null,由于某些原因数据库写入null,会导致系统逻辑错误。

三态问题的本质是:在你只打算支持“是/否”两种状态时,数据库却默认你支持三种(是/否/未知)。你不主动约束,系统就会给你制造歧义和 bug。因此对于像 status 这样的字段,强烈建议加 NOT NULL,并给出默认值(如 DEFAULT 0),这样才能保持业务逻辑稳定、数据语义清晰。

Boolean 字段允许 NULL = 引入第三种逻辑状态 = 系统隐性 bug 温床。

10. user_agent

user_agent 是前端浏览器或客户端在发起请求时,自动携带的一个 HTTP Header 字段,其作用是标识客户端的设备、操作系统、浏览器、版本、设备类型、内核信息等信息。

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
             AppleWebKit/537.36 (KHTML, like Gecko) 
             Chrome/123.0.0.0 Safari/537.36

为什么要记录 User-Agent?

1. 安全审计和风控分析 2. 登录设备识别 3. 数据统计分析 4. 异常排查

11. mysql text 存储结构分析(为什么有人觉得 TEXT 慢)

类型

存储方式

是否在行内存储

VARCHAR

存在行内(页中),定长/变长

✅ 通常在页内,效率更高

TEXT

存在页外,行内只存指针

❌ 页内有指针,值在页外查一次

所以访问 TEXT 会多一次指针寻址开销,这就是它“慢”的来源。但——这是 I/O 层级的微优化,而且:

不要做的事情:

场景

示例

为什么问题大

WHERE

查询条件

WHERE request_params LIKE '%xxx%'

不能走索引,强制全表扫描,性能极差

排序

ORDER BY response_data DESC

TEXT

不能参与有效排序,需要临时表

分组聚合

GROUP BY request_params

无法高效聚合,通常没意义还极慢

连接条件

JOIN ... ON a.request_params = b.xxx

大字段连接逻辑混乱,极易炸掉资源

允许的事情:

场景

示例

为什么安全

只做日志写入

INSERT INTO operation_log (...) VALUES (...)

写性能不会受 TEXT影响

查询时“顺带带出”

SELECT request_params FROM operation_log WHERE id = ?

主键过滤效率高,TEXT 开销可控

审计时人工查看

用日志系统页面展示 TEXT 内容(例如 truncate + tooltip 展示)

用户点开再看,不频繁

最后,用的JSON存的日志,更合适一点。

12. ip 0:0:0:0:0:0:0:1和::1

它们在 IPv6 中表示的是同一个地址 —— IPv6 的本地回环地址,相当于 IPv4 中的 127.0.0.1。

13. 反向代理(如 Nginx、Apache、F5、Kong) 或 负载均衡器 的 HTTP 请求获取ip

1. X-Forwarded-For 最常用的标准头

  • 作用: 表示请求经过的代理服务器链,记录原始客户端 IP。

  • 格式:X-Forwarded-For: client, proxy1, proxy2

  • 其中第一个 IP 是原始客户端 IP,后面是各级代理服务器 IP。

  • 使用广泛程度:非常广泛,几乎所有反向代理支持该头。

  • 注意事项:需要自行提取第一个 IP 地址。

2. Proxy-Client-IP 早期 WebLogic/Apache 插件使用

  • 作用: 早期 Web 服务器插件设置的客户端 IP。
  • 常见场景:BEA WebLogic 插件、Apache HTTP Server 某些旧模块。
  • 使用频率:已较少使用。

3. L-Proxy-Client-IP WebLogic 专用

  • 作用:Oracle WebLogic 的 Web 服务器插件设置的客户端 IP。
  • 使用频率:在使用 WebLogic 并配置了 HTTPClusterServlet 时出现。

4. X-Real-IP Nginx 推荐配置

  • 作用:直接记录原始客户端 IP。
  • 优点:不会包含多个 IP,直接就是客户端 IP。
  • 使用频率: Nginx 用户最常用,安全性也更高。

推荐使用顺序的原因:

在实际生产环境中,一个请求可能经过多个代理,最前面的才是真实客户端。这个顺序设计的原则是:

  1. X-Forwarded-For → 可能包含多个 IP,优先提取第一个。
  2. X-Real-IP→ 如果由 Nginx 设置,可信度高,结构简单。
  3. 其他如 WebLogic 专用头(WL-Proxy-Client-IP、Proxy-Client-IP) → 如果部署环境用的是这些中间件才有可能设置。

14. 集群模式下,事务里面操作的key必须落在一个slot里面

redisTransaction.execute(stringObjectRedisOperations -> {
     // 必须操作一个slot下的key
 });

lua脚本也是。想用的话,需要用到Hash Tag

在 Redis Cluster 模式下,你不能直接指定 slot 编号,但你可以通过控制 key 的格式来强制多个 key 落入同一个 slot。Redis 提供了一种机制:哈希标签(Hash Tag)。

Redis 会对 key 中 {} 包裹的部分做 CRC16 % 16384 计算,作为最终的 slot 编号。只要多个 key 的 {} 中内容一样,它们就会落在同一个 slot。

15. @AllArgsConstructor和@RequiredArgsConstructor,有什么区别

@AllArgsConstructor —— 全参构造器,它会为 类中所有字段(不管是否 final、是否有默认值) 生成一个构造函数。

@RequiredArgsConstructor —— ,只为 final 和 @NonNull 字段生成构造器。

用的时候要注意,注入的时候用@RequiredArgsConstructor!!!

16. java bean is 老古董

字段命名别用is开头,老坑了,各种兼容问题。不是头一次遇到了,这回是MapStruct。

17. 关于浏览器的user-agent,用的谷歌浏览器,为何会有Safari??

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36

很多网站会用正则判断 User-Agent 字符串来识别浏览器,如果没有 Safari 字段,部分只支持 Safari 的老网站可能会拒绝访问或功能异常。为了**“骗过”这些判断逻辑**,Chrome 在自己的 UA 中加上了 Safari。 这其实就是历史兼容性导致的“伪装行为”,现代浏览器几乎都在 UA 中“扮演多个角色”,避免被不够严谨的判断逻辑误伤。

后续发展:User-Agent 将被淘汰,因为 UA 太乱、容易被伪造,Google 等浏览器厂商已经在推进一种新方案:Client Hints,它通过 HTTP Header 更精确地传递浏览器信息,且更隐私友好。