redistemplate使用_Spring Boot使用Server-Sent Events

fe8a9814e608561bba648e64ed8ab996.png

实时性获取数据的几个方案

对于某些需要实时更新的数据(例如Facebook/Twitter 更新、股价更新、新的博文、赛事结果等)来说,有这么几种解决方案:

Polling(轮询)

在客户端重复的向服务端发送新请求。如果服务器没有新的数据更动,关闭本次连接。然后客户端在稍等一段时间之后,再次发起新请求,一直重复这样的步骤。
缺点:是有一定的时间间隔,如果间隔变小,会对服务端造成比较大的压力。

Long-polling(长轮询)

在长轮询中,客户端发送一个请求到服务端。如果服务端没有新的数据更动,那么本次连接将会被保持,直到等待到更新后的数据,返回给客户端并关闭这个连接。
缺点:服务器维持长连接的资源消耗,且浏览器对于长连接数有限制。

Server-Sent Events

SSE类似于长轮询的机制,但是它在每一次的连接中,不只等待一次数据的更动。客户端发送一个请求到服务端 ,服务端保持这个请求直到一个新的消息准备好,将消息返回至客户端,此时不关闭连接,仍然保持它,供其它消息使用。SSE的一大特色就是重复利用一个连接来处理每一个消息(又称event)。
缺点:并不是所有浏览器都支持。

WebSocket

WebSocket不同于以上的这些技术,因为它提供了一个真正意义上的双向连接,我们会在以后的内容里介绍WebSocket这部分。

相对于WebSocket,Server Sent Events有一些优点:

  • SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
  • SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
  • SSE 默认支持断线重连,WebSocket 需要自己实现。
  • SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
  • SSE 支持自定义发送的消息类型。

我们来看一下SSE在Spring Boot里面是怎么实现的。

下面是用SSE来实现redis的BLPOP,

@RestController
@RequestMapping("/redis/list")
public class RedisListController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ApplicationContext applicationContext;
    
    @GetMapping("/bLeftPop/{key}")
    public SseEmitter bLeftPop(@PathVariable(name = "key")String key){
        SseEmitter emitter=new SseEmitter();
        applicationContext.publishEvent(new SseQueueEvent(emitter,key,true,SseQueueEvent.TYPE_LEFT));
        return emitter;
    }
}

请注意Controller的返回值是SseEmitter,我们在controller里面通过Spring的publishEvent发布了一个异步事件。

@Component
@Slf4j
public class RedisQueueListener {
    private final StringRedisTemplate redisTemplate;

    @Autowired
    public RedisQueueListener(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @EventListener
    @Async
    public void listen(SseQueueEvent event){
        event.getSseEmitter().onCompletion(()-> event.setRunning(false));
        while (event.isRunning()){
            String value;
            if(event.getType()==SseQueueEvent.TYPE_LEFT){
                value=redisTemplate.opsForList().leftPop(event.getKey(),0, TimeUnit.SECONDS);
            }else {
                value=redisTemplate.opsForList().rightPop(event.getKey(),0, TimeUnit.SECONDS);
            }
            try {
                SseEmitter.SseEventBuilder builder=SseEmitter.event()
                        .id(UUID.randomUUID().toString())
                        .data(value,MediaType.APPLICATION_JSON);
                event.getSseEmitter().send(builder);
            } catch (Exception e) {
                log.info("stop sse {}",e.getMessage());
                event.getSseEmitter().complete();
                event.setRunning(false);
            }

        }
    }

}

可以看到在异步的监听器里面,执行了leftPop的操作,如果队列没有值,则该操作会阻塞等等。如果SSE断开,则会抛出异常,然后退出监听器。

客户端代码

<script>
var source = new EventSource('/redis/bLeftPop/test');

source.addEventListener("message",function(message){
console.log(message);
});
</script>

Java客户端代码

@RunWith(JUnit4.class)
@Slf4j
public class RedisRestApplicationTests {

    @Test
    public void sseClient() throws InterruptedException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("http://localhost:8080/redis/list/bLeftPop/test")
                .build();
        EventSource.Factory factory = EventSources.createFactory(client);
        EventSource eventSource = factory.newEventSource(request, new EventSourceListener() {
            @Override
            public void onEvent(EventSource eventSource, String id, String type, String data) {
                log.info("{}:{}",id,data);
            }
        });

        Thread.sleep(10000);

    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值