066:基于Netty+Zookeeper手写Dubbo框架(下)
1 回顾上节课手写Dubbo环境思路
课程内容:
1.基于Netty+Zookeeper实现服务注册
2.基于策略模式手写Dubbo负载均衡器
3.基于Netty+Zookeeper实现服务发现
4.完全百分百实现Dubborpc远程调用框架
2 Dubbo原理分析之Dubbo协议
发布的接口注册到zk节点上,key为接口路径地址,value为连接地址;
连接地址不一致(例如端口号改变)会和之前的地址一起形成集群;
dubbo一个接口中存在多个方法,也只会生成一个连接
eg:客户端使用的是前部分的ip端口和接口地址,后部分给dubbo-admin解析配置。dubbo://10.16.10.87:20880/com.mayikt.api.service.UserService?anyhost=true&application=mayikt-provider&dubbo=2.5.3&interface=com.mayikt.api.service.UserService&methods=delUser,getUser&pid=17608&side=provider×tamp=1612427752096
DubboAdmin部署流程
将dubbo-admin放入到tomcat webapps目录下,修改dubbo-admin/WEB-INF/dubbo.properties zk连接地址,运行tomcat。
访问http://127.0.0.1:8080/dubbo-admin/,账号root,密码root123。
3 自定义RPC注解发布Dubbo服务接口
手写Dubbo核心思想与代码
mayikt-netty-dubbo-server — dubbo核心的服务
netty-dubbo-common —工具类
member-service-producer-api – 会员服务接口
member-service-producer-impl –会员服务接口实现
order-consumer-impl –订单消费者
netty-dubbo-common
/**
* 相当于dubbo服务注册,替代xml
*/
@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcAnnotation {
Class value();
}
member-service-producer-api
public interface MemberService {
/**
* 公共的api接口
*
* @param userId
* @return
*/
String getUser(Long userId);
int delUser(Long userId);
}
member-service-producer-impl
@RpcAnnotation(MemberService.class)
public class MemberServiceImpl implements MemberService {
@Override
public String getUser(Long userId) {
return "mayikt";
}
@Override
public int delUser(Long userId) {
return 666;
}
}
4 将本地服务信息注册到zk上
Dubbo实现思想流程
- 把需要发布的服务(会员服务)注册到zk,注册成功后启动netty;
- 订单服务去zk找到地址再通过本地实现rpc调用;
- netty服务端接收到参数再通过反射执行方法。
服务端
public class MayiktRpcServer {
private ServiceRegistration serviceRegistration;
private String host;
private int port;
private Map<String, Object> handlerMap = new HashMap<String, Object>();
public MayiktRpcServer(String host, int port) {
this.host = host;
this.port = port;
serviceRegistration = new ServiceRegistrationImpl();
}
/**
* 核心流程
* bind() 将本地服务注册到zk
* 启动netty服务器端实现监听
*/
public void bind(Object obj) {
// 获取类上面是否有加上rpc注解
RpcAnnotation declaredAnnotation = obj.getClass().getDeclaredAnnotation(RpcAnnotation.class);
if (declaredAnnotation == null) {
// 抛出异常
return;
}
// 获取到service_name
String serviceName = declaredAnnotation.value().getName();
String serviceAddress = "mayikt://" + host + ":" + port;
serviceRegistration.register(serviceName, serviceAddress);
// obj已经实例化出了对象,存map集合后续handler解析出地址直接拿对象不用再new一个
handlerMap.put(serviceName, obj);
}
private void nettyStart() {
/**
* 客户端创建两个线程池组分别为 boss线程组和工作线程组
*/
// 用于接受客户端连接的请求 (并没有处理请求)
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// 用于处理客户端连接的读写操作
NioEventLoopGroup workGroup = new NioEventLoopGroup();
// 用于创建我们的ServerBootstrap
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 监听handler
socketChannel.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
socketChannel.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
socketChannel.pipeline().addLast(new DubboServerHandler(handlerMap));
}
});
// 绑定我们的端口号码
try {
// 绑定端口号,同步等待成功
ChannelFuture future = serverBootstrap.bind(port).sync();
System.out.println("服务器启动成功:" + port);
// 等待服务器监听端口
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 优雅的关闭连接
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public void start(Object object){
// 将服务注册到zk上
bind(object);
// 启动netty服务
nettyStart();
}
}
5 生产者启动Netty服务器端监听消息
消费者调用生产者需要一个非常详细的地址:
mayikt://192.168.x.x:8080/com.mayikt.service.userService.getUser 参数、类型、返回值
传递的消息对象
@Data
@AllArgsConstructor
public class RpcRequest implements Serializable {
private static final long SerialVersionUID = 1L;
// mayikt://192.168.1.1:8080/com.mayikt.api.service.UserService.getUser Long 10 序列化成对象
/**
* 类的className
*/
private String className;
/**
* 方法名称
*/
private String methodName;
/**
* 参数类型
*/
Class<?> parameterTypes[];
/**
* 参数value
*/
Object paramsValue[];
@Override
public String toString() {
return className + "," + methodName + "," + parameterTypes + paramsValue;
}
}
public class DubboServerHandler extends ChannelInboundHandlerAdapter {
private Map<String, Object> handlerMap;
public DubboServerHandler(Map<String, Object> handlerMap) {
this.handlerMap = handlerMap;
}
/**
* 服务端监听客户端发送消息
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 获取请求对象
RpcRequest rpcRequest = (RpcRequest) msg;
System.out.println("服务器端获取到消息:" + rpcRequest.toString());
// 采用反射机制执行实现类的方法
if (rpcRequest == null) {
return;
}
String serviceName = rpcRequest.getClassName();
Object objectImpl = handlerMap.get(serviceName);
if (objectImpl == null) {
return;
}
// 使用反射机制执行目标方法
Method method = objectImpl.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());
Object result = method.invoke(objectImpl, rpcRequest.getParamsValue());
// 返回结果
System.out.println("服务器端响应给客户端:" + result);
ctx.writeAndFlush(result);
}
}
6 消费端从zk上获取注册地址信息
mayikt-netty-dubbo-server
public interface ServiceDiscover {
/**
* 地址可能存在多个
* @param serviceName
* @return
*/
List<String> getDiscover(String serviceName);
}
public class ServiceDiscoverImpl implements ServiceDiscover {
/**
* zk连接地址
*/
private final String zkServers = "127.0.0.1";
/**
* 会话时间
*/
private final int connectionTimeout = 5000;
/***
* zkClient
*/
private ZkClient zkClient;
private String rootNamePath = "/mayikt_rpc";
public ServiceDiscoverImpl() {
zkClient = new ZkClient(zkServers, connectionTimeout);
}
@Override
public List<String> getDiscover(String serviceName) {
List<String> children = zkClient.getChildren(rootNamePath + "/" + serviceName + "/providers");
return children;
}
// 负载均衡器 轮询、权重、取模、随机、哈希
}
7 基于策略模式实现负载均衡轮询机制
public interface LoadBalance {
/**
* 实现Dubbo 负载均衡器 轮询、随机、一致性hash
* @param repos
* @return
*/
String select(List<String> repos);
}
public class LoopLoadBalance implements LoadBalance {
private int index;
@Override
public synchronized String select(List<String> repos) {
if (index > repos.size()) {
index = 0;
}
String value = repos.get(index++);
System.out.println("value:" + value);
return value;
}
}
public class RandomLoadBalance implements LoadBalance {
@Override
public String select(List<String> repos) {
String value = repos.get(new Random().nextInt(repos.size()));
System.out.println("value:" + value);
return value;
}
}
8 消费端启动Netty发送消息给服务器端
public class RpcClientProxy {
public <T> T create(final Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 使用代理类拼接地址
ServiceDiscover serviceDiscover = new ServiceDiscoverImpl();
String serviceName = interfaceClass.getName();
List<String> discover = serviceDiscover.getDiscover(serviceName);
// 默认使用轮询负载均衡器
LoopLoadBalance loopLoadBalance = new LoopLoadBalance();
// 获取到地址 zk存的值 mayikt://192.168.x.x:8080
String select = URLDecoder.decode(loopLoadBalance.select(discover));
String[] split = select.split(":");
String host = split[1].replace("//","");
String port = split[2];
// 封装具体调用的参数
RpcRequest rpcRequest = new RpcRequest(serviceName, method.getName(), method.getParameterTypes(), args);
// 启动netty客户端发送消息
return sendMsg(host, Integer.parseInt(port), rpcRequest);
}
});
}
public Object sendMsg(String host, int port, final RpcRequest rpcRequest) {
final DubboClientHandler dubboClientHandler = new DubboClientHandler();
//创建nioEventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
final Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
ch.pipeline().addLast(dubboClientHandler);
}
});
try {
// 发起同步连接
ChannelFuture sync = bootstrap.connect().sync();
// 客户端发送我们的对象
System.out.println("生产者发送消息:" + rpcRequest.toString());
sync.channel().writeAndFlush(rpcRequest);
sync.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
return dubboClientHandler.getResponse();
}
}
public class DubboClientHandler extends ChannelInboundHandlerAdapter {
private Object response;
public Object getResponse() {
return response;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.response = msg;
// 客户端获取服务器端响应之后应该主动关闭连接
System.out.println("客户端获取到服务器端响应:" + this.response);
ctx.close();
}
}
9 生产与消费端解决发送消息序列化问题
使用marshalling实现编码器
public final class MarshallingCodeCFactory {
/**
* 创建Jboss Marshalling解码器MarshallingDecoder
* @return MarshallingDecoder
*/
public static MarshallingDecoder buildMarshallingDecoder() {
//首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
//创建了MarshallingConfiguration对象,配置了版本号为5
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
//根据marshallerFactory和configuration创建provider
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
//构建Netty的MarshallingDecoder对象,俩个参数分别为provider和单个消息序列化后的最大长度
MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1);
return decoder;
}
/**
* 创建Jboss Marshalling编码器MarshallingEncoder
* @return MarshallingEncoder
*/
public static MarshallingEncoder buildMarshallingEncoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
//构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
引入依赖
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling</artifactId>
<version>1.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling-serial</artifactId>
<version>1.4.10.Final</version>
</dependency>
Netty服务端/客户端引入编码器
socketChannel.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
socketChannel.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
10 生产者核心代码启动断点调试分析
public class MemberProducer {
public static void main(String[] args) {
new MayiktRpcServer("127.0.0.1",8080).start(new MemberServiceImpl());
}
}
断点调试结果:
11 调试消费端发送消息给生产者端
public class Consumer {
public static void main(String[] args) {
// 使用代理模式生成代理类
MemberService memberService = new RpcClientProxy().create(MemberService.class);
String user = memberService.getUser(10L);
System.out.println("result:" + user);
int delUser = memberService.delUser(1L);
System.out.println("delUser:" + delUser);
}
}
断点调试:
备注:断点调试服务端收到的消息有bug,服务器端直接运行就是正常情况。
运行结果: