Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

用Java实现一个简单的RPC,包括功能注册中心、服务发现、服务调用、共享数据、负载均衡、重试机制、服务mock...

Notifications You must be signed in to change notification settings

kixuan/SimpleRPC

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

6 Commits

Repository files navigation

SimpleRPC 1.0

一、先把tomcat跑起来

  1. 新建consumer模块和provider模块
  2. provider模块写HelloSeverHelloServiceImpl,consumerconsumer调用HelloServe【注意pom要引用】
  3. 新建provide-common,把HelloSever放进来【抽离接口】
  4. SpringBoot启动,接收方法调用,必须通过网络请求的方法来接收【netty/tomcat】--新建一个rpc 模块【pom引用,希望provider在启动的时候能够tomcat,rpc负责】
  5. 新建protocol包,HttpServer类,start方法;provider在main方法调用start方法
public class HttpServer {
 public void start(String hostname, Integer port) {
 // 读取用户配置:端口号,这里先写死
 //启动tomcat
 Tomcat tomcat = new Tomcat();
 Server server = (Server) tomcat.getServer();
 Service service = server.findService("Tomcat");
 Connector connector = new Connector();
 connector.setPort(port);
 Engine engine = new StandardEngine();
 engine.setDefaultHost(hostname);
 Host host = new StandardHost();
 host.setName(hostname);
 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);
 // tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet());
 // context.addServletMappingDecoded("/*", "dispatcher");
 try {
 tomcat.start();
 tomcat.getServer().await();
 } catch (LifecycleException e) {
 e.printStackTrace();
 }
 }
}

二、RPC 1.0

  1. 接收请求后就要处理请求,httpserve加上以下代码【新增DispatcherServlet 类,相当于中转站去处理请求--》新增HttpServerHandler,处理请求的具体方式--》新增Invocation类, 封装了接口名、方法名、参数、参数类型
// 处理请求
tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet());
context.addServletMappingDecoded("/*", "dispatcher");
  1. 还是在HttpServerHandler,我们现在通过Invocation 只能获取接口名,是不知道对应的实现类,所以就要通过本地注册的方法存起来LocalRegister 类【因为我们要拿到实现类,才能通过getMethod获得对象,才能通过反射调用对象】,注意provider在启动前要先进行本地注册,consumer要先生成invocation对象

  2. 通过网络把invocation对象发出去--》httpclient类,send方法--》consumer利用send方法进行发送对象

  3. 测试:先启动provider,在运行comsumer,终端成功显示!

image-20240117193812013

三、优化consumer

这样写consumer还是太麻烦了--优化方法:得到具体的HelloService 对象--代理对象

public class consumer {
 public static void main(String[] args) {
 // HelloService helloService = ?;
 // String result = helloService.sayHello("xuanzai");
 // System.out.println(result);
 // 生成invocation对象
 Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello",
 new Class[]{String.class}, new Object[]{"xuanzai"});
 // 利用send方法进行发送对象
 HttpClient httpClient = new HttpClient();
 String result = httpClient.send("localhost", 8080, invocation);
 System.out.println(result);
 }
}
  1. proxyProxyFactory类,重写invoke方法,consumer改成getproxy

四、继续优化ProxyFactory

public class ProxyFactory<T> {
 @SuppressWarnings("unchecked")
 public static <T> T getProxy(final Class interfaceClass) {
 // 代理对象:第三个参数是代理逻辑
 return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
 @Override
 // invoke方法干的就是把远程调用的过程封装成Invocation对象,然后通过网络传输给服务端,
 // 服务端接收到Invocation对象后,再通过反射调用本地的实现类,最后把结果返回给客户端
 // 这里的逻辑和之前写在consumer的逻辑是一样的
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 // 生成invocation对象
 Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(),
 method.getParameterTypes(), args);
 // 利用send方法进行发送对象
 HttpClient httpClient = new HttpClient();
 String result = httpClient.send("localhost", 8080, invocation);
 return result;
 }
 });
 }
}

send方法的端口地址还是写死的,而且这个地方还是在rpc模块,不可能为了改端口地址还跑到rpc模块去,继续优化!

localhost8080表示的是provider的,也就是要根据接口名字找到当前接口所在的provider的ip--》注册中心:provider在启动的时候把ip存到注册中心里面去

--》RemoteMapRegister类--》新建URL

  1. provider加注册中心注册
  2. ProxyFactory实现服务发现

这里获取的是List,这就涉及到负载均衡--》LoadBalance

五、继续优化

  1. 容错机制:ee不知道为什么没跑出去

  2. 重试机制:注意要调用不同的URL

 //【重试机制 -- 注意要调用不同的URL】
 int max = 3;
 while (max > 0) {
 // 记录已经调用过的URL,排除掉
 List<URL> invokedURLs = new ArrayList<>();
 invokedURLs.add(randomURL);
 urls.remove(invokedURLs);
 //【服务调用】
 try {
 result = httpClient.send(randomURL.getHostname(), randomURL.getPort(), invocation);
 } catch (Exception e) {
 if (--max != 0)
 continue;
 //【容错机制】不知道为什么前面的异常抛不出去🤔
 return "服务调用报错";
 }
 }
  1. 服务mock:不启动provider也不会报错,直接进入mock
//【服务mock】
String mock = System.getProperty("mock");
if (mock != null&&mock.startsWith("return:")) {
 String result = mock.replace("return:", "");
 return result;
}

image-20240117193657861

image-20240117193742666

About

用Java实现一个简单的RPC,包括功能注册中心、服务发现、服务调用、共享数据、负载均衡、重试机制、服务mock...

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /