JAVA 后端如何做上下文缓存?Redis + Token 策略减少 AI 调用

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。

具体流程如下:

  1. 用户发起请求。
  2. 后端服务检查 Redis 中是否存在与该 Token 对应的缓存数据。
    • 如果存在,则直接返回缓存数据,无需调用 AI 服务。
    • 如果不存在,则调用 AI 服务获取结果,并将结果存储到 Redis 中,key 为 Token,value 为 AI 服务的返回结果。
  3. 将 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.propertiesapplication.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. 测试

  1. 启动 Spring Boot 应用。
  2. 使用 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 接口的调用次数,降低成本,提高性能,并提升用户体验。在实际应用中,需要根据具体的业务场景,选择合适的缓存策略和配置参数,才能达到最佳效果。 此外,安全性也是至关重要的,需要采取必要的措施,保护缓存系统的安全。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注