月度记录-2025-4月

80 阅读12分钟

1. spring解除循环引用为何要三级缓存?二级不行吗?

支持aop。

2. 启用AOP功能,且强制使用CGLIB代理,无论其是否实现接口

@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)

proxyTargetClass:强制使用 CGLIB 代理,默认值:false(不强制)。

proxyTargetClass = false:若目标对象实现了接口,则优先使用 JDK 动态代理;若未实现接口,则使用 CGLIB 代理。

proxyTargetClass = true:强制使用 CGLIB 代理,无论目标对象是否实现接口。

exposeProxy:暴露代理对象到 AopContext,默认值:false(不暴露)。

exposeProxy = true:Spring 会将当前代理对象绑定到线程上下文(AopContext),允许通过 AopContext.currentProxy() 获取代理对象。

解决同类内部方法调用时 AOP 失效的问题:当目标对象内部方法 A 调用方法 B 时,直接调用 this.methodB() 会绕过代理,导致 B 的 AOP 逻辑不生效。通过代理对象调用可解决此问题。

3. 闭包表(Closure Table)

闭包表是一种专门用于存储和查询层级关系(树形结构)的数据库设计模式,它通过显式存储所有节点之间的路径关系来解决层级查询的效率问题。闭包表的核心思想是:显式存储所有节点间的祖先-后代关系,而不仅仅是父子关系。

CREATE TABLE closure_table (
    ancestor INT NOT NULL,   -- 祖先节点ID
    descendant INT NOT NULL, -- 后代节点ID
    depth INT NOT NULL,      -- 两节点间的深度差(0表示自身)
    PRIMARY KEY (ancestor, descendant)
);

4. 命名方法论总结

a.接口命名优先抽象出“状态”而非“行为”,行为是作用于状态的操作。 

b.你不是仓颉,不要造词

 c.方法命名能看懂就得了,不必强求处处一致。理想当然是一致,但是做不到的。

5. rocketmq 可以一个生产者对应多个消费者吗?

可以,一个 Producer 发送的消息可以被多个 Consumer 消费。

6. rocketmq 在发送的时候就要指定消费者组吗?

不需要在发送消息时指定消费者组,生产者只管发消息到某个 Topic,至于谁来消费,是消费者(Consumer)通过订阅来决定的。

A发,B消费,B消费者组命名,是否用 A 的前缀?

  • 不建议用 A 系统前缀(例如 a-push-group),这样会让人误以为是 A 系统的消费者。
  • 消费者组属于 谁来消费就归谁命名,和谁发的没关系。
  • 所以应该用 B 系统作为前缀,这才符合职责归属和维护习惯。

7. rocketmq Tag

Tag 是 Topic 下的“子分类”,一个 Topic 可以有多个不同的 Tag。一般建议一个 Topic 搭配 2~5 个 Tag,避免过度分散。

场景

建议用

完全不同的业务线

不同 Topic

相同业务的大类 + 子类型

同一个 Topic + 多个 Tag

需要独立限流、隔离策略

不同 Topic

只是内容结构不同,但归属同一类

同 Topic,分 Tag 处理

8. 表设计,双 ID

"业务 ID 与数据库主键分离"。数据库是数据库,业务是业务,主键不是给业务用的,业务 ID 不是主键。

都说快很多,但是我测试也就一个水平。是我电脑还是代码写的有问题?

9. mvn -U

Maven 默认对 SNAPSHOT 会做一定的缓存(每24小时或更久才检查一次)。这在多人协作或自己本地发布依赖的场景下,会遇到以下问题:

  • 你已经上传了新版本 -SNAPSHOT,但其他人 mvn install 却还用的是老版本(因为 Maven 没更新)。
  • 本地缓存没清除,导致测试结果不一致。

这时候,加 -U,就能解决。强制 Maven 更新远程仓库中的快照(SNAPSHOT)依赖。

非 -SNAPSHOT,无效。

10. 基础数据字典标准

同一个团队,数据尽量遵循一个标准,比如学历code,别搞那么多套,一套足以。多个系统之间交互也比较方便,最重要的是数据。

那么,为什么很多团队还是做不到统一?

现象

原因

子系统各自定义字段标准

系统开发时间不同,历史遗留,没有统一的数据规范

字段标准混乱

没有数据标准文档,也没有统一规范责任人

不同业务线推自己标准

部门壁垒、KPI单独考核,缺乏集团级统一治理

临时需求压倒标准化

快上线优先,时间压制下牺牲规范

领导只看功能上线,不看后期数据资产

缺乏工程文化,短视

说白了就是一句话:缺少数据标准化意识,缺少标准制定者和推行者。长远看,数据混乱是组织走不远的前兆。字段标准化不是技术问题,而是工程治理和组织成熟度问题。真正优秀的工程团队,都会从一开始就建立统一的基础字典体系。

11. idea maven reimport

有时候改了maven文件,包下过来了,但是一直加载不到idea,可以执行这个。

