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>