在 Spring Boot 应用中使用 Server-Sent Events (SSE) 实现实时消息推送,在单一实例下实现相对简单。然而,在集群环境下,由于每个应用实例都有其独立的内存空间,所以直接使用本地存储(如 List 或 Map)来维护订阅者信息将不再有效。为了在集群环境中实现 SSE 消息推送,我们需要一种共享状态的机制
- 使用分布式缓存(如 Redis, Memcached)
- 使用消息队列(如 RabbitMQ, Kafka)
- 使用数据库(虽然效率较低,但在某些情况下可能是可行的)
使用 Redis 作为共享状态的示例,来说明如何在 Spring Boot 集群环境中实现 SSE 消息推送:
步骤 1: 添加 Redis 依赖
在 pom.xml
或 build.gradle
中添加 Spring Data Redis 的依赖:
<!-- For Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
// For Gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
步骤 2: 配置 Redis
在 application.properties
或 application.yml
中配置 Redis 的连接信息:
spring.redis.host=localhost
spring.redis.port=6379
步骤 3: 创建 Redis 存储订阅者信息的逻辑
你需要创建一个服务类,用来管理 Redis 中的订阅者信息。这里我们使用 Redis 的 Set 结构来存储订阅者的 ID,并使用 Hash 结构来存储每个订阅者的具体信息。
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class SubscriptionService {
private final RedisTemplate<String, String> redisTemplate;
public SubscriptionService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void addSubscriber(String subscriberId) {
redisTemplate.opsForSet().add("subscribers", subscriberId);
// Add more logic to store subscriber details if needed
}
public void removeSubscriber(String subscriberId) {
redisTemplate.opsForSet().remove("subscribers", subscriberId);
}
public Set<String> getSubscribers() {
return redisTemplate.opsForSet().members("subscribers");
}
}
步骤 4: 创建 SSE 控制器
在 SSE 控制器中,使用上述的 SubscriptionService
来管理订阅者。当用户订阅时,将其 ID 添加到 Redis 中;当用户取消订阅时,从 Redis 中移除。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.concurrent.CopyOnWriteArrayList;
@RestController
public class SseController {
private final SubscriptionService subscriptionService;
private final CopyOnWriteArrayList<SseEmitter> emitters = new CopyOnWriteArrayList<>();
@Autowired
public SseController(SubscriptionService subscriptionService) {
this.subscriptionService = subscriptionService;
}
@GetMapping("/subscribe")
public SseEmitter subscribe() {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
String subscriberId = UUID.randomUUID().toString(); // Generate a unique ID
subscriptionService.addSubscriber(subscriberId);
emitters.add(emitter);
emitter.onCompletion(() -> {
subscriptionService.removeSubscriber(subscriberId);
emitters.remove(emitter);
});
// Send initial data immediately after subscription
emitter.send("Welcome to the SSE feed!");
return emitter;
}
// Implement your sendToAll method using Redis to notify all instances
}
步骤 5: 实现全局的消息推送
为了在所有实例间同步消息,你可以使用 Redis 的 Pub/Sub 功能或者使用 Redisson 的 RAtomicsLong 类型来触发消息推送。每当你想要发送消息时,可以发布一个消息到 Redis 的频道上,所有监听该频道的实例都会收到这个消息并推送给各自的订阅者。
这一步可能涉及到较为复杂的代码实现,因为你需要在每个实例上设置 Redis 的监听器,并在收到消息时执行推送操作。
步骤 6: 测试
最后,使用前端的 EventSource 对象来测试 SSE 的实时推送功能是否正常工作。
以上在 Spring Boot 集群环境下使用 Redis 实现多客户端的 SSE 实时消息推送的基本流程。实际部署时,你可能还需要考虑异常处理、性能调优以及安全性等问题。