点击上方 "程序员小乐"关注, 星标或置顶一起成长
每天凌晨00点00分, 第一时间与你相约
每日英文
Someday, you will find the one, who will watch every sunrise with you until the sunset of your life.
总有一天,你会遇上那个人,陪你看每一次日出,直到你的人生落幕。
每日掏心话
有时候不是不明白,而是明白了也不知道该怎么做,于是就保持了沉默。
来自:Autu | 责编:乐乐
链接:autumn200.com/2020/06/21/write-rpc/
程序员小乐(ID:study_tech)第 954 次推文 图源:百度往日回顾:老板说“把系统升级到https”,我用一个脚本实现了,而且永久免费!
正文
RPC:remote procedure call Protocol 远程过程调用 调用远程服务,就像调用本地的服务一样,不用关心调用细节,就像调用本机的服务一样的
实现RPC通信的程序包括5个部分:rpc-client、客户端proxy、socket、服务端proxy、rpc-server
客户端:当rpc-client发起远程调用时,实际上是通过客户端代理 将要调用的接口、方法、参数、参数类型进行序列化,然后通过socket实时将封装调用参数的实例发送到服务端。
服务端:socket接收到客户端传来的信息进行反序列化,然后通过这些信息委派到具体的实现对象
服务端:目标方法执行完成后,将执行结果返回给socket
客户端:socket接收到结果后,返回给rpc-client,调用结束
java
spring
序列化
socket
反射
动态代理
https://github.com/autumnqfeng/write_rpc
rpc-server项目包含2个子项目:order-api、order-provider
order-api中存放请求接口与RpcRequest(类名、方法名、参数的实体类)
order-provider为请求接口实现、socket、proxy相关类
order-api
order-provider
要想实现动态调用ServiceImpl,关键就需要将service类管理起来,那问题来了,我们如何管理这些服务类呢?
关注公众号程序员小乐回复关键字“offer”获取算法面试题和答案。
我们可以参照spring中的@Service注解,通过自定义注解的方式来做到服务注册,我们定义一个注解@RpcRemoteService作用在ServiceImpl类上,将标记此注解的类名、方法名保存到Map中,以此来定位到具体实现类。
/**
* 服务端服务发现注解
*
* @author: ***
* @date: 2020/6/21 16:21
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcRemoteService {
}
在spring容器初始化完成之后,扫描到@RpcRemoteService标记的类,并保存到Mediator.ROUTING中。
/**
* 初始化中间代理层对象
*
* @author: ***
* @date: 2020/6/21 16:33
*/
@Component
public class InitialMerdiator implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//加了服务发布标记的bean进行远程发布
if (bean.getClass().isAnnotationPresent(RpcRemoteService.class)) {
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method : methods) {
String routingKey = bean.getClass().getInterfaces()[0].getName() + "." + method.getName();
BeanMethod beanMethod = new BeanMethod();
beanMethod.setBean(bean);
beanMethod.setMethod(method);
Mediator.ROUTING.put(routingKey, beanMethod);
}
}
return bean;
}
}
socket监听客户端请求
spring容器加载完成之后,启动socket
/**
* spring 容器启动完成之后,会发布一个ContextRefreshedEven
* 容器启动后启动socket监听
*
* @author: ***
* @date: 2020/6/21 16:51
*/
@Component
public class SocketServer implements ApplicationListener {
private final ExecutorService executorService= Executors.newCachedThreadPool();
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
ServerSocket serverSocket=null;
try {
serverSocket = new ServerSocket(8888);
while (true) {
Socket accept = serverSocket.accept();
// 线程池处理socket
executorService.execute(new ProcessorHandler(accept));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
处理监听到的每个socket
public class ProcessorHandler implements Runnable {
private Socket socket;
public ProcessorHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
ObjectInputStream inputStream = null;
ObjectOutputStream outputStream = null;
try {
inputStream = new ObjectInputStream(socket.getInputStream());
// 反序列化
RpcRequest rpcRequest = (RpcRequest) inputStream.readObject();
// 中间代理执行目标方法
Mediator mediator = Mediator.getInstance();
Object response = mediator.processor(rpcRequest);
System.out.println("服务端的执行结果:"+response);
outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(response);
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
closeStream(inputStream, outputStream);
}
}
private void closeStream(ObjectInputStream inputStream, ObjectOutputStream outputStream) {
// 关闭流
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Mediator
/**
* 服务端socket与目标方法的中间代理层
*
* @author: ***
* @date: 2020/6/21 16:25
*/
public class Mediator {
/** 用来存储发布的服务的实例(服务调用的路由) */
public static Map ROUTING = new ConcurrentHashMap<>();
/** 单例模式创建该代理层实例 */
private volatile static Mediator instance;
private Mediator() {
}
public static Mediator getInstance() {
if (instance == null) {
synchronized (Mediator.class) {
if (instance == null) {
instance = new Mediator();
}
}
}
return instance;
}
public Object processor(RpcRequest rpcRequest) {
// 路由key
String routingKey = rpcRequest.getClassName() + "." + rpcRequest.getMethodName();
BeanMethod beanMethod = ROUTING.get(routingKey);
if (beanMethod == null) {
return null;
}
// 执行目标方法
Object bean = beanMethod.getBean();
Method method = beanMethod.getMethod();
try {
return method.invoke(bean, rpcRequest.getArgs());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
BeanMethod
/**
* 中间层反射调用时,存储目标方法、目标类的实体
*
* @author: ***
* @date: 2020/6/21 16:43
*/
public class BeanMethod {
private Object bean;
private Method method;
// setter、getter略
}
服务发现我们同样使用注解来做,这就需要参照Spring中@Autowired的原理实现,我们自定义@RpcReference注解,定义在字段上,将接口实现的代理类注入到该字段上。
关注公众号程序员小乐回复关键字“Java”获取Java面试题和答案。
@RpcReference注解
/**
* 服务注入注解
*
* @author: ***
* @date: 2020/6/20 22:41
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcReference {
}
在spring容器初始化之前,扫描bean中所有@RpcReference注解标记的字段。
/**
* 远程动态调用service代理
*
* @author: ***
* @date: 2020/6/20 22:44
*/
@Component
public class ReferenceInvokeProxy implements BeanPostProcessor {
@Autowired
private RemoteInvocationHandler invocationHandler;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(RpcReference.class)) {
field.setAccessible(true);
// 针对这个加了RpcReference注解的字段,设置为一个代理的值
Object proxy = Proxy.newProxyInstance(field.getType().getClassLoader(), new Class>[]{field.getType()}, invocationHandler);
try {
// 相当于针对加了RpcReference的注解,设置了一个代理,这个代理的实现是invocationHandler
field.set(bean, proxy);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
}
客户端动态代理InvocationHandler实现类RemoteInvocationHandler
将目标方法名、目标类名、参数信息封装到RpcRequest,然后交给socket发送到服务端。
/**
* @author: ***
* @date: 2020/6/20 22:51
*/
@Component
public class RemoteInvocationHandler implements InvocationHandler {
@Autowired
private RpcNetTransport rpcNetTransport;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest rpcRequest = new RpcRequest();
rpcRequest.setClassName(method.getDeclaringClass().getName());
rpcRequest.setMethodName(method.getName());
rpcRequest.setTypes(method.getParameterTypes());
rpcRequest.setArgs(args);
return rpcNetTransport.send(rpcRequest);
}
}
网络传输RpcNetTransport
/**
* rpc socket 网络传输
*
* @author: ***
* @date: 2020/6/20 22:59
*/
@Component
public class RpcNetTransport {
@Value("${rpc.host}")
private String host;
@Value("${rpc.port}")
private int port;
public Object send(RpcRequest rpcRequest) {
ObjectOutputStream outputStream = null;
ObjectInputStream inputStream = null;
try {
Socket socket = new Socket(host, port);
// 发送目标方法信息
outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(rpcRequest);
outputStream.flush();
// 接收返回值
inputStream = new ObjectInputStream(socket.getInputStream());
return inputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
closeStream(inputStream, outputStream);
}
return null;
}
private void closeStream(ObjectInputStream inputStream, ObjectOutputStream outputStream) {
// 关闭流
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。
猜你还想看
阿里、腾讯、百度、华为、京东最新面试题汇集
不想CRUD干到老,就来看看这篇OOM排查的实战案例!
面试鹅厂,我被虐的体无完肤!糗大了!
Nginx + Spring Boot 实现负载均衡
关注订阅号「程序员小乐」,收看更多精彩内容
嘿,你在看吗?