一、RPC场景和过程+实现原理
1、rpc场景
在微服务环境下,存在大量的跨 JVM 进行方法调用的场景,如下图:
具体到某一个调用来说,希望 A 机器能通过网络,调用 B 机器内的某个服 务方法,并得到返回值。
2、rpc如何实现
我们知道调用一个服务的方法最基本的是获取到jvm中的bean实例,jdk还提供了一种通过反射的方式来调用,只需要提供:反射需要的目标对象、方法名称、参数即可。传递这些信息的桥梁有很多,例如:webService、Http、Rmi等等。
实现一个远程调用所需要的组件:反射所需要的信息、网络传输协议
,下面用代码实现一个远程调用
2.1、反射调用类
package com.enjoy.utils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
/*
* @author wangle25
* @description 反射调用
* @date 11:27 2020-07-18
* @param
* @return
**/
public class InvokeUtils {
public static ClassPathXmlApplicationContext context;
/**
* java反射
* @param target 目标对象
* @param methodName 目标方法
* @param argTypes 方法参数类型
* @param args 实参
* @return
*/
public static Object call(Object target,String methodName,
Class[] argTypes,Object[] args)
throws NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
Method method = target.getClass().getMethod(methodName, argTypes);
return method.invoke(target, args);
}
public static Object call(Map<String,String> info, ApplicationContext ctx){
String targetStr = info.get("target");
String methodName = info.get("methodName");
String arg = info.get("arg");
try {
return call(ctx.getBean(targetStr), methodName,new Class[]{String.class},new Object[]{arg});
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
2.2 Provider
package com.enjoy;
import com.alibaba.fastjson.JSON;
import com.enjoy.entity.OrderEntiry;
import com.enjoy.service.InfoService;
import com.enjoy.service.InfoServiceImpl;
import com.enjoy.service.OrderService;
import com.enjoy.service.OrderServiceImpl;
import com.enjoy.utils.InvokeUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.util.HashMap;
import java.util.Map;
public class Provider {
@Configuration
static class ProviderConfiguration {
@Bean
public OrderService orderService() {
return new OrderServiceImpl();
}
}
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
ctx.start();
System.out.println("---------spring启动成功--------");
// 开启rmi服务
initProtocol2(ctx);
System.in.read();
}
/*
* @author wangle25
* @description 开启rmi服务
* @date 11:30 2020-07-18
* @param
* @return
**/
public static void initProtocol2(ApplicationContext ctx) throws RemoteException, AlreadyBoundException, MalformedURLException {
InfoService infoService = new InfoServiceImpl(){
public Object passInfo(Map<String, String> info) {//对象,方法,参数
super.passInfo(info);//info内包含的信息,是反射需要的信息
Object result = InvokeUtils.call(info,ctx);
System.out.println("测试InvokeUtils.call调用功能,调用结果:" + JSON.toJSONString(result));
return result;
}
};
//注冊通讯端口
LocateRegistry.createRegistry(InfoService.port);
//注冊通讯路径
Naming.bind(InfoService.RMI_URL, infoService);
System.out.println("初始化rmi绑定");
}
}
2.3、Consumer
package com.enjoy.utils;
import com.alibaba.fastjson.JSON;
import com.enjoy.entity.OrderEntiry;
import com.enjoy.service.InfoService;
import com.enjoy.service.OrderService;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
public class RmiClient {
public static void main(String[] args) throws RemoteException, MalformedURLException {
InfoService infoService = null;
try {
//取远程服务实现
infoService = (InfoService) Naming.lookup(InfoService.RMI_URL);
Object ret = infoService.sayHello("james");
System.out.println("测试远程调用功能infoService.sayHello,调用结果:" + JSON.toJSONString(ret));
//呼叫远程反射方法
Map<String,String> info = new HashMap();
info.put("target","orderService");
info.put("methodName","getDetail");
info.put("arg","1");
Object result = infoService.passInfo(info);
System.out.println("测试远程调用功能,调用结果:" + JSON.toJSONString(result));
//静态代理方法
OrderService service = getService(infoService);
Object result2 = service.getDetail("1");//透明化调用,不增加开发人员的负担
System.out.println("测试远程调用功能,调用结果:" + JSON.toJSONString(result2));
} catch (NotBoundException e) {
e.printStackTrace();
}
}
/**
* 静态代理,动态编译类来实现
*/
public static OrderService getService(InfoService infoService){
OrderService service = new OrderService(){
@Override
public OrderEntiry getDetail(String id) {
Map<String,String> info = new HashMap();
//写死了反射的目标,静态代理
info.put("target","orderService");//对象
info.put("methodName","getDetail");//方法
info.put("arg",id);//参数
OrderEntiry result = null;
try {
result = (OrderEntiry)infoService.passInfo(info);
} catch (RemoteException e) {
e.printStackTrace();
}
return result;
}
};
return service;
}
}
二、dubbo简介+基础使用
简介
- 在分布式服务架构下,各个服务间的相互 rpc 调用会越来越复杂。最终形成网状结构,此时服务的治理极为关键
- Dubbo 是一个带有服务治理功能的 RPC 框架,提供了一套较为完整的服务治理方案,其底层直接实现了 rpc 调用的全过程,并尽力做事 rpc 远程对使用者透明
功能:
- 远程通讯:提供对多种基于长连接的 NIO 框架抽象封装,包括多种线程 模型、序列化以及“请求-响应”模式的信息交换方式。
- 集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持以 及软负载均衡,失败容错、地址路由、动态配置等集群支持。
- 自动发现:基于注册中心目录服务,使服务消费方能动态的查×××提 供方,使地址透明,使服务提供方可以平滑增加或减少机器。
使用方式
1、xml使用方式
- Provider配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--全局配置-->
<dubbo:provider timeout="3000" />
<!-- 服务提供方应用名称, 方便用于依赖跟踪 -->
<dubbo:application name="busi-server" />
<!-- 使用本地zookeeper作为注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!--name指示使用什么协议监听端口:dubbo/rmi/rest-->
<dubbo:protocol id="d1" name="dubbo" port="20880" />
<dubbo:protocol id="d2" name="dubbo" port="20882" />
<!-- 通过xml方式配置为bean, 让spring托管和实例化 -->
<bean id="orderService" class="com.enjoy.service.OrderServiceImpl"/>
<!-- 声明服务暴露的接口,并暴露服务 -->
<dubbo:service interface="com.enjoy.service.OrderService" ref="orderService" protocol="d1" />
</beans>
- Provider服务启动
package com.enjoy.dubbo.xml;
import com.alibaba.fastjson.JSON;
import com.enjoy.entity.OrderEntiry;
import com.enjoy.service.OrderService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class XmlServer {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/dubbo-server.xml");
ctx.start();
System.out.println("---------dubbo启动成功--------");
// 保证服务一直开着
synchronized (XmlServer.class) {
try {
XmlServer.class.wait();
} catch (Throwable e) {
}
}
}
}
- Consumer配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="busi-clint" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:reference id="orderService" interface="com.enjoy.service.OrderService" />
</beans>
- Consumer启动
package com.enjoy.dubbo.xml;
import com.enjoy.entity.OrderEntiry;
import com.enjoy.service.OrderService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class XmlClient {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/dubbo-client.xml");
ctx.start();
System.out.println("---------dubbo启动成功--------");
OrderService orderService = (OrderService) ctx.getBean("orderService"); // get remote service proxy
OrderEntiry entiry = orderService.getDetail("1");
System.out.println("echo result: " + entiry.getMoney());
}
}
2、注解的方式
- Provider配置
dubbo.application.name=busi-provider
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
- Provider服务启动
package com.enjoy.dubbo.config;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
public class Provider {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}
@Configuration
@EnableDubbo(scanBasePackages = "com.enjoy.service")
@PropertySource("classpath:/dubbo-provider.properties")
static class ProviderConfiguration {
}
}
- Consumer配置
dubbo.application.name=busi-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.consumer.timeout=3000
- Consumer服务启动
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.enjoy.dubbo.config;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import com.enjoy.action.ServiceConsumer;
import com.enjoy.entity.OrderEntiry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
public class Consumer {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
ServiceConsumer serviceConsumer = context.getBean(ServiceConsumer.class);
OrderEntiry entiry = serviceConsumer.getDetail("1");
System.out.println("result: " + entiry.getMoney());
}
@Configuration
@EnableDubbo(scanBasePackages = "com.enjoy.action")
@PropertySource("classpath:/dubbo-consumer.properties")
@ComponentScan(value = {"com.enjoy.action"})
static class ConsumerConfiguration {
}
}
- 把服务拉取到consumer本地
package com.enjoy.action;
import com.alibaba.dubbo.config.annotation.Reference;
import com.enjoy.entity.OrderEntiry;
import com.enjoy.service.OrderService;
import org.springframework.stereotype.Component;
@Component("annotatedConsumer")
public class ServiceConsumer {
@Reference
private OrderService orderService;
public OrderEntiry getDetail(String id) {
return orderService.getDetail(id);
}
}