本文较长,如果想直接看代码可以查看项目源码地址: https://github.com/hetutu5238/rpc-demo.git

  要想实现分布式服务调用框架,我们需要了解分布式服务框架一般需要的功能有哪些。目前要想实现一个最简单的服务调用框架要做到的有以下的功能。

  服务注册与发现,调用过程封装,消费负载均衡,序列化与反序列化,网关(可以用nginx实现)等。本文则从实现这些功能点的步骤出发来模拟一个

  简单的服务调用框架

1.idea中创建父项目rpc-parent,子项目 rpc-common ,rpc-client(测试客户端),rpc-server(测试服务端)

  项目基于maven ,项目创建过程略,效果如下即可

  

  rpc-parent的pom.xml如下即可

  

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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.maglith</groupId>
<artifactId>rpc-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>rpc-server</module>
<module>rpc-client</module>
<module>rpc-common</module>
</modules>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.43.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.4</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

2.下载并启动nacos

  本项目使用nacos作为注册中心

  这个在 https://www.cnblogs.com/hetutu-5238/p/11089577.html 中的第2步已有说明

现在开始编写核心的common项目

  在common项目下创建com.rpc包 ,以下cmmon项目的操作均已该包为基础

1.创建util包,并创建Assert类

public class Assert {

    private Assert(){};

    public static void notNull(Object obj, String message) {
if (obj == null) {
throw new RuntimeException(message);
}
} public static void on(boolean flag, String message) {
if (flag) {
throw new RuntimeException(message);
}
}
}

2.创建anno包,并创建RpcService注解 该注解的主要用来标注提供服务 标注该接口的类必须实现接口

 @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcService { Class value();
}

3. 创建Respository包

  ClientRepository 用来存储服务调用连接

 public class ClientRepository {

     private static Map<String, Channel> repo = new ConcurrentHashMap<>();

     public static void put(String key , Channel channel) {
if ( key == null || channel == null ) {
System.err.println("channel or it's name can't be null ");
return;
}
repo.put(key , channel);
} public static void remove(String key) {
repo.remove(key);
} public static Channel getChannel(String key) {
if ( repo.get(key) != null && !repo.get(key).isActive() ) {
remove(key);
}
return repo.get(key);
}
}

    ServiceRepository 用来存储可提供服务信息

 public class ServiceRepository {

     private static final Map<String, Object> repo = new HashMap<>();

     public static void put(String serviceName , Object service) {
repo.put(serviceName , service);
} public static void remove(String key) {
repo.remove(key);
} public static Object getService(String serviceName) {
return repo.get(serviceName);
} public static Set<String> getAllServiceName() {
return repo.keySet();
}
}

  

  ServiceConnectionFactory用来创建新的调用连接

 public class ServiceConnectionFactory {

     public static Channel createConnection(String host , int port) throws InterruptedException {
String key = host + ":" + port;
System.out.println("创建新连接:" + key);
if ( ClientRepository.getChannel(key) != null ) {
ClientRepository.remove(key);
}
Bootstrap server = new Bootstrap();
Channel c = server.group(workerGroup())
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host , port))
.handler(new MyClientChannelInitializer())
.connect()
.sync()
.channel();
ClientRepository.put(key , c);
return c;
} private static EventLoopGroup workerGroup() {
return new NioEventLoopGroup(10);
}
}

  该类中用的MyClientChannelInitializer会在后面写到

4. 创建config包。并创建RegisterConfig

package com.rpc.config;

