开云集致 Java开发 实习 一面

实习中可量化成果

1. Token 消耗优化

我主要做了几件事:

  • 重构 Prompt 模板
  • 做上下文裁剪
  • 减少重复 System Prompt
  • 对历史消息做窗口压缩
  • 对固定业务规则做结构化参数替代
  • 增加缓存,避免重复生成

通过查看日志和token usage 监控得到token消耗降低了30%-40%

2.修复调用LLM失败之后无限入RabbitMQ导致token消耗问题,增加了失败重试次数的限制

"调用链路追踪 + 日志统计 + MQ 重试次数"

同一条requestid不断地入队,然后被消耗

这些具体是怎么实现地呢?

1. 重构 Prompt 模板

核心目标:

减少无效 token + 提高 Prompt 结构稳定性

传统低级写法

复制代码
String prompt = """
你是一个企业知识库AI助手,
你需要遵守以下规则:

1. 不允许胡编乱造
2. 回答必须专业
3. 使用中文
4. 输出必须包含总结
5. 如果不知道要拒答

用户问题:
%s

参考知识:
%s
""";

我的优化方式

(1)拆分固定规则

做成固定模板

复制代码
SYSTEM_TEMPLATE
USER_TEMPLATE
OUTPUT_TEMPLATE

最终组合

复制代码
String finalPrompt =
    SYSTEM_TEMPLATE
    + USER_TEMPLATE.formatted(question, context)
    + OUTPUT_TEMPLATE;

Prompt 是一大段字符串,很多身份说明、输出要求、业务规则都混在一起,而且多个场景里有重复描述。后面我把它拆成 System 模板、User 模板、Output 模板三部分。"

(2)用户Prompt 参数化

传统写法的问题

比如:

复制代码
请帮我分析用户张三在北京地区2026年的订单情况

改成结构化

复制代码
{
  "rawQuestion": "请帮我分析用户张三在北京地区2026年的订单情况,并重点关注退款率异常的月份",
  "task": "订单分析",
  "userName": "张三",
  "region": "北京",
  "year": "2026",
  "focus": "退款率异常的月份"
}

可以使用NLP进行这些参数提取

LLM会更好地理解用户问题,减少对问题拆分理解需要的token

2.上下文裁剪

我的优化方式

(1)Window Memory

只保留最近 N 轮。

java 复制代码
ChatMemory chatMemory = MessageWindowChatMemory.builder()
                .maxMessages(10) // 只保留最近 10 条 message,不是 10 轮
                .build();

        this.chatClient = builder
                .defaultAdvisors(
                        MessageChatMemoryAdvisor.builder(chatMemory).build()
                )
                .build();

调用时通过 conversationId 区分会话:

java 复制代码
chatClient.prompt()
        .advisors(a -> a.param(
                AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY,
                conversationId
        ))
        .user(question)
        .call()
        .content();

因为不会把整个会话历史都塞给模型,只保留最近几条和当前问题最相关的消息。

例如原来有 50 条历史,现在只带 10 条,历史 token 会明显减少。

(2)按 token 数裁剪:超过阈值就删旧内容

只按消息条数不够,因为一条消息可能很短,也可能很长。所以还需要设置最大 token 阈值

java 复制代码
List<Message> history = chatMemory.get(conversationId);

// 1. Window Memory 已经保证最多 10 条
// 2. 再做 token 裁剪
while (countTokens(history) > maxHistoryTokens) {
    history.remove(0); // 删除最旧消息
}

// 3. 组装最终上下文
String finalPrompt = buildPrompt(
        systemPrompt,
        summary,
        history,
        currentQuestion,
        ragContext
);

(3)只保留有效消息:过滤低价值内容

java 复制代码
private boolean isUsefulMessage(String content) {
    if (content == null || content.isBlank()) {
        return false;
    }

    String text = content.trim();

    Set<String> useless = Set.of("好的", "嗯", "嗯嗯", "谢谢", "收到", "继续");

    if (useless.contains(text)) {
        return false;
    }

    return text.length() >= 4;
}

(4)历史上下文压缩:旧历史转成摘要或事实

实现方式

当历史超过阈值时,不直接丢弃旧历史,而是把旧历史压缩成更短的信息

Redis 可以分两部分存:

复制代码
chat:memory:{conversationId}   // 最近窗口消息
chat:summary:{conversationId}  // 历史摘要/关键事实

触发条件:

复制代码
历史消息超过 20 条
或者 history token 超过 3000

压缩流程:

复制代码
读取旧历史
→ 提取关键事实/当前目标/用户约束
→ 生成 summary
→ 写入 Redis
→ 裁剪原始历史

3.增加缓存,避免重复生成

(1)问题简单规则归一化

(2)构建缓存 Key

(3)Redis 缓存答案

(4)命中直接返回

线上的OOM问题你是怎么定位的?具体是什么问题?

1.首先我看了服务日志和监控

java 复制代码
java.lang.OutOfMemoryError: Java heap space

发现是堆空间满了

2.用 visualvm看JVM 内存变化

(1)Heap 内存曲线

发现:

复制代码
内存不断上涨
Full GC 后下降不明显

(2)GC 情况

发现:

  • Full GC 频率越来越高
  • GC 时间变长

说明:

老年代压力比较大。

3.分析 GC 日志

(1)Full GC 是否频繁

(2)GC 后堆内存是否下降

4. 导出 Heap Dump

5. 使用 MAT 分析 Heap Dump(JVM 某一时刻的"内存快照"

重点去观察

(1)谁占内存最多

(2)删除这个对象后能释放多少内存

(3)GC Roots

为什么这些对象没有被 GC 回收


常见的内存泄露原因

1.集合类未清理(最常见)

List / Map / Set 从不 remove

2.ThreadLocal 未 remove

java 复制代码
ThreadLocal<User> local = new ThreadLocal<>();

local.set(user);

3.连接资源未关闭

  • IO
  • Socket
  • JDBC

4.静态变量持有对象

java 复制代码
static Map<String,Object> cache

静态变量生命周期和 JVM 一样长

Threadlocal是什么,有什么用,底层原理是什么(面试热门)

ThreadLocal 是什么

"线程自己的局部变量"

作用:

给每个线程单独保存一份变量副本

1. 用户上下文传递

2. TraceId / 日志链路细节点

底层原理

Thread

内部有 ThreadLocalMap

关系图

复制代码
Thread
  ↓
ThreadLocalMap
  ↓
Entry(ThreadLocal -> value)

电商订单如何保证幂等性?

最经典方案:唯一业务号

前端防重复

按钮点击后置灰

requestId 幂等

下单时:

复制代码
userId + requestId

Redis setnx

java 复制代码
Boolean success = redisTemplate.opsForValue()
        .setIfAbsent(
            "order:req:" + requestId,
            "1",
            Duration.ofMinutes(10)
        );

if (!success) {
    return "重复下单";
}

支付如何保证幂等

典型方案:订单状态机

例如:

复制代码
INIT
→ PAYING
→ PAID

回调时:

复制代码
if(order.status == PAID){
    return;
}

只有:

复制代码
未支付

才能:

复制代码
更新为已支付

库存怎么保证幂等?

MQ:

复制代码
重复扣库存

会:

复制代码
库存负数

扣减流水表

例如:

复制代码
stock_deduct_log

记录:

复制代码
orderId
skuId

扣之前:

先查:

复制代码
是否已经扣过
相关推荐
求知也求真佳1 小时前
S07---S11 | 系统加固闭环总结:让你的 AI Agent 从 “能跑” 到 “稳跑、安全跑、长期跑”
开发语言·agent
小陈工2 小时前
Python异步编程进阶:asyncio高级模式与性能调优
开发语言·前端·数据库·人工智能·python·flask·numpy
阿旭超级学得完2 小时前
C++11(初始化)
java·开发语言·数据结构·c++·算法
是有头发的程序猿2 小时前
竞品店铺拆解:1688店铺首页装修数据API Python实战教程
开发语言·python
一只大袋鼠2 小时前
SpringMVC全局异常处理
java·开发语言·springmvc·javaweb
多加点辣也没关系2 小时前
设计模式-抽象工厂模式
java·设计模式·抽象工厂模式
rit84324992 小时前
基于 MATLAB 的坐标变换程序
开发语言·matlab
不知名的老吴2 小时前
C++中emplace函数的不适场景总结(一)
java·开发语言·c++
LJianK12 小时前
线程安全、线程同步、竞态条件
java·开发语言