手写RPC框架--3.搭建服务注册与发现目录结构

RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧)
RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧)

搭建服务注册与发现目录结构

搭建服务注册与发现目录结构

工程一旦变成一个微服务的工程,就意味着每一个特定的服务可能存在多个实例,就是同一个服务部署在多个节点上,这样可以提升服务的可用性,也就是挂了一个还能接着用

1.服务注册:在服务提供方启动的时候,将对外暴露的接口注册到注册中心中,注册中心将这个服务节点的IP和接口保存下来

2.服务订阅:在服务调用方启动的时候,去注册中心查找并订阅服务提供方的IP,然后缓存到本地,并用于后续的远程调用。

a.基于ZooKeeper的服务发现

服务发现的本质,就是完成接口跟服务提供者IP直接的映射 。注册中心也需要完成实时变更推送

利用ZooKeeper的Watcher机制完成服务订阅与服务发现功能

b.搭建基础工程

1.创建目录

  • demo下的api / client-demo / provider-demo
    • consumer-demo 和 provider-demo 都需要导入api的module
  • framework下的common / core
  • manager

2.在父工程中,编写基础的依赖管理

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dcy</groupId>
    <artifactId>dcyrpc</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <modules>
        <module>dcyrpc-demo</module>
        <module>dcyrpc-framework</module>
        <module>dcyrpc-framework/dcyrpc-common</module>
        <module>dcyrpc-framework/dcyrpc-core</module>
        <module>dcyrpc-manager</module>
    </modules>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <netty.version>4.1.89.Final</netty.version>
        <junit.version>4.13.2</junit.version>
        <zookeeper.version>3.8.2</zookeeper.version>
        <logback.version>1.4.8</logback.version>
        <fastjson2.version>2.0.34</fastjson2.version>
        <commons-lang3.version>3.12.0</commons-lang3.version>
        <lombok.version>1.18.8</lombok.version>
    </properties>

    <dependencies>
    </dependencies>

    <!--父工程版本管理-->
    <dependencyManagement>
        <dependencies>
            <!--netty-->
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>${netty.version}</version>
            </dependency>
            <!--junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <!--zookeeper-->
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>${zookeeper.version}</version>
            </dependency>
            <!--logback-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logback.version}</version>
            </dependency>
            <!--fastjson-->
            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>${fastjson2.version}</version>
            </dependency>
            <!--commons-lang3-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons-lang3.version}</version>
            </dependency>
            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

3.创建.gitignore文件 (通用)

xml 复制代码
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
logs/

### VS Code ###
.vscode/

4.commit and push to master

5.创建dev分支,切换至dev

6.在core的pom.xml中,添加netty依赖

7.提交dev分支

8.后期开发均使用dev分支

c.基础代码

1.在api模块下创建DcyRpc接口

java 复制代码
package com.dcyrpc;

public interface DcyRpc {

    /**
     * 通用接口,server和client都需要依赖
     * @param msg 发送的具体消息
     * @return
     */
    String sayHi(String msg);

}

2.在provider-demo中创建impl包的 DcyRpcImpl类 实现接口

java 复制代码
package com.dcyrpc.impl;

public class DcyRpcImpl implements DcyRpc {
    @Override
    public String sayHi(String msg) {
        return "hi consumer: " + msg;
    }
}

3.在服务提供方provider-demo中创建Application

java 复制代码
package com.dcyrpc;

public class Application {
    public static void main(String[] args) {
        // 服务提供方:需要注册服务,启动服务
        // 1.封装要发布的服务
        ServiceConfig<DcyRpc> service = new ServiceConfig<>();
        service.setInterface(DcyRpc.class);
        service.setRef(new DcyRpcImpl());

        // 2.定义注册中心

        // 3.通过启动引导程序,启动服务提供方
        //  (1) 配置 -- 服务应用的名称 / 注册中心 / 序列化协议 / 压缩方式
        //  (2) 发布服务
        DcyRpcBootstrap.getInstance()
                // 配置应用名称
                .application("first-dcyrpc-provider")
                // 配置注册中心
                .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
                // 配置服务使用的协议
                .protocol(new ProtocolConfig("jdk"))
                // 发布服务
                .publish(service)
                // 启动服务
                .start();

    }
}

4.在服务消费者consumer-demo中创建Application

java 复制代码
package com.dcyrpc;

public class Application {
    public static void main(String[] args) {
        // 服务消费者:获取代理对象
        // 使用ReferenceConfig进行封装
        // reference一定用生成代理的模板方法,get()
        ReferenceConfig<DcyRpc> reference = new ReferenceConfig<>();
        reference.setInterface(DcyRpc.class);

        // 代理做些什么:
        //   1.连接注册中心
        //   2.拉取服务列表
        //   3.选择一个服务并建立连接
        //   4.发送请求:携带一些信息(接口名,参数列表,方法名),获得结果
        DcyRpcBootstrap.getInstance()
                .application("first-dcyrpc-consumer")
                .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
                .reference(reference);

        // 获取一个代理对象
        DcyRpc dcyRpc = reference.get();
        dcyRpc.sayHi("你好");
    }
}

d.编写架子工程

1.在framework下的core模块中,定义 DcyRpcBootstrap

java 复制代码
@Slf4j
public class DcyRpcBootstrap {

    private static DcyRpcBootstrap dcyRpcBootstrap = new DcyRpcBootstrap();

    private DcyRpcBootstrap(){
        // 构造启动引导程序时,需要做一些什么初始化的事
    }

    public static DcyRpcBootstrap getInstance() {
        return dcyRpcBootstrap;
    }

    /**
     * 定义当前应用的名字
     * @param applicationName 应用名称
     * @return
     */
    public DcyRpcBootstrap application(String applicationName) {
        return this;
    }

    /**
     * 配置一个注册中心
     * @param registryConfig 注册中心
     * @return this
     */
    public DcyRpcBootstrap registry(RegistryConfig registryConfig) {
        return this;
    }

    /**
     * 配置当前暴露的服务使用的协议
     * @param protocolConfig 协议的封装
     * @return this
     */
    public DcyRpcBootstrap protocol(ProtocolConfig protocolConfig) {
        if (log.isDebugEnabled()) {
            log.debug("当前工程使用了:{}协议进行序列化", protocolConfig.toString());
        }
        return this;
    }

    /**
     * --------------------------------服务提供方的相关api--------------------------------
     */

    /**
     * 发布服务:将接口与匹配的实现注册到服务中心
     * @param service 封装需要发布的服务
     * @return
     */
    public DcyRpcBootstrap publish(ServiceConfig<?> service) {
        if (log.isDebugEnabled()) {
            log.debug("服务{},已经被注册", service.getInterface().getName());
        }
        return this;
    }

    /**
     * 批量发布服务
     * @param service 封装需要发布的服务集合
     * @return this
     */
    public DcyRpcBootstrap publish(List<?> service) {
        return this;
    }

    /**
     * 启动netty服务
     */
    public void start() {
    }


    /**
     * --------------------------------服务调用方的相关api--------------------------------
     */

    public DcyRpcBootstrap reference(ReferenceConfig<?> reference) {
        // 配置reference,将来调用get方法时,方便生成代理对象
        return this;
    }

}

2.在framework下的core模块中,定义 ServiceConfig

java 复制代码
public class ServiceConfig<T> {

    // 接口
    private Class<T> interfaceProvider;

    // 具体实现
    private Object ref;

    public Class<T> getInterface() {
        return interfaceProvider;
    }

    public void setInterface(Class<T> interfaceProvider) {
        this.interfaceProvider = interfaceProvider;
    }

    public Object getRef() {
        return ref;
    }

    public void setRef(Object ref) {
        this.ref = ref;
    }
}

3.在framework下的core模块中,定义 ReferenceConfig

java 复制代码
public class ReferenceConfig<T> {

    private Class<T> interfaceRef;

    public Class<T> getInterface() {
        return interfaceRef;
    }

    public void setInterface(Class<T> interfaceConsumer) {
        this.interfaceRef = interfaceConsumer;
    }

    public T get() {
        // 使用动态代理完成工作
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class[] classes = new Class[]{interfaceRef};

        // 使用动态代理生成代理对象
        Object helloProxy = Proxy.newProxyInstance(classLoader, classes, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("hello proxy");
                return null;
            }
        });

        return (T) helloProxy;
    }
}

4.在framework下的core模块中,定义 RegistryConfig

java 复制代码
public class RegistryConfig {

    private String connectString;

    public RegistryConfig(String connectString) {
        this.connectString = connectString;
    }
}

5.在framework下的core模块中,定义 ServiceConfig

java 复制代码
public class ServiceConfig<T> {
    // 接口
    private Class<T> interfaceProvider;
    // 具体实现
    private Object ref;

    public Class<T> getInterface() {
        return interfaceProvider;
    }

    public void setInterface(Class<T> interfaceProvider) {
        this.interfaceProvider = interfaceProvider;
    }

    public Object getRef() {
        return ref;
    }

    public void setRef(Object ref) {
        this.ref = ref;
    }
}

6.在provider-demo下的resources目录中,创建logback.xml

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="pattern" value="%d{HH:mm:ss.SSS} %msg%n"/>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改成 System.err-->
        <target>System.out</target>
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

e.创建zookeeper基础目录结构

注册服务

dcyrpc-metadata (持久节点)
 L providers (持久节点)
 	L service (持久节点:接口的全限定名)
 		L node1 [data]	(节点名:ip:port) (数据:相关的特性数据/配置等)
 		L node2 [data]
 		L node3 [data]
 L consumers
 	L service
 		L node1 [data]
 		L node2 [data]
 		L node3 [data]
 L config
.........

1.导入相关的依赖包到相关的module中

xml 复制代码
<dependencies>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>com.dcy</groupId>
        <artifactId>dcyrpc-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

2.在manager的com.dcyrpc下创建Application

java 复制代码
/**
 * 注册中心的管理页面
 */
