用友金融二面:SM2 与 SM4、消息可靠消费与索引失效
- 轮次
- 二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍
- 你用 SM2 对签名进行加密,为什么采用 SM2?和 SM4 有什么区别?除了刚说的还有别的原因吗?
- 全日志追踪方案怎么设计的?讲讲
- 你平常使用什么 AI 工具?有没有用 AI 工具优化过项目?
- 你是怎么做异步处理的?
- 如何保证消息的持久化,能被正确消费且不重复消费?
- 讲一下执行计划有哪些字段
- 讲一下索引失效的情况
- 你实习项目的线程池参数配置是什么?拒绝策略是什么?
- 对 JVM 调优的指令知道哪些?
《参考解析》
SM2 与 SM4 的根本区别是「非对称 vs 对称」,用途完全不同。 SM2 是基于椭圆曲线(sm2p256v1)的非对称算法,包含数字签名、密钥交换和公钥加密三部分,适合「签名验签」和「协商密钥」,特点是私钥不参与传输、能提供不可否认性(谁签的可以验证),性能上比 RSA 快但仍是公钥运算量级;SM4 是对称分组密码(128 位分组与密钥,32 轮),加解密速度快几个数量级,适合大批量数据加密。所以「用 SM2 对签名加密」这个说法本身就不严谨:签名不是「加密」,签名是用私钥对摘要运算得到签名值,公钥验签,目的不是保密而是防篡改与身份认证;如果还要求签名值本身保密,才需要再叠加一层对称加密。工程上的标准组合是混合加密:用 SM2 交换/加密一个临时对称密钥,再用 SM4 加密实际报文,并用 SM2 对报文摘要签名——两套算法各司其职。选国密的额外原因(面试官追问「还有别的原因吗」想听的):合规要求(金融、政务、等保与密评要求使用国密)、自主可控与供应链安全、以及国产密码算法的政策推动;相比国际算法(RSA/ECDSA/AES),SM2 在相同安全强度下密钥更短(256 位 ≈ RSA 3072 位)。
全链路日志追踪的核心是「一个请求一个 traceId,贯穿所有环节」。 设计要点:① 生成与透传:在入口(网关/过滤器/拦截器)为每个请求生成全局唯一 traceId(必要时加 spanId 表示调用层级),通过 MDC(ThreadLocal)绑定到当前线程,并放进响应头与下游调用的 header(HTTP 的 traceparent、RPC 的 attachment、MQ 的消息属性);② 跨线程与跨进程:线程池提交任务时要手动把 MDC 上下文传进去(否则子线程丢 traceId),异步与 MQ 消费端要从消息头恢复上下文;③ 采集与检索:日志按统一 JSON 格式落盘(带 traceId、时间、级别、服务名、耗时、关键业务字段),由 Filebeat/Fluentd 采集进 ES 或 Loki,按 traceId 一键串联;④ 采样与成本:高 QPS 下全量打点成本高,通常按错误率/慢请求全采样 + 正常请求低比例采样;⑤ 规范:明确哪些字段必须打(出入口、外部调用、异常堆栈)、禁止打敏感信息(手机号、身份证、密码、银行卡、token 要脱敏)。可以顺带把 traceId 与指标、链路追踪(OpenTelemetry/Jaeger)串起来讲,形成「日志查细节、指标看趋势、链路看瓶颈」的三件套。
消息「持久化 + 不重复消费」要拆成两件事。 持久化与不丢失:生产者 acks=all + 开幂等生产者;Broker 多副本(replication.factor>=3、min.insync.replicas>=2、禁 unclean 选举)并理解刷盘策略与页缓存的取舍;消费者先处理业务再提交 offset(关闭自动提交),崩溃后最多重复一次。不重复消费:MQ 一般只保证 at-least-once,真正的不重复只能靠消费端幂等——用业务唯一键(订单号/消息 ID)建表唯一索引或 Redis SETNX 去重,重复消息直接返回成功而不重复执行;进阶方案是用事务消息/幂等表把「消费」和「业务写入」放进同一个本地事务,或者把下游操作设计成天然幂等(状态机迁移 + 条件更新)。还要提顺序消费:同一业务键的消息路由到同一分区,消费端单线程处理该分区,否则并发消费会打乱顺序。异步处理的答法要包含:异步的载体(线程池、MQ、CompletableFuture、响应式)、任务的可观测(任务表 + 状态机 + 重试与死信)、失败补偿(重试上限 + 告警 + 人工介入入口)、以及不要用异步掩盖同步问题(该同步返回结果的不能异步)。
执行计划与索引失效。 EXPLAIN 关注:id(执行顺序)、select_type(SIMPLE/PRIMARY/SUBQUERY/DERIVED/UNION)、table、partitions、type(最重要:system > const > eq_ref > ref > range > index > ALL)、possible_keys 与 key(候选 vs 实际选用)、key_len(用于判断联合索引用了几列)、ref、rows(预估扫描行数)、filtered(过滤后剩余比例)、Extra(Using index 是覆盖索引,Using where 表示需回表后再过滤,Using filesort / Using temporary 是性能警示,Using index condition 是索引下推 ICP)。索引失效的典型场景:在索引列上做函数或表达式运算、隐式类型转换(字符串列传数字)、联合索引不满足最左前缀、like '%x' 前导模糊、or 两侧有一个没索引、范围查询之后的列无法用于排序/过滤、!=/not in 选择性差时优化器放弃索引、以及统计信息过期导致选错索引(可 ANALYZE TABLE)。
线程池参数与 JVM 调优指令。 线程池要能背出自己项目里的真实配置并解释依据:核心/最大线程数、队列类型与容量、空闲时间、线程命名(便于排查)、拒绝策略(自定义:记录指标 + 降级返回,避免 Abort 抛异常打挂上游)。JVM 调优常用指令:jps -l 看进程、jstat -gcutil <pid> 1000 看 GC 频率与各代占用、jmap -heap <pid> 看堆配置与分区、jmap -dump:format=b,file=x.hprof <pid> 拿堆快照(或用 jcmd <pid> GC.heap_dump)、jmap -histo:live 看对象直方图、jstack <pid> 看线程栈(定位死锁与 CPU 热点线程)、jinfo -flags <pid> 看启动参数、jcmd 是新一代多合一入口;配合 top -H -p 找线程、arthas 在线诊断。调优的顺序永远是「先观测再改参数」,并且要说明「减少对象创建、修复泄漏」通常比调 GC 参数有效。