分布式架构中的序列化方式都有哪些?看看它们是如何使用的。

前言

在分布式架构中,序列化是将对象状态转换为可存储或传输的格式的过程,而反序列化则是将这个过程逆转,将存储或传输的格式转换回原始对象状态。

分布式架构中的序列化方式多种多样,以满足不同场景和需求。以下是一些常见的序列化方式。


1. Java原生序列化

  • 特点:Java通过实现java.io.Serializable接口来支持对象的序列化。使用ObjectOutputStream进行序列化,ObjectInputStream进行反序列化。
  • 缺点:不能跨语言,序列化后的数据体积较大,性能相对较差,且存在安全隐患。
  • 适用场景:主要用于Java内部的对象传输和持久化。
  • 代码示例:
//首先,我们需要一个实现了Serializable接口的类。
//注意,所有实现Serializable接口的类都应该有一个版本UID,以避免因类定义更改而导致序列化不兼容的问题。
import java.io.Serializable;  
  
public class Employee implements Serializable {  
    private static final long serialVersionUID = 1L; // 用于版本控制  
  
    private String name;  
    private int age;  
  
    // 构造函数  
    public Employee(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    // Getter 和 Setter 方法  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public int getAge() {  
        return age;  
    }  
  
    public void setAge(int age) {  
        this.age = age;  
    }  
  
    // toString 方法方便打印对象信息  
    @Override  
    public String toString() {  
        return "Employee{" +  
                "name='" + name + '\'' +  
                ", age=" + age +  
                '}';  
    }  
}
// 实现 Employee 对象的序列化
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.ObjectOutputStream;  
  
public class SerializeExample {  
    public static void main(String[] args) {  
        Employee emp = new Employee("John Doe", 30);  
  
        try (  
            FileOutputStream fileOut = new FileOutputStream("employee.ser");  
            ObjectOutputStream out = new ObjectOutputStream(fileOut);  
        ) {  
            out.writeObject(emp);  
            System.out.printf("Serialized data is saved in employee.ser");  
        } catch (IOException i) {  
            i.printStackTrace();  
        }  
    }  
}
//实现Employee对象的反序列化
import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.ObjectInputStream;  
  
public class DeserializeExample {  
    public static void main(String[] args) {  
        Employee emp = null;  
  
        try (  
            FileInputStream fileIn = new FileInputStream("employee.ser");  
            ObjectInputStream in = new ObjectInputStream(fileIn);  
        ) {  
            emp = (Employee) in.readObject();  
            System.out.println("Deserialized Employee...");  
            System.out.println("Employee Name: " + emp.getName());  
            System.out.println("Employee Age: " + emp.getAge());  
        } catch (IOException i) {  
            i.printStackTrace();  
            return;  
        } catch (ClassNotFoundException c) {  
            System.out.println("Employee class not found");  
            c.printStackTrace();  
            return;  
        }  
    }  
}

2. Hessian序列化

  • 特点:Hessian是一个基于二进制的高效序列化协议,支持跨语言传输。Dubbo框架早期版本默认使用Hessian作为序列化方式。
  • 优点:性能好,支持多种语言。
  • 缺点:相对JSON等格式,可读性较差。
  • 代码示例:
//我们使用 HessianOutput 来序列化 Person 对象,并将结果写入一个字节流中。
//然后,我们使用 HessianInput 从这个字节流中读取并反序列化对象。
//最后,我们打印出反序列化后的对象信息。
import com.caucho.hessian.io.HessianInput;  
import com.caucho.hessian.io.HessianOutput;  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
  
public class HessianSerializationExample {  
  
    public static void main(String[] args) throws IOException {  
        // 创建 Person 对象  
        Person person = new Person("John Doe", 30);  
  
        // 序列化  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();  
        HessianOutput out = new HessianOutput(baos);  
        out.writeObject(person);  
        out.close();  
  
        byte[] serializedData = baos.toByteArray();  
  
        // 反序列化  
        ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);  
        HessianInput in = new HessianInput(bais);  
        Person deserializedPerson = (Person) in.readObject();  
        in.close();  
  
