Netty实现简单RPC

使用Netty实现简单RPC,主要是netty的使用,所以缓存,服务的暴露等都使用缓存来代替了

netty使用的是5.0+ 所以和4.0+可能有点不同……

下面先看一下主要类:



从两个方面来看,一是netty部分,netty客户端和服务端的实现 二是从调用netty来看,其实也就是代理的实现。

一、netty服务端

因为这里是单线程实现所以服务端和我们平时写的都是一样的。

1、ServerBootstrap 服务引导:

package com.netty.server;

import com.netty.common.constants.Constants;
import com.netty.common.utils.LoadService;
import com.netty.core.ServerInitializerChannel;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * 服务端类,启动时加载暴露的服务
 * 
 * @author whd
 * @date 2017年12月9日 下午1:52:06
 */
public class ServerStart {
	private void init() {
		// 服务端的引导
		ServerBootstrap strap = new ServerBootstrap();
		// 服务端接收事件
		EventLoopGroup boss = new NioEventLoopGroup();
		// 服务端处理事件
		EventLoopGroup work = new NioEventLoopGroup();
		strap.group(boss, work);
		strap.channel(NioServerSocketChannel.class);
		strap.option(ChannelOption.SO_BACKLOG, 1000);
		strap.childHandler(new ServerInitializerChannel());
		try {
			ChannelFuture future = strap.bind(Constants.IP, Constants.PORT).sync();
			future.addListener(future1 -> {
				if(future1.isSuccess()){
					System.out.println("server statr success ……");
				}
			});
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			System.err.println(e.getMessage());
			//在出现异常的时候平滑的释放资源,也就是关闭线程池,正常请情况下不用释放线程资源,因为一直有请求,如果释放了则创建线程又要消耗一部分资源
			boss.shutdownGracefully();
			work.shutdownGracefully();
		}
	}
	public static void main(String[] args) {
		ServerStart start = new ServerStart();
		LoadService.loadService();
		start.init();
	}

}

其中的LoadService就是接口暴露的服务

2、Pipeline配置类:

package com.netty.core;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

/**
 * 服务端channel流处理链
 * 
 * @author whd
 * @date 2017年12月6日 下午9:46:15
 */
public class ServerInitializerChannel extends ChannelInitializer<SocketChannel> {

	@Override
	protected void initChannel(SocketChannel sc) throws Exception {
		ChannelPipeline pipeline = sc.pipeline();
		pipeline.addLast("encoderOfJKD", new EncoderOfJDK());
		pipeline.addLast("decoderOfJDK", new DecoderOfJDK());
		pipeline.addLast("serverChanneHandler", new ServerHandler());
	}

}

Channel通道的处理类,两个编解码一个服务端处理类,这里的编解码的序列化使用了jdk的序列化方式。


3、ChannelHandler处理类即客户端请求的处理:

package com.netty.core;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.netty.common.pojo.RequestMessage;
import com.netty.common.pojo.ResponseMessage;
import com.netty.common.utils.MapperClassInterface;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

/**
 * 服务端处理类,处理客户端请求
 * 
 * @author whd
 * @date 2017年12月9日 下午4:42:14
 */
public class ServerHandler extends ChannelHandlerAdapter {
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws UnsupportedEncodingException, InstantiationException, IllegalAccessException, ClassNotFoundException,
			NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
		RequestMessage info = (RequestMessage) msg;
		String className = MapperClassInterface.instance().get(info.getClassName());
		Object obj = Class.forName(className).newInstance();
		String methodName = info.getMethodName();
		Class<?>[] type = info.getParamType();
		Object[] param = info.getParams();
		Method method = obj.getClass().getMethod(methodName, type);
		Object res = method.invoke(obj, param);
		ResponseMessage<Object> resInfo = new ResponseMessage<>();
		resInfo.setId(info.getId());
		resInfo.setCode(200);
		resInfo.setMsg("ok");
		resInfo.setData(res);
		ctx.write(resInfo);
		System.out.println("server res:"+resInfo.getId()+" "+(String)resInfo.getData());
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) {
		//这里ctx所属的channel就是客户端连接服务端的channel,这里的关闭应该也是这个channel的关闭,close应该是会引发tcp四次挥手
		ctx.flush();
		ctx.close();
	}
}


二、netty客户端

1、请求发送和返回消息处理类

package com.netty.core;

import com.netty.common.pojo.RequestMessage;
import com.netty.common.pojo.ResponseMessage;
import com.netty.common.utils.LocalCache;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

public class ClientHandler extends ChannelHandlerAdapter {
	/** 这个处理类所属的channel */
	private Channel channel;

	/**
	 * 一个channel(通道)有一个对应的channelPipeline所以我们通过channel可以获取到具体处理类的
	 * 这个方法就是在通道注册完后获取这个处理方法所属的channel
	 */
	public void channelRegistered(ChannelHandlerContext ctx) {
		channel = ctx.channel();
	}

	/**
	 * 向channel中write消息 缓存消息id
	 * 
	 * @param obj
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 */
	public void sendMessage(Object obj) throws NoSuchMethodException, SecurityException {
		channel.writeAndFlush(obj);
		RequestMessage requestInfo = (RequestMessage) obj;
		LocalCache cache = LocalCache.instance();
		//缓存消息唯一id即UUID
		cache.put(requestInfo.getId(), null);

	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		ResponseMessage<Object> res = (ResponseMessage<Object>) msg;
		LocalCache cache = LocalCache.instance();
		// 用读取到的响应消息覆盖原来的null值,关联关系UUID
		cache.put(res.getId(), res);
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		// 读完之后关闭channel
		// 这里也不需要关闭,因为在服务端向客户端写完消息后服务端会主动关闭,个人理解按照四次挥手所以服务端有了则客户端不用再close了
		// ctx.close();
	}

	// 出现异常触发该方法
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		System.err.println("clientHandler error:" + cause.getMessage());
		ctx.close();
	}
}

其中sendMessage是我们自己订单的,在代理类中调用该方法进行发送消息,而channelRead方法是用来获取服务端返回消息的


2、客户端Pipeline类

package com.netty.core;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

/**
 * netty 客户端channel 流处理链
 * 
 * @author whd
 * @date 2017年12月6日 下午9:45:36
 */
public class ClientInitializerChannel extends ChannelInitializer<SocketChannel> {

	@Override
	protected void initChannel(SocketChannel sc) throws Exception {
		ChannelPipeline pipeline = sc.pipeline();
		pipeline.addLast("encoderOfJKD", new EncoderOfJDK());
		pipeline.addLast("decoderOfJDK", new DecoderOfJDK());
		pipeline.addLast("clientChanneHandler", new ClientHandler());
	}
}

这里同样是编解码类和客户端自定义处理类!


3、客户端引导类

package com.netty.client;

import com.netty.common.constants.Constants;
import com.netty.common.pojo.RequestMessage;
import com.netty.common.utils.LocalCache;
import com.netty.core.ClientHandler;
import com.netty.core.ClientInitializerChannel;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * netty客户端引导类
 * 
 * @author whd
 * @date 2017年12月10日 下午5:55:22
 */
public class ClientStart {

	private ClientStart() {
	};

	private static class InnerClass {
		private static final ClientStart clientStart = new ClientStart();
	}

	public static ClientStart instance() {
		return InnerClass.clientStart;
	}

	/**
	 * 配置客户端信息并连接服务端
	 * 
	 * @param message
	 * @throws InterruptedException
	 */
	public void init(RequestMessage message) throws InterruptedException {
		Bootstrap bootstrap = new Bootstrap();
		EventLoopGroup work = new NioEventLoopGroup();
		bootstrap.group(work);
		bootstrap.channel(NioSocketChannel.class);
		bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
		bootstrap.handler(new ClientInitializerChannel());
		try {
			ChannelFuture future = bootstrap.connect(Constants.IP, Constants.PORT);
			// 对连接添加监听
			future.addListener(status -> {
				if (status.isSuccess()) {
					// 从通道获取属于该通道的处理链,从处理链中获取具体的一个处理类实例
					ClientHandler handler = future.channel().pipeline().get(ClientHandler.class);
					// 发送消息,其实也就是调用channel.write()向channel中写入消息
					handler.sendMessage(message);
				}
			});
			// 处理客户端向服务端的请求和服务端返回数据的获取的线程
			Object obj = null;
			long total;
			long begin = System.currentTimeMillis();
			do {
				// 从缓存中按照UUID获取对象
				obj = LocalCache.instance().get(message.getId());
				total = System.currentTimeMillis() - begin;
				// 首先netty是非阻塞的其次网络传输是有延迟的所以这里要等待一下
			} while (null == obj && total < Constants.MAX_WAIT_MILLSECOND);
		} finally {
			// 这个资源一定要释放否则,运行循环1000多次就会内存溢出
			work.shutdownGracefully();
		}
	}
}