@Slf4j
public class Application {
    public static void main(String[] args) {

        // 创建zookeeper实例
        ZooKeeper zooKeeper = ZookeeperUtils.createZookeeper();

        // 定义节点和数据
        String basePath = "/dcyrpc-metadata";
        String providePath = basePath + "/providers";
        String consumerPath = basePath + "/consumers";

        ZookeeperNode baseNode = new ZookeeperNode(basePath, null);
        ZookeeperNode provideNode = new ZookeeperNode(providePath, null);
        ZookeeperNode consumerNode = new ZookeeperNode(consumerPath, null);

        // 创建节点
        List.of(baseNode, provideNode, consumerNode).forEach(node -> {
            ZookeeperUtils.createNode(zooKeeper, node, null, CreateMode.PERSISTENT);
        });

        // 关闭连接
        ZookeeperUtils.close(zooKeeper);
    }
}

3.在common的com.dcyrpc下创建exceptions工具类:exceptions.ZookeeperException类 (未写具体内容)

java 复制代码
public class ZookeeperException extends RuntimeException{
}

4.在common的com.dcyrpc下创建Constants

java 复制代码
public class Constant {
    // zookeeper的默认连接地址
    public static final String DEFAULT_ZK_CONNECT = "127.0.0.1:2181";

    // zookeeper默认的超时时间
    public static final int TIME_OUT = 10000;
}

5.在common的com.dcyrpc下创建utils.zookeeper工具类:ZookeeperUtils

java 复制代码
@Slf4j
public class ZookeeperUtils {

    /**
     * 使用默认配置创建Zookeeper实例
     * @return zookeeper实例
     */
    public static ZooKeeper createZookeeper() {
        // 定义连接参数
        String connectString = Constant.DEFAULT_ZK_CONNECT;
        // 定义超时时间 10秒
        int sessionTimeout = Constant.TIME_OUT;

        return createZookeeper(connectString, sessionTimeout);
    }

    /**
     * 使用自定义配置创建Zookeeper实例
     * @param connectionString
     * @param timeout
     * @return
     */
    public static ZooKeeper createZookeeper(String connectionString, int timeout) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        // 定义连接参数
        String connectString = Constant.DEFAULT_ZK_CONNECT;
        // 定义超时时间 10秒
        int sessionTimeout = Constant.TIME_OUT;

        try {
            // 创建zookeeper实例,简历连接
            final ZooKeeper zooKeeper = new ZooKeeper(connectString, sessionTimeout, event -> {
                if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                    System.out.println("客户端连接成功");
                    countDownLatch.countDown();
                }
            });

            // 等待连接成功
            countDownLatch.await();
            
            return zooKeeper;
        } catch (IOException | InterruptedException e) {
            log.error("创建zookeeper实例时发生异常:",e);
            throw new ZookeeperException();
        }
    }
}

5.在common的com.dcyrpc下创建节点工具类:ZookeeperNode

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ZookeeperNode {

    private String nodePath;
    private byte[] data;

}

6.在ZookeeperUtils类中创建createNode方法:创建zookeeper的节点的工具方法

java 复制代码
/**
 * 创建zookeeper的节点的工具方法
 * @param zooKeeper zookeeper实例
 * @param node 节点
 * @param watcher watcher实例
 * @param createMode 节点的类型
 * @return true:成功创建  false:已经存在  异常:抛出
 */
public static Boolean createNode(ZooKeeper zooKeeper, ZookeeperNode node, Watcher watcher, CreateMode createMode) {
    try {
        if (zooKeeper.exists(node.getNodePath(), watcher) == null){
            String result = zooKeeper.create(node.getNodePath(), node.getData(), ZooDefs.Ids.OPEN_ACL_UNSAFE, createMode);
            log.info("节点{},已经成功创建", result);
            return true;
        }else {
            if (log.isDebugEnabled()) {
                log.info("节点{},已经存在,无需创建", node.getNodePath());
            }
        }
    } catch (KeeperException | InterruptedException e) {
        log.error("创建基础目录时,产生异常,如下:",e);
        throw new ZookeeperException();
    }

    return false;
}

7.在ZookeeperUtils类中创建close方法:关闭zookeeper的方法

java 复制代码
/**
 * 关闭zookeeper
 * @param zooKeeper
 */
public static void close(ZooKeeper zooKeeper) {
    try {
        zooKeeper.close();
    } catch (InterruptedException e) {
        log.error("关闭zookeeper时发生异常:",e);
        throw new ZookeeperException();
    }
}

8.在manager的resource目录下创建logback.xml文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="pattern" value="%d{HH:mm:ss.SSS} %msg%n"/>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改成 System.err-->
        <target>System.out</target>
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>
相关推荐
P.H. Infinity16 分钟前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天20 分钟前
java的threadlocal为何内存泄漏
java
caridle32 分钟前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^37 分钟前
数据库连接池的创建
java·开发语言·数据库
苹果醋341 分钟前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花1 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端1 小时前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan1 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
全栈开发圈1 小时前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