        // 打印反序列化后的对象信息  
        System.out.println(deserializedPerson);  
    }  
  
    // 一个简单的 JavaBean 类  
    public static class Person implements java.io.Serializable {  
        private static final long serialVersionUID = 1L;  
  
        private String name;  
        private int age;  
  
        public Person(String name, int age) {  
            this.name = name;  
            this.age = age;  
        }  
  
        // Getter 和 Setter 方法  
        public String getName() {  
            return name;  
        }  
  
        public void setName(String name) {  
            this.name = name;  
        }  
  
        public int getAge() {  
            return age;  
        }  
  
        public void setAge(int age) {  
            this.age = age;  
        }  
  
        // toString 方法方便打印对象信息  
        @Override  
        public String toString() {  
            return "Person{" +  
                    "name='" + name + '\'' +  
                    ", age=" + age +  
                    '}';  
        }  
    }  
}

使用Hassian前,现将Hession库添加到项目中:

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

3. JSON序列化

  • 特点:JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。
  • 优点:跨语言支持,可读性好,易于调试。
  • 缺点:相对于二进制序列化,数据体积较大,性能略低。
  • 常用库:Jackson、Fastjson、Gson等。
  • 代码示例:
//使用Jackson进行JSON序列化
import com.fasterxml.jackson.databind.ObjectMapper;  
  
public class JacksonSerializationExample {  
    public static void main(String[] args) {  
        try {  
            // 创建一个Person对象  
            Person person = new Person("John Doe", 30);  
  
            // 创建ObjectMapper实例  
            ObjectMapper mapper = new ObjectMapper();  
  
            // 将Person对象序列化为JSON字符串  
            String jsonString = mapper.writeValueAsString(person);  
  
            // 打印JSON字符串  
            System.out.println(jsonString);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    // 假设的Person类  
    @Data
    public static class Person {  
        private String name;  
        private int age;  
    }  
}

引入Jackson依赖

<dependency>  
    <groupId>com.fasterxml.jackson.core</groupId>  
    <artifactId>jackson-databind</artifactId>  
    <version>2.13.0</version>
</dependency>

4. XML序列化

  • 特点:XML(eXtensible Markup Language)是一种标记语言,常用于表示和交换数据。
  • 优点:可读性好,支持跨语言,易于理解和调试。
  • 缺点:序列化后的数据体积较大,效率不高。
  • 实现方式:可以使用JAXB(Java Architecture for XML Binding)等库来实现。
  • 代码示例:
import javax.xml.bind.annotation.XmlRootElement;  
import javax.xml.bind.annotation.XmlElement;  
  
@XmlRootElement // 标记这个类为XML的根元素  
public class Person {  
    private String name;  
    private int age;  
  
    // 构造函数、getter和setter省略  
    public Person() {}  
  
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    @XmlElement // 标记这个字段为XML元素  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    @XmlElement // 标记这个字段为XML元素  
    public int getAge() {  
        return age;  
    }  
  
    public void setAge(int age) {  
        this.age = age;  
    }  
  
    // 序列化方法  
    public static void serialize(Person person, String filePath) throws Exception {  
        JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);  
  
        Marshaller marshaller = jaxbContext.createMarshaller();  
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // 格式化输出  
  
        // 将Person对象序列化为XML并写入文件  
        marshaller.marshal(person, new File(filePath));  
    }  
  
    // 反序列化方法  
    public static Person deserialize(String filePath) throws Exception {  
        JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);  
  
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();  
  
        // 从文件读取XML并反序列化为Person对象  
        return (Person) unmarshaller.unmarshal(new File(filePath));  
    }  
  
