概述
網(wǎng)絡(luò)傳輸和序列化這兩部分的功能相對來說是非常通用并且獨(dú)立的,在設(shè)計的時候,只要能做到比較好的抽象,這兩部的實現(xiàn),它的通用性是非常強(qiáng)的。不僅可以用于 RPC 框架中,同樣可以直接拿去用于實現(xiàn)消息隊列,或者其他需要互相通信的分布式系統(tǒng)中。
我們先來實現(xiàn)序列化和反序列化部分,因為后面的部分會用到序列化和反序列化。
設(shè)計實現(xiàn)
通用的序列化接口
首先我們需要設(shè)計一個可擴(kuò)展的,通用的序列化接口,為了方便使用,我們直接使用靜態(tài)類的方式來定義這個接口(嚴(yán)格來說這并不是一個接口)
public class SerializeSupport {
public static <E> E parse(byte [] buffer) {
// ...
}
public static <E> byte [] serialize(E entry) {
// ...
}
}
- parse 方法用于反序列化
- serialize 方法用于序列化
比如
// 序列化
MyClass myClassObject = new MyClass();
byte [] bytes = SerializeSupport.serialize(myClassObject);
// 反序列化
MyClass myClassObject1 = SerializeSupport.parse(bytes);
通用的序列化實現(xiàn)【推薦】 vs 專用的序列化實現(xiàn)
在講解序列化和反序列化的時候說過,可以使用通用的序列化實現(xiàn),也可以自己來定義專用的序列化實現(xiàn)。
- 專用的序列化性能最好,但缺點是實現(xiàn)起來比較復(fù)雜,你要為每一種類型的數(shù)據(jù)專門編寫序列化和反序列化方法。
- 一般的 RPC 框架采用的都是通用的序列化實現(xiàn),比如 gRPC 采用的是 Protobuf 序列化實現(xiàn),Dubbo 支持 hession2 等好幾種序列化實現(xiàn)
為什么這些 RPC 框架不像消息隊列一樣,采用性能更好的專用的序列化實現(xiàn)呢?這個原因很簡單,消息隊列它需要序列化數(shù)據(jù)的類型是固定的,只是它自己的內(nèi)部通信的一些命令。但 RPC 框架,它需要序列化的數(shù)據(jù)是,用戶調(diào)用遠(yuǎn)程方法的參數(shù),這些參數(shù)可能是各種數(shù)據(jù)類型,所以必須使用通用的序列化實現(xiàn),確保各種類型的數(shù)據(jù)都能被正確的序列化和反序列化。
我們這里還是采用專用的序列化實現(xiàn),主要的目的是一起來實踐一下,如何來實現(xiàn)序列化和反序列化
專用序列化接口定義
public interface Serializer<T> {
/**
* 計算對象序列化后的長度,主要用于申請存放序列化數(shù)據(jù)的字節(jié)數(shù)組
* @param entry 待序列化的對象
* @return 對象序列化后的長度
*/
int size(T entry);
/**
* 序列化對象。將給定的對象序列化成字節(jié)數(shù)組
* @param entry 待序列化的對象
* @param bytes 存放序列化數(shù)據(jù)的字節(jié)數(shù)組
* @param offset 數(shù)組的偏移量,從這個位置開始寫入序列化數(shù)據(jù)
* @param length 對象序列化后的長度,也就是{@link Serializer#size(java.lang.Object)}方法的返回值。
*/
void serialize(T entry, byte[] bytes, int offset, int length);
/**
* 反序列化對象
* @param bytes 存放序列化數(shù)據(jù)的字節(jié)數(shù)組
* @param offset 數(shù)組的偏移量,從這個位置開始寫入序列化數(shù)據(jù)
* @param length 對象序列化后的長度
* @return 反序列化之后生成的對象
*/
T parse(byte[] bytes, int offset, int length);
/**
* 用一個字節(jié)標(biāo)識對象類型,每種類型的數(shù)據(jù)應(yīng)該具有不同的類型值
*/
byte type();
/**
* 返回序列化對象類型的Class對象。
*/
Class<T> getSerializeClass();
}
這個接口中,除了 serialize 和 parse 這兩個序列化和反序列化兩個方法以外,還定義了下面這幾個方法:
- size 方法計算序列化之后的數(shù)據(jù)長度,用于事先來申請存放序列化數(shù)據(jù)的字節(jié)數(shù)組;
- type 方法定義每種序列化實現(xiàn)的類型,這個類型值也會寫入到序列化之后的數(shù)據(jù)中,主要的作用是在反序列化的時候,能夠識別是什么數(shù)據(jù)類型的,以便找到對應(yīng)的反序列化實現(xiàn)類;
- getSerializeClass 這個方法返回這個序列化實現(xiàn)類對應(yīng)的對象類型,目的是,在執(zhí)行序列化的時候,通過被序列化的對象類型找到對應(yīng)序列化實現(xiàn)類
序列化實現(xiàn)
利用這個 Serializer 接口,我們就可以來實現(xiàn) SerializeSupport 這個支持任何對象類型序列化的通用靜態(tài)類了。
首先我們定義兩個 Map,這兩個 Map 中存放著所有實現(xiàn) Serializer 接口的序列化實現(xiàn)類
private static Map<Class<?>/*序列化對象類型*/, Serializer<?>/*序列化實現(xiàn)*/> serializerMap = new HashMap<>();
private static Map<Byte/*序列化實現(xiàn)類型*/, Class<?>/*序列化對象類型*/> typeMap = new HashMap<>();
- serializerMap 中的 key 是序列化實現(xiàn)類對應(yīng)的序列化對象的類型,它的用途是在序列化的時候,通過被序列化的對象類型,找到對應(yīng)的序列化實現(xiàn)類。
- typeMap 的作用和 serializerMap 是類似的,它的 key 是序列化實現(xiàn)類的類型,用于在反序列化的時候,從序列化的數(shù)據(jù)中讀出對象類型,然后找到對應(yīng)的序列化實現(xiàn)類
理解了這兩個 Map 的作用,實現(xiàn)序列化和反序列化這兩個方法就很容易了。這兩個方法的實現(xiàn)思路是一樣的,都是通過一個類型在這兩個 Map 中進(jìn)行查找,查找的結(jié)果就是對應(yīng)的序列化實現(xiàn)類的實例,也就是 Serializer 接口的實現(xiàn),然后調(diào)用對應(yīng)的序列化或者反序列化方法就可以了。
public class SerializeSupport {
private static final Logger logger = LoggerFactory.getLogger(SerializeSupport.class);
private static Map<Class<?>/*序列化對象類型*/, Serializer<?>/*序列化實現(xiàn)*/> serializerMap = new HashMap<>();
private static Map<Byte/*序列化實現(xiàn)類型*/, Class<?>/*序列化對象類型*/> typeMap = new HashMap<>();
static {
for (Serializer serializer : ServiceSupport.loadAll(Serializer.class)) {
registerType(serializer.type(), serializer.getSerializeClass(), serializer);
logger.info("Found serializer, class: {}, type: {}.",
serializer.getSerializeClass().getCanonicalName(),
serializer.type());
}
}
private static byte parseEntryType(byte[] buffer) {
return buffer[0];
}
private static <E> void registerType(byte type, Class<E> eClass, Serializer<E> serializer) {
serializerMap.put(eClass, serializer);
typeMap.put(type, eClass);
}
@SuppressWarnings("unchecked")
private static <E> E parse(byte [] buffer, int offset, int length, Class<E> eClass) {
Object entry = serializerMap.get(eClass).parse(buffer, offset, length);
if (eClass.isAssignableFrom(entry.getClass())) {
return (E) entry;
} else {
throw new SerializeException("Type mismatch!");
}
}
public static <E> E parse(byte [] buffer) {
return parse(buffer, 0, buffer.length);
}
private static <E> E parse(byte[] buffer, int offset, int length) {
byte type = parseEntryType(buffer);
@SuppressWarnings("unchecked")
Class<E> eClass = (Class<E> )typeMap.get(type);
if(null == eClass) {
throw new SerializeException(String.format("Unknown entry type: %d!", type));
} else {
return parse(buffer, offset + 1, length - 1,eClass);
}
}
public static <E> byte [] serialize(E entry) {
@SuppressWarnings("unchecked")
Serializer<E> serializer = (Serializer<E>) serializerMap.get(entry.getClass());
if(serializer == null) {
throw new SerializeException(String.format("Unknown entry class type: %s", entry.getClass().toString()));
}
byte [] bytes = new byte [serializer.size(entry) + 1];
bytes[0] = serializer.type();
serializer.serialize(entry, bytes, 1, bytes.length - 1);
return bytes;
}
}
所有的 Serializer 的實現(xiàn)類是怎么加載到 SerializeSupport 的那兩個 Map 中的呢?這里面利用了 Java 的一個 SPI 類加載機(jī)制
public class ServiceSupport {
private final static Map<String, Object> singletonServices = new HashMap<>();
public synchronized static <S> S load(Class<S> service) {
return StreamSupport.
stream(ServiceLoader.load(service).spliterator(), false)
.map(ServiceSupport::singletonFilter)
.findFirst().orElseThrow(ServiceLoadException::new);
}
public synchronized static <S> Collection<S> loadAll(Class<S> service) {
return StreamSupport.
stream(ServiceLoader.load(service).spliterator(), false)
.map(ServiceSupport::singletonFilter).collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
private static <S> S singletonFilter(S service) {
if(service.getClass().isAnnotationPresent(Singleton.class)) {
String className = service.getClass().getCanonicalName();
Object singletonInstance = singletonServices.putIfAbsent(className, service);
return singletonInstance == null ? service : (S) singletonInstance;
} else {
return service;
}
}
}
到這里,我們就封裝好了一個通用的序列化的接口,
-
對于使用序列化的模塊來說,它只要依賴 SerializeSupport 這個靜態(tài)類,調(diào)用它的序列化和反序列化方法就可以了,不需要依賴任何序列化實現(xiàn)類。
-
對于序列化實現(xiàn)的提供者來說,也只需要依賴并實現(xiàn) Serializer 這個接口就可以了。
比如,我們的 HelloService 例子中的參數(shù)是一個 String 類型的數(shù)據(jù),我們需要實現(xiàn)一個支持 String 類型的序列化實現(xiàn)
public class StringSerializer implements Serializer<String> {
@Override
public int size(String entry) {
return entry.getBytes(StandardCharsets.UTF_8).length;
}
@Override
public void serialize(String entry, byte[] bytes, int offset, int length) {
byte [] strBytes = entry.getBytes(StandardCharsets.UTF_8);
System.arraycopy(strBytes, 0, bytes, offset, strBytes.length);
}
@Override
public String parse(byte[] bytes, int offset, int length) {
return new String(bytes, offset, length, StandardCharsets.UTF_8);
}
@Override
public byte type() {
return Types.TYPE_STRING;
}
@Override
public Class<String> getSerializeClass() {
return String.class;
}
}
在把 String 和 byte 數(shù)組做轉(zhuǎn)換的時候,一定要指定編碼方式,確保序列化和反序列化的時候都使用一致的編碼,我們這里面統(tǒng)一使用 UTF8 編碼。否則,如果遇到執(zhí)行序列化和反序列化的兩臺服務(wù)器默認(rèn)編碼不一樣,就會出現(xiàn)亂碼。我們在開發(fā)過程用遇到的很多中文亂碼問題,絕大部分都是這個原因
還有一個更復(fù)雜的序列化實現(xiàn) MetadataSerializer,用于將注冊中心的數(shù)據(jù)持久化到文件中文章來源:http://www.zghlxwxcb.cn/news/detail-721287.html
/**
* Size of the map 2 bytes
* Map entry:
* Key string:
* Length: 2 bytes
* Serialized key bytes: variable length
* Value list
* List size: 2 bytes
* item(URI):
* Length: 2 bytes
* serialized uri: variable length
* item(URI):
* ...
* Map entry:
* ...
*
*/
public class MetadataSerializer implements Serializer<Metadata> {
@Override
public int size(Metadata entry) {
return Short.BYTES + // Size of the map 2 bytes
entry.entrySet().stream()
.mapToInt(this::entrySize).sum();
}
@Override
public void serialize(Metadata entry, byte[] bytes, int offset, int length) {
ByteBuffer buffer = ByteBuffer.wrap(bytes, offset, length);
buffer.putShort(toShortSafely(entry.size()));
entry.forEach((k,v) -> {
byte [] keyBytes = k.getBytes(StandardCharsets.UTF_8);
buffer.putShort(toShortSafely(keyBytes.length));
buffer.put(keyBytes);
buffer.putShort(toShortSafely(v.size()));
for (URI uri : v) {
byte [] uriBytes = uri.toASCIIString().getBytes(StandardCharsets.UTF_8);
buffer.putShort(toShortSafely(uriBytes.length));
buffer.put(uriBytes);
}
});
}
private int entrySize(Map.Entry<String, List<URI>> e) {
// Map entry:
return Short.BYTES + // Key string length: 2 bytes
e.getKey().getBytes().length + // Serialized key bytes: variable length
Short.BYTES + // List size: 2 bytes
e.getValue().stream() // Value list
.mapToInt(uri -> {
return Short.BYTES + // Key string length: 2 bytes
uri.toASCIIString().getBytes(StandardCharsets.UTF_8).length; // Serialized key bytes: variable length
}).sum();
}
@Override
public Metadata parse(byte[] bytes, int offset, int length) {
ByteBuffer buffer = ByteBuffer.wrap(bytes, offset, length);
Metadata metadata = new Metadata();
int sizeOfMap = buffer.getShort();
for (int i = 0; i < sizeOfMap; i++) {
int keyLength = buffer.getShort();
byte [] keyBytes = new byte [keyLength];
buffer.get(keyBytes);
String key = new String(keyBytes, StandardCharsets.UTF_8);
int uriListSize = buffer.getShort();
List<URI> uriList = new ArrayList<>(uriListSize);
for (int j = 0; j < uriListSize; j++) {
int uriLength = buffer.getShort();
byte [] uriBytes = new byte [uriLength];
buffer.get(uriBytes);
URI uri = URI.create(new String(uriBytes, StandardCharsets.UTF_8));
uriList.add(uri);
}
metadata.put(key, uriList);
}
return metadata;
}
@Override
public byte type() {
return Types.TYPE_METADATA;
}
@Override
public Class<Metadata> getSerializeClass() {
return Metadata.class;
}
private short toShortSafely(int v) {
assert v < Short.MAX_VALUE;
return (short) v;
}
}
到這里序列化的部分就實現(xiàn)完成了。我們這個序列化的實現(xiàn),對外提供服務(wù)的就只有一個 SerializeSupport 靜態(tài)類,并且可以通過擴(kuò)展支持序列化任何類型的數(shù)據(jù),這樣一個通用的實現(xiàn),不僅可以用在我們這個 RPC 框架的例子中,完全可以把這部分直接拿過去用在業(yè)務(wù)代碼中文章來源地址http://www.zghlxwxcb.cn/news/detail-721287.html
到了這里,關(guān)于Simple RPC - 02 通用高性能序列化和反序列化設(shè)計與實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!