远程调用服务,同步RPC
01、远程过程调用(remote procedure call,RPC):
RPC类似于调用一个本地对象的一个方法。
是同步操作,会阻塞调用代码的执行,直到被调用的过程执行完毕。这是与消息队列MQ最大的区别。
所以这种方式的远程服务,也叫做同步RPC。
02、编程模型一致的Spring支持:
2.1、服务端,配置ServiceExporter,导出Spring bean为服务端点(Endpoint),暴露端点的访问路径(serviceName),配置导出服务接口(serviceInterface)
2.2、客户端,配置服务端点访问路径,使用xxProxyFactoryBean,为远程服务创建代理对象,然后注入使用,配置代理服务接口
1、远程调用服务是独立服务:
1.1、RMI提供基于 RMI注册表 的独立服务。需要占用端口。所以存在防火墙穿透问题。
1.2、Hessian、Burlap、HttpInvoker、JAX-WS、REST依赖HTTP服务器,提供基于的远程调用服务。
其中针对Hessian、Burlap、HttpInvoker的Spring支持,提供了编程模型一致的方案:
ServiceExporter本质上是一个SpringMVC控制器。同时,为了定义这个springMVC控制器的requestMapping,也是为了暴露远程服务端点endpoint,需要通过SimpleUrlHandlerMapping 这个spring bean配置URL和控制器(bean ID)之间的映射
1.3、目前同步RPC服务,倾向于REST + json的方案来解决。。其他的参考了解。
3、RMI导出服务时配置的serviceName类似于URI,作为特定RMI服务端点的访问标识
RMI客户端通过serviceUrl属性配置服务端点endpoint,访问协议是rmi,然后是host+port,然后是serviceName
4、对象的序列化机制,支撑RPC的对象远程传输。类似于MQ中的消息转换器。
RMI、HttpInvoker,使用的是Java的对象序列化机制。Hessian、Burlap使用的私有机制,所以复杂对象可能出现问题
5、SOA面向服务的架构核心理念:
公共的核心模块应该设计成独立服务,以集群方式提供弹性扩展,通过消息总线通信(或者同步RPC)
集群是指服务的集群,可以弹性扩展。分布式是指资源数据的分布式存储。(我理解的区别就是,一个是计算服务,一个是数据资源存储服务)
7、RMI、Hessian、HttpInvoker都是基于二进制消息
Burlap、webservice都是基于XML文本消息
Hessian、Burlap、webservice可以屏蔽两端的语言差异(后两个是因为基于XML),RMI是Java自己关于远程方法调用的实现,HttpInvoker需要spring的支持
8、 只要是基于字符集charset的可读性数据,都是文本数据(文件)。。其他都是二进制数据。
9、导出webservice端点:
9.1、针对POJO,使用@WebServise声明端点(serviceName属性指定端点名称)和@WebMethod声明操作
9.2、JAX-WS运行时(jdk1.6自带实现支持,1.8不清楚是否支持)支持将端点发布到指定地址时,可以将SpringBean导出为端点,并且可以使用SimpleJaxWsServiceExporter作为导出器。
同时可以指定baseAddress属性,来设置端点的基本地址,配合serviceName,可以实现,将端点发布到指定地址。
9.3、当JAX-WS运行时不支持将端点发布为到指定地址时,端点的生命周期将由运行时来管理,如果要使用Spring自动装配特性,则必须继承SpringBeanAutowiringSupport。并且不能使用SimpleJaxWsServiceExporter导出器。具体怎么发布端点,语焉不详。
10、客服端访问Webservice端点,基于编程模型一致性,基本跟其他远程技术一样。
区别时,有三个属性需要去wsdl文档中查看获取:serviceName、portName、namespaceUri
spring异步消息
1、消息驱动POJO,MDP。说白了就是配置一个基于POJO的监听器。
1A、配置:
- 基于XML配置一个监听器容器(注入连接工厂),然后在其中配置一系列监听器。。
- 监听器,指定Spring bean(POJO)、其中的方法、监听的目的地,并结合消息转换器,从而通过事件监听机制来实现异步接收消息。
2、基于Spring提供的编程模型一致性。。对JMS配置的MDP和对AMQP的基本一样
3、所以,除非通过xxTemplate主动从目的地接收消息,其他异步形式接收消息,本质上都是通过监听器监听目的地来实现的。
4、另外,虽然在JMS中,目的地分为queue和topic,但其实本质上,都是queue队列(要不怎么就有MQ消息队列的概念了)。
- 无非topic是主题-订阅模型,在topic中的消息,会通过副本形式发送给所有监听该目的地的监听器(类似于订阅),然后从队列移除。
- 而点对点模型,仅仅会被监听该目的地中其中一个收到(类似于消费),并从队列移除。
- 这种区别,仅仅是消息路由模式的区别。
5、AMQP,则是通过exchange+binding+routing key的方式,来实现,更加灵活的消息路由模式。
6、在分布式集群项目中,消息作为常用的通信通道
而存在。又称作消息总线
7、异步消息核心概念:
- 消息代理message Broker
- 基于内存的
- 基于独立服务的
- 支持JMS的ActiveMQ
- 支持AMQP的RabbitMQ
- 支持JMS的ActiveMQ
- 目的地Destination
WebSocket以及基于WebSocket的STOMP消息
1、STOMP之于WebSocket,类似于http之于TCP socket
2、STOMP是底层基于WebSocket的一种消息协议,相对http的优势是,全双工通信
03、JMS、AMQP等消息协议,是用于两个应用之间的通信。这两个应用只是发送端、接收端的关系,不是客户端、服务端的关系
而对于STOMP消息,它是用于web浏览器-web服务器之间通信的应用场景。所以,存在js客户端、STOMP服务端的概念。
3、Spring配置STOMP:
3.1、启用WebSocket消息代理(@EnableWebSocketMessageBroker注解)
3.2、继承AbstractWebSocketMessageBrokerConfigurer
3.3、注册StompEndpoint。。用于客户端通过WebSocket来连接MessageHandler(消息处理器)。
3.4、启用代理中继,注册代理目的地前缀、注册应用目的地前缀
如果不启用代理中继,可以使用Spring的简单代理基于内存来提供消息代理服务
04、客户端在订阅或发布消息到目的地路径前,要连接端点StompEndpoint。
是因为客户端需要先跟web服务器通信,才能进一步经过MessageHandler处理,转发到消息代理服务器(或者像@SubscribeMapping直接返回客户端)
05、在远程服务中,提供服务的可调用接口,叫做端点Endpoint
4、STOMP消息目的地,分为应用目的地和代理目的地:
其中应用目的地,由AnnotationMethodMessageHandler,根据@MessageMapping和@SubscribeMapping注解的路径,路由到相应的处理器方法中。。发往这里的消息,也叫应用消息
代理目的地,则是由SimpleBrokerMessageHandler转发到简单代理中的目的地,或者如果启用了消息中继,则由StompBrokerRelayMessageHandler转发到第三方消息代理的目的地。发往这里的消息,也叫代理消息。
8、因为STOMP是消息协议,所以本质上存在几种概念:发送端、接受端、消息代理服务(消息总线)
其中,任何一方发送和接收消息,都是与消息代理目的地之间的通信。不会存在,发送端和接收端的直接联系。
然后,可以通过模版类(XXTemplate)主动发送、接收消息。。
也可以通过监听(订阅)目的地的方式,被动接收消息(消息驱动POJO)
9、应用目的地,可以处理消息,也可以处理订阅:意思就是
9.1、处理消息,就是@MessageMapping方法处理客户端通过stomp连接调用send之后所产生的消息。
9.2、处理订阅,就是@SubscribeMapping方法处理客户端通过stomp连接调用subscribe
订阅应用目的地之后产生的消息,叫做订阅消息。
客户端调用subscribe,首次都会发送一个订阅消息。但是发往应用目的地的订阅消息,有可能并没有经过消息代理,而是直接返回客户端。这时,这种订阅,就是一次性的请求-回应模式。因为订阅,要起到长期监听作用,必须由消息代理来支持。首次订阅消息,会起到一种注册观察者的作用。(这是我理解的,不知道对不对)
11、在STOMP消息模型中:
11.1、js客户端:
拿到SocketJS实例(连接StompEndpoint),拿到Stomp实例,连接(connect),在连接成功的回调方法中
客户端发送消息(异步的吧?):调用send,传入目的地、header map、负载
客户端接收消息(异步订阅):调用subscribe,传入目的地、回调函数
11.2、web服务端:
接收消息:接收的消息都是发往应用目的地的消息
@MessageMapping接收应用消息
@SubscribeMapping接收订阅消息:接收订阅应用目的地的消息
@SubscribeMapping的特殊之处在于,以异步消息的形式实现类似于http请求-回应模型。
当不存在@SendTo注解时,处理器返回后的消息,将直接发送给客户端(消息所带的destination就是原来的目的地),此时,消息不再经过消息代理。除非添加@SendTo注解,消息才会发布到消息代理。
发送消息:
处理器方法的返回值
@MessageMapping返回值和带@SendTo注解的@SubscribeMapping返回值,都将作为消息发布到消息代理。客户端通过订阅代理目的地,来接收消息。
注意:
返回值发出的消息,还会经过MessageHandler进行处理,转发到消息代理目的地上
使用模版类,主动向目的地发送消息
通过模版类( SimpMessagingTemplate,配置STOMP之后,Spring上下文就会有这个bean)、消息转换器,主动发送消息到目的地
12、结合Spring Security,实现为目标用户发送消息:
跟用户绑定的消息,将使用UserDestinationMessageHandler来处理,叫做用户消息:
12.1、客户端订阅/user前缀的目的地产生的订阅消息:
如果客户端订阅了'/user/topic/greetings',服务端UserDestinationMessageHandler会将其转发到类似"/topic/greetings-usererbgz2rq"这样格式的目的地中,其中"usererbgz2rq"中,user是关键字,erbgz2rq是sessionid。这样客户端就会只订阅发布到当前用户当前会话的消息。
12.2、处理器方法使用@SendToUser注解产生的返回值消息:
服务端处理器方法@SendToUser('/topic/greetings')将发布消息到'/user/loginname/topic/greetings',其中loginname是结合Spring Security获得的当前登录用户帐号。。这时候,如果一个帐号打开了多个浏览器窗口,也就是打开了多个websocket session通道,UserDestinationMessageHandler转发时默认会把消息推送到同一个帐号所有session的用户目的地中,但可以将@SendToUser的属性broadcast = false,使得消息只推送到当前session(即发送订阅消息的那个session)
12.3、通过模版类SimpMessagingTemplate,调用convertAndSendToUser('username', '/topic/greetings', XX),效果跟@SendToUser一样。区别是,可以在应用任意位置发送消息。
012、只要消息的目的地不是代理目的地,就会被相应的MessageHandler处理并转发,例如应用目的地、/user前缀目的地等。直到消息转发到代理目的地或者转发回客户端(@SubscribeMapping)
13、MessageHandler可接收到的消息:
发往应用目的地的消息,由AnnotationMethodMessageHandler处理:
应用消息@MessageMapping
订阅消息@SubscribeMapping
用户消息,由UserDestinationMessageHandler处理
发往代理目的地的订阅消息,由SimpleBrokerMessageHandler或者StompBrokerRelayMessageHandler处理。
14、捕获消息处理器方法抛出的异常:
@MessageExceptionHandler,基于spring的编程模型一致性,类似于SpringMVC中的@ExceptionHandler。。。同时借助@SendTo注解,可以发布一个回应消息