1.背景介绍
分布式系统:RPC与RMI
RPC:一个对远程节点上的过程的调用就像调用本地节点那样完成
RMI:与特定的Java RMI要进行区分,与RPC最大区别在于使用了面向对象的概念,那么在远程调用时可以把对象应用作为参数传递,它的实现最典型的就是Java RMI,但不限于Java RMI
交互协议
请求协议:可以用在不需要从远程操作中返回值或者客户端不需要得到远程操作执行确认的场景中,请求发送后客户端不需要等待应答消息而可以继续执行
请求-应答协议:这种方式使我们最常见的,服务器的应答消息可以看做是客户端请求消息的一个确认
请求-应答-确认应答协议
请求-应答协议有同步和异步之分,HTTP协议即为请求-应答协议
故障处理
将超时信息抛给客户端
超时可以重试、重试多少次后取消
客户端异步处理服务端响应
Java中的RMI
java内部语言进行的远程方法调用
无法像webservice一样跨语言调用
SpringRMI
RmiServiceExporter:服务端暴露其接口
把任何Spring管理的Bean输出成一个RMI服务。通过把Bean包装在一个适配器类中工作。适配器类被绑定到RMI注册表中,并且将请求代理给服务类。
RmiProxyFactoryBean:客户端通过通过JRMP访问服务
JRMP:java remote method protocol,Java特有的,基于流的协议。
2.知识剖析
RMI的实现
Remote Method Invocation
如何通信?
客户端-调用本地对象stub存根-远程stub-远程skeleton-service
Java1.2之后service直接和stub联系,不需要skeleton
客户端和远程服务器应该都有一个stub
数据传输问题
分布式系统对象不在一个内存空间,如何解决?
序列化(远程对象的引用传递)
条件:基本类型、实现Serializable接口(UnicastRemoteObject已经实现了该接口)
对于容器类,如果其中的对象是可以序列化的,那么该容器也是可以序列化的
可序列化的子类也是可以序列化的
远程对象的发现问题
IP地址相当于远程对象的引用、DNS相当于一个注册表
格式类似于rmi://host:port/name,host指明注册表运行的注解,port表明接收调用的端口,name是一个标识该对象的简单名称。
主机和端口都是可选的,如果省略主机,则默认运行在本地;如果端口也省略,则默认端口是1099(默认注册端口号);
springRMI
不用我们去实现remote等接口
配置文件简单明了
版本太老
怎么实现随机service访问
注意服务端口要手动指定不让它随机生成
不停的从初始化的配置文件中获得bean
这方法有点low
3.编码实战
<bean id="rmiClientSocketFactory" class="beanfactory.RMICustomClientSocketFactory"> <property name="timeout" value="5000"/> </bean> <!-- 客户端 --> <bean id="userRmiServiceA" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"> <property name="serviceUrl" value="rmi://127.0.0.1:8080/studentService"/> <property name="serviceInterface" value="service.UserRmiService"/> <property name="refreshStubOnConnectFailure" value="true"/> <property name="lookupStubOnStartup" value="false"/> <!--<property name="registryClientSocketFactory" ref="rmiClientSocketFactory"/>--> </bean> <bean id="userRmiServiceB" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"> <property name="serviceUrl" value="rmi://120.131.8.132:8082/studentService"/> <property name="serviceInterface" value="service.UserRmiService"/> <property name="refreshStubOnConnectFailure" value="true"/> <property name="lookupStubOnStartup" value="false"/> <!--<property name="registryClientSocketFactory" ref="rmiClientSocketFactory"/>--> </bean> <bean id="loadBalancedService" class="beanfactory.LoadBalancedFactoryBean"> <property name="serviceInterface" value="service.UserRmiService"/> <property name="stateful" value="false"/> <property name="balanceStrategy"> <bean class="beanfactory.RoundRobinStrategy"/> </property> <property name="serviceProxyList"> <list> <ref bean="userRmiServiceA"/> <ref bean="userRmiServiceB"/> </list> </property> </bean>
private static org.slf4j.Logger logger = LoggerFactory.getLogger(JnshuController.class); private static UserRmiService userRmiService; private static ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); private static int flagA=0 ; private static int flagB=0 ; @Scheduled(cron = "0 0/3 * * * ?") private static void print(){ flagA=0; flagB=0; System.out.print("将A、B清0\n"); } // private static ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // private static UserRmiService userRmiService = (UserRmiService) context.getBean("loadBalancedService"); private static UserRmiService load(){ if (Math.random()<0.5 && flagA==0) { try { System.out.print("准备调用:"); userRmiService = (UserRmiService) context.getBean("userRmiServiceA"); System.out.print("调用成功8080端口"+userRmiService+"\n"); } catch (Exception e ){ flagA = 1; userRmiService = (UserRmiService) context.getBean("userRmiServiceB"); System.out.print("调用失败尝试调用:"+userRmiService+"\n"); } } else if (flagB==0){ try { System.out.print("准备调用:"); userRmiService = (UserRmiService) context.getBean("userRmiServiceB"); System.out.print("调用成功8082端口"+userRmiService+"\n"); } catch (Exception e){ flagB = 1; userRmiService = (UserRmiService) context.getBean("userRmiServiceA"); System.out.print("调用失败尝试调用:"+userRmiService+"\n"); } } else if(flagA==1 && flagB ==1) { System.out.print("两个端口都挂了,你在搞毛线\n"); } return userRmiService; }
4.常见问题
如何捕获一个服务端已经挂掉的异常
如果用读取bean的方式很容易就捕获了那个异常
轮询机制暂时找不到方法
服务端挂掉之后如何重连
<property name="refreshStubOnConnectFailure" value="true"/>
<property name="lookupStubOnStartup" value="false"/>
为什么本地调试正常放在远程就不行了
Linux对localhost解析有问题
5.知识拓展
spring中定时器的使用
corn表达式
6.参考文献
https://blog.youkuaiyun.com/u014001866/article/details/50936246
https://blog.youkuaiyun.com/lmy86263/article/details/72594760
https://blog.youkuaiyun.com/lmy86263/article/details/51771845
7.更多讨论
鸣谢
感谢观看,如有出错,恳请指正