自学内容网 自学内容网

SpringBoot:使用HTTP2+protobuf实现高性能微服务调用(一)服务器端实现

      在当前的企业IT系统建设中,基于HTTP/1.1 + JSON的微服务架构因其实现简单、部署灵活和维护方便等特性而广受青睐,逐渐成为主流选择。然而,随着业务需求的增长,特别是高并发场景的出现,这种传统架构的局限性也日益凸显:
(1)报文转换效率较低:由于JSON格式的解析和序列化过程相对复杂,导致了较高的CPU和内存消耗。
(2)传输数据量较大:JSON文本格式较为冗长,增加了网络传输的数据量,尤其是在处理大批量数据时,这一问题更为显著。
(3)HTTP连接资源占用多:HTTP/1.1协议创建的连接一次只能处理一个请求或响应,若需要同时处理多个请求则需要创建多个连接,导致占用较多的内存和线程资源,容易造成性能瓶颈。
       在这些问题共同影响下,往往会引起计算机资源消耗过高、调用处理时间延长、单机TPS(每秒事务处理数)上限被拉低等一系列性能挑战,影响了微服务应用的整体响应速度和服务质量,难以充分满足对性能有较高要求的应用场景的需求。

     基于HTTP/2.0 + protobuf的微服务调用框架,将很好的解决上述问题,下面将介绍一下如何基于SpringBoot实现HTTP/2.0 + protobuf。

1、服务器端配置

从SpringBoot2.x.x开始都已默认支持HTTP2.0功能,只需要配置中开启即可。

若要支持HTTP2.0+SSL,则使用如下配置

server.http2.enabled=true

 若只想支持HTTP2.0,不想使用SSL,则配置如下

server.http2.enabled=false

且要添加代码,下面以tomcat9举例:

@Bean
public TomcatConnectorCustomizer connectorCustomizer() {
    return (connector) -> connector.addUpgradeProtocol(new Http2Protocol());
}

其它服务器的配置方式,可在SpringBoot的如下文档中找到:

【“How-to” Guides】>【3. Embedded Web Servers】>【3.8. Configure HTTP/2】>【3.8.5. HTTP/2 Cleartext with supported servers】 

配置完后,启动应用,会在日志中查看到如下记录

09:21:00.898 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - The ["http-nio-8080"] connector has been configured to support HTTP upgrade to [h2c]

2、服务器端代码

先给出一个采用JSON报文的常用微服务调用的例子。需要说明的是,对于已有的大量Controller层代码,几乎不用做任何修改,就可以支持HTTP/2.0 + protobuf。

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.test.model.ReqtObj;
import com.test.model.RespObj;

@RestController
public class ObjectController {

@PostMapping(value = "/object/doTest")
public RespObj doTest(@RequestBody ReqtObj reqtObj) {

RespObj resp = new RespObj();
resp.setNo(101);
resp.setCode("ERR");
resp.setMsg("something is wrong");

return resp;
}
}
public class ReqtObj {

private String name;

private String sex;

private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "ReqtObj [name=" + name + ", sex=" + sex + ", age=" + age + "]";
}

}
public class RespObj {

private int no;

private String code;

private String msg;

public int getNo() {
return no;
}

public void setNo(int no) {
this.no = no;
}

public String getCode() {
return code;
}

public void setCode(String code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

@Override
public String toString() {
return "RespObj [no=" + no + ", code=" + code + ", msg=" + msg + "]";
}

}

 通过执行如下命令并查看运行结果:

$ curl -H 'Content-Type: application/json' 127.0.0.1:8080/object/doTest  -d '{"name": "xxxx", "sex": "male", "age": 123}' -s
{"no":101,"code":"ERR","msg":"something is wrong"}

若要支持protobuf,需要在pom.xml中添加如下内容。其中protostuff库的最大特点是不用生成.proto文件,protostuff会自动根据对象生成.proto文件对应的内存结构,从而免去了维护.proto文件的工作。

pom.xml

<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.29.3</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.3.1</version>
</dependency>

java代码

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;

public class ProtoBufTools {

public static <T> byte[] serialize(T obj) {
@SuppressWarnings("unchecked")
Schema<T> schema = (Schema<T>) RuntimeSchema.getSchema(obj.getClass());
return ProtostuffIOUtil.toByteArray(obj, schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
}

public static <T> T deserialize(byte[] data, Class<T> cls) throws Exception {
T message = cls.getDeclaredConstructor().newInstance();
Schema<T> schema = RuntimeSchema.getSchema(cls);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
}

public static byte[] getBytes(InputStream in) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();

byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
bos.write(buf, 0, len);
}

return bos.toByteArray();
}
}
import java.io.IOException;
import java.io.InputStream;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