import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService; import com.rpc.repository.ServiceRepository;
import com.rpc.util.Assert;
import org.apache.commons.lang3.StringUtils; import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.*; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-28
* @Time: 16:24
*/
public class RegisterConfig {
//配置文件
private static Properties register;
//nacos注册服务
private static NamingService namingService; private static volatile boolean init = false; private static volatile boolean serverInit = false; public static boolean getInit() {
return init;
} public static boolean getServerInit() {
return serverInit;
} /**
* serverFlag为true则代表服务端启动 serverFlag为false则代表客户端启动
* @param serverFlag
*/
public static void init(boolean serverFlag) {
if ( (serverFlag && getServerInit()) || (!serverFlag && getInit()) ) {
return;
}
System.out.println("服务端初始化开始" + new Date());
Properties properties = new Properties();
try (InputStream resourceAsStream = RegisterConfig.class.getClassLoader().getResourceAsStream("register.properties");) {
Assert.notNull(resourceAsStream,"register.properties is required");
properties.load(resourceAsStream);
register = properties;
//获取nacos服务
namingService = NamingFactory.createNamingService(String.valueOf(register.get("register.host")));
if ( serverFlag ){
regis();
serverInit = true;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("服务端初始化结束" + new Date());
init = true;
} public static Integer getPort() {
Assert.notNull(register,"RegisterConfig must be initialized");
return Integer.valueOf(Objects.toString(register.get("server.port")));
} public static NamingService getNamingService() {
Assert.notNull(namingService,"RegisterConfig must be initialized");
return namingService;
} /**
* 注册到注册中心
*/
private static void regis() {
try {
String scanpkgs = String.valueOf(register.get("scanpkgs"));
Assert.on(StringUtils.isBlank(scanpkgs),"scan service package can't be null");
initService(scanpkgs.split(","));
//将服务注册到注册中心
Set<String> serviceNames = ServiceRepository.getAllServiceName();
if ( !serviceNames.isEmpty() ) {
for (String serviceName : serviceNames) {
//将服务注册到注册中心
namingService.registerInstance(serviceName , String.valueOf(register.get("server.host")) , Integer.valueOf(Objects.toString(register.get("server.port"))));
System.out.println(String.format("已注册服务:%s,服务地址为:%s" , register.get("server.host") , register.get("server.port")));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} /**
* 初始化服务
* @param pkgs 需要扫描的包
*/
private static void initService(String[] pkgs) {
List<Class<?>> classes = new ArrayList<>();
//获取所有包名下的符合条件的服务
try {
for (String pkg : pkgs) {
classes.addAll(getRpcClass(pkg));
}
for (Class<?> c : classes) {
Object o = c.newInstance();
//将服务存储到服务仓库
ServiceRepository.put(c.getAnnotation(com.rpc.anno.RpcService.class).value().getName() , o);
}
} catch (Exception e) {
throw new RuntimeException(e);
} } private static List<Class<?>> getRpcClass(String pkg) throws ClassNotFoundException {
List<Class<?>> result = new ArrayList<>();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Assert.notNull(classLoader,"Can't get ClassLoader,Thread: " + Thread.currentThread().getName());
//获取目录
String packageName = pkg.replace('.' , '/');
URL resource = classLoader.getResource(packageName);
Assert.notNull(resource,"Can't get package,package doesn't exist: " + packageName);
String fileUrl = resource.getFile();
File f = new File(fileUrl);
if ( f.exists() ) {
File[] files = f.listFiles();
if ( files != null ) {
for (File file : files) {
String fileName;
//如果是文件
if ( !file.isDirectory() && (fileName = file.getName()).endsWith(".class") ) {
Class<?> c = Class.forName(pkg + "." + fileName.substring(0 , fileName.length() - 6));
if ( c.getAnnotation(com.rpc.anno.RpcService.class) != null ) {
result.add(c);
}
} else if ( file.isDirectory() ) {
List<Class<?>> rpcClass = getRpcClass(pkg + "." + file.getName());
if ( rpcClass != null && !rpcClass.isEmpty() ) {
result.addAll(rpcClass);
}
}
}
}
} else {
throw new RuntimeException("Can't get package,package doesn't exist: " + pkg);
}
return result; } }
该类用来初始化客户端或者服务端,init()方法传入的参数即标识初始化类型
register.properties主要为配置类,在后面的客户端或者服务端项目添加 不要在common项目下添加,内容如下
#必须设置注册中心地址
register.host=127.0.0.1:8848
#如果为服务端则必须设置 设置扫描包 多个包以","分割
scanpkgs=com.rpc
#如果为服务端则必须设置 设置本地服务地址
server.host=127.0.0.1
#如果为服务端则必须设置 设置本地服务端口
server.port=8080

5.创建support包 该包下的类主要为传输类

  RpcRequest 服务请求类

package com.rpc.support;

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLong; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 10:52
*/
public class RpcRequest implements Serializable {
//任务id生成器
private static final AtomicLong REQUEST_ID = new AtomicLong(0);
//请求id
private long id;
//请求的服务名 该项目为接口的全限定名
private String serviceName;
//请求的方法名
private String methodName;
//请求的参数类型
private Class<?>[] paramsTypes;
//请求的参数
private Object[] params; public long getId() {
return id;
} public void setId(long id) {
this.id = id;
} public void newId() {
this.id = REQUEST_ID.getAndIncrement();
} public String getServiceName() {
return serviceName;
} public void setServiceName(String serviceName) {
this.serviceName = serviceName;
} public String getMethodName() {
return methodName;
} public void setMethodName(String methodName) {
this.methodName = methodName;
} public Class<?>[] getParamsTypes() {
return paramsTypes;
} public void setParamsTypes(Class<?>[] paramsTypes) {
this.paramsTypes = paramsTypes;
} public Object[] getParams() {
return params;
} public void setParams(Object[] params) {
this.params = params;
} @Override
public String toString() {
return "RpcRequest{" +
"id=" + id +
", serviceName='" + serviceName + '\'' +
", methodName='" + methodName + '\'' +
'}';
}
}

  RpcResponse类  即请求的回应类

package com.rpc.support;

import java.io.Serializable;

/**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 10:52
*/
public class RpcResponse implements Serializable { //错误信息
private Throwable error;
//返回数据
private Object response;
//对应的请求id
private long id; public long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public Throwable getError() {
return error;
} public void setError(Throwable error) {
this.error = error;
} public Object getResponse() {
return response;
} public void setResponse(Object response) {
this.response = response;
} }

  RpcFuture  该类主要用来存储发送的请求并获得返回结果  ,这儿借鉴了dubbo的封装思路

package com.rpc.support;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-28
* @Time: 10:18
*/
public class RpcFuture {
//存储所有已发送 但还未得到响应的请求
private static final Map<Long, RpcFuture> REPO = new ConcurrentHashMap<>();
//每次请求对应的锁
private final Object lock = new Object();
//超时时间
private final int timeOut = 10;
//任务id
private long id;
//请求内容
private RpcRequest rpcRequest;
//请求对应的相应内容
private volatile RpcResponse rpcResponse; public RpcFuture(long id , RpcRequest rpcRequest) {
this.id = id;
this.rpcRequest = rpcRequest;
} public static void receive(RpcResponse resp) {
RpcFuture remove = REPO.remove(resp.getId());
remove.doRecieve(resp); } public static void putFuture(Long id , RpcFuture rpcFuture) {
REPO.put(id , rpcFuture);
} private boolean done() {
return this.rpcResponse != null;
} private void doRecieve(RpcResponse resp) {
synchronized (lock) {
this.rpcResponse = resp;
//唤醒请求时的等待
lock.notifyAll();
}
} public Object getResponse() {
long millis = System.currentTimeMillis();
//也可以使用BlockingQueue代替锁 但是会使线程阻塞 java8的话竞争不激烈情况下建议synchronized代替显式锁
synchronized (lock) {
if ( !done() ) {
try {
while ( !done() ) {
lock.wait(timeOut * 1000);
//被唤醒后判断下完成或者超时
if ( done() || System.currentTimeMillis() - millis > timeOut * 1000 ) {
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
if ( !done() ) {
throw new RuntimeException("服务已超时 服务id:" + id + " 服务内容:" + rpcRequest);
}
}
} return rpcResponse.getResponse();
}
}

6.创建client包 该包下的类供客户端或服务端调用

  RpcClient 主要为客户端角色时使用,用来传输请求信息

package com.rpc.client;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.rpc.config.RegisterConfig;
import com.rpc.repository.ServiceConnectionFactory;
import com.rpc.support.RpcRequest;
import com.rpc.repository.ClientRepository;
import com.rpc.support.RpcFuture;
import com.rpc.util.Assert;
import io.netty.channel.Channel; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 11:41
*/
public class RpcClient { public static Object transfer(RpcRequest rpcRequest) {
try {
//配置中心已实现负载均衡
RegisterConfig.init(false);
//服务注册中心拿服务 服务中心已实现负载均衡  也可以自己实现负载均衡(轮询/hash方式)
// List<Instance> instances = RegisterConfig.getNamingService().selectInstances(rpcRequest.getServiceName() , true);
// int one = rpcRequest.hashCode()%instances.size();
// Instance ins = instances.get(one);
            Instance ins = RegisterConfig.getNamingService().selectOneHealthyInstance(rpcRequest.getServiceName());
Assert.notNull(ins,"service not be find :"+rpcRequest.getServiceName());
String key = ins.getIp() + ":" + ins.getPort();
Channel c = ClientRepository.getChannel(key);
if ( c == null ) {
//如果系统没有缓存这个服务的连接则创建
String[] split = key.split(":");
c = ServiceConnectionFactory.createConnection(split[ 0 ] , Integer.valueOf(split[ 1 ]));
}
Long id = rpcRequest.getId();
RpcFuture rpcFuture = new RpcFuture(id , rpcRequest);
RpcFuture.putFuture(id , rpcFuture);
c.writeAndFlush(rpcRequest);
return rpcFuture.getResponse();
} catch (NacosException e) {
throw new RuntimeException("register connection error" , e);
} catch (InterruptedException e) {
throw new RuntimeException("register connection error" , e);
} }
}

  Server类主要为服务端使用 用来创建服务 监听信息

package com.rpc.client;

import com.rpc.config.RegisterConfig;
import com.rpc.netty.MyServerChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler; /**
* @Description: 客户1
* @author: zhoum
* @Date: 2019-11-14
* @Time: 10:41
*/
public class Server { private static ServerBootstrap server; private static volatile boolean start = false; public static void init() throws InterruptedException {
if ( start ) {
return;
}
System.err.println("开始初始化 端口:");
//注册服务
RegisterConfig.init(true);
if ( server == null ) {
server = new ServerBootstrap();
start = true;
}
Channel channel = server.group(parentGroup() , workerGroup())
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler())
.childHandler(new MyServerChannelInitializer())
.option(ChannelOption.SO_BACKLOG , 100)
.childOption(ChannelOption.SO_KEEPALIVE , true)
.bind(RegisterConfig.getPort())
.sync()
.channel(); channel.closeFuture().sync(); } private static EventLoopGroup parentGroup() {
return new NioEventLoopGroup(1);
} private static EventLoopGroup workerGroup() {
return new NioEventLoopGroup(10);
} }

  该类中用到的MyServerChannelInitializer类会在后面说明

7.创建netty包  该包主要用来存放 对请求和响应的数据进行序列化,反序列化,以及编码解码工具类,以及对请求的处理和返回结果

 在netty下创建serilaze包,创建序列化接口以及实现类 ,对传输数据进行序列化,本项目目前使用json序列化


package com.rpc.netty.serilaze;

/**
* @Description:
* @author: zhoum
* @Date: 2019-11-28
* @Time: 11:21
*/
public interface Serilazier {
byte[] serialize(Object msg); <T> T deserialize(byte[] bytes , Class<T> clz);
}

package com.rpc.netty.serilaze;

import com.alibaba.fastjson.JSONObject;

import java.nio.charset.Charset;

/**
* @Description:
* @author: zhoum
* @Date: 2019-11-28
* @Time: 11:22
*/
public class JsonSerilizer implements Serilazier { @Override
public byte[] serialize(Object msg) {
return JSONObject.toJSONString(msg).getBytes(Charset.defaultCharset());
} @Override
public <T> T deserialize(byte[] bytes , Class<T> clz) {
return JSONObject.parseObject(new String(bytes , Charset.defaultCharset()) , clz);
}
}

  *  当然也可以使用其他序列化方式,比如想使用hessian序列化的话可以如下    首先引入hessian包

        <dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>

    然后新建hessian序列化类实现序列化接口

public class HessianSerlizer implements Serilazier {
@Override
public byte[] serialize(Object msg) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(out);
try {
hessian2Output.writeObject(msg);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
hessian2Output.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
} return out.toByteArray();
} @Override
public <T> T deserialize(byte[] bytes , Class<T> clz) {
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(in);
try {
T result = (T)hessian2Input.readObject(clz);
return result;
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
hessian2Input.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}

  

  在netty包下创建codec包,在codec包下创建编码解码器  用来对传输的信息进行编码解码

package com.rpc.netty.codec;

import com.rpc.netty.serilaze.Serilazier;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-28
* @Time: 11:45
*/
public class RpcEncoder extends MessageToByteEncoder<Object> { private Serilazier serilazier; public RpcEncoder(Serilazier serilazier) {
this.serilazier = serilazier;
} @Override
protected void encode(ChannelHandlerContext ctx , Object msg , ByteBuf out) {
byte[] serialize = serilazier.serialize(msg);
out.writeInt(serialize.length);
out.writeBytes(serialize);
}
}
package com.rpc.netty.codec;

import com.rpc.netty.serilaze.Serilazier;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-28
* @Time: 11:47
*/
public class RpcDecoder extends ByteToMessageDecoder { private Serilazier serilazier; private Class<?> c; public RpcDecoder(Serilazier serilazier , Class<?> c) {
this.serilazier = serilazier;
this.c = c;
} @Override
protected void decode(ChannelHandlerContext ctx , ByteBuf in , List<Object> out) throws Exception {
if ( in.readableBytes() < 4 ) {
return;
}
in.markReaderIndex();
int dataLength = in.readInt();
if ( dataLength < 0 ) {
ctx.close();
}
if ( in.readableBytes() < dataLength ) {
in.resetReaderIndex();
return;
}
byte[] bytes = new byte[ dataLength ];
in.readBytes(bytes);
Object obj = serilazier.deserialize(bytes , c);
out.add(obj);
}
}

  在netty包下创建RpcInvoker消息处理接口,用来处理消息,并在netty包下创建invoke包,创建RpcRequest请求消息处理器,RpcResponse响应消息处理器

package com.rpc.netty;

import io.netty.channel.Channel;

/**
* @Description:
* @author: zhoum
* @Date: 2019-11-28
* @Time: 17:46
*/
public interface RpcInvoker { void handle(Channel channel , Object object);
}
package com.rpc.netty.invoke;

import com.rpc.netty.RpcInvoker;
import com.rpc.support.RpcRequest;
import com.rpc.support.RpcResponse; import com.rpc.repository.ServiceRepository;
import io.netty.channel.Channel; import java.lang.reflect.Method; /**
* RpcRequest请求处理器
*
* @author: zhoum
* @Date: 2019-11-26
* @Time: 11:50
*/
public class RpcRequestInvoker implements RpcInvoker { @Override
public void handle(Channel channel , Object object) {
RpcResponse res = new RpcResponse();
try {
RpcRequest request;
if ( !(object instanceof RpcRequest) ) {
res.setError(new Exception("params must be instance of com.rpc.support.RpcRequest"));
channel.writeAndFlush(res);
return;
}
request = (RpcRequest) object;
//将请求的id直接赋给响应实体
res.setId(request.getId());
//找到该服务类的处理类
Object service = ServiceRepository.getService(request.getServiceName());
if ( service == null ) {
res.setError(new Exception("can't find service for " + request.getServiceName()));
channel.writeAndFlush(res);
return;
}
//执行方法
Method method = service.getClass().getMethod(request.getMethodName() , request.getParamsTypes());
Object invoke = method.invoke(service , request.getParams());
res.setResponse(invoke);
channel.writeAndFlush(res);
} catch (Exception e) {
res.setError(e);
channel.writeAndFlush(res);
}
} }
package com.rpc.netty.invoke;

import com.rpc.netty.RpcInvoker;
import com.rpc.support.RpcResponse;
import com.rpc.support.RpcFuture;
import io.netty.channel.Channel; /**
* RpcResponse响应处理器
* @author: zhoum
* @Date: 2019-11-26
* @Time: 11:50
*/
public class RpcResponseInvoker implements RpcInvoker { @Override
public void handle(Channel channel , Object object) {
if ( object instanceof RpcResponse ) {
RpcResponse resp = (RpcResponse) object;
//处理响应数据
RpcFuture.receive(resp);
}
} }

  

  在netty包下创建AbstractHandleAdapter类继承ChannelInboundHandlerAdapter来约束必须传入消息处理器,并且创建netty请求处理器与响应处理器。并交由构造函数中的消息处理器处理

package com.rpc.netty;

import io.netty.channel.ChannelInboundHandlerAdapter;

/**
* @Description:
* @author: zhoum
* @Date: 2019-11-29
* @Time: 9:39
*/
public abstract class AbstractHandleAdapter extends ChannelInboundHandlerAdapter { protected RpcInvoker rpcInvoker; public AbstractHandleAdapter(RpcInvoker rpcInvoker) {
this.rpcInvoker = rpcInvoker;
}
}

 

 

package com.rpc.netty;

import io.netty.channel.ChannelHandlerContext;

/**
* @Description: 请求处理适配器
* @author: zhoum
* @Date: 2019-11-14
* @Time: 11:40
*/
public class ChannelServerMessageHandler extends AbstractHandleAdapter { public ChannelServerMessageHandler(RpcInvoker rpcInvoker) {
super(rpcInvoker);
} /**
* 有新连接
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String address = ctx.channel().remoteAddress().toString();
System.out.println("收到信息: " + address);
super.channelActive(ctx);
} /**
* 连接断开
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.err.println("断开连接: " + ctx.channel().remoteAddress().toString());
super.channelInactive(ctx);
} /**
* 读取到的消息
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception {
System.out.println("服务器收到信息" + msg);
//处理器处理
rpcInvoker.handle(ctx.channel() , msg);
} /**
* 出现异常
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx , Throwable cause) throws Exception {
System.err.println("出现异常: " + ctx.channel().remoteAddress().toString());
super.exceptionCaught(ctx , cause);
}
}

 

package com.rpc.netty;

import com.rpc.repository.ClientRepository;
import io.netty.channel.ChannelHandlerContext; import java.net.InetSocketAddress; /**
* netty响应处理适配器
* @Description:
* @author: zhoum
* @Date: 2019-11-14
* @Time: 11:40
*/
public class ChannelClientMessageHandler extends AbstractHandleAdapter { public ChannelClientMessageHandler(RpcInvoker rpcInvoker) {
super(rpcInvoker);
}
/**
* 有新连接
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String address = ctx.channel().remoteAddress().toString();
System.out.println("有新连接:"+address);
super.channelActive(ctx);
} /**
* 连接断开
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress ipSocket = (InetSocketAddress)ctx.channel().remoteAddress();
//移除连接
ClientRepository.remove(ipSocket.getAddress().getHostAddress()+":"+ipSocket.getPort());
System.out.println("连接断开");
super.channelInactive(ctx);
} /**
* 读取到的消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx , Object msg) {
//处理器处理
rpcInvoker.handle(ctx.channel() , msg);
} /**
* 出现异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx , Throwable cause) throws Exception {
System.err.println("出现异常: " + ctx.channel().remoteAddress().toString());
InetSocketAddress ipSocket = (InetSocketAddress)ctx.channel().remoteAddress();
//移除连接
ClientRepository.remove(ipSocket.getAddress().getHostAddress()+":"+ipSocket.getPort());
super.exceptionCaught(ctx , cause);
}
}

最后在netty包下创建 MyClientChannelInitializer ,MyServerChannelInitializer  (填之前的坑) ,这个在netty初始化客户端或者服务端的时候用上

package com.rpc.netty;

import com.rpc.netty.serilaze.JsonSerilizer;
import com.rpc.netty.codec.RpcDecoder;
import com.rpc.netty.codec.RpcEncoder;
import com.rpc.netty.invoke.RpcRequestInvoker;
import com.rpc.support.RpcRequest;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel; /**
* @Description: 服务端处理器
* @author: zhoum
* @Date: 2019-11-14
* @Time: 11:25
*/
public class MyServerChannelInitializer extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
//添加编解码器
pipeline.addLast(new RpcDecoder(new JsonSerilizer() , RpcRequest.class));
pipeline.addLast(new RpcEncoder(new JsonSerilizer()));
//添加消息处理器
pipeline.addLast(new ChannelServerMessageHandler(new RpcRequestInvoker()));
} }
package com.rpc.netty;

import com.rpc.netty.serilaze.JsonSerilizer;
import com.rpc.netty.codec.RpcDecoder;
import com.rpc.netty.codec.RpcEncoder;
import com.rpc.netty.invoke.RpcResponseInvoker;
import com.rpc.support.RpcResponse;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel; /**
* @Description: 客户端处理器
* @author: zhoum
* @Date: 2019-11-14
* @Time: 11:25
*/
public class MyClientChannelInitializer extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
//添加编解码器
pipeline.addLast(new RpcEncoder(new JsonSerilizer()));
pipeline.addLast(new RpcDecoder(new JsonSerilizer() , RpcResponse.class));
//添加消息处理器
pipeline.addLast(new ChannelClientMessageHandler(new RpcResponseInvoker())); } }

8.在util包下创建ProxyUtil 用来构造代理类

package com.rpc.util;

import com.alibaba.fastjson.JSONObject;
import com.rpc.client.RpcClient;
import com.rpc.support.RpcRequest; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 14:09
*/
public class ProxyUtil implements InvocationHandler { public <T> T getProxy(Class<T> t) {
T o = (T) Proxy.newProxyInstance(t.getClassLoader() , new Class<?>[]{t} , this);
return o;
} @Override
public Object invoke(Object proxy , Method method , Object[] args) throws Throwable {
//创建请求
RpcRequest request = new RpcRequest();
//每次请求构造新的id
request.newId();
request.setServiceName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParams(args);
request.setParamsTypes(method.getParameterTypes());
Object resp = RpcClient.transfer(request);
Assert.on(resp == null || resp == null,"返回结果序列化错误");
if ( resp instanceof JSONObject ) {
Class<?> returnType = method.getReturnType();
JSONObject jobj = (JSONObject) resp;
Object o = jobj.toJavaObject(returnType);
return o;
} return resp;
}
}

至此 框架代码已经开发完毕,测试如下

测试阶段

在依旧在common项目下建立test包 然后创建测试接口和测试类

package com.rpc.test;

/**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 14:38
*/
public interface TestService { TestEntity getTest(String username , String password); TestEntity getTest1(String username , String password);
}
package com.rpc.test;

import java.io.Serializable;

/**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 14:38
*/
public class TestEntity implements Serializable { private String username; private String password; public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} @Override
public String toString() {
return "TestEntity{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}

  

  服务端

然后打开rpc-server项目的pom.xml 引入common项目,内容如下即可
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rpc-parent</artifactId>
<groupId>com.maglith</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion> <artifactId>rpc-server</artifactId>
<dependencies>
<dependency>
<artifactId>rpc-common</artifactId>
<groupId>com.maglith</groupId>
<version>1.0-SNAPSHOT</version>
</dependency> </dependencies> </project>

  在rpc-server项目中的resource下创建register.properties文件 内容如下

  

#必须设置注册中心地址
register.host=127.0.0.1:8848
#如果为服务端则必须设置 设置扫描包 多个包以","分割
scanpkgs=com.rpc
#如果为服务端则必须设置 设置本地服务地址
server.host=127.0.0.1
#如果为服务端则必须设置 设置本地服务端口
server.port=8080

  创建上面测试接口的实现类

package com.rpc.netty.invoke;

import com.rpc.anno.RpcService;
import com.rpc.test.TestEntity;
import com.rpc.test.TestService; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 14:40
*/
@RpcService(TestService.class)
public class TestServiceImpl implements TestService {
@Override
public TestEntity getTest(String username , String password) {
TestEntity testEntity = new TestEntity();
testEntity.setUsername(username);
testEntity.setPassword(password);
return testEntity;
} @Override
public TestEntity getTest1(String username , String password) {
TestEntity testEntity = new TestEntity();
testEntity.setUsername(username);
testEntity.setPassword(password);
return testEntity;
}
}

  创建测试主类


package com.rpc.util;

import com.rpc.client.Server;

/**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 14:44
*/
public class RpcServerMain { public static void main(String[] args) throws InterruptedException {
Server.init();
}
}

  点击运行后出现如下即代表服务端初始化成功

  客户端

打开rpc-client项目 依旧引入cmmon项目 内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rpc-parent</artifactId>
<groupId>com.maglith</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion> <artifactId>rpc-client</artifactId>
<dependencies>
<dependency>
<artifactId>rpc-common</artifactId>
<groupId>com.maglith</groupId>
<version>1.0-SNAPSHOT</version>
</dependency> </dependencies> </project>

  在rpc-client项目中的resource下创建register.properties文件 内容如下

#必须设置注册中心地址
register.host=127.0.0.1:8848

  创建调用测试类

 package com.rpc;

 import com.rpc.util.ProxyUtil;
import com.rpc.test.TestEntity;
import com.rpc.test.TestService; /**
* @Description:
* @author: zhoum
* @Date: 2019-11-27
* @Time: 14:41
*/
public class Consumer { public static void main(String[] args) {
ProxyUtil proxyUtil = new ProxyUtil();
TestService proxy = proxyUtil.getProxy(TestService.class);
int i = 1;
while ( i < 500 ) {
TestEntity test = proxy.getTest("你好" + i++ , "helloword" + i);
TestEntity test1 = proxy.getTest1("你好" + i++ , "helloword" + i);
System.out.println("收到信息:" + test);
System.out.println("收到信息:" + test1);
}
}
}

  然后点击运行

  这时候切换到server项目的控制台,会出现如下信息

到此  整个框架已经编写完毕并且测试成功

主要思想即使用netty构造一个rpc调用框架,并使用nacos作为服务注册与发现中心,也可以使用zookeeper,看个人喜好,可以用来理解分布式微服务的思想。

项目的数据流转如下图

java使用netty模拟实现一个类dubbo的分布式服务调用框架的更多相关文章

  1. Dubbo消费方服务调用过程源码分析

    参考:dubbo消费方服务调用过程源码分析dubbo基于spring的构建分析Dubbo概述--调用过程dubbo 请求调用过程分析dubbo集群容错机制代码分析1dubbo集群容错策略的代码分析2d ...

  2. 分布式服务治理框架Dubbo的前世今生及应用实战

    Dubbo的出现背景 Dubbo从开源到现在,已经出现了接近10年时间,在国内各大企业被广泛应用. 它到底有什么魔力值得大家去追捧呢?本篇文章给大家做一个详细的说明. 大规模服务化对于服务治理的要求 ...

  3. 基于Nginx和Zookeeper实现Dubbo的分布式服务

    一.前言 公司的项目基于阿里的Dubbo 微服务框架开发.为了符合相关监管部门的安全要求,公司购买了华东1.华东2两套异地服务器,一套是业务服务器,一套是灾备服务器.准备在这两套服务器上实现 Dubb ...

  4. 高并发架构系列:如何从0到1设计一个类Dubbo的RPC框架

    在过去持续分享的几十期阿里Java面试题中,几乎每次都会问到Dubbo相关问题,比如:“如何从0到1设计一个Dubbo的RPC框架”,这个问题主要考察以下几个方面: 你对RPC框架的底层原理掌握程度. ...

  5. 如何从0到1设计一个类Dubbo的RPC框架

    之前分享了如何从0到1设计一个MQ消息队列,今天谈谈"如何从0到1设计一个Dubbo的RPC框架",重点考验: 你对RPC框架的底层原理掌握程度. 以及考验你的整体RPC框架系统设 ...

  6. Java中如何在另一个类里面使用运行类中的对象,举例说明了一下。

    package 计时器; import java.util.Timer; import java.util.TimerTask; /* * 主要是想在另一个类里面,使用该类的对象,如何使用呢?如何传递 ...

  7. [改善Java代码]不要只替换一个类

    建议20: 不要只替换一个类 我们经常在系统中定义一个常量接口(或常量类),以囊括系统中所涉及的常量,从而简化代码,方便开发,在很多的开源项目中已采用了类似的方法,比如在Struts2中,org.ap ...

  8. 用java的socket来发送一个类

    用socket可以简单的发送一些文本信息,太复杂的可能发送不了,比如图片音频可能要用到http来发送和接收了.最基本的使用socket来发送一个字符串,但有的时候我们希望能够发送一个类,这样serve ...

  9. Java TreeSet集合排序 && 定义一个类实现Comparator接口,覆盖compare方法 && 按照字符串长度排序

    package TreeSetTest; import java.util.Iterator; import java.util.TreeSet; import javax.management.Ru ...

随机推荐

  1. etc/hosts文件详解

    Linux 修改 etc/hosts文件 hosts文件 hosts —— the static table lookup for host name(主机名查询静态表). hosts文件是Linux ...

  2. P & R 11

    要做好floorplan需要掌握哪些知识跟技能? 首先熟悉data flow对摆floorplan 有好处,对于减少chip的congestion 是有帮助的,但是也不是必需的,尤其是EDA工具快速发 ...

  3. iframe重新加载

    方法1: document.getElementById('iframeId').contentWindow.location.reload(true); 方法2: document.getEleme ...

  4. [Linux kali] Kali KDE桌面安装中文输入法 不能登录系统

    #开始 第一次实体机上面安装kali的KDE桌面版本 结果就遇到了很多的BUG 比如这次就是安装中文输入法有问题 这次安装的是fcitx框架的 尝试了 谷歌输入法 还有搜狗输入法 都有这个问题 也就是 ...

  5. 吴裕雄 python 机器学习——模型选择验证曲线validation_curve模型

    import numpy as np import matplotlib.pyplot as plt from sklearn.svm import LinearSVC from sklearn.da ...

  6. 新手指引,php什么是常量、变量、数组、类和对象及方法?

    众所周知,常量.变量.数组.类和对象及方法共同构成了PHP的基石.那么什么是常量?什么是变量?什么是数组?什么是类和对象及方法?我在此谈谈个人浅见,新手指引,高手勿喷. PHP 常量 定义:常量是单个 ...

  7. c++高斯消元法求解线性方程组

    #include<iostream> #include<math.h> #include<string.h> using namespace std; #defin ...

  8. P1149

    这题不难,我写的一个复杂度 $ O(n^2) $ 的递归算法.. #include <bits/stdc++.h> using namespace std; #define rep(i, ...

  9. C语言:去除一个字符串中所有的空格。-函数fun传入形参m,求t=1/2-1/3+1/4.....+1/m的值。-判断形参a指定的矩阵是不是“幻方“。

    //函数fun功能:判断形参a指定的矩阵是不是“幻方“,若是返回1.(”幻方”:每列,每行,对角线,反对角线相加都相等) #include <stdio.h> #define N 3 in ...

  10. [JLOI2009]神秘的生物

    题目链接 题目大意 给定一个\(n*n\)的矩阵,从其中选取恰好一个连通块,使选取的格子所对应的权值和最大. \(n\leq 9\) 解题思路 由于\(n\)特别小,考虑插头dp. 和一般的插头dp不 ...