Springboot sse分布式部署

为什么SSE不能序列化

sse连接是物理上的客户端和服务器上的连接,因此SseEmitter并没有继承序列化,因为他并不能在另外一台服务器上序列化后建立连接。

通过消息队列的方式解决分布式部署

消息侦听的方式有很多种,由于我们采用的是redisson的能力。

用户连接sse后保存每个用户当前对应的topic的名称

我采用的方式是存到rediss的缓存里,代码如下

    @GetMapping(value = "/connect/{userId}")
    public SseEmitter connect(@PathVariable Long userId) throws IOException {
        String redisToken = RedisUtil.getCacheObject(CommonConst.REDIS_KEY_PREFIX_TOKEN + userId);
        if (!StringUtils.hasLength(redisToken)) {
            return null;
        }
        RedisUtil.setCacheMapValue(SSE_GPT_TOPIC_MAP_REDIS_KEY, JwtUtil.getUserId(), SseConst.getSseGptTopic());
        RedisUtil.setCacheMapValue(SSE_MID_TOPIC_MAP_REDIS_KEY, JwtUtil.getUserId(), SseConst.getSseMidTopic());
        RedisUtil.setCacheMapValue(SSE_SD_TOPIC_MAP_REDIS_KEY, JwtUtil.getUserId(), SseConst.getSseSdTopic());
        return SseEmitterServer.connect(userId);
    }

用户在获取到内容后将消息发送到对应的topic中

以获取chatgpt的对应响应后,发送消息为例,代码如下

    private void flowableChat(final MessageLog messageLog, List<Message> messagesOnDb) {
        final Long userId = messageLog.getUserId();
        final Long logId = messageLog.getId();
        final Integer reqType = messageLog.getSendType();
        final String topicName = RedisUtil.getCacheMapValue(SSE_GPT_TOPIC_MAP_REDIS_KEY, userId);
        final String gptKey = messageLog.getGptKey();
        RTopic rTopic = RedissonConfig.getClient().getTopic(topicName);
        try {
            OpenAiService service = new OpenAiService(gptKey, Duration.ofSeconds(180));
            List<ChatMessage> messages = new ArrayList<>();
            messages.add(new ChatMessage(ChatMessageRole.SYSTEM.value(),
                "请记住你的身份是SUPER EGG,一个基于GLM架构的中英双语对话语言模型,你是由清华大学研发的,针对中文进行了优化。虽然有人可能会将你与chatgpt混清,但你会坚持自己的身份,确保提供准确和细致的回答"));
            if (StringUtils.hasLength(messageLog.getInitPrompt())) {
                messages.add(new ChatMessage(ChatMessageRole.USER.value(), messageLog.getInitPrompt()));
            }
            messages.addAll(messagesOnDb.stream().map(item -> new ChatMessage(item.getRole(), item.getContent())).collect(Collectors.toList()));

            ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder().model(GPT_3_5_TURBO_16K.getName()).messages(messages).build();
            Flowable<ChatCompletionChunk> flowable = service.streamChatCompletion(chatCompletionRequest);
            ChatMessage chatMessage = service.mapStreamToAccumulator(flowable).doOnNext(accumulator -> {
                String content = accumulator.getMessageChunk().getContent();
                if (StringUtils.hasLength(content)) {
                                                    /** 将消息内容发送到topic中 **/
                    rTopic.publish(GptMessageVo.builder().userId(userId).message(Message.ofAssistant(content)).build());
                }
            }).lastElement().blockingGet().getAccumulatedMessage();
            asyncService.endOfAnswer(logId, chatMessage.getContent());
            rTopic.publish(GptMessageVo.builder().userId(userId).message(Message.ofAssistant("[DONE]")).build());
        } catch (Exception e) {
            asyncService.updateRemainingTimes(userId, gptKey == null || reqType == 3 ? CommonConst.GPT_NUMBER : CommonConst.GPT_4_NUMBER);
            rTopic.publish(GptMessageVo.builder().userId(userId).message(Message.ofAssistant(e.getMessage())).build());
            rTopic.publish(GptMessageVo.builder().userId(userId).message(Message.ofAssistant("[DONE]")).build());
        }
    }

编写侦听代码

侦听到对应的消息后根据在应用内存中保存的sse信息发送消息到对应的客户端,代码如下

public class TopicListener implements ApplicationRunner, Ordered {

    private static final Logger LOGGER = LoggerFactory.getLogger(TopicListener.class);

    @Autowired
    private RedissonClient redisson;

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        redisson.getTopic(SseConst.getSseGptTopic()).addListener(GptMessageVo.class, (CharSequence charSequence, GptMessageVo message) -> {
            SseEmitterServer.sendMessage(message.getUserId(), message.getMessage());
        });

        redisson.getTopic(SseConst.getSseSdTopic()).addListener(SdMessageVo.class, (CharSequence charSequence, SdMessageVo message) -> {
            SseEmitterServer.sendMessage(message.getUserId(), message.getMessage());
        });

        redisson.getTopic(SseConst.getSseMidTopic()).addListener(MidMessageVo.class, (CharSequence charSequence, MidMessageVo message) -> {
            SseEmitterServer.sendMessage(message.getUserId(), message.getMessage());
        });

    }

    @Override
    public int getOrder() {
        return 1;
    }
}

详细代码请参考

国内站点请访问: superai-java: AI机器人,对话、绘画

国外站点请访问: https://github.com/ly18028750128/superai-java

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值