1. Avro RPC简介
1.1. RPC
- RPC逻辑上分为二层,一是传输层,负责网络通信;二是协议层,将数据按照一定协议格式打包和解包
- 从序列化方式来看,Apache Thrift 和Google的Protocol Buffers和Avro应该是属于同一个级别的框架,都能跨语言,性能优秀,数据精简,但是Avro的动态模式(不用生成代码,而且性能很好)这个特点让人非常喜欢,比较适合RPC的数据交换。
1.2. Avro RPC的主要特点
Avro RPC 是一个支持跨语言实现的RPC服务框架。非常轻量级,实现简洁,使用方便,同时支持使用者进行二次开发,逻辑上该框架分为两层:
- 网络传输层使用Netty的Nio实现。
- 协议层可扩展,目前支持的数据序列化方式有Avro,Protocol Buffers ,Json, Hessian,Java序列化。 使用者可以注册自己的协议格式及序列化方式。
Avro RPC主要特点:
- 客户端传输层与应用层逻辑分离,传输层主要职责包括创建连接,连接查找与复用,传输数据,接收服务端回复后回调应用层;
- 客户端支持同步调用和异步调用。服务异步化能很好的提高系统吞吐量,建议使用异步调用。为防止异步发送请求过快,客户端增加了“请求流量限制”功能;
- 服务端有一个协议注册工厂和序列化注册工厂。这样方便针对不同的应用场景来定制服务方式。RPC应该只是服务方式的一种。在分布式的系统架构中,分布式节点之间的通信会存在多种方式,比如MQ的TOP消息,一个消息可以有多个订阅者。因此avro-rpc不仅仅是一个RPC服务框架,还是一个分布式通信的一个基础骨架,提供了很好的扩展性;
- Avro序列化框架是Hadoop下的一个子项目,其特点是数据序列化不带标签,因此序列化后的数据非常小。支持动态解析, 不像Thrift 与 Protocol Buffers必须根据IDL来生成代码,这样侵入性有点强。性能很好,基本上和 Protocol Buffers差不多;
2. Avro RPC开发
2.1 Maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>learn</groupId>
<artifactId>learn.avro</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--avro core-->
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.7.7</version>
</dependency>
<!--avro rpc support-->
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro-ipc</artifactId>
<version>1.7.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.avro</groupId>
<artifactId>avro-maven-plugin</artifactId>
<version>1.7.7</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<!--Maven goal that helps for code generation-->
<goal>schema</goal>
<!--For RPC used-->
<goal>protocol</goal>
<goal>idl-protocol</goal>
</goals>
<configuration>
<sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory>
<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2 定义协议schema文件(在src/main/avro/mail.avpr)
{"namespace": "examples.avro.rpc", "protocol": "Mail", "types": [ {"name": "Message", "type": "record", "fields": [ {"name": "to", "type": "string"}, {"name": "from", "type": "string"}, {"name": "body", "type": "string"} ] } ], "messages": { "send": { "request": [{"name": "message", "type": "Message"}], "response": "string" } } }
2.3 生成代码:
在Intellij Idea的Maven视图中,learn avro->Plugins->avro->avro:protocol,右击avro:protocol,执行Run Maven Build,生成protocol schema对应的Java实体类
2.3.1 Mail接口
/**
* Autogenerated by Avro
*
* DO NOT EDIT DIRECTLY
*/
package examples.avro.rpc;
@SuppressWarnings("all")
@org.apache.avro.specific.AvroGenerated
public interface Mail {
public static final org.apache.avro.Protocol PROTOCOL = org.apache.avro.Protocol.parse("{\"protocol\":\"Mail\",\"namespace\":\"example.proto\",\"types\":[{\"type\":\"record\",\"name\":\"Message\",\"fields\":[{\"name\":\"to\",\"type\":\"string\"},{\"name\":\"from\",\"type\":\"string\"},{\"name\":\"body\",\"type\":\"string\"}]}],\"messages\":{\"send\":{\"request\":[{\"name\":\"message\",\"type\":\"Message\"}],\"response\":\"string\"}}}");
///Mail接口有1个方法send,参数是Message,Message是一个Avro类,可以序列化和反序列化
java.lang.CharSequence send(Message message) throws org.apache.avro.AvroRemoteException;
@SuppressWarnings("all")
public interface Callback extends Mail {
public static final org.apache.avro.Protocol PROTOCOL = Mail.PROTOCOL;
void send(Message message, org.apache.avro.ipc.Callback<CharSequence> callback) throws java.io.IOException;
}
}
2.3.2 Message类(根据schema文件生成)
/**
* Autogenerated by Avro
*
* DO NOT EDIT DIRECTLY
*/
package examples.avro.rpc;
@SuppressWarnings("all")
@org.apache.avro.specific.AvroGenerated
public class Message extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"Message\",\"namespace\":\"example.proto\",\"fields\":[{\"name\":\"to\",\"type\":\"string\"},{\"name\":\"from\",\"type\":\"string\"},{\"name\":\"body\",\"type\":\"string\"}]}");
public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
@Deprecated public java.lang.CharSequence to;
@Deprecated public java.lang.CharSequence from;
@Deprecated public java.lang.CharSequence body;
/**
* Default constructor. Note that this does not initialize fields
* to their default values from the schema. If that is desired then
* one should use <code>newBuilder()</code>.
*/
public Message() {}
/**
* All-args constructor.
*/
public Message(java.lang.CharSequence to, java.lang.CharSequence from, java.lang.CharSequence body) {
this.to = to;
this.from = from;
this.body = body;
}
public org.apache.avro.Schema getSchema() { return SCHEMA$; }
// Used by DatumWriter. Applications should not call.
public java.lang.Object get(int field$) {
switch (field$) {
case 0: return to;
case 1: return from;
case 2: return body;
default: throw new org.apache.avro.AvroRuntimeException("Bad index");
}
}
// Used by DatumReader. Applications should not call.
@SuppressWarnings(value="unchecked")
public void put(int field$, java.lang.Object value$) {
switch (field$) {
case 0: to = (java.lang.CharSequence)value$; break;
case 1: from = (java.lang.CharSequence)value$; break;
case 2: body = (java.lang.CharSequence)value$; break;
default: throw new org.apache.avro.AvroRuntimeException("Bad index");
}
}
/**
* Gets the value of the 'to' field.
*/
public java.lang.CharSequence getTo() {
return to;
}
/**
* Sets the value of the 'to' field.
* @param value the value to set.
*/
public void setTo(java.lang.CharSequence value) {
this.to = value;
}
/**
* Gets the value of the 'from' field.
*/
public java.lang.CharSequence getFrom() {
return from;
}
/**
* Sets the value of the 'from' field.
* @param value the value to set.
*/
public void setFrom(java.lang.CharSequence value) {
this.from = value;
}
/**
* Gets the value of the 'body' field.
*/
public java.lang.CharSequence getBody() {
return body;
}
/**
* Sets the value of the 'body' field.
* @param value the value to set.
*/
public void setBody(java.lang.CharSequence value) {
this.body = value;
}
/** Creates a new Message RecordBuilder */
public static Message.Builder newBuilder() {
return new Message.Builder();
}
/** Creates a new Message RecordBuilder by copying an existing Builder */
public static Message.Builder newBuilder(Message.Builder other) {
return new Message.Builder(other);
}
/** Creates a new Message RecordBuilder by copying an existing Message instance */
public static Message.Builder newBuilder(Message other) {
return new Message.Builder(other);
}
/**
* RecordBuilder for Message instances.
*/
public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase<Message>
implements org.apache.avro.data.RecordBuilder<Message> {
private java.lang.CharSequence to;
private java.lang.CharSequence from;
private java.lang.CharSequence body;
/** Creates a new Builder */
private Builder() {
super(Message.SCHEMA$);
}
/** Creates a Builder by copying an existing Builder */
private Builder(Message.Builder other) {
super(other);
if (isValidValue(fields()[0], other.to)) {
this.to = data().deepCopy(fields()[0].schema(), other.to);
fieldSetFlags()[0] = true;
}
if (isValidValue(fields()[1], other.from)) {
this.from = data().deepCopy(fields()[1].schema(), other.from);
fieldSetFlags()[1] = true;
}
if (isValidValue(fields()[2], other.body)) {
this.body = data().deepCopy(fields()[2].schema(), other.body);
fieldSetFlags()[2] = true;
}
}
/** Creates a Builder by copying an existing Message instance */
private Builder(Message other) {
super(Message.SCHEMA$);
if (isValidValue(fields()[0], other.to)) {
this.to = data().deepCopy(fields()[0].schema(), other.to);
fieldSetFlags()[0] = true;
}
if (isValidValue(fields()[1], other.from)) {
this.from = data().deepCopy(fields()[1].schema(), other.from);
fieldSetFlags()[1] = true;
}
if (isValidValue(fields()[2], other.body)) {
this.body = data().deepCopy(fields()[2].schema(), other.body);
fieldSetFlags()[2] = true;
}
}
/** Gets the value of the 'to' field */
public java.lang.CharSequence getTo() {
return to;
}
/** Sets the value of the 'to' field */
public Message.Builder setTo(java.lang.CharSequence value) {
validate(fields()[0], value);
this.to = value;
fieldSetFlags()[0] = true;
return this;
}
/** Checks whether the 'to' field has been set */
public boolean hasTo() {
return fieldSetFlags()[0];
}
/** Clears the value of the 'to' field */
public Message.Builder clearTo() {
to = null;
fieldSetFlags()[0] = false;
return this;
}
/** Gets the value of the 'from' field */
public java.lang.CharSequence getFrom() {
return from;
}
/** Sets the value of the 'from' field */
public Message.Builder setFrom(java.lang.CharSequence value) {
validate(fields()[1], value);
this.from = value;
fieldSetFlags()[1] = true;
return this;
}
/** Checks whether the 'from' field has been set */
public boolean hasFrom() {
return fieldSetFlags()[1];
}
/** Clears the value of the 'from' field */
public Message.Builder clearFrom() {
from = null;
fieldSetFlags()[1] = false;
return this;
}
/** Gets the value of the 'body' field */
public java.lang.CharSequence getBody() {
return body;
}
/** Sets the value of the 'body' field */
public Message.Builder setBody(java.lang.CharSequence value) {
validate(fields()[2], value);
this.body = value;
fieldSetFlags()[2] = true;
return this;
}
/** Checks whether the 'body' field has been set */
public boolean hasBody() {
return fieldSetFlags()[2];
}
/** Clears the value of the 'body' field */
public Message.Builder clearBody() {
body = null;
fieldSetFlags()[2] = false;
return this;
}
@Override
public Message build() {
try {
Message record = new Message();
record.to = fieldSetFlags()[0] ? this.to : (java.lang.CharSequence) defaultValue(fields()[0]);
record.from = fieldSetFlags()[1] ? this.from : (java.lang.CharSequence) defaultValue(fields()[1]);
record.body = fieldSetFlags()[2] ? this.body : (java.lang.CharSequence) defaultValue(fields()[2]);
return record;
} catch (Exception e) {
throw new org.apache.avro.AvroRuntimeException(e);
}
}
}
}
2.3.3 AvroServer类
package examples.avro.rpc;
import org.apache.avro.ipc.NettyServer;
import org.apache.avro.ipc.Server;
import org.apache.avro.ipc.specific.SpecificResponder;
import org.apache.avro.util.Utf8;
import java.io.IOException;
import java.net.InetSocketAddress;
//Server端的实现Mai服务
class MailImpl implements Mail {
public Utf8 send(Message message) {
System.out.println("Message Received:" + message);
return new Utf8("Received your message: " + message.getFrom().toString()
+ " with body " + message.getBody().toString());
}
}
public class AvroServer {
private static Server server;
public static void main(String[] args) throws Exception {
System.out.println("Starting server");
startServer();
Thread.sleep(1000);
System.out.println("Server started");
Thread.sleep(60 * 1000);
server.close();
}
private static void startServer() throws IOException {
server = new NettyServer(new SpecificResponder(Mail.class, new MailImpl()), new InetSocketAddress(65111));
}
}
2.3.3 AvroClient类
package examples.avro.rpc;
import org.apache.avro.ipc.NettyTransceiver;
import org.apache.avro.ipc.specific.SpecificRequestor;
import org.apache.avro.util.Utf8;
import java.net.InetSocketAddress;
public class AvroClient {
public static void main(String[] args) throws Exception {
NettyTransceiver client = new NettyTransceiver(new InetSocketAddress(65111));
///获取Mail接口的proxy实现
Mail proxy = SpecificRequestor.getClient(Mail.class, client);
System.out.println("Client of Mail Proxy is built");
// fill in the Message record and send it
args = new String[]{"to:Tom", "from:Jack", "body:How are you"};
Message message = new Message();
message.setTo(new Utf8(args[0]));
message.setFrom(new Utf8(args[1]));
message.setBody(new Utf8(args[2]));
System.out.println("RPC call with message: " + message.toString());
///底层给服务器发送send方法调用
System.out.println("Result: " + proxy.send(message));
// cleanup
client.close();
}
}
本文支持对Avro RPC的粗浅尝试,Avro Client端用的同步通信方式