【RPC】自己建一个RPC框架

RPC框架流程图

在这里插入图片描述

Demo结构

 连接Zookeeper

public class ZkConnect {
    //地址 ip:port
    private String zkServer;
    //会话超时时间
    private int sessionTimeout;

    public ZkConnect() {
        super();
        //设置默认值
        this.zkServer="localhost:2181";
        this.sessionTimeout=10000;
    }

    public ZkConnect(String zkServer, int sessionTimeout) {
        this.zkServer = zkServer;
        this.sessionTimeout = sessionTimeout;
    }

    public ZooKeeper getZk() throws Exception{
        return new ZooKeeper(zkServer, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("watch方法在执行中");
            }
        });
    }
}

建立注册中心

功能:(1)将服务注册到注册中心,并且保存到Zookeeper上。(2)客户端调用时,可以从Zookeeper中获得服务的地址、名称,在注册中心找到该服务并调用服务。

/**
 * 注册工具
 * zk连接 remote接口实现 完成rmi拼接
 */
@Data
public class RpcRegistry {

    private ZkConnect zkConnect;

    private String ip;
    private int port;
    /**
     * 注册服务
     * 服务接口类【remote接口的子接口】 服务实现类【实现服务接口类】
     * 访问地址存在zk中
     */
    @SneakyThrows
    public void registryService(Class<? extends Remote> serviceInterface, Remote serviceObject){
        String rmi="rmi://"+ip+":"+port+"/"+serviceInterface.getName();
        //zk节点名
        String path="/rpc/"+serviceInterface.getName();
        List<String> children = zkConnect.getZk().getChildren("/rpc", false);
        if (children.contains(serviceInterface.getName())){
            //节点存在需要删除
            Stat stat = new Stat();
            zkConnect.getZk().getData(path,false,stat);
            zkConnect.getZk().delete(path,stat.getCversion());
        }
        zkConnect.getZk().create(path,rmi.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        //注册服务
        Naming.rebind(rmi,serviceObject);
        System.out.println("注册成功:"+rmi);
    }

    /**
     * 查询、发现服务 根据接口类型 访问zk 获得rmi远程代理对象
     * 1.拼接zk节点名称
     * 2.访问zk的数据
     * 3.根据2的数据创建代理对象
     * @return
     */
    public <T extends Remote> T getService(Class<? extends Remote> serviceInterface) throws Exception {
        String path="/rpc/"+serviceInterface.getName();
        //节点获得的数据是二进制数组,需要转换为String
        byte[] data = zkConnect.getZk().getData(path, false, null);
        String rmi = new String(data);
        System.out.println("rmi:"+rmi);
        //在注册中心找该服务
        Object obj = Naming.lookup(rmi);
        return (T)obj;
    }
}

框架入口

factory需要初始化Zookeeper,建立Zookeeper连接。初始化注册中心,创建RMI注册器
 LocateRegistry.createRegistry(serverPort);

静态的registryService和getProxy方法,是用于快速创建注册服务的。

public class RpcFactory {
    private static final Properties config=new Properties();
    private static final ZkConnect zkConnect;
    private static final RpcRegistry registry;
    //用于读取初始化的配置对象
    private static final Properties services=new Properties();
    /**
     * 初始化,提供配置文件rpc.properties
     * 文件:registry.ip=
     *      registry.port=
     *      zk.server=zk访问地址
     *      zk.session=
     */
    static {
        try {
            InputStream inputStream = RpcFactory.class.getClassLoader().getResourceAsStream("rpc.properties");
            //读取配置文件
            config.load(inputStream);
            String serverIP=config.getProperty("registry.ip")==null?"localhost":config.getProperty("registry.ip");
            int serverPort=config.getProperty("registry.port")==null?9090:Integer.parseInt(config.getProperty("registry.port"));
            String serverZk=config.getProperty("zk.server")==null?"localhost:2181":config.getProperty("zk.server");
            int serverZkSession=config.getProperty("zk.session")==null?10000:Integer.parseInt(config.getProperty("zk.session"));
            //创建连接对象
            zkConnect = new ZkConnect(serverZk, serverZkSession);
            //创建注册器对象
            registry=new RpcRegistry();
            registry.setIp(serverIP);
            registry.setPort(serverPort);
            registry.setZkConnect(zkConnect);
            //创建RMI注册器
            LocateRegistry.createRegistry(serverPort);
            //初始化zk中的父节点
            List<String> children = zkConnect.getZk().getChildren("/", false);
            if(!children.contains("rpc")){
                zkConnect.getZk().create("/rpc",null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            //判断在classpath下是否有 rpc-services.properties【如果有多个服务,可以遍历】
            //接口=实现类
            InputStream servicesInput=RpcFactory.class.getClassLoader().getResourceAsStream("rpc-services.properties");
            if (servicesInput!=null){
                services.load(servicesInput);
                //遍历services
                for (Object key:services.keySet()){
                    Object value=services.get(key);
                    //key是接口的名字 value是实现类的名字
                    Class<Remote> serviceInterface=(Class<Remote>)Class.forName(key.toString());
                    Remote serviceObject=(Remote)Class.forName(value.toString()) .newInstance();
                    //注册接口对象、服务对象
                    registry.registryService(serviceInterface,serviceObject);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            throw new ExceptionInInitializerError(e);
        }
    }
    //快速创建注册服务和创建客户端的静态工具方法
    public static void registryService(Class<? extends Remote> serviceInterface, Remote serviceObject){
        registry.registryService(serviceInterface,serviceObject);
    }

    public static  <T extends Remote> T  getProxy(Class<? extends Remote> serviceInterface) throws Exception{
        return registry.getService(serviceInterface);
    }
}

上面的注册多个接口的rpc-service.properties文件可以见文末补充


Demo 调用RPC框架 

结构

Entity类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private int id;
    private String last_name;
    private String email;
    private int gender;
    private int age;
}

Api接口

需要被客户端调用的接口

public interface UserService extends Remote {
    //模糊查询
    List<User> getUserByName(String name) throws RemoteException;
}

服务端

1、Mapper

@Mapper
public interface UserMapper {
    @Select("SELECT * FROM tbl_employee WHERE last_name LIKE concat('%',#{name},'%')")
    public List<User> selectByName(String name);
}

2、接口的impl

/**
 * 服务对象,只要spring容器管理即可
 * 要用自定义框架,把当前对象和实现接口信息
 * 通过FactoryRpc注册到zk中
 */
@Service
public class UserServiceImpl extends UnicastRemoteObject implements UserService {
    //注册该接口到注册中心
    public UserServiceImpl() throws RemoteException{
        System.out.println("UserServiceImpl对象创建");
        RpcFactory.registryService(UserService.class,this);
        System.out.println("注册服务:UserServiceImpl");
    }

    @Autowired
    private UserMapper userMapper;
    @Override
    public List<User> getUserByName(String name) throws RemoteException {
        return userMapper.selectByName(name);
    }
}

3、rpc.properties

zk.server=你的IP:2181
#注册中心的端口
registry.port=9091

客户端

1、Controller层调用接口UserService

@Controller
public class UserController {
    private UserService userService;

    //在注册中心找userService
    public UserController() throws Exception {
        this.userService= RpcFactory.getProxy(UserService.class);
    }

    @RequestMapping("/getByName")
    @ResponseBody
    public List<User> getUserByName(String name) {
        try {
            List<User> users = userService.getUserByName(name);
            return users;
        } catch (RemoteException e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }
}

RPC框架的service不是用到autowired注入,而是调用factory中的getproxy()方法,在注册中心找到。

2、rpc.properties

zk.server=你的IP:2181

补充:

如果要注册多个接口,可以添加rpc-service.properties

com.jane.service.UserService=com.jane.impl.UserServiceImpl
com.jane.service.CustomerService=com.jane.impl.CustomerServiceImpl

1、服务端注册时,执行

Class.forName("com.jane.RpcFactory");

2、客户端调用服务时,同上的Controller层调用方法一致

该框架要先启动服务端,后启动客户端

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值