Browse Source

令牌桶限流-调用算法生题接口

master
luogw 2 months ago
parent
commit
d9c567c6c3
  1. 8
      pom.xml
  2. 24
      src/main/java/com/project/interaction/domain/service/impl/PostToGenerateQuestionDomainServiceImpl.java
  3. 53
      src/main/java/com/project/interaction/utils/NotifyUtil.java
  4. 8
      src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java
  5. 27
      src/main/java/com/project/question/config/QuestionGenerationConfig.java
  6. 36
      src/main/java/com/project/question/domain/service/impl/QuestionGenerationRateLimiter.java
  7. 6
      src/main/resources/application-dev.yml
  8. 6
      src/main/resources/application.yml

8
pom.xml

@ -140,6 +140,14 @@
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>5.8.3</version> <version>5.8.3</version>
</dependency> </dependency>
<!-- Guava (用于限流 RateLimiter) -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<!-- Spring Security (新增) --> <!-- Spring Security (新增) -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

24
src/main/java/com/project/interaction/domain/service/impl/PostToGenerateQuestionDomainServiceImpl.java

@ -1,12 +1,13 @@
package com.project.interaction.domain.service.impl; package com.project.interaction.domain.service.impl;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import com.project.information.domain.service.KnowledgePointBaseService;
import com.project.interaction.domain.dto.GenerateQuestionRequestDTO; import com.project.interaction.domain.dto.GenerateQuestionRequestDTO;
import com.project.interaction.domain.dto.GenerateQuestionResponseDTO; import com.project.interaction.domain.dto.GenerateQuestionResponseDTO;
import com.project.interaction.domain.service.PostToGenerateQuestionDomainService; import com.project.interaction.domain.service.PostToGenerateQuestionDomainService;
import com.project.interaction.utils.NotifyUtil;
import com.project.question.domain.dto.QuestionDTO; import com.project.question.domain.dto.QuestionDTO;
import com.project.question.domain.dto.TaskKnowledgePointDTO; import com.project.question.domain.dto.TaskKnowledgePointDTO;
import com.project.question.domain.service.impl.QuestionGenerationRateLimiter;
import com.project.question.domain.service.SaveQuestionDomainService; import com.project.question.domain.service.SaveQuestionDomainService;
import com.project.task.domain.enums.QuestionTypeEnum; import com.project.task.domain.enums.QuestionTypeEnum;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -37,6 +38,12 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu
@Autowired @Autowired
private SaveQuestionDomainService saveQuestionDomainService; private SaveQuestionDomainService saveQuestionDomainService;
@Autowired
private QuestionGenerationRateLimiter rateLimiter;
@Autowired
private NotifyUtil notifyUtil;
private final String[] optionListStr = {"A", "B", "C", "D"}; private final String[] optionListStr = {"A", "B", "C", "D"};
private final String[] trueFalseListStr = {"A", "B"}; private final String[] trueFalseListStr = {"A", "B"};
@ -59,13 +66,20 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu
taskId, clusterId, questionType.getDescription(), num); taskId, clusterId, questionType.getDescription(), num);
try { try {
// 应用限流:获取令牌,超过限流速率则等待
long waitStart = System.currentTimeMillis();
rateLimiter.acquire();
long waitTime = System.currentTimeMillis() - waitStart;
if (waitTime > 100) {
log.info(">>> [题目生成] 因限流等待了 {} ms, TaskId: {}", waitTime, taskId);
}
//尝试调用算法服务生成题目 //尝试调用算法服务生成题目
GenerateQuestionResponseDTO response = callAlgorithmService(dtoList, questionType, num); GenerateQuestionResponseDTO response = callAlgorithmService(dtoList, questionType, num);
if (response != null && StringUtils.isNotBlank(response.getMessage()) && response.getMessage().contains("success")) { if (response != null && StringUtils.isNotBlank(response.getMessage()) && response.getMessage().contains("success")) {
log.info(">>> [题目生成] 算法服务接受任务成功, 任务: {}, 状态: {}, 信息:{}", log.info(">>> [题目生成] 算法服务接受任务成功, 任务: {}, 状态: {}, 信息:{}",
response.getTaskId(), response.getStatus(),response.getMessage()); response.getTaskId(), response.getStatus(),response.getMessage());
return;
} }
} catch (Exception e) { } catch (Exception e) {
@ -74,8 +88,10 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu
} }
//熔断降级:算法服务失败时使用硬编码生成默认题目 //熔断降级:算法服务失败时使用硬编码生成默认题目
log.info(">>> [题目生成] 启用熔断降级,生成默认题目"); // log.info(">>> [题目生成] 启用熔断降级,生成默认题目");
generateFallbackQuestions(dtoList, questionType, num); // generateFallbackQuestions(dtoList, questionType, num);
//发送预警通知
notifyUtil.notify(taskId,clusterId);
} }
/** /**

53
src/main/java/com/project/interaction/utils/NotifyUtil.java

@ -0,0 +1,53 @@
package com.project.interaction.utils;
import com.github.tingyugetc520.ali.dingtalk.api.DtService;
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMessage;
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMsgSendResult;
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Slf4j
@Component
public class NotifyUtil {
@Autowired
private DtService dtService;
public void notify(Long taskId,Long clusterId) {
// "283712335120874575", "01231011386731976125",
ArrayList<String> userIDList = Lists.newArrayList( "283712335120874575", "01231011386731976125","590204300932000212");
try {
String markdown = String.format("### 【告警】算法生题API调用失败通知 \n\n") +
String.format("## 核心信息\n") +
String.format("- 考试任务ID:%d\n", taskId) +
String.format("- 知识点簇ID:%d\n\n", clusterId) +
String.format("## 异常说明\n") +
String.format("算法服务生成题目时调用失败,可能导致考试题目不完整或无法正常作答,请尽快排查以下问题:\n") +
String.format("- 算法服务是否正常运行\n");
DtCorpConversationMessage corpConversationMessage = DtCorpConversationMessage.builder()
.agentId(dtService.getDtConfigStorage().getAgentId())
.userIds(Lists.newArrayList(userIDList))
.msg(DtMessage.MARKDOWN()
.content("算法生题API调用失败通知")
.text(markdown)
.build())
.build();
DtCorpConversationMsgSendResult result = dtService.getCorpConversationMsgService().send(corpConversationMessage);
if (result.getErrCode() == 0) {
log.info(">>> [算法生题API调用失败通知] 成功发送通知 ,考试任务ID:{} ,知识点簇ID:{}", taskId, clusterId);
} else {
log.error(">>> [算法生题API调用失败通知] 发送失败.考试任务ID:{} ,知识点簇ID:{}, 错误码: {}, 错误信息: {}", taskId, clusterId,
result.getErrCode(), result.getErrMsg());
}
} catch (Exception e) {
log.error(">>> [算法生题API调用失败通知] 钉钉接口调用异常, 考试任务ID:{} ,知识点簇ID:{}, 原因: {}",taskId, clusterId, e.getMessage());
}
}
}

8
src/main/java/com/project/milvus/domain/service/impl/CheckMilvusDomainServiceImpl.java

@ -43,9 +43,8 @@ public class CheckMilvusDomainServiceImpl implements CheckMilvusDomainService {
} }
List<Long> pointIdsList = title.getTaskKpIdsList(); List<Long> pointIdsList = title.getTaskKpIdsList();
List<Float> titleVectorList = title.getTitleVectorList();
if (CollectionUtil.isEmpty(pointIdsList) || CollectionUtil.isEmpty(titleVectorList)) { if (CollectionUtil.isEmpty(pointIdsList)) {
throw new MissingParameterException("请求参数缺失或格式错误"); throw new MissingParameterException("请求参数缺失或格式错误");
} }
@ -57,11 +56,6 @@ public class CheckMilvusDomainServiceImpl implements CheckMilvusDomainService {
throw new MissingParameterException("考试任务ID不能为空"); throw new MissingParameterException("考试任务ID不能为空");
} }
if(titleVectorList.size() != 1024){
throw new MissingParameterException("向量格式错误");
}
// 校验 QuestionDetailDTO 及其内部参数 // 校验 QuestionDetailDTO 及其内部参数
QuestionDTO.QuestionDetailDTO detailDTO = title.QuestionDetailDTO; QuestionDTO.QuestionDetailDTO detailDTO = title.QuestionDetailDTO;
if (detailDTO == null) { if (detailDTO == null) {

27
src/main/java/com/project/question/config/QuestionGenerationConfig.java

@ -0,0 +1,27 @@
package com.project.question.config;
import com.project.question.domain.service.impl.QuestionGenerationRateLimiter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 题目生成相关配置
*/
@Configuration
public class QuestionGenerationConfig {
@Value("${question.generation.rate-limit:5}")
private double rateLimit;
/**
* 创建题目生成限流器
*/
@Bean
@ConditionalOnMissingBean(QuestionGenerationRateLimiter.class)
public QuestionGenerationRateLimiter questionGenerationRateLimiter() {
return new QuestionGenerationRateLimiter(this.rateLimit);
}
}