    public static void main(String[] args) {  
        try {  
            // 序列化示例  
            Person person = new Person("John Doe", 30);  
            serialize(person, "person.xml");  
  
            // 反序列化示例  
            Person deserializedPerson = deserialize("person.xml");  
            System.out.println("Deserialized Person: " + deserializedPerson.getName() + ", " + deserializedPerson.getAge());  
  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

5. Protocol Buffers(Protobuf)

  • 特点:由Google开发的一种数据序列化协议,支持多种编程语言,具有较小的数据大小和较高的性能。
  • 优点:高效、跨语言、数据体积小。
  • 缺点:需要定义.proto文件,并生成对应的序列化/反序列化代码。
  • 代码示例:
    首先,你需要定义一个 .proto 文件来描述你的数据结构,然后使用 Protobuf 编译器(protoc)生成 Java 代码。
    1. 定义 .proto 文件
      创建一个名为 person.proto 的文件,内容如下:
    syntax = "proto3";  
      
    package tutorial;  
      
    // 定义一个Person消息  
    message Person {  
      string name = 1;  
      int32 id = 2;  
      string email = 3;  
      
      enum PhoneType {  
        MOBILE = 0;  
        HOME = 1;  
        WORK = 2;  
      }  
      
      message PhoneNumber {  
        string number = 1;  
        PhoneType type = 2;  
      }  
      
      repeated PhoneNumber phones = 4;  
    }
    
    1. 使用 Protobuf 编译器生成 Java 代码
      运行以下命令(确保 protoc 在你的 PATH 中):
    protoc --java_out=. person.proto
    
    这将在当前目录下生成一个 tutorial 目录,其中包含 Person.java 和其他生成的类。
    3. Java 序列化和反序列化代码
    在你的 Java 项目中,你可以使用生成的 Person 类来序列化和反序列化数据。
    import tutorial.Person;  
    import tutorial.Person.PhoneNumber;  
    import tutorial.Person.PhoneType;  
      
    import com.google.protobuf.ByteString;  
      
    import java.io.FileInputStream;  
    import java.io.FileOutputStream;  
    import java.io.IOException;  
      
    public class ProtobufExample {  
        public static void main(String[] args) throws IOException {  
            // 创建一个Person实例  
            Person.Builder personBuilder = Person.newBuilder();  
            personBuilder.setId(1234);  
            personBuilder.setName("John Doe");  
            personBuilder.setEmail("john.doe@example.com");  
      
            // 添加电话号码  
            PhoneNumber.Builder phoneBuilder = PhoneNumber.newBuilder();  
            phoneBuilder.setNumber("555-4321");  
            phoneBuilder.setType(PhoneType.HOME);  
            personBuilder.addPhones(phoneBuilder.build());  
      
            Person person = personBuilder.build();  
      
            // 序列化到文件  
            try (FileOutputStream output = new FileOutputStream("person.dat")) {  
                person.writeTo(output);  
            }  
      
            // 从文件反序列化  
            Person newPerson;  
            try (FileInputStream input = new FileInputStream("person.dat")) {  
                newPerson = Person.parseFrom(input);  
            }  
      
            // 输出反序列化后的Person信息  
            System.out.println("Name: " + newPerson.getName());  
            System.out.println("ID: " + newPerson.getId());  
            System.out.println("Email: " + newPerson.getEmail());  
            for (PhoneNumber phone : newPerson.getPhonesList()) {  
                System.out.println("Phone Number: " + phone.getNumber() + ", Type: " + phone.getType());  
            }  
        }  
    }
    
    在这个示例中,我们首先构建了一个 Person 对象,并添加了一个电话号码。然后,我们使用 writeTo 方法将其序列化到一个文件中。之后,我们使用 parseFrom 方法从文件中读取并反序列化 Person 对象,并打印出它的详细信息。
    Protobuf 序列化后的数据是二进制的,因此不建议直接查看或编辑这些文件。

6. Kryo序列化

  • 特点:Kryo是一种高性能的序列化框架,支持多种数据类型和复杂的对象图。
  • 优点:序列化速度快,数据体积小。
  • 缺点:不支持跨语言,且依赖于JVM环境。
  • 代码示例:
    加入Kryo的依赖:
<dependency>  
    <groupId>com.esotericsoftware.kryo</groupId>  
    <artifactId>kryo</artifactId>  
    <version>5.2.0</version>  
</dependency>
import com.esotericsoftware.kryo.Kryo;  
import com.esotericsoftware.kryo.io.Input;  
import com.esotericsoftware.kryo.io.Output;  
  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
  
// 示例类,用于序列化  
public class User {  
    private String name;  
    private int age;    
    public User(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }    
    // User类的getter和setter方法  
    public String getName() {  
        return name;  
    }    
    public void setName(String name) {  
        this.name = name;  
    }  
    public int getAge() {  
        return age;  
    }    
    public void setAge(int age) {  
        this.age = age;  
    }  
  
    // 主类,包含序列化和反序列化的示例  
    public static void main(String[] args) {  
        // 创建一个Kryo实例  
        Kryo kryo = new Kryo();  
  
        // 创建一个User实例  
        User user = new User("Alice", 30);  
  
        // 序列化  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();  
        Output output = new Output(baos);  
        kryo.writeObject(output, user);  
        output.close();  
        byte[] bytes = baos.toByteArray();  
  
        // 反序列化  
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);  
        Input input = new Input(bais);  
        User deserializedUser = kryo.readObject(input, User.class);  
        input.close();  
  
        // 验证反序列化结果  
        System.out.println("Name: " + deserializedUser.getName());  
        System.out.println("Age: " + deserializedUser.getAge());  
    }  
  
}

7. Avro序列化

  • 特点:Avro是一种数据序列化系统,设计用于大批量数据交换的应用。支持JSON格式,具有架构演化的能力。
  • 优点:动态语言友好,支持数据压缩。
  • 缺点:需要定义schema文件,并生成对应的序列化/反序列化代码。
  • 代码示例:
<!-- 要先引入avro的依赖 -->
<dependency>
      <groupId>org.apache.avro</groupId>
      <artifactId>avro</artifactId>
      <version>1.10.1</version>
    </dependency>
import org.apache.avro.Schema;  
import org.apache.avro.Schema.Field;  
import org.apache.avro.Schema.Type;  
import org.apache.avro.generic.GenericData;  
import org.apache.avro.generic.GenericDatumWriter;  
import org.apache.avro.generic.GenericRecord;  
import org.apache.avro.io.DatumWriter;  
import org.apache.avro.io.Encoder;  
import org.apache.avro.io.EncoderFactory;  
import org.apache.avro.io.BinaryDecoder;  
import org.apache.avro.io.BinaryDecoderFactory;  
import org.apache.avro.io.DatumReader;  
import org.apache.avro.generic.GenericDatumReader;  
import java.io.ByteArrayOutputStream;  
import java.io.ByteArrayInputStream;  
import java.io.IOException;  
  
public class AvroExample {  
  
    public static void main(String[] args) throws IOException {  
        // 定义 Avro Schema  
        Schema schema = Schema.createRecord("Person", null, null, false,  
                Arrays.asList(  
                    new Field("name", Schema.create(Type.STRING), "A person's name", null),  
                    new Field("age", Schema.create(Type.INT), "A person's age", null),  
                    new Field("emails", Schema.createArray(Schema.create(Type.STRING)), "Email addresses", null)  
                ));  
  
        // 创建一个 GenericRecord 实例并填充数据  
        GenericRecord person = new GenericData.Record(schema);  
        person.put("name", "John Doe");  
        person.put("age", 30);  
        person.put("emails", Arrays.asList("john.doe@example.com", "johndoe@work.com"));  
  
        // 序列化  
        ByteArrayOutputStream out = new ByteArrayOutputStream();  
        Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);  
        DatumWriter<GenericRecord> writer = new GenericDatumWriter<>(schema);  
        writer.write(person, encoder);  
        encoder.flush();  
        byte[] data = out.toByteArray();  
  
        // 反序列化  
        ByteArrayInputStream in = new ByteArrayInputStream(data);  
        BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(in, null);  
        DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);  
        GenericRecord decodedPerson = reader.read(null, decoder);  
  
        // 打印反序列化后的数据  
        System.out.println("Name: " + decodedPerson.get("name"));  
        System.out.println("Age: " + decodedPerson.get("age"));  
        System.out.println("Emails: " + decodedPerson.get("emails"));  
    }  
}