(1)、在客户端和往常的demo不同的应该也就是客户端自定义处理类和引导类,其中的处理类中我们添加了一个sendMessage方法,这个方法使用channel的write方法来发送客户端请求,

(2)、而引导类中不同的就是我们通过ChannelFuture 获得channel在从channel中获得该channel的Pipeline,在从这个处理链中获得具体的我们定义的处理类,这样我们就能在这里主动调用处理类中的sendMessage发送消息了!

(3)、当发送完消息后我们拿着UUID从缓存中获取返回的消息对象,但是服务端的处理和网络有延时所以我们要等待一下,所以这里添加了do{}while().

(4)、最后我们要注意,finally{} 我们一定要释放资源,个人经历如果这里不释放资源则客户端循环调用1000次左右内存用完(eden 使用率100% ParOldGen 使用率100%然后本机cpu爆满,内存用完……)


OK这里我们netty相关的完了,下面看看java的动态代理是怎么调用netty来发送请求的!

三、为接口生成动态代理,调用Netty发送请求

1、InvocationHandler实现类

package com.netty.common.utils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.UUID;

import com.netty.client.ClientStart;
import com.netty.common.pojo.RequestMessage;
import com.netty.common.pojo.ResponseMessage;

/**
 * 处理类,其实接口代理类方法中最终都会调用这里的invoke方法来执行相关处理
 * 
 * @author whd
 * @date 2017年12月9日 下午4:25:55
 */
public class SendMessageProxy implements InvocationHandler {
	private SendMessageProxy() {
	}

	/**
	 * 静态内部类实现单列
	 * 
	 * @author whd
	 * @date 2017年12月9日 下午4:27:14
	 */
	private static class InnerClass {
		private static final SendMessageProxy proxy = new SendMessageProxy();
	}

	/**
	 * 获取实例
	 * 
	 * @return
	 */
	public static SendMessageProxy instance() {
		return InnerClass.proxy;
	}

	/**
	 * 接口方法的归宿
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 组装请求实体
		RequestMessage message = new RequestMessage();
		// 消息唯一id
		message.setId(UUID.randomUUID().toString());
		message.setClassName(method.getDeclaringClass().getName());
		message.setMethodName(method.getName());
		message.setParamType(method.getParameterTypes());
		message.setParams(args);
		//启动一个netty客户端,调用方法发送请求
		ClientStart.instance().init(message);
		ResponseMessage<?> response;
		LocalCache localCache = LocalCache.instance();
		//按照UUID从缓存中获取服务端返回数据
		response = localCache.get(message.getId());
		//释放资源
		localCache.remove(message.getId());
		message = null;
		if (null == response) {
			return null;
		}
		return response.getData();
	}

}

2、获取接口动态代理类

package com.netty.common.utils;

import java.lang.reflect.Proxy;

/**
 * 代理类
 * 
 * @author whd
 * @date 2017年12月9日 下午4:25:18
 */
@SuppressWarnings("unchecked")
public class SendMessage {

	private SendMessage() {
	};

	private static class InnerClass {
		private static final SendMessage message = new SendMessage();
	}

	public static SendMessage instance() {
		return InnerClass.message;
	}

	/**
	 * 获取接口代理类
	 * 
	 * @param classInfo
	 * @return
	 */
	public <T> T execture(Class<T> classInfo) {
		SendMessageProxy sendMessageProxy = SendMessageProxy.instance();
		T t = (T) Proxy.newProxyInstance(classInfo.getClassLoader(), new Class[] { classInfo }, sendMessageProxy);
		return t;
	}
}

3、接口调用相应方法

(1)、生成接口代理类

(2)、代理类调用方法触发invoke方法

(3)、invoke方法中获取请求信息,组装请求对象、调用netty方法发送请求、从缓存获取返回结果、return 结果

import java.io.IOException;

import com.netty.common.pojo.Person;
import com.netty.common.utils.LocalCache;
import com.netty.common.utils.SendMessage;

public class Test {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		long staret = System.currentTimeMillis();
		for (int i = 0; i < 20000; i++) {
			long begin = System.currentTimeMillis();
			SendMessage messages = SendMessage.instance();
			Person person = messages.execture(Person.class);
			Object res = person.say(" hello " + i);
			System.err.println("res:" + String.valueOf(res == null ? "is null" : res));
			LocalCache localCache = LocalCache.instance();
			int size = localCache.getSize();
			System.out.println("LocalCache size:" + size);
			long end = System.currentTimeMillis();
			System.out.println("total time :" + (end - begin) + "ms");
		}
		long ends = System.currentTimeMillis();
		System.out.println("2w耗时:" + (ends - staret));
	}
}


