距离写的代码的时间比较久了,在这里只是简单的总结介绍一下。
简单实现了基于Netty的RPC框架并将其注册到Nacos,介绍内容包含序列化,自定义协议,负载均衡算法,Nacos相关服务。
文末有源码链接。
著名的分布式服务框架Dubbo使用Dubbo协议进行节点间通信,而Dubbo协议默认使用Netty作为基础通信组件。还有Zookeeper,RocketMQ等底层rpc通讯也使用的是Netty。因此学习Netty对掌握这些框架原理还是比不可少的。
序列化:
为实现传输数据和通信,序列化是比不可少的,选择合适的序列化算法是非常重要的,通常要选择性能高,生成字节数少的算法。这里我准备了以下实现:JDK自带的,Json ,还有一个貌似当初因为时间关系没实现的Protobuf,以后有时间补上。
/**
* JDK原生序列化
*/
Java{
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
log.error("Failed to serialize deserialize java");
e.printStackTrace();
}
return null;
}
@Override
public <T> byte[] serialize(T object) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
return bos.toByteArray();
} catch (IOException e) {
log.error("Failed to serialize serialize java");
e.printStackTrace();
}
return null;
}
},
/**
* Json序列化
*/
Json{
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new SerializeType.ClassCodec()).create();
//Gson gson=new Gson();
String json = new String(bytes, StandardCharsets.UTF_8);
log.debug("deserialize--{}",json);
System.out.println("json--deserialize--"+json);
return gson.fromJson(json, clazz);
}
@Override
public <T> byte[] serialize(T object) {
//Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new SerializeType.ClassCodec()).create();
Gson gson=new Gson();
String json = null;
try {
json = gson.toJson(object);
} catch (Exception e) {
e.printStackTrace();
}
log.debug("serialize--{}",json);
System.out.println("json--serialize--"+json);
return json.getBytes(StandardCharsets.UTF_8);
}
},
/**
* google Protobuf 序列化
*/
Protobuf{
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
return null;
}
@Override
public <T> byte[] serialize(T object) {
return new byte[0];
}
};
/**
* 自定义类型转换器,解决 Gson String序列化
*/
class ClassCodec implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
@Override
public Class<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
String str = json.getAsString();
return Class.forName(str);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
@Override // String.class
public JsonElement serialize(Class<?> src, Type typeOfSrc, JsonSerializationContext context) {
// class -> json
return new JsonPrimitive(src.getName());
}
}
自定义的协议:
@ChannelHandler.Sharable
public class MessageCodec extends MessageToMessageCodec<ByteBuf, Message> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Message message, List<Object> outList) throws Exception {
ByteBuf out = channelHandlerContext.alloc().buffer();
//魔数 5字节
out.writeBytes(new byte[]{'b','a','i','y','e'});
//版本号 1字节
out.writeByte(1);
//序列化方式 1字节
out.writeByte(Config.SERIALIZER.ordinal());
//消息类型 1字节
out.writeByte(message.getMessageType());
// 消息序列号 8字节
out.writeBytes(Longs.toByteArray(message.getSequenceId()));
//序列化后的消息内容
byte[] bytes = new byte[0];
try {
bytes = Config.SERIALIZER.serialize(message);
} catch (Exception e) {
e.printStackTrace();
}
//消息长度
out.writeInt(bytes.length);
//写入消息内容
out.writeBytes(bytes);
outList.add(out);
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
byte[] magic=new byte[5];
//魔数 5字节
byteBuf.readBytes(magic,0,magic.length);
//版本号 1字节
byte version=byteBuf.readByte();
//序列化方式 1字节
byte serializationType = byteBuf.readByte();
//消息类型 1字节
byte messageType=byteBuf.readByte();
//消息序列号 8字节
long sequenceId=byteBuf.readLong();
//消息长度
int length=byteBuf.readInt();
//消息具体内容
byte[] bytes = new byte[length];
byteBuf.readBytes(bytes, 0, length);
//反序列化后的消息内容
SerializeType type = SerializeType.values()[serializationType];
Class<? extends Message> messageClass = Message.getMessageClass(messageType);
Message message = null;
try {
message = type.deserialize(messageClass, bytes);
} catch (Exception e) {
e.printStackTrace();
}
list.add(message);
}
}
负载均衡算法:实现了两个分别是:随机和轮换
public interface LoadBalancer {
Instance getInstance(List<Instance> instances);
}
public class RandomLoadBalance implements LoadBalancer{
private static final Random random = new Random();
@Override
public Instance getInstance(List<Instance> instances) {
return instances.get(random.nextInt(instances.size()));
}
}
public class RoundRobinLoadBalance implements LoadBalancer{
private AtomicInteger atomicInteger=new AtomicInteger(0);
@Override
public Instance getInstance(List<Instance> instances) {
int current;
int next;
do{
current=atomicInteger.get();
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
}while (!atomicInteger.compareAndSet(current,next));
return instances.get(next%instances.size());
}
}
解决粘包/拆包的方法:
Netty对粘包和拆包问题的处理
Netty对解决粘包和拆包的方案做了抽象,提供了一些解码器(Decoder)来解决粘包和拆包的问题。如:
LineBasedFrameDecoder:以行为单位进行数据包的解码;
DelimiterBasedFrameDecoder:以特殊的符号作为分隔来进行数据包的解码;
FixedLengthFrameDecoder:以固定长度进行数据包的解码;
LenghtFieldBasedFrameDecode:适用于消息头包含消息长度的协议(最常用);
主要思想是添加一个长度字段,用于记录偏移量。
public class ProcotolFrameDecoder extends LengthFieldBasedFrameDecoder {
public ProcotolFrameDecoder() {
this(1024, 16, 4, 0, 0);
}
public ProcotolFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
}
关于在Nacos获取服务的部分:
自动扫描注册
public static void registerTogNacos(){
Set<Class<?>> classes = ServicesFactory.map.keySet();
for (Class clazz:classes
) {
try {
NacosRegistryCentre.namingService.registerInstance(clazz.getName(),Config.IP,Config.PORT);
} catch (NacosException e) {
log.error("{} 注册nacos异常",clazz.getName());
e.printStackTrace();
}
}
}
public class NacosRegistryCentre implements RegistryCentre{
/** dubbo代码 内存中的缓存notified是ConcurrentHashMap里面又嵌套了一个Map,外层Map的key是消费者的URL,内层Map的key是分类,包含providers、consumers、coutes、configurators四种。
* value则是对应的服务列表,对于没有服务提供者提供服务的URL,它会以特殊的empty://前缀开头。
服务实例缓存,初始化时从注册中心初始化一次 */
private static ConcurrentHashMap<URL, Map<String,List<URL>>> notified = new ConcurrentHashMap<>();
/** 如同dubbo的缓存 服务名 分组 实例 */
private static ConcurrentHashMap<String, Map<String,List<URL>>> instancesCache = new ConcurrentHashMap<>();
private final RoundRobinLoadBalance roundRobinLoadBalance=new RoundRobinLoadBalance();
public static NamingService namingService;
static {
try {
namingService = NamingFactory.createNamingService(Config.NACOS_SERVER_ADDR);
} catch (NacosException e) {
log.error("Failed to connect to nacos");
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public Instance getServices(String serviceName, String GroupName) {
try {
List<Instance> allInstances = namingService.getAllInstances(serviceName, GroupName);
return roundRobinLoadBalance.getInstance(allInstances);
} catch (NacosException e) {
e.printStackTrace();
}
throw new RuntimeException("no services efficient");
}
@Override
public Instance getServices(String serviceName) {
try {
List<Instance> allInstances = namingService.getAllInstances(serviceName);
return roundRobinLoadBalance.getInstance(allInstances);
} catch (NacosException e) {
e.printStackTrace();
}
throw new RuntimeException("no services efficient");
}
}