手写Dubbo框架
1 简介
Dubbo框架本身就是一个RPC框架。RPC的服务提供者将自身的服务名、IP和端口存放在服务协调器ZK的某个节点下,服务消费者通过监听ZK的该节点,获取可调用的服务名,以及服务提供者的IP和端口信息。
本文使用netty实现一个简单的Dubbo框架,使用zk作为注册服务器,完成RPC的整个过程。
2 实现步骤
2.1 服务端实现步骤
Dubbo服务提供者的实现步骤如下:
1、在zk上创建持久节点/dubboregistry。
2、在zk的/dubboregistry节点下以服务名创建持久节点。
3、在zk的服务节点下,创建服务器提供者临时节点,以IP和端口信息作为临时节点名称。
2.2 消费端实现步骤
Dubbo服务消费者的实现步骤如下:
1、获取zk节点/dubboregistry 下的子节点,得到可调用的远程服务列表。
2、监听所以子节点,读取子节点的IP和端口。
3、通过负载均衡选择一个服务提供者。
4、消费者发现调用消息给服务提供者,并接受返回的结果作为调用结果。
3 创建dubbo-api工程
api工程主要定义业务接口,通用模型和常量。
3.1 业务接口
// 业务接口
public interface SomeService {
String doSome(String city);
}
3.2 调用模型
@Data
public class InvokeMessage implements Serializable {
// 服务名称
private String serviceName;
// 方法名
private String methodName;
// 参数列表
private Class<?>[] paramTypes;
// 方法参数值
private Object[] paramValues;
}
3.3 常量定义
public class ApiConstant {
public static final String ZK_CLUSTER = "localhost:12181";
public static final String ZK_DUBBO_PATH = "/dubboregistry";
}
4 创建dubbo-server工程
4.1 添加依赖
包含api、netty、zkclient
<dependencies>
<!--api-->
<dependency>
<groupId>com.hornsey</groupId>
<artifactId>11-dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--netty-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!--zkclient-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
4.2 添加zk注册接口
添加接口
public interface RegistryCenter {
/**
* 将服务注册到zk
* @param serviceName
* @param serviceAddress
*/
void register(String serviceName, String serviceAddress);
}
添加实现类
- 添加zk客户端
// zkclient
private CuratorFramework curator;
public ZKRegistryCenter() {
// 创建客户端
curator = CuratorFrameworkFactory.builder()
.connectString(ApiConstant.ZK_CLUSTER)
.sessionTimeoutMs(3000)
.retryPolicy(new RetryNTimes(3, 2000))
.build();
// 启动客户端
curator.start();
}
- 实现注册函数
@Override
public void register(String serviceName, String serviceAddress) {
String servicePath = ApiConstant.ZK_DUBBO_PATH + "/" + serviceName;
try {
if (curator.checkExists().forPath(servicePath) == null) {
curator.create()
// 父节点不存在先创建
.creatingParentContainersIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath(servicePath, "0".getBytes());
}
String addressPath = servicePath + "/" + serviceAddress;
String hostNode = curator.create()
.withMode(CreateMode.EPHEMERAL)
.forPath(addressPath);
System.out.println("hostNode = " + hostNode);
} catch (Exception e) {
e.printStackTrace();
}
}
测试
直接在实现类加个main函数,作为测试代码
public static void main(String[] args) {
RegistryCenter registryCenter = new ZKRegistryCenter();
registryCenter.register("com.hornsey.dubbo.api.TestService", "127.0.0.1:8888");
}
测试结果
使用zk客户端连接,查看结果,如下
4.3 添加服务提供者
API的接口实现
public class SomeServiceImpl implements SomeService {
@Override
public String doSome(String city) {
return "Welcome to " + city;
}
}
添加服务提供者Server
服务器提供者扫描指定包内的类,将接口注册到一个集合,并同时注册到zk,供消费者查询。同时,作为Server,监听来自消费者的请求,处理接口调用,并返回结果。
- 解析指定包
public void getProviderClass(String providerPackage) {
URL resource = this.getClass().getClassLoader()
.getResource(providerPackage.replace(".", "/"));
File dir = new File(resource.getFile());
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
getProviderClass(providerPackage + "." + file.getName());
} else if (file.getName().endsWith(".class")){
String fileName = file.getName().replace(".class", "");
classCache.add(providerPackage + "." + fileName);
}
}
System.out.println(classCache);
}
- 注册接口到zk
private void doRegister(RegistryCenter registryCenter, String serviceAddress)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (classCache.size() == 0) {
return;
}
boolean isRegisted = false;
for (String className : classCache) {
Class<?> clazz = Class.forName(className);
String interfaceName = clazz.getInterfaces()[0].getName();
registryMap.put(interfaceName, clazz.newInstance());
if (!isRegisted) {
registryCenter.register(interfaceName, serviceAddress);
isRegisted = true;
}
}
System.out.println("registryMap = " + registryMap);
}
- 发布服务
public void publish(RegistryCenter registryCenter, String serviceAddress,
String providerPackage) throws Exception {
// 扫描指定包下的所有实现类:要将指定包下的class添加到一个集合
getProviderClass(providerPackage);
// 将服务注册到zk
doRegister(registryCenter, serviceAddress);
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE,
ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new RpcServerHandler(registryMap));
}
});
String ip = serviceAddress.split(":")[0];
String port = serviceAddress.split(":")[1];
ChannelFuture future = bootstrap.bind(ip, Integer.valueOf(port)).sync();
System.out.println("MicService register success, port " + port);
future.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
- 接口调用处理函数
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Receive from Client [" + ctx.channel().remoteAddress() + "], msg = [" + msg + "]");
if (msg instanceof InvokeMessage) {
InvokeMessage message = (InvokeMessage) msg;
Object result = "Provider has no method.";
if (registryMap.containsKey(message.getServiceName())) {
Object provider = registryMap.get(message.getServiceName());
result = provider.getClass()
.getMethod(message.getMethodName(),message.getParamTypes())
.invoke(provider, message.getParamValues());
}
ctx.writeAndFlush(result);
ctx.close();
}
}
添加启动函数
public class RpcServerStarter {
public static void main(String[] args) throws Exception {
String serviceAddress = "127.0.0.1:8088";
String packageName = "com.hornsey.dubbo.service";
RpcServer rpcServer = new RpcServer();
rpcServer.publish(new ZKRegistryCenter(), serviceAddress, packageName);
}
}
启动后在zk查看,结果如下,注册成功。
5 创建dubbo-client工程
5.1 添加依赖
dubblo-client依赖同dubbo-server。
5.2 添加服务发现实现
创建服务器发现接口
public interface ServiceDiscovery {
/**
* 根据服务名称返回提供者IP+port
* @param serviceName
* @return
*/
String discover(String serviceName);
}
创建zkclient
实现服务发现接口,在实现类中添加zkclient。
public ServiceDiscoveryImpl() {
this.curator = CuratorFrameworkFactory.builder()
.connectString(ApiConstant.ZK_CLUSTER)
.sessionTimeoutMs(3000)
.retryPolicy(new RetryNTimes(3, 2000))
.build();
curator.start();
}
获取服务子节点列表
@Override
public String discover(String serviceName) {
String servicePath = ApiConstant.ZK_DUBBO_PATH + "/" + serviceName;
try {
servers = curator.getChildren().forPath(servicePath);
if (servers.size() == 0) {
return null;
}
registerWatch(servicePath);
} catch (Exception e) {
e.printStackTrace();
}
return new RandomLoadBalance().choose(servers);
}
监听服务子节点
// 向指定路径子节点添加watcher
private void registerWatch(String servicePath) throws Exception {
PathChildrenCache cache = new PathChildrenCache(curator, servicePath, true);
cache.getListenable().addListener((curatorFramework, pathChildrenCacheEvent) ->
servers = curatorFramework.getChildren().forPath(servicePath));
cache.start();
}
5.3 添加随机负载均衡实现
创建负载均衡接口
public interface LoadBalance {
String choose(List<String> services);
}
实现随机负载均衡函数
public class RandomLoadBalance implements LoadBalance {
@Override
public String choose(List<String> services) {
int index = new Random().nextInt(services.size());
return services.get(index);
}
}
5.4 添加客户端代理实现
创建代理
public static <T> T create(Class<?> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
new Class[]{
clazz},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
return rpcInvoke(clazz, method, args);
}
});
}
调用方法
private static Object rpcInvoke(Class<?> clazz, Method method, Object[] args) {
ServiceDiscovery serviceDiscovery = new ServiceDiscoveryImpl();
String serverAddress = serviceDiscovery.discover(clazz.getName());
if (serverAddress == null) {
return null;
}
RpcClientHandler handler = new RpcClientHandler();
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE,
ClassResolvers.cacheDisabled(null)));
pipeline.addLast(handler);
}
});
String ip = serverAddress.split(":")[0];
String port = serverAddress.split(":")[1];
try {
ChannelFuture future = bootstrap.connect(ip, Integer.valueOf(port)).sync();
InvokeMessage message = new InvokeMessage();
message.setServiceName(clazz.getName());
message.setMethodName(method.getName());
message.setParamTypes(method.getParameterTypes());
message.setParamValues(args);
future.channel().writeAndFlush(message);
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
return handler.getResult();
}
5.5 添加客户端启动函数
public static void main(String[] args) {
SomeService someService = RpcProxy.create(SomeService.class);
String result = someService.doSome("Beijing");
System.out.println(result);
}
启动执行,正常结果如下:
Server: /127.0.0.1:8088
Welcome to Beijing
6 总结
手写dubbo总体做了这几件事:
- 服务端解析指定包,注册接口到zk,并将接口与方法存入集合
- 客户端监听zk,获取接口列表,并得到服务提供者列表
- 客户端选择某个服务提供者,发送调用请求
- 服务器接收调用请求,执行实现的方法,并返回调用结果