8. MessagePack

  • 特点:MessagePack是一种高效的二进制序列化格式,类似于JSON但更轻量和高效。
  • 优点:跨语言支持,数据体积小,性能高。
  • 代码示例:

引入MassagePack依赖:

<dependency>  
    <groupId>org.msgpack</groupId>  
    <artifactId>msgpack-core</artifactId>  
    <version>0.8.21</version> 
</dependency>
import org.msgpack.core.MessagePacker;
import org.msgpack.core.MessageUnpacker;
import org.msgpack.value.Value;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
 
public class MessagePackExample {
 
    public static void main(String[] args) throws IOException {
        // 创建MessagePacker对象进行序列化
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        MessagePacker packer = MessagePack.newDefaultPacker(out);
        
        // 写入一些数据
        packer.packString("Hello, MessagePack!");
        packer.packInt(100);
        packer.close();
        
        // 获取序列化后的字节数组
        byte[] bytes = out.toByteArray();
        
        // 创建MessageUnpacker对象进行反序列化
        MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes);
        
        // 读取数据
        Value val1 = unpacker.unpackValue(); // 字符串
        Value val2 = unpacker.unpackValue(); // 整数
        
        // 输出反序列化后的数据
        System.out.println(val1); // 输出字符串
        System.out.println(val2); // 输出整数
    }
}

