手写rpc简单demo以及相关学习思考

RPC介绍

RPC是远程过程调用(Remote Procedure Call)的缩写形式。

个人理解:相关框架会封装相应的http请求或者其他类似socker的请求,解析数据,得到结果。让调用远程方法就像本地方法一样easy~
相关框架 :dubbo,fegin…

Fegin原理

Fegin是Ribbon的扩展,封装实现远程调用接口

@FeignClient

参数

  1. name:使用Ribbon去服务注册中心查询相关服务的ip,进行调用
  2. url:适合本地调试,直接使用http去调用接口

至于原理,看到上面两个重要参数自然了解Fegin原理啦,这里不继续讲下去。

Dubbo理解

也是一个远程调用封装的框架,异步拿到结果,可以进行版本控制so on.

@Reference
这个是dubbo重要注解,标记在消费端。服务端会将服务暴露到zk,客户端进行消费。

个人理解

由于本人平时使用dubbo框架比较少,简单说下,也是使用动态代理的模式,采用异步框架进行相关的请求返回到结果集进行解析。

手写rpc框架

参考文章

首先观摩下大佬烟的文章https://www.cnblogs.com/rjzheng/p/8798556.html
以及一位大佬的文章(使用zk进行注册)https://blog.csdn.net/zhanglei082319/article/details/88930872

个人观后感
大佬烟那一篇使用的是socker,以及动态代理来实现的。(dubbo的影子)
另一篇文章则是类似http请求去获取结果(Fegin的影子)

时序图

在这里插入图片描述
分为两部分,服务端,注册中心zk,消费者。。。

个人的实战

参考了上面两篇文章,主要实现还是第二篇文章,从而进一步了解微服务的远程接口调用过程!

启动zk

这个就不详细说了,(windows)双击zkServer

maven

		<dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.12</version>
        </dependency>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

编写rpc-provider

注册到zk(模拟微服务的注册服务)

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author M
 */
@Component
public class Register {

    @Value("${server.port}")
    private String port;

    //类似服务名
    private static final String SERVER_PATH = "/provide";

    private static final String ZK_ADDRESS = "127.0.0.1:2181";

    private static final int ZK_TIMEOUT = 20000;

    private static final String LOCAL_IP = "127.0.0.1";

    /**
     * 注册
     */
    @PostConstruct
    public void register() {
        try {
            ZooKeeper zooKeeper = new ZooKeeper(ZK_ADDRESS, ZK_TIMEOUT, (WatchEvent) -> {
            });
            Stat stat = zooKeeper.exists(SERVER_PATH, false);
            if (stat == null) {
                zooKeeper.create(SERVER_PATH, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            String path = LOCAL_IP + ":" + port;
            System.out.println("注册的端口:"+port);
            //创建短暂的可排序的子节点
            zooKeeper.create(SERVER_PATH + "/instance", path.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

写死了本地的ip,以及相关的配置,比如路径/provide

请求类

import com.example.demo.entity.Student;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProvideController {

    @RequestMapping(value = "/provide",method = RequestMethod.GET)
    public Student get(){
        return new Student("大鸡腿",2);
    }

}

编写rpc-consumer

读取注册服务

import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

@Component
public class ZookListener {
    private static final String SERVER_PATH = "/provide";

    private static final String ZK_ADDRESS = "127.0.0.1:2181";

    private static final int ZK_TIMEOUT = 20000;

    private ZooKeeper zooKeeper;

    private List<String> paths = new LinkedList<>();

    @PostConstruct
    public void init(){
        try {
            zooKeeper = new ZooKeeper(ZK_ADDRESS, ZK_TIMEOUT, (WatchEvent) -> {
                //监听该节点的变化,如果节点出现变化,则重新获取节点下的ip和端口
                if(WatchEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged &&
                        WatchEvent.getPath().equals(SERVER_PATH)){
                    getChilds();
                }

            });
            getChilds();
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    private  void  getChilds(){
        List<String> ips =new LinkedList<>();
        try {
            //添加监听
            List<String> childs = this.zooKeeper.getChildren(SERVER_PATH, true);
            for(String child : childs){
                byte[] obj = zooKeeper.getData(SERVER_PATH+"/"+child,false,null);
                String path = new String(obj,"utf-8");
                ips.add(path);
            }
            this.paths = ips;
        }catch (Exception e){
            e.printStackTrace();
        }

    }


    public String  getPath(){
        if(paths.isEmpty()){
            return null;
        }
        //这里我们随机获取一个ip端口使用
        int index =  new Random().nextInt(paths.size());
        return paths.get(index);
    }

}

读取之后保持到List,而且进行变化事件的监听。
getPath可以实现负载均衡

调用远程接口

import com.example.demo.entity.Student;
import com.example.demo.util.ZookListener;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class ConsumerController {

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private ZookListener zookListener;

    @RequestMapping(value = "/provide", method = RequestMethod.GET)
    public Student get() throws Exception {
        //从zookeeper中获取调用的ip
        String path = zookListener.getPath();
        if (StringUtils.isEmpty(path)) {
            throw new Exception("对不起,产品暂时停止服务!");
        }
        System.out.println(path);
        return restTemplate.getForObject("http://" + zookListener.getPath() + "/provide", Student.class);
    }


}

这里使用RestTemplate进行远程调用,有没有Fegin的影子,西西~

手写框架相关改造

我们可以使用动态代理进行改造,可以让使用者不关心相关的调用,而直接使用。

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

动态代理

import com.example.demo.entity.Student;
import com.example.demo.util.ZookListener;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author M
 */
@Component
public class ProxyHandler implements InvocationHandler {

    @Resource
    private ZookListener zookListener;

    @Resource
    private RestTemplate restTemplate;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String path = zookListener.getPath();
        if (StringUtils.isEmpty(path)) {
            throw new Exception("对不起,产品暂时停止服务!");
        }
        System.out.println(path);
        return restTemplate.getForObject("http://" + zookListener.getPath() + "/provide", Student.class);
    }

}

当调用代理后会进入invoke方法,请求后将结果返回。

import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Proxy;

@Component
public class RpcConsumer {

    @Resource
    ProxyHandler proxyHandler;

    public <T> T getService(Class<T> clazz) {
        return (T)Proxy.newProxyInstance(RpcConsumer.class.getClassLoader(), new Class<?>[] {clazz}, proxyHandler);
    }

}

调用动态代理。

调用远程接口

import com.example.demo.entity.Student;
import com.example.demo.rpc.RpcConsumer;
import com.example.demo.service.DoSomeThingService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

    @Resource
    RpcConsumer rpcConsumer;

    @Test
    public void contextLoads() {
        DoSomeThingService doSomeThingService= rpcConsumer.getService(DoSomeThingService.class);
        Student result=doSomeThingService.sayHello();
        System.out.println(result);
    }

}

Debug模式进去看看
在这里插入图片描述
当调用到方法的时候进入了代理类,进行相关http请求。

返回结果

{“name”:“大鸡腿”,“age”:2}

@Component理解

中性注解,为了让Spring扫描到该类,类似@Bean进行容器注入。

源码地址

https://github.com/dajitui/rpc-test/tree/master.

发布了213 篇原创文章 · 获赞 31 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/weixin_38336658/article/details/102153733