Github源码下载地址:https://github.com/chenxingxing6/myrpc
一、前言
RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外的为这个交互作用编程,如果涉及的软件采用面向对象编程(java),那么远程过程调用亦可称作远程调用或远程方法调用。只要支持网络传输的协议就是RPC协议,RPC是一种框架。简单的说就是远程调用,以API的方式调用远程的服务器上的方法,像调本地方法一样!
RPC要解决的两个问题
1.解决分布式系统中,服务之间的调用问题。 2.远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
完整RPC调用过程:
以左边的Client端为例,Application就是rpc的调用方,Client Stub就是我们上面说到的代理对象,其实内部是通过rpc方 式来进行远程调用的代理对象,至于Client Run-time Library,则是实现远程调用的工具包,比如jdk的Socket,最后通过底 层网络实现实现数据的传输。
这个过程中最重要的就是序列化和反序列化了,因为数据传输的数据包必须是二进制的,你直接丢一个Java对象过去,人家可不 认识,你必须把Java对象序列化为二进制格式,传给Server端,Server端接收到之后,再反序列化为Java对象。
二、项目结构
要实现一个RPC不算难,难的是实现一个高性能高可靠的RPC框架。
实现功能
1.支持多种协议传输socket,http,dubbo 2.支持多种注册方式file,redis,zookeepre 3.可以动态切换协议和注册方式,只需要更改配置文件 4.Netty实现 (更新2019.10.1) 5.注解方式发布服务@Rpc (更新2019.10.1)
服务端:提供API,启动的时候要注册服务 消费端:从注册中心获取服务,调用子服务 注册中心:保存服务配置 RPC协议:基于Tomcat的HttpProtocol,基于Netty的DubboProtocol,Socket 注册中心:本地文件,Redis,Zookeeper方式进行注册
三、核心代码
如果要补充协议,只需要实现IProtocolClient,IProtocolService接口
package com
.mydubbo
.rpc
.protocol
;
import com
.mydubbo
.rpc
.framework
.Invocation
;
import com
.mydubbo
.rpc
.framework
.URL
;
public interface IProtocolClient {
public Object
send(URL url
, Invocation invocation
);
}
package com
.mydubbo
.rpc
.protocol
;
import com
.mydubbo
.registry
.AbstractRegistryDiscovery
;
import com
.mydubbo
.rpc
.framework
.URL
;
public interface IProtocolServer {
public void start(URL url
, String charset
, AbstractRegistryDiscovery registryDiscovery
);
}
如果要实现多种注册中心,只需继承AbstractRegistryDiscovery
package com
.mydubbo
.registry
;
import com
.mydubbo
.rpc
.framework
.URL
;
import java
.util
.HashMap
;
import java
.util
.Map
;
import java
.util
.Optional
;
import java
.util
.Set
;
public abstract class AbstractRegistryDiscovery {
protected static Map
<String
, Map
<URL, Class>> REGISTER
= new HashMap<String
, Map
<URL, Class>>();
public void register(URL url
, String interfaceName
, Class
implClass){
Map
<URL, Class> map
= new HashMap<URL, Class>();
map
.put(url
, implClass
);
REGISTER
.put(interfaceName
, map
);
save();
}
public Class
discovery(final URL url
, String interfaceName
){
REGISTER
= get();
return Optional
.ofNullable(REGISTER
.get(interfaceName
)).map(r
-> r
.get(url
)).orElseThrow(() -> new RuntimeException("service not found!"));
}
public URL
randomServer(String interfaceName
){
REGISTER
= get();
Set
<URL> urls
= Optional
.ofNullable(REGISTER
.get(interfaceName
)).map(r
-> r
.keySet()).orElseThrow(() -> new RuntimeException("service not found!"));
if (urls
== null
|| urls
.isEmpty()){
throw new RuntimeException("service not found!");
}
return urls
.iterator().next();
}
public abstract void save();
public abstract Map
<String
, Map
<URL, Class>> get();
}
四、内置tomca
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8"/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="" doBase="WORKDIR" reloadable="true"/>
</Host>
</Engine>
</Service>
</Server>
package com
.mydubbo
.rpc
.protocol
.http
;
import com
.mydubbo
.registry
.AbstractRegistryDiscovery
;
import com
.mydubbo
.rpc
.framework
.URL
;
import com
.mydubbo
.rpc
.protocol
.IProtocolServer
;
import org
.apache
.catalina
.*
;
import org
.apache
.catalina
.connector
.Connector
;
import org
.apache
.catalina
.core
.StandardContext
;
import org
.apache
.catalina
.core
.StandardEngine
;
import org
.apache
.catalina
.core
.StandardHost
;
import org
.apache
.catalina
.startup
.Tomcat
;
public class HttpServer implements IProtocolServer {
private static final String TOMCAT
= "Tomcat";
@Override
public void start(URL url
, String charset
, AbstractRegistryDiscovery registryDiscovery
){
Tomcat tomcat
= new Tomcat();
Server server
= tomcat
.getServer();
Service service
= server
.findService(TOMCAT
);
Connector connector
= new Connector();
connector
.setPort(url
.getPort());
connector
.setURIEncoding(charset
);
Engine engine
= new StandardEngine();
engine
.setDefaultHost(url
.getHostName());
Host host
= new StandardHost();
host
.setName(url
.getHostName());
String contextPath
= "";
Context context
= new StandardContext();
context
.setPath(contextPath
);
context
.addLifecycleListener(new Tomcat.FixContextListener());
host
.addChild(context
);
engine
.addChild(host
);
service
.setContainer(engine
);
service
.addConnector(connector
);
String servletName
= "dispatcher";
tomcat
.addServlet(contextPath
, servletName
, new DispatcherServlet(url
, registryDiscovery
));
context
.addServletMappingDecoded("/client/*", servletName
);
try {
System
.out
.println("Tomcat..服务启动成功.....");
tomcat
.start();
tomcat
.getServer().await();
}catch (Exception e
){
e
.printStackTrace();
}
}
}
五、注册中心
1.本地文件 2.zk 3.redis
六、测试
## 协议配置信息
## 支持socket,http,dubbo
config.protocol.name=socket
config.protocol.host=localhost
config.protocol.port=8080
config.protocol.charset=UTF-8
## 服务注册 支持localfile,redis,zookeeper
service.registry.type=redis
#service.registry.type=localfile
#service.registry.type=zookeeper
<?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"
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.xsd">
<context:property-placeholder location="classpath:spring.properties"/>
<bean id="protocolConfig" class="com.mydubbo.config.ProtocolConfig">
<constructor-arg name="name" value="${config.protocol.name}"/>
<constructor-arg name="host" value="${config.protocol.host}"/>
<constructor-arg name="port" value="${config.protocol.port}"/>
<constructor-arg name="charset" value="${config.protocol.charset}"/>
</bean>
<bean id="registryDiscoveryFactory" class="com.mydubbo.registry.RegistryDiscoveryFactory">
<constructor-arg name="registryType" value="${service.registry.type}"/>
</bean>
<bean id="rpcClient" class="com.mydubbo.rpc.RpcClient">
<constructor-arg name="config" ref="protocolConfig"/>
<constructor-arg name="registryDiscoveryFactory" ref="registryDiscoveryFactory"/>
</bean>
<bean id="rpcServer" class="com.mydubbo.rpc.RpcServer">
<constructor-arg name="config" ref="protocolConfig"/>
<constructor-arg name="registryDiscoveryFactory" ref="registryDiscoveryFactory"/>
</bean>
</beans>
package com
.demo
.consumer
;
import com
.demo
.provider
.api
.IHelloService
;
import com
.mydubbo
.rpc
.RpcClient
;
import org
.springframework
.context
.ApplicationContext
;
import org
.springframework
.context
.support
.ClassPathXmlApplicationContext
;
public class ConsumerStart {
public static void main(String
[] args
) {
ApplicationContext context
= new ClassPathXmlApplicationContext("spring.xml");
RpcClient rpcClient
= (RpcClient
) context
.getBean("rpcClient");
IHelloService helloService
= rpcClient
.getProxy(IHelloService
.class);
System
.out
.println(helloService
.sayHello("lanxinghua"));
System
.out
.println(helloService
.getUser());
System
.out
.println(helloService
.saveUser(helloService
.getUser()));
}
}
package com
.demo
.provider
;
import com
.demo
.provider
.api
.IHelloService
;
import com
.demo
.provider
.impl
.HelloService
;
import com
.mydubbo
.rpc
.RpcServer
;
import org
.springframework
.context
.ApplicationContext
;
import org
.springframework
.context
.support
.ClassPathXmlApplicationContext
;
public class ProviderStart {
public static void main(String
[] args
) {
ApplicationContext context
= new ClassPathXmlApplicationContext("spring.xml");
RpcServer rpcServer
= (RpcServer
) context
.getBean("rpcServer");
rpcServer
.register(IHelloService
.class.getName(), HelloService
.class);
rpcServer
.start();
}
}
注解方式发布服务
package com
.demo
.provider
.impl
;
import com
.demo
.provider
.api
.ILogService
;
import com
.mydubbo
.config
.Rpc
;
@Rpc
public class LogService implements ILogService {
@Override
public void log(String msg
) {
System
.out
.println("【日志】" + msg
);
}
}
总结
希望路过的童鞋们,自己手写RPC后,可以更加清楚知道服务远程间是怎么调用的,后期还要许多东西需要完善,比如:负载均衡,熔断机制,Mock数据等等…