public class ProtoBufHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

public ProtoBufHttpMessageConverter() {
this.setSupportedMediaTypes(Collections.singletonList(new MediaType("application", "protobuf")));
}

@Override
protected boolean supports(Class<?> clazz) {
return true;
}

@Override
protected void writeInternal(Object t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
byte[] buf = ProtoBufTools.serialize(t);
outputMessage.getBody().write(buf);
}

@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
InputStream in = inputMessage.getBody();
try {
byte[] buf = ProtoBufTools.getBytes(in);
return ProtoBufTools.deserialize(buf, clazz);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
import java.util.Collections;
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ProtoBufHttpMessageConverter converter = new ProtoBufHttpMessageConverter();

converters.add(converter);
}
}

创建ProtoBufHttpMessageConverter,处理请求时,将protobuf报文转换成对象,处理响应时,将对象转换成protobuf报文。

3、客户器端测试代码

3.1、使用apacheHttpClient4

pom.xml

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>

代码:

import java.io.InputStream;
import java.nio.charset.Charset;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.HttpClientBuilder;

import com.test.model.ReqtObj;
import com.test.model.RespObj;

public class ProtoBufTester {

private static void doTest() {
try {
HttpClient httpClient = HttpClientBuilder.create().build();

String url = "http://127.0.0.1:8080/object/doTest";
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/protobuf");
httpPost.setHeader("Accept", "application/protobuf");  // #1

ReqtObj reqt = new ReqtObj();
reqt.setName("PPPPPP");
reqt.setSex("female");
reqt.setAge(13);

byte[] data = ProtoBufTools.serialize(reqt);

System.out.println("======================" + reqt.toString());
System.out.println("reqtLen=" + data.length);

httpPost.setEntity(
new ByteArrayEntity(data, ContentType.create("application/protobuf", Charset.forName("UTF-8"))));

HttpResponse httpResponse = httpClient.execute(httpPost);
InputStream in = httpResponse.getEntity().getContent();

byte[] buf = ProtoBufTools.getBytes(in);

System.out.println("respLen=" + buf.length);

RespObj resp = ProtoBufTools.deserialize(buf, RespObj.class);
System.out.println("======================" + resp.toString());
} catch (Throwable e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
doTest();
}
}

#1,这行代码非常关键,如果没有这行代码,服务器端生成的响应报文还会是JSON的

apacheHttpClient4只支持HTTP/1.1,不支持HTTP/2.0,因此若要使用HTTP/2.0,则看下面的代码

3.2、使用reactorHttpClient

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import reactor.core.publisher.Flux;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.client.HttpClient;

public class ReactorHttpClientTester {

public static void main(String[] args) {

ReqtObj reqt = new ReqtObj();
reqt.setName("PPPPPP");
reqt.setSex("female");
reqt.setAge(13);

try {
byte[] data = ProtoBufTools.serialize(reqt);

HttpClient client = HttpClient.create().protocol(HttpProtocol.H2C).headers(headers -> {
headers.add("Content-Type", "application/protobuf");
headers.add("Accept", "application/protobuf");
});

Flux<ByteBuf> bufFlux = Flux.just(data).map(Unpooled::wrappedBuffer);

byte[] retData = client.post().uri("http://127.0.0.1:8080/object/doTest").send(bufFlux)
.responseSingle((resp, bytes) -> {
System.out.println(resp.status());
return bytes.asByteArray();
}).block();

RespObj resp = ProtoBufTools.deserialize(retData, RespObj.class);
System.out.println("======================" + resp.toString());
} catch (Throwable e) {
e.printStackTrace();
}
}
}

      这个例子中HTTP2和protobuf就都支持了。但美中不足的是这个客户端没有跟SpringBoot中的RestTemplate或WebClient相结合,也没有自动做对象到protobuf的相互转换,后续会有文章专门解决这个问题。

参考文档
“How-to” Guides


原文地址:https://blog.csdn.net/netyeaxi/article/details/145118737

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!