前言
ProtoBuf简介
protocol buffers
是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
简单来讲, ProtoBuf 是结构数据序列化 方法,可简单类比于 XML,其具有以下特点:
- 语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
- 高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
- 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序
序列化:将结构数据或对象转换成能够被存储和传输(例如网络传输)的格式,同时应当要保证这个序列化结果在之后(可能在另一个计算环境中)能够被重建回原来的结构数据或对象。
ProtoBuf文件说明
文件范本示例:
syntax = "proto3"; #语法格式
package demo; #包
option java_package = "com.kp.demo"; #可赋值非必须赋值 java_package:java包名
option java_outer_classname="UserProto"; #java类名
#message定义所需要序列化的数据的格式。每一个Message都是一个小的信息逻辑单元,包含了一些列的name-value对
message User{
required int32 id = 1;
required string name = 2;
optional string age = 3;
enum sexType { #枚举
MAN= 0;
WOMAN = 1;
}
message sex {
required string code= 1;
optional sexType type = 0 [default = MAN];
}
required sex phone = 4;
}
字段规则:
required -> 字段只能也必须出现 1 次
optional -> 字段可出现 0 次或多次
repeated -> 字段可出现任意多次(包括 0)类型:
int32、int64、sint32、sint64、string、32-bit …
字段编号:
0 ~ 536870911(除去 19000 到 19999 之间的数字)
声明格式:
字段规则 类型 名称 = 字段编号; eq:required string name = 2;
Kafka简介
一个分布式的基于发布\订阅模式的消息队列,主要应用与大数据实时处理领域。
集成
1)引入依赖pom.xml
<properties>
<java.version>1.8</java.version>
<grpc.version>1.6.1</grpc.version>
<protobuf.version>3.13.0</protobuf.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2)idea引入插件,配合使用protoBuf
下载安装后重启,配置proto自动转换规则
如果使用协议“proto3
”,需要配置参数--experimental_allow_proto3_optional
3)编写proto文件
syntax = "proto3";
option java_outer_classname="UserProto";
message User{
int32 id = 1;
string name = 2;
string age = 3;
}
这里可以不指明字段规则
4)转换成java文件
右键文件后生成文件UserProto.java
5)测试文件是否可执行
public class TestMain {
public static void main(String[] args) throws InvalidProtocolBufferException {
UserProto.User.Builder user = new UserProto.User.Builder();
user.setAge("25");
user.setName("125");
user.setId(1111);
byte[] s = user.build().toByteArray();
System.out.println("序列化" + s.length);
UserProto.User user1 = UserProto.User.parseFrom(s);
System.out.println("反序列化" + user1.toString());
}
}
6)创建公用接口IProtobuf
public interface IProtobuf {
//将对象转为字节数组
byte[] encode();
}
7)创建自定义的User
类
public class User implements IProtobuf {
private static Logger logger = LoggerFactory.getLogger(User.class);
int id;
String name;
String age;
//有参构造及无参构造
public User(){}
public User(int id,String name,String age){
super();
this.id = id;
this.name = name;
this.age = age;
}
//重写toString
@Override
public String toString() {
return "User{" +
"ID=" + id +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
/**
* @Author fyy
* @Description 类序列化
* @Date 2020/10/28 9:29
*/
@Override
public byte[] encode() {
UserProto.User.Builder builder = UserProto.User.newBuilder();
builder.setId(id);
builder.setName(name);
builder.setAge(age);
return builder.build().toByteArray();
}
/**
* @Author fyy
* @Description 反序列化
* @Date 2020/10/28 9:32
*/
public User(byte[] bytes){
try {
UserProto.User user = UserProto.User.parseFrom(bytes);
this.id = user.getId();
this.name = user.getName();
this.age = user.getAge();
} catch (InvalidProtocolBufferException e) {
logger.error("User反序列化失败:{}",e.getMessage());
}
}
//set get 方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
8)定义kafka
序列化基于protobuf
实现ProtobufSerializer
public class ProtobufSerializer implements Serializer<IProtobuf> {
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
@Override
public byte[] serialize(String topic, IProtobuf data) {
return data.encode(); //其实质是调用encode方法
}
@Override
public void close() {
}
}
9)修改配置文件,添加kafka
配置
server:
port: 8080
spring:
kafka:
bootstrap-servers: [your.url]:9092 # 以逗号分隔的地址列表,用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
producer:
retries: 5 # 发生错误后,消息重发的次数
batch-size: 2048 #当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小
buffer-memory: 33554432 # 设置生产者内存缓冲区的大小
key-serializer: org.apache.kafka.common.serialization.IntegerSerializer # 键的序列化方式
value-serializer: com.fyy.protobuf.serializer.ProtobufSerializer # 值的序列化方式
# acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
# acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
acks: 1
consumer:
auto-commit-interval: 1S # 自动提交的时间间隔,如1S,1M,2H,5D
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
# latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据
# earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
auto-offset-reset: earliest
enable-auto-commit: false # 是否自动提交偏移量
key-deserializer: org.apache.kafka.common.serialization.IntegerDeserializer # 键的反序列化方式
value-deserializer: org.apache.kafka.common.serialization.ByteArrayDeserializer # 值的反序列化方式
group-id: testkafka #分组id
listener:
concurrency: 5 # 在侦听器容器中运行的线程数
ack-mode: manual_immediate #listner负责ack,每调用一次,就立即commit
10)实现消息发送及消息接收
@Controller
@RequestMapping("/kafka/api")
public class TestController {
private static Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
private KafkaTemplate kafkaTemplate;
/**
* @Author fyy
* @Description 发送消息
* @Date 2020/10/28 9:51
*/
@RequestMapping("/send")
@ResponseBody
public String sendMesg(String name){
User user = new User();
user.setId(1);
user.setName(name);
user.setAge("25");
kafkaTemplate.send("kafka",1234,user);
return "发送成功";
}
@KafkaListener(topics = "kafka")
public void ConsumerMsg(ConsumerRecord<Integer,byte[]> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic
, Consumer consumer, Acknowledgment ack){
int key = record.key();
User user = new User(record.value());
logger.info("消费到的key:" + key + ",消费到的消息:" + user.toString());
logger.info("消费消息:topic:{}",topic);
ack.acknowledge();
}
}