这是重新读取并解析 pom.xml,同步依赖、插件、模块、仓库配置到 IDE 项目结构中。这一步本质上就是让 IDEA 把项目和 Maven 的真实状态“对齐”,避免出现依赖缺失、类无法识别、构建失败等问题。

12. Pair是什么?

Pair 是一种简单的二元组结构,通常用于封装两个相关的值,比如 (startTime, endTime),避免你每次都用两个返回值或者创建专门的类。

在 Java 中,Pair 不是 JDK 原生提供的类,但常见的库里都有,比如:Apache Commons Lang 提供的 org.apache.commons.lang3.tuple.Pair

13. spring的rocketmq注解使用场景

注解名

用途

应用层级

@RocketMQMessageListener

注册普通消费者

业务常用(必会)

@RocketMQTransactionListener

事务消息回查

分布式事务场景

@ExtRocketMQConsumerConfiguration

自定义消费者配置

框架/高级定制

@ExtRocketMQTemplateConfiguration

多模板、定制 producer

多环境支持场景

  • 99%的业务开发者,只需要掌握 @RocketMQMessageListener 和 @RocketMQTransactionListener 就足够用了。

  • 另外两个 ExtXXX 注解更多是给中间件封装者、平台开发者用的。

1. @RocketMQMessageListener

使用场景:注册一个 普通消费者,监听某个 topic 下的消息。

2. @RocketMQTransactionListener

使用场景:用于监听事务消息的状态回查,和 RocketMQTemplate.sendMessageInTransaction() 一起用。

14. git千行bug率

git log --author="513725425@.com" --pretty=tformat: --numstat | \ awk '{ add += 1;subs+=1; subs += 2; loc += 11 - 2 } END { printf "新增行数: %s\n删除行数: %s\n净增: %s\n", add, subs, loc }'

千行 bug 率计算公式:bug rate = (bug 数量 / 净增代码行数) × 1000

15. Kibana为什么要建Data View(Index Pattern)?

index pattern是kibana可视化的前提。它相当于告诉kibana要使用哪些索引作为数据进行可视化展示,没有它 Kibana是瞎的!

16. ES match_phrase查询?

match_phrase 就是:查询一段连续的完整短语(phrase),而且单词顺序不能乱、不能断开。

假设你有一段文本:"The quick brown fox jumps over the lazy dog",如果你用不同查询:

查询

匹配吗?

说明

match "quick fox"

匹配

match 只要求包含这些词,不管顺序,不要求连续。

match_phrase "quick fox"

不匹配

因为 "quick" 和 "fox" 中间隔了 "brown",不连续。

match_phrase "quick brown fox"

匹配

因为这三个词连续、顺序对,完美。

  • match 查询是把搜索词各自分词,然后只要求"包含这些词"就能匹配。
  • match_phrase 查询,不仅分词,还要:保证词顺序一致。保证词之间的距离是连续的(默认slop=0,即不能插别的词)。当然你可以自己调slop(允许多少个词距离错开),但默认是严格连续。
  • 所以**match_phrase比match更加精确**,但又不是绝对精准匹配(因为底层还是分词器先切的)。

如果你想:

  • 绝对精准,比如名字、身份证号这种 → 用 keyword + term
  • 允许小范围模糊,比如用户搜“张学友”但是允许多一点自由度 → 用 match_phrase,调小slop。

17. wildcard 和 match_phrase对比

项目

wildcard 查询

match_phrase 查询

本质

字符串模式匹配(带通配符 *?)

分词后,短语连续匹配

是否分词

❌ 不分词

✅ 分词(但是要求词序一致)

字段要求

通常要 keyword 类型

通常是 text 类型

输入灵活度

高,可以任意位置模糊,比如 *学友*

低,必须是连贯的短语,比如 张学友

性能

较差(尤其在大数据量下)

中等(比 wildcard好,但比 term差)

典型使用场景

很自由的模糊查询(like %xxx%)

用户打出来的一段正常短语

查询速度

慢,尤其是后缀匹配 *xxx特别慢

相对快,适中

查询精准度

可以特别宽泛(比如搜到乱七八糟的)

更精准,连词顺序都要求

是否能走倒排索引

部分能,但性能依赖模式位置

能,高效利用分词的倒排索引

分词器影响

基本不影响(直接匹配原始字段)

很受影响(看用什么 analyzer)

🔵 一句话核心总结:

  • wildcard:就是拿原始字符串做类似正则的搜索,非常灵活,但超吃性能。
  • match_phrase:分词以后,要求短语连续、顺序一致,性能比 wildcard 好,但需要数据是分词友好的。

⚠️ 关键警告(很多人不知道的坑)

  • wildcard 在百万级别数据还能扛,千万以上量就开始痛苦,特别是后缀模糊(*xxx)极慢。
  • match_phrase 如果字段分词策略不合理(比如 "张学友" 分成了"张"、"学友"),那搜的时候也会偏差。
  • 如果是姓名这种中文连续词,要注意中文分词器(最好自定义或者用 ik_smart)。