9. Apache Thrift

  • 特点:Apache Thrift是一个跨语言的序列化框架,支持多种数据传输格式,包括二进制、JSON和XML。
  • 优点:支持跨语言,具有高效的序列化/反序列化能力。
  • 代码示例:
  1. 定义 Thrift 文件
    首先,你需要定义一个 Thrift 文件(比如 Person.thrift),该文件定义了数据结构和服务接口。在这个示例中,我们只定义一个数据结构。
namespace java com.example.thrift    
struct Person {  
   1: string name,  
   2: i32 age,  
   3: bool employed,  
}
  1. 生成 Java 代码
    使用 Thrift 编译器 (thrift) 生成 Java 代码。在命令行中运行:
thrift --gen java Person.thrift

这将生成一个包含 Person 类和必要序列化与反序列化逻辑的 Java 文件。

  1. Java 序列化与反序列化示例
    引入依赖
    确保你的项目中包含了 Thrift 生成的 Java 文件以及 Thrift 库的依赖。如果使用 Maven,>可以添加如下依赖(注意版本号可能需要根据实际情况调整):
<dependency>  
   <groupId>org.apache.thrift</groupId>  
   <artifactId>libthrift</artifactId>  
   <version>0.14.1</version>  
</dependency>

序列化与反序列化代码

import com.example.thrift.Person;  
import org.apache.thrift.TDeserializer;  
import org.apache.thrift.TSerializer;  
 
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
 
public class ThriftExample {  
   public static void main(String[] args) {  
       try {  
           // 创建一个 Person 实例  
           Person person = new Person();  
           person.setName("John Doe");  
           person.setAge(30);  
           person.setEmployed(true);  
 
           // 序列化  
           TSerializer serializer = new TSerializer();  
           ByteArrayOutputStream out = new ByteArrayOutputStream();  
           serializer.serialize(person, out);  
           byte[] serializedData = out.toByteArray();  
 
           // 反序列化  
           TDeserializer deserializer = new TDeserializer();  
           Person deserializedPerson = new Person();  
           ByteArrayInputStream in = new ByteArrayInputStream(serializedData);  
           deserializer.deserialize(deserializedPerson, in);  
 
           // 输出反序列化后的数据  
           System.out.println("Name: " + deserializedPerson.getName());  
           System.out.println("Age: " + deserializedPerson.getAge());  
           System.out.println("Employed: " + deserializedPerson.isEmployed());  
 
       } catch (IOException e) {  
           e.printStackTrace();  
       }  
   }  
}

总结

分布式架构中的序列化方式多种多样,每种方式都有其特点和适用场景。

在选择序列化方式时,需要根据实际需求和场景进行权衡和选择。例如,如果需要跨语言支持且对数据体积和性能有较高要求,可以选择Protocol Buffers或Thrift;如果追求简单和易读性,可以选择JSON或XML序列化方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值