Netty入门——手写Dubbo框架

手写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,获取接口列表,并得到服务提供者列表
  • 客户端选择某个服务提供者,发送调用请求
  • 服务器接收调用请求,执行实现的方法,并返回调用结果

7 参考源码

dubbo-api 参考源码

dubbo-server 参考源码

dubbo-client 参考源码

猜你喜欢

转载自blog.csdn.net/nalw2012/article/details/100055100