项目简介: 实现分布式下两台服务器之间的远程过程调用。可以让客户端直接调用服务端方法就像调用本地方法一样简单的框架。通过引入Zookeeper作为服务端的服务管理,借助Zookeeper解耦服务端和客户端的关系,服务端只需要向Zookeeper注册服务,客户端直接从Zookeeper上获取提供服务的服务器信息进行服务调用。
功能特色: (1) 使用Netty作为网络传输框架,利用Netty的高性能传输效率建立客户端和服务端的会话。(2)为了减轻单个服务器的压力,目前项目实现了3种负载均衡算法:随机、轮询和源地址哈希。(3)实现了包括JDK自带序列化在内的3种序列化方式:JDK序列化、Kryo序列化和FastJson序列化。(4)客户端使用动态代理技术,实现服务的无感调用。(5)通过注解加包扫描的方式实现了简单的IOC容器,实现了Bean的容器化管理。
- 在数据的末尾添加特殊的符号标识数据包的边界。通常会加 、 、 或者其他的符号;
- 规定报文的长度,不足则补空位。读取时按规定好的长度来读取。比如 100 字节,如果不够就补空格;
- 将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段,按长度获取数据;(我使用的是这个)
// 如果msg的类型是响应,则先发送一个响应代表的整形数据;否者发送请求代表的code
if (msg instanceof RPCResponse) {
out.writeInt(MessageType.RESPONSE.getCode());
} else if (msg instanceof RPCRequest) {
out.writeInt(MessageType.REQUEST.getCode());
} else {
log.error("待编码的msg类型不符合要求");
return;
}
// 发送序列化code,以便解码器能使用对应的反序列化
out.writeInt(serializer.getCode());
byte[] content = serializer.serialize(msg);
// 写入字节长度,避免粘包问题
out.writeInt(content.length);
out.writeBytes(content);
解码器:
// 首先读取消息的类型,是请求消息还是响应消息
int msgType = in.readInt();
Class> msgClazz = null;
if (msgType == MessageType.REQUEST.getCode()) {
msgClazz = RPCRequest.class;
} else if (msgType == MessageType.RESPONSE.getCode()) {
msgClazz = RPCResponse.class;
}
// 获取序列化的方式
int serializerType = in.readInt();
CommonSerializer serializer = CommonSerializer.getByCode(serializerType);
if (serializer == null) {
log.error("序列化方式不存在");
throw new SerializeException("序列化方式不存在");
}
// 获取消息真正的字节长度
int byteLen = in.readInt();
byte[] content = new byte[byteLen];
in.readBytes(content);
// 反序列化
Object obj = serializer.deserialize(content, msgClazz);
out.add(obj);
负载均衡算法:
随机、轮询和源地址哈希。
随机:
int idx = new Random().nextInt(servers.size());
return servers.get(idx);
轮询:
private static Jedis jedis;
// private static int idx=0;
static {
jedis = new Jedis("192.168.145.132");
}
@Override
public String getServerAddr(List servers) {
// idx++;//线程不安全
Long idx = jedis.incr("lb");//也可以使用atomicInteger类保证线程安全
return servers.get((int) (idx % servers.size()));
}
哈希:
public int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//新的32位 = 原高16位 + 原高16位与原低16位异或。
}
设计模式--动态代理
客户端使用动态代理技术,实现服务的无感调用。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用 一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
这个方法接收三个参数和返回一个Object类型,它们分别代表的意思如下:
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的方法的Method对象
args: 指代的是调用真实对象某个方法时接受的参数
返回的Object是指真实对象方法的返回类型,
Proxy这个类的作用就是用来动态创建一个代理对象。我们经常使用的是newProxyInstance这个方法。
Proxy.newProxyInstance
三个参数:
loader: 用哪个类加载器去加载代理对象
interfaces:动态代理类需要实现的接口
动态代理方法在执行时,会调用h里面的invoke方法去执行



