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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spring Boot SSE(Server-Sent Events)是一种服务器推送技术,用于实现服务器向客户端实时发送数据的功能。它基于HTTP协议,通过建立长连接,在服务器端有数据更新时,将数据主动推送给客户端。 在Spring Boot中使用SSE,可以通过创建一个控制器方法,返回类型为SseEmitter的对象。这个对象可以被用来发送事件和数据给连接的客户端。通过调用SseEmitter的send()方法,可以将数据推送给客户端。 以下是一个使用Spring Boot SSE的简单示例: ```java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; @RestController public class SSEController { @GetMapping("/sse") public SseEmitter sse() { SseEmitter emitter = new SseEmitter(); // 在后台线程中持续发送数据给客户端 new Thread(() -> { try { for (int i = 0; i < 10; i++) { emitter.send("Data " + i); Thread.sleep(1000); } emitter.complete(); // 完成发送 } catch (Exception e) { emitter.completeWithError(e); // 发送错误 } }).start(); return emitter; } } ``` 在上述示例中,当访问`/sse`路径时,将会创建一个SseEmitter对象并返回给客户端。后台线程通过循环发送数据给客户端,并最后调用`complete()`方法标识发送完成。 客户端可以使用JavaScript的EventSource对象来接收SSE事件,如下所示: ```javascript var eventSource = new EventSource('/sse'); eventSource.onmessage = function(event) { console.log("Received data: " + event.data); }; eventSource.onerror = function(event) { console.error("Error occurred: " + event); }; ``` 通过监听`onmessage`事件,客户端可以处理接收到的数据。`onerror`事件用于处理错误情况。 这就是使用Spring Boot SSE实现服务器推送的基本流程。通过这种方式,可以实现实时的数据更新和通知功能。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值