Java 后端上下文缓存:Redis + Token 策略减少 AI 调用
大家好!今天我们来聊聊如何利用 Java 后端技术,结合 Redis 和 Token 策略,构建一个上下文缓存系统,从而有效地减少对 AI 接口的调用次数。在 AI 应用日益普及的今天,降低 AI 服务的调用成本,提高系统性能和用户体验显得尤为重要。
1. 背景与挑战
在许多应用场景中,我们需要多次调用 AI 服务,例如:
- 对话机器人: 用户与机器人进行多轮对话,每一轮对话都需要调用 AI 引擎理解用户意图并生成回复。
- 内容生成: 根据用户提供的上下文,生成文章、代码等内容。
- 信息提取: 从用户提供的文档中提取关键信息。
频繁调用 AI 服务会带来以下问题:
- 成本高昂: AI 服务的调用通常按次数收费。
- 延迟增加: 每次调用 AI 服务都需要网络传输和计算,会增加响应时间。
- 服务压力大: 大量请求会给 AI 服务带来压力,可能导致服务不稳定。
为了解决这些问题,我们需要在后端构建一个上下文缓存系统,将 AI 服务的计算结果缓存起来,避免重复调用。
2. 核心思路:Redis + Token
我们的核心思路是利用 Redis 存储 AI 服务的上下文信息,并使用 Token 作为缓存的标识符。
- Redis: Redis 是一个高性能的键值存储数据库,非常适合存储缓存数据。
- Token: Token 是一个唯一的字符串,用于标识用户的会话或请求。我们可以使用 JWT (JSON Web Token) 生成 Token,并将其作为缓存的 key。
具体流程如下:
- 用户发起请求。
- 后端服务检查 Redis 中是否存在与该 Token 对应的缓存数据。
- 如果存在,则直接返回缓存数据,无需调用 AI 服务。
- 如果不存在,则调用 AI 服务获取结果,并将结果存储到 Redis 中,key 为 Token,value 为 AI 服务的返回结果。
- 将 Token 返回给客户端,客户端在后续请求中携带该 Token。
3. 技术选型与环境准备
- Java: 作为后端开发语言。
- Spring Boot: 快速构建 Java 应用框架。
- Redis: 缓存数据库。
- Jedis/Lettuce: Java Redis 客户端。
- JWT (JSON Web Token): 用于生成 Token。 例如:jjwt 库
- Jackson/Gson: JSON 处理库。
环境准备:
- 安装 JDK 8 或更高版本。
- 安装 Maven 或 Gradle。
- 安装 Redis 服务器。
4. 代码实现:Spring Boot + Redis + JWT
接下来,我们将使用 Spring Boot、Redis 和 JWT 实现上下文缓存系统。
4.1 项目初始化
使用 Spring Initializr 创建一个新的 Spring Boot 项目,添加以下依赖:
- Spring Web
- Spring Data Redis
- jjwt-api
- jjwt-impl
- jjwt-jackson
4.2 Redis 配置
在 application.properties 或 application.yml 文件中配置 Redis 连接信息:
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
4.3 JWT 工具类
创建一个 JWT 工具类,用于生成和解析 Token:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.Map;
@Component
public class JwtUtil {
private static final String SECRET = "your-secret-key"; // 替换为你的密钥
private static final long EXPIRATION_TIME = 3600000; // 1 hour
private Key key = Keys.hmacShaKeyFor(SECRET.getBytes());
public String generateToken(Map<String, Object> claims) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public Claims extractAllClaims(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
public String extractUsername(String token) {
return extractAllClaims(token).getSubject();
}
public Date extractExpiration(String token) {
return extractAllClaims(token).getExpiration();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public Boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
}
4.4 Redis 缓存服务
创建一个 Redis 缓存服务,用于存储和读取 AI 服务的返回结果:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisCacheService {
@Autowired
private StringRedisTemplate redisTemplate;
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}
public void set(String key, String value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public void delete(String key) {
redisTemplate.delete(key);
}
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
}
4.5 AI 服务模拟
为了方便演示,我们创建一个 AI 服务模拟类:
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class AIService {
public String process(String input) {
// 模拟 AI 服务的处理逻辑
try {
Thread.sleep(new Random().nextInt(1000)); // 模拟 AI 处理时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "AI Response: " + input.toUpperCase();
}
}
4.6 Controller 实现
创建一个 Controller,处理用户的请求,并利用 Redis 缓存和 JWT Token 减少 AI 调用:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class AIController {
@Autowired
private RedisCacheService redisCacheService;
@Autowired
private AIService aiService;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private ObjectMapper objectMapper; // 用于序列化和反序列化 JSON
@PostMapping("/ai/process")
public ResponseEntity<Map<String, Object>> process(@RequestBody Map<String, Object> request) throws JsonProcessingException {
String input = (String) request.get("input");
String token = (String) request.get("token");
Map<String, Object> response = new HashMap<>();
if (token != null && redisCacheService.hasKey(token)) {
// 从缓存中获取结果
String cachedResult = redisCacheService.get(token);
response.put("result", cachedResult);
response.put("message", "From Cache");
response.put("token", token);
} else {
// 调用 AI 服务
String aiResult = aiService.process(input);
// 生成新的 Token
Map<String, Object> claims = new HashMap<>();
claims.put("input", input);
String newToken = jwtUtil.generateToken(claims);
// 将结果存储到 Redis 中
redisCacheService.set(newToken, aiResult, 60, TimeUnit.MINUTES); // 缓存 60 分钟
response.put("result", aiResult);
response.put("message", "From AI Service");
response.put("token", newToken);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
5. 代码解释
- JWT 的使用:
JwtUtil类负责生成和解析 JWT Token。generateToken方法根据传入的claims(可以包含用户信息、请求参数等) 生成 Token。extractAllClaims方法从 Token 中提取所有 claims。 - Redis 缓存:
RedisCacheService类封装了 Redis 的操作。get方法根据 key 从 Redis 获取缓存数据。set方法将数据存储到 Redis,并设置过期时间。hasKey方法检查 Redis 中是否存在指定的 key。 - AI 服务模拟:
AIService类模拟 AI 服务的处理逻辑。process方法接收用户输入,并返回 AI 服务的响应。 - Controller:
AIController类处理用户的请求。process方法首先检查 Redis 中是否存在与 Token 对应的缓存数据。如果存在,则直接从缓存中获取结果并返回。如果不存在,则调用 AI 服务获取结果,并将结果存储到 Redis 中,然后生成新的 Token 并返回。
6. 测试
- 启动 Spring Boot 应用。
- 使用 Postman 或 curl 等工具发送 POST 请求到
/ai/process接口,请求体如下:
{
"input": "Hello, AI"
}
第一次请求时,会调用 AI 服务,并将结果缓存到 Redis 中,同时返回一个新的 Token。
{
"result": "AI Response: HELLO, AI",
"message": "From AI Service",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJpbnB1dCI6IkhlbGxvLCBBSSIsImlhdCI6MTY4ODY1ODQzMCwiZXhwIjoxNjg4NjYyMDMwfQ.tX2J-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
后续请求携带该 Token,请求体如下:
{
"input": "Hello, AI",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJpbnB1dCI6IkhlbGxvLCBBSSIsImlhdCI6MTY4ODY1ODQzMCwiZXhwIjoxNjg4NjYyMDMwfQ.tX2J-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
会直接从 Redis 缓存中获取结果,无需调用 AI 服务。
{
"result": "AI Response: HELLO, AI",
"message": "From Cache",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJpbnB1dCI6IkhlbGxvLCBBSSIsImlhdCI6MTY4ODY1ODQzMCwiZXhwIjoxNjg4NjYyMDMwfQ.tX2J-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
7. 优化与扩展
- 缓存失效策略: 可以根据实际情况调整缓存的过期时间,或者使用更复杂的缓存失效策略,例如 LRU (Least Recently Used)。
- 缓存预热: 可以在系统启动时,预先将一些常用的数据加载到缓存中。
- 分布式缓存: 如果系统规模较大,可以使用 Redis 集群或其他的分布式缓存解决方案。
- Token 续期: 可以实现 Token 续期机制,延长 Token 的有效期,避免频繁生成 Token。
- 更细粒度的缓存: 可以根据不同的请求参数,使用更细粒度的缓存策略。例如,如果只有部分参数发生变化,可以只缓存变化的部分。
- 异步更新缓存: 对于一些不重要的缓存,可以使用异步方式更新,避免阻塞主线程。
- 缓存监控: 可以添加缓存监控,实时了解缓存的使用情况,及时发现和解决问题。
8. 安全性考虑
- 密钥管理: 需要安全地存储和管理 JWT 的密钥,避免泄露。
- Token 验证: 在每次请求时,都需要验证 Token 的有效性,防止伪造或篡改。
- 防止重放攻击: 可以添加时间戳或 nonce 等机制,防止重放攻击。
- HTTPS: 使用 HTTPS 加密所有通信,保护 Token 的安全。
9. 总结:缓存策略的实用价值
通过结合 Redis 和 Token 策略,我们成功构建了一个上下文缓存系统,能够有效地减少对 AI 接口的调用次数,降低成本,提高性能,并提升用户体验。在实际应用中,需要根据具体的业务场景,选择合适的缓存策略和配置参数,才能达到最佳效果。 此外,安全性也是至关重要的,需要采取必要的措施,保护缓存系统的安全。