36
src/main/java/com/project/question/domain/service/impl/QuestionGenerationRateLimiter.java

@ -0,0 +1,36 @@
package com.project.question.domain.service.impl;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 题目生成API限流器
* 用于控制调用算法服务的请求速率防止一大批请求同时涌入导致服务压力过大
*/
@Component
@Slf4j
public class QuestionGenerationRateLimiter {
/**
* RateLimiter 使用令牌桶算法
* 每秒允许固定数量的请求通过
*/
private final RateLimiter rateLimiter;
public QuestionGenerationRateLimiter(@Value("${question.generation.rate-limit:5}") double permitsPerSecond) {
this.rateLimiter = RateLimiter.create(permitsPerSecond);
log.info(">>> [限流器初始化] 题目生成限流速率设置为: {}/秒", permitsPerSecond);
}
/**
* 获取令牌如果令牌不足则阻塞等待
* 调用此方法的线程会被阻塞直到获取到令牌为止
*/
public void acquire() {
rateLimiter.acquire();
}
}

6
src/main/resources/application-dev.yml

@ -74,3 +74,9 @@ algo:
baseUrl: / baseUrl: /
generateQuestionUrl: /v1/generate/questions_from_cluster generateQuestionUrl: /v1/generate/questions_from_cluster
apiUrl: http://8.129.84.155:8000 apiUrl: http://8.129.84.155:8000
# 题目生成相关配置
question:
generation:
# 限流速率:每秒允许的API请求数
rate-limit: 20

6
src/main/resources/application.yml

@ -79,3 +79,9 @@ algo:
apiUrl: http://8.129.84.155:8000 apiUrl: http://8.129.84.155:8000
jwt: jwt:
secret: "my-very-fixed-and-secure-secret-key-1234567890" secret: "my-very-fixed-and-secure-secret-key-1234567890"
# 题目生成相关配置
question:
generation:
# 限流速率:每秒允许的API请求数
rate-limit: 20

Loading…
Cancel
Save