执行结果:

res:Student hello 19999
LocalCache size:7
total time :8ms
2w耗时:294166
在2w次的请求中有7次请求失败,每次从客户端发送请求到获取响应对象耗时5-8ms左右,2w请求的所有执行时间在5分钟左右,这些都是在自己的一天机器上跑的,所以网络延时当然是没有了……

OK 到这里这个简单的rpc就完成了,个人能力有限就先学到这里,之后当然会有多线程、zookeeper暴露服务的版本……


总结:

还是总结一下,因为写的时候有几个地方要注意一下,不然可能会出现内存等问题:

1、服务端的线程池在正常执行的情况下是不用关闭的,只有在出现异常的时候关闭,因为服务端一直会收到客户端的请求,所以复用线程池节省资源。

try {
			ChannelFuture future = strap.bind(Constants.IP, Constants.PORT).sync();
			future.addListener(future1 -> {
				if(future1.isSuccess()){
					System.out.println("server statr success ……");
				}
			});
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			System.err.println(e.getMessage());
			//在出现异常的时候平滑的释放资源,也就是关闭线程池,正常请情况下不用释放线程资源,因为一直有请求,如果释放了则创建线程又要消耗一部分资源
			boss.shutdownGracefully();
			work.shutdownGracefully();
		}


2、在处理完客户端的请求后服务端可以发起关闭连接请求,这样在客户端就不用重复发起关闭请求了

@Override
	public void channelReadComplete(ChannelHandlerContext ctx) {
		//这里ctx所属的channel就是客户端连接服务端的channel,这里的关闭应该也是这个channel的关闭,close应该是会引发tcp四次挥手
		ctx.flush();
		ctx.close();
	}

3、客户端每次请求后好关闭这次创建的线程池

try {
			ChannelFuture future = bootstrap.connect(Constants.IP, Constants.PORT);
			// 对连接添加监听
			future.addListener(status -> {
				if (status.isSuccess()) {
					// 从通道获取属于该通道的处理链,从处理链中获取具体的一个处理类实例
					ClientHandler handler = future.channel().pipeline().get(ClientHandler.class);
					// 发送消息,其实也就是调用channel.write()向channel中写入消息
					handler.sendMessage(message);
				}
			});
			// 处理客户端向服务端的请求和服务端返回数据的获取的线程
			Object obj = null;
			long total;
			long begin = System.currentTimeMillis();
			do {
				// 从缓存中按照UUID获取对象
				obj = LocalCache.instance().get(message.getId());
				total = System.currentTimeMillis() - begin;
				// 首先netty是非阻塞的其次网络传输是有延迟的所以这里要等待一下
			} while (null == obj && total < Constants.MAX_WAIT_MILLSECOND);
		} finally {
			// 这个资源一定要释放否则,运行循环1000多次就会内存溢出
			work.shutdownGracefully();
		}

finally中的关闭一定要执行,客户端端和服务端不同,客户端没请求一次会创建一个服务该channel的线程池,所以当channel关闭的时候要释放服务于该channel的线程池,否则后果很严重……

4、NioSocketChannel 和NioServerSocketChannel 之间有关闭单是不关联,NioSocketChannel  就是说底层使用socketChannel 而NioServerSocketChannel 则是使用serverSocketChannel,如果你看了之前复用器的文章你应该明白socketChannel 是客户端向服务端的连接,客户端向服务端的请求和服务端的响应都是通过socketChannel 来传递的,而serverSocketChannel则是监听端口,也就是监听有没有客户端连接,这样说来他们是没有关联的。

但是还是有联系的serverSocketChannel可以监听到socketChannel所以我们也是从serverSocketChannel中获取socketChannel这个对象,来从中获取请求消息和从服务器向客户端写消息,socketChannel是双向的即可写也可读。

代码下载:http://download.csdn.net/download/qh_java/10153097


展开阅读全文

没有更多推荐了,返回首页