做名字搜索时,不是比谁检索得多,而是比谁检索得“快、准、稳”。宁愿在极端场景丢一些检索命中,也绝不会让用户等3秒。

18. ES 查询总数一直显示1万,"track_total_hits": true

在 Elasticsearch 查询(search)时,有个返回字段叫 hits.total,表示查询命中的总条数。

  • 早期(比如 ES6.x 之前):默认就返回精准总数。

  • 后来(从 ES7.x 开始):出于性能优化,默认 只精准计算 0~10000 以内的总数。

  • 超过 10000,ES 为了避免影响查询速度,不去精确统计,而是告诉你:“大概有很多”,并设置 relation: "gte"(greater than or equal)。

所以:如果你想要 100%精准地知道到底有多少条命中,就需要显式加上:

"track_total_hits": true

更灵活的用法

除了 true / false,还可以设置具体的阈值,比如:

"track_total_hits": 5000

意思是:

  • 只精确到5000以内
  • 超过 5000,就不继续数了,只告诉你“>=5000”。

19. Es,自定义分词器,逐字符切文本,模拟数据库like,搜名字

模拟数据库like效果,中国人起名字,姓和名,姓就那几个,不用考虑。名,虽然目前重复度在下降,但是重复度依旧很高。性能应该不成问题。char_ngram+match_phrase(+slop),模拟数据库like。

"analysis": {
     "tokenizer": {
        "char_ngram_tokenizer": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 1,
          "token_chars": ["letter", "digit", "punctuation", "symbol", "whitespace"]
        }
    }
}

char_ngram_tokenizer 配置的意思:

  • type: "ngram":这是使用的分词器类型,叫 n-gram 分词器,主要目的是将文本切成很小的片段(n-gram),比如字符级、字母级、数字级切分。

  • min_gram: 1, max_gram: 1:表示最小和最大都是 1 个字符,所以这个 tokenizer 是逐字符切分,每个 token 就是文本的单个字符。

  • token_chars:限制哪些字符被切出来。

  • "letter"(字母)

  • "digit"(数字)

  • "punctuation"(标点符号)

  • "symbol"(符号)

  • "whitespace"(空格)

也就是说,任何字符基本都会被当作一个合法 token。

20. es会自动建索引

上线项目,先启动了项目,才去建索引。导致es插入数据的时候,还没有建索引,但是数据插入进去了,还建了索引,再去建索引怎么也建不上。因为 Elasticsearch 的默认行为是“动态映射”和“自动建索引”。

为什么 ES 会自动建索引?

因为,Elasticsearch 默认配置就是“只要有数据写入,就自动帮你建索引”,即便那个索引不存在。这是为了兼容性好、上手简单,但在生产环境里,这种默认行为是个大坑。如果你不管它,长期下来索引会乱七八糟,类型漂移、字段爆炸、磁盘爆满。

具体机制

  • 写入不存在的索引(比如 POST /not_exist_index/_doc)
  • ➡️ ES 自动新建一个同名索引。
  • ➡️ 自动生成 Mapping(默认推断字段类型,比如字符串/数字)。

21. :在 Kibana 上(用 DevTools 执行命令),可以通过动态更新集群设置,来禁止自动建索引!

自动建索引是出厂默认打开的:action.auto_create_index: true。

PUT _cluster/settings
{
  "persistent": {
    "action.auto_create_index": "false"
  }
}

22. maven SNAPSHOT

现实中,开发过程中会遇到这种情况:

Maven 的 SNAPSHOT 机制就允许你:自动获取最新的 SNAPSHOT 内容(如果有更新),省去手动改版本号的麻烦。

当你依赖一个 SNAPSHOT:Maven 会:

  • 去远程仓库查找是否有 更新的 SNAPSHOT(默认一天查一次,除非 -U 强制刷新)。
  • 如果有,就下载新的 SNAPSHOT JAR,替换本地缓存。

-SNAPSHOT 是 Maven 为支持“频繁迭代 + 自动更新”而设计的特殊版本标记,用于开发阶段的依赖,但永远不应该出现在生产环境中。

23. rocketmq拉取后消费者宕机了怎么办?

场景

处理方式

拉取机制

客户端定时主动拉,批量(默认32条)

线程不足

暂存内存队列,必要时暂停拉取

消费失败

自动重试,最终进入死信队列

宕机未消费

Broker 自动重投,消息不丢

RocketMQ 的消费是 “拉完未提交 = 未消费” 的模型,所以:

  • 只要 onMessage() 没执行成功(没有 ack 提交),这条消息 不会被认为成功消费。
  • 消费者宕机后,Broker 会将消息重新投递给同一个 Group 内的其它实例。
  • 如果没有其它实例在线,消息会一直积压在 Broker 端,直到恢复消费。

注意:RocketMQ 的消息是持久化在 Broker 磁盘上的,不怕宕机或网络抖动。