You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot错误消息存储优化:同内容限流与资源占用问询

问题解答

一、按内容维度限制单位时间内的错误消息写入次数

当前实现仅按用户维度限流,未区分错误内容,会导致同一重复错误被多次写入数据库,既冗余又增加DB压力。可以通过用户标识+错误内容+请求URL作为唯一键,结合本地缓存实现限流+UUID复用,完全匹配你“同一会话重复操作返回相同UUID错误消息”的需求,具体方案如下:

1. 核心逻辑

  • 用缓存存储复合键:{jwtToken}:{messageContent}:{url},对应值包含两个信息:当前时间窗口内的请求计数、已生成的错误UUID
  • 每次请求先检查缓存:
    • 缓存存在且计数未达上限:计数自增,直接返回已有的UUID对应的DTO,跳过DB写入
    • 缓存存在但计数达上限:返回null触发限流
    • 缓存不存在:写入DB生成新UUID,再将计数(初始1)和UUID存入缓存并设置过期时间

2. 代码实现示例(基于Caffeine本地缓存)

首先添加Caffeine依赖(Spring Boot可直接用starter):

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

修改后的Service类:

@Slf4j
@Service
@RequiredArgsConstructor
public class FrontendEventsService {

    private final ErrorMessageRepository messageRepository;
    private final JwtService jwtService;
    private final DateTimeProvider dateTimeProvider;

    @Value("${error.message.limit:10}")
    private Integer messagesLimit;
    @Value("${error.message.expire.minutes:1}")
    private Integer expireMinutes;

    // 本地缓存:键为用户+内容+URL,值为计数+UUID的封装对象
    private final LoadingCache<String, ErrorCacheEntry> errorCache = Caffeine.newBuilder()
            .expireAfterWrite(expireMinutes, TimeUnit.MINUTES)
            .maximumSize(10000) // 限制缓存条目数,防止内存溢出
            .build(key -> null); // 手动控制缓存写入,无需自动加载逻辑

    public ErrorMessageDto handleMessage(ErrorMessageDto message) {
        String jwtToken = StringJwtParser.getTokenShortcut(jwtService.getRawToken());
        String cacheKey = String.format("%s:%s:%s", jwtToken, message.getMessage(), message.getUrl());

        ErrorCacheEntry cacheEntry = errorCache.getIfPresent(cacheKey);
        if (cacheEntry != null) {
            if (cacheEntry.count >= messagesLimit) {
                log.warn("Error limiter triggered: key={}, message={}", cacheKey, message.getMessage());
                return null;
            }
            // 更新缓存计数,返回已有UUID的DTO
            errorCache.put(cacheKey, new ErrorCacheEntry(cacheEntry.count + 1, cacheEntry.uuid));
            ErrorMessageDto dto = new ErrorMessageDto();
            dto.setUuid(cacheEntry.uuid);
            dto.setMessage(message.getMessage());
            dto.setUrl(message.getUrl());
            return dto;
        }

        // 缓存不存在,写入数据库
        ErrorMessageEntity entity = ErrorMessageEntity.builder()
                .createdAt(dateTimeProvider.getCurrentDateTime())
                .uuid(UUID.randomUUID().toString())
                .tokenHash(jwtToken)
                .userLogin(jwtService.getUserLogin())
                .message(message.getMessage())
                .url(message.getUrl())
                .build();
        ErrorMessageEntity saved = messageRepository.save(entity);
        ErrorMessageDto dto = ErrorMessageMapper.toDto(saved);

        // 写入缓存,初始化计数为1
        errorCache.put(cacheKey, new ErrorCacheEntry(1, saved.getUuid()));
        return dto;
    }

    // 缓存值封装类
    private static class ErrorCacheEntry {
        int count;
        String uuid;

        public ErrorCacheEntry(int count, String uuid) {
            this.count = count;
            this.uuid = uuid;
        }
    }
}

3. 核心优势

  • 彻底避免同一用户的重复错误写入DB,大幅降低数据库压力
  • 自动复用UUID,完全满足重复操作返回相同错误消息的需求
  • 本地缓存性能远高于DB查询,降低接口响应延迟

二、本地缓存Map的生产环境资源消耗问题

只要合理配置参数,本地缓存(如Caffeine)的资源消耗完全可控:

  • 容量限制:通过maximumSize设置最大条目数(比如10000),即使有大量用户,缓存也不会无限膨胀
  • 自动过期:expireAfterWrite会自动清理过期缓存,释放内存
  • 内存占用极低:每个缓存条目仅包含字符串键和一个小对象(整数+UUID字符串),10000条缓存仅占用几十KB到几百KB内存
  • 若需分布式全局限流,可替换为Redis缓存,但本地缓存已能覆盖绝大多数单节点或低并发场景的需求

可通过监控缓存命中率、条目数等指标,动态调整maximumSize和过期时间参数,进一步优化资源占用。


内容的提问来源于stack exchange,提问作者haoz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.07 15:03:10