西方著名宗教史家米尔恰·伊利亚德说,如果今天我们不生活在未来,那么未来,我们会生活在过去。

随着移动互联网的普及,基于C/S模式的APP开始了它的又一轮爆发式增长,过去,我们总说要去C/S模式,拥抱B/S,而实际上,任何开发模式都不过是具体的需求应用场景下的产物罢了,并没有绝对的好与坏。今天我们从推送的历史开始说起,介绍一下目前常见的推送实现方式,并结合反应式的例子来继续实战反应式的编程模型的应用。

一、关于推送

推送最早是诞生于 Email 中,是用于提醒新的消息,而移动互联网时代则更多的运用在了移动客户端程序(APP)。

推送服务通常是基于提前的信息约束而形成的。也就是采用的设计模式是 生产者/订阅者 模型(publish/subscribe)。wiki定义是,客户端通过订阅由服务器生产的各种信息的频道,不论何时都可以在其中一个频道得到新的内容,同样服务器通过推送把信息传递给相应的客户端。打个比方说,推送服务就像是建立了一个像水管一样的数据管道。

而要获想取服务器的数据,通常有两种方式:第一种是客户端 PULL(拉)方式,即每隔一段时间去服务器获取是否有数据;第二种是服务端 PUSH(推)方式,服务器在有数据的时候主动发给客户端。很显然,PULL 方案优点是简单但是实时性较差,反之PUSH 方案基于 TCP 长连接方式实现,消息实时性好,但是要保持客户端和服务端的长连接心跳,而目前主流的推送实现方式都是基于 PUSH 的方案。

具体的推送实现方式有以下三种——

1、轮询方式(PULL)

客户端和服务器定期的建立连接,通过消息队列等方式来查询是否有新的消息,需要控制连接和查询的频率,频率不能过慢或过快,过慢会导致部分消息更新不及时,过快会就会出现数据压力,严重时甚至会导致后台系统宕机或客服端卡顿等问题。

2、短信推送方式(SMS PUSH)

通过短信发送推送消息,并在客户端植入短信拦截模块(主要针对 Android 平台),可以实现对短信进行拦截并提取其中的内容转发给 App 应用处理,这个方案借助于运营商的短消息,能够保证最好的实时性和到达率,但此方案对于成本要求较高,开发者需要为每一条 SMS 支付费用。

3、长连接方式(PUSH)

基于 TCP 长连接的实现方式, 客户端在主动和服务器建立 TCP 长连接之后, 还会定期向服务器发送心跳包用于保持连接, 有消息的时候, 服务器直接通过这个已经建立好的 TCP 连接通知客户端。尽管长连接也会造成一定的开销,但目前随着基础设施的不断升级完善(比如,更低的流量资费,更大的带宽资源),反而成了最优的方式。不过,随着客户端数量和消息并发量的上升,对于消息服务器的性能和稳定性要求提出了非常大的考验。

二、实战

下面我们用两种不同的PUSH技术来构建反应式API。

场景1:服务器推送事件

服务器推送事件(Server-Sent Events,SSE),允许服务器端不断地推送数据到客户端。作为 W3C 的推荐规范,SSE 在浏览器端的支持也比较广泛,除了 IE 之外的其他浏览器都提供了支持。在 IE 上也可以使用 polyfill 库来提供支持。

在 WebFlux 中创建 SSE 的服务器端是非常简单的。只需要返回的对象的类型是 Flux,就会被自动按照 SSE 规范要求的格式来发送响应。

创建一个API控制器 SseController,代码如下:

@Controller
@SpringBootApplication
public class SseController {public static void main(String[] args) {SpringApplication.run(SseController.class,args);}@GetMapping("/randomNumbers")public Flux<ServerSentEvent<Integer>> randomNumbers() {return Flux.interval(Duration.ofSeconds(1)).map(seq -> Tuples.of(seq, ThreadLocalRandom.current().nextInt())).map(data -> ServerSentEvent.<Integer>builder().event("random").id(Long.toString(data.getT1())).data(data.getT2()).build());}}

代码中的 SseController 是一个使用 SSE 的控制器的示例。其中的方法 randomNumbers()表示的是每隔一秒产生一个随机数的 SSE 端点。我们可以使用类 ServerSentEvent.Builder 来创建 ServerSentEvent 对象。这里我们指定了事件名称为 random,以及每个事件的标识符和数据。事件的标识符是一个递增的整数,而数据则是产生的随机数。

测试1场景

测试类SSEClient代码:

public class SSEClient {public static void main(final String[] args) {final WebClient client = WebClient.create();client.get().uri("http://localhost:8080/sse/randomNumbers").accept(MediaType.TEXT_EVENT_STREAM).exchange().flatMapMany(response -> response.body(BodyExtractors.toFlux(new ParameterizedTypeReference<ServerSentEvent<String>>() {}))).filter(sse -> Objects.nonNull(sse.data())).map(ServerSentEvent::data).buffer(11).doOnNext(System.out::println).blockFirst();}
}

测试代码中,我们使用 WebClient 访问 SSE 在发送请求部分与访问 REST API 是相同的,不同的地方在于对 HTTP 响应的处理。由于 SSE 服务的响应是一个消息流,我们需要使用 flatMapMany 把 Mono转换成一个 Flux对象,通过方法BodyExtractors.toFlux来完成,其中的参数 new ParameterizedTypeReference

场景2:WebSocket

WebSocket 支持客户端与服务器端的双向通讯。当客户端与服务器端之间的交互方式比较复杂时,可以使用 WebSocket。WebSocket 在主流的浏览器上都得到了支持。

WebFlux 也对创建 WebSocket 服务器端提供了支持。在服务器端,我们需要实现接口 org.springframework.web.reactive.socket.WebSocketHandler 来处理 WebSocket 通讯。接口 WebSocketHandler 的方法 handle 的参数是接口 WebSocketSession 的对象,可以用来获取客户端信息、接送消息和发送消息。

同样创建一个API控制器WebsocketController,代码如下:

@SpringBootApplication
public class WebsocketController {public static void main(String[] args) {SpringApplication.run(WebsocketController.class,args);}@Beanpublic HandlerMapping webSocketMapping(final EchoHandler echoHandler) {final Map<String, WebSocketHandler> map = new HashMap<>(1);map.put("/echo", echoHandler);final SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);mapping.setUrlMap(map);return mapping;}@Beanpublic WebSocketHandlerAdapter handlerAdapter() {return new WebSocketHandlerAdapter();}}

代码中的 EchoHandler 对于每个接收的消息,会发送一个添加了”ECHO -> “前缀的响应消息。WebSocketSession 的 receive 方法的返回值是一个 Flux对象,表示的是接收到的消息流。而 send 方法的参数是一个 Publisher对象,表示要发送的消息流。在 handle 方法,使用 map 操作对 receive 方法得到的 Flux中包含的消息继续处理,然后直接由 send 方法来发送。

测试2场景

测试类WSClient代码:

public class WSClient {public static void main(final String[] args) {final WebSocketClient client = new ReactorNettyWebSocketClient();client.execute(URI.create("ws://localhost:8080/echo"), session ->session.send(Flux.just(session.textMessage("Hello"))).thenMany(session.receive().take(1).map(WebSocketMessage::getPayloadAsText)).doOnNext(System.out::println).then()).block(Duration.ofMillis(5000));}}

注意,访问 WebSocket 不能使用 WebClient,而应该使用专门的 WebSocketClient 客户端。Spring Boot 的 WebFlux 模板中默认使用的是 Reactor Netty 库。Reactor Netty 库提供了 WebSocketClient 的实现。WebSocketClient 的 execute 方法与 WebSocket 服务器建立连接,并执行给定的 WebSocketHandler 对象。在 WebSocketHandler 的实现中,首先通过 WebSocketSession 的 send 方法来发送字符串 Hello 到服务器端,然后通过 receive 方法来等待服务器端的响应并输出。方法 take(1)的作用是表明客户端只获取服务器端发送的第一条消息。

小结

通过对推送技术的常用实现方式的介绍,我们了解到推送的内在特征,并通过两个简单的例子进一步的学习了反应式编程的应用场景。我们更能明白,任何一种技术都是在特定的场景下产生和应用的,没有绝对的好与坏,只是看我们如何更好地驾驭它。

示例代码:boot-flux

参考资源

1、Spring Boot 官方文档
2、WebFlux 参考指南
3、Reactor Netty


我的其它穿越门——持续践行,我们一路同行。
头条号:「说言风语」
简书ID:「mickjoust」
公号:「说言风语」

Spring Boot 实践折腾记(13):使用WebFlux构建响应式「推送API 」相关推荐

  1. Spring Boot 实践折腾记(10):响应式编程支持库Reactor

    Spring Boot 2.0发布已经过去了2个多月,随着微服务的流行,Spring Boot也越来越受到青睐,更好的隔离编程范式得到了越来越多项目的应用,这是一件值得高兴的事. Spring Boo ...

  2. Spring Boot 实践折腾记(11):使用 Spring 5的WebFlux快速构建效响应式REST API

    关于Spring 5中的反应式编程支持Reactor类库,上一篇文章< Spring Boot 实践折腾记(10):2.0+版本中的反应式编程支持--Reactor>已经简要介绍过,Spr ...

  3. Spring Boot 实践折腾记(15):使用Groovy

    Java是在JVM上运行的最广泛使用的编程语言.不过,还有很多其他基于JVM的语言,比如Groovy,Scala,JRuby,Jython,Kotlin等等.其中,Groovy和Scala现在在Jav ...

  4. Spring Boot 实践折腾记(12):支持数据缓存Cache

    不管是什么类型的应用程序,都离不开数据,即便如现在的手机APP,我们依然需要使用数数据库,对于不懂的人,当然,我们可以告诉他们一些高大上的概念,但是作为专业人士,就一定要明白背后的真实原理到底是什么. ...

  5. Spring Boot 实践折腾记(20):Thymleaf + webjar + ECharts 构建本地图表

    前言 作为geek后端程序员,画图的选择其实有很多,手画,excel,ppt,copy别人的图等.虽然用excel最方便,但是类似按中国省份来显示详细数据的图,excel是画不了的,而PPT画起来又很 ...

  6. Spring Boot 实践折腾记(16):使用Scala

    英国作家萨缪尔·约翰逊曾说过,成大事不在于力量的大小,而在于能坚持多久. 前面我们介绍了如何在Spring Boot下使用Kotlin和Groovy,还有一个热门语言是Scala,做大数据的同学对Sc ...

  7. Spring Boot 实践折腾记(四):配置即使用,常用配置

    生活不可能像你想象的那么好,但也不会像你想象的那么糟.我觉得人的脆弱和坚强都超乎自己的想象.有时,可能脆弱得一句话就泪流满面:有时,也发现自己咬着牙走了很长的路.--源自 莫泊桑 开始前- 本章内容主 ...

  8. Spring Boot实践

    Spring Boot实践 在本文中,我将重点介绍Spring Boot特有的实践(大多数时候,也适用于Spring项目).以下依次列出了最佳实践,排名不分先后. 1.使用自定义BOM来维护第三方依赖 ...

  9. Spring Boot实践——Spring AOP实现之动态代理

    Spring AOP 介绍 AOP的介绍可以查看 Spring Boot实践--AOP实现 与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改 ...

最新文章

  1. 阿里达摩院刷新纪录,开放域问答成绩比肩人类水平,超微软、Facebook
  2. 宏基因组理论教程2扩增子分析
  3. Linux运维实战之用户和组
  4. SQL Server 2005即将终止服务 你准备好了么?
  5. Extending_and_embedding_php翻译
  6. Hadoop SequenceFile
  7. Python Day15 jQuery
  8. rabbitmq-plugins.bat enable rabbitmq_management
  9. IT职场人生系列之八:行业与公司类型
  10. python不带颜色的图形_用python给黑白图像上色
  11. python调用Java代码并执行--------jpype使用篇
  12. 【生信进阶练习1000days】day2-学习summarized experimental data与Down stream analysis
  13. Windows 8 开启 NetFX3
  14. CPU占用100%的一般原因及解决办法
  15. linux文件系统与磁盘(五)分区的取消挂载、调整分区大小
  16. 柱状图怎么设置xy轴_经验-Origin做柱状图常遇问题-柱状图X坐标轴如何设置—小技巧...
  17. 读文章《新阶级论:寒门难贵,豪门难收》
  18. Maven下载及目录结构
  19. html 苹果手机输入法,苹果手机输入法的小技巧,你知道几个?最后一个一般人都不知道...
  20. logstash中无法解析nginx日志中的\x09类似字符导致服务停止

热门文章

  1. 液晶12864显示字符
  2. PW系列 | 用windres 编译.rc 资源文件
  3. 如何让公司其他人(同一个局域网)访问自己电脑静态.html
  4. 搜索排序LambdaMART中Lambda的计算过程java版本
  5. echarts 渲染3d地图
  6. win10平台下搭建python-pcl环境
  7. MS COCO数据集输出数据的结果格式(result format)和如何参加比赛(participate)(来自官网)
  8. 理想低通滤波器、Butterworth滤波器和高斯滤波器
  9. 小巧易用的分区工具——MiniTool Partition Wizard
  10. ABB机器人的程序结构与模块属性