看了我的 RPC 實(shí)戰(zhàn),同事拍案叫絕

1. RPC

1.1 什么是 RPC ?

RPC(Remote Procedure Call Protocol)遠(yuǎn)程過(guò)程調(diào)用協(xié)議,目標(biāo)就是讓遠(yuǎn)程服務(wù)調(diào)用更加簡(jiǎn)單、透明。

RPC 框架負(fù)責(zé)屏蔽底層的傳輸方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二進(jìn)制)和通信細(xì)節(jié),服務(wù)調(diào)用者可以像調(diào)用本地接口一樣調(diào)用遠(yuǎn)程的服務(wù)提供者,而不需要關(guān)心底層通信細(xì)節(jié)和調(diào)用過(guò)程。

image.png

1.2 為什么要用 RPC ?

當(dāng)我們的業(yè)務(wù)越來(lái)越多、應(yīng)用也越來(lái)越多時(shí),自然的,我們會(huì)發(fā)現(xiàn)有些功能已經(jīng)不能簡(jiǎn)單劃分開(kāi)來(lái)或者劃分不出來(lái)。

此時(shí)可以將公共業(yè)務(wù)邏輯抽離出來(lái),將之組成獨(dú)立的服務(wù) Service 應(yīng)用,而原有的、新增的應(yīng)用都可以與那些獨(dú)立的 Service 應(yīng)用 交互,以此來(lái)完成完整的業(yè)務(wù)功能。

所以我們急需一種高效的應(yīng)用程序之間的通訊手段來(lái)完成這種需求,RPC 大顯身手的時(shí)候來(lái)了!

1.3 常用的 RPC 框架

  • gRPC:一開(kāi)始由 google 開(kāi)發(fā),是一款語(yǔ)言中立、平臺(tái)中立、開(kāi)源的遠(yuǎn)程過(guò)程調(diào)用(RPC)系統(tǒng)。
  • Thrift:thrift 是一個(gè)軟件框架,用來(lái)進(jìn)行可擴(kuò)展且跨語(yǔ)言的服務(wù)的開(kāi)發(fā)。它結(jié)合了功能強(qiáng)大的軟件堆棧和代碼生成引擎,以構(gòu)建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些編程語(yǔ)言間無(wú)縫結(jié)合的、高效的服務(wù)。
  • Dubbo:Dubbo 是一個(gè)分布式服務(wù)框架,以及 SOA 治理方案,Dubbo自2011年開(kāi)源后,已被許多非阿里系公司使用。
  • Spring Cloud:Spring Cloud 由眾多子項(xiàng)目組成,如 Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul 等,提供了搭建分布式系統(tǒng)及微服務(wù)常用的工具。

1.4 RPC 的調(diào)用流程

要讓網(wǎng)絡(luò)通信細(xì)節(jié)對(duì)使用者透明,我們需要對(duì)通信細(xì)節(jié)進(jìn)行封裝,我們先看下一個(gè) RPC 調(diào)用的流程涉及到哪些通信細(xì)節(jié):

image.png
  1. 服務(wù)消費(fèi)方(client)調(diào)用以本地調(diào)用方式調(diào)用服務(wù);
  2. client stub接收到調(diào)用后負(fù)責(zé)將方法、參數(shù)等組裝成能夠進(jìn)行網(wǎng)絡(luò)傳輸?shù)南Ⅲw;
  3. client stub找到服務(wù)地址,并將消息發(fā)送到服務(wù)端;
  4. server stub收到消息后進(jìn)行解碼;
  5. server stub根據(jù)解碼結(jié)果調(diào)用本地的服務(wù);
  6. 本地服務(wù)執(zhí)行并將結(jié)果返回給 server stub;
  7. server stub將返回結(jié)果打包成消息并發(fā)送至消費(fèi)方;
  8. client stub接收到消息,并進(jìn)行解碼;
  9. 服務(wù)消費(fèi)方得到最終結(jié)果。

RPC 的目標(biāo)就是要 2~8 這些步驟都封裝起來(lái),讓用戶對(duì)這些細(xì)節(jié)透明,下面是網(wǎng)上的另外一幅圖,感覺(jué)一目了然:

image.png

2. gRPC

2.1 什么是 gRPC ?

gRPC 是一個(gè)高性能、通用的開(kāi)源 RPC 框架,其由 Google 2015 年主要面向移動(dòng)應(yīng)用開(kāi)發(fā)并基于 HTTP/2 協(xié)議標(biāo)準(zhǔn)而設(shè)計(jì),基于 ProtoBuf 序列化協(xié)議開(kāi)發(fā),且支持眾多開(kāi)發(fā)語(yǔ)言。

由于是開(kāi)源框架,通信的雙方可以進(jìn)行二次開(kāi)發(fā),所以客戶端和服務(wù)器端之間的通信會(huì)更加專注于業(yè)務(wù)層面的內(nèi)容,減少了對(duì)由 gRPC 框架實(shí)現(xiàn)的底層通信的關(guān)注。

如下圖,DATA 部分即業(yè)務(wù)層面內(nèi)容,下面所有的信息都由 gRPC 進(jìn)行封裝。

image.png

2.2 gRPC 的特點(diǎn)

  • 跨語(yǔ)言使用,支持 C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP 等編程語(yǔ)言;
  • 基于 IDL 文件定義服務(wù),通過(guò) proto3 工具生成指定語(yǔ)言的數(shù)據(jù)結(jié)構(gòu)、服務(wù)端接口以及客戶端 Stub;
  • 通信協(xié)議基于標(biāo)準(zhǔn)的 HTTP/2 設(shè)計(jì),支持雙向流、消息頭壓縮、單 TCP 的多路復(fù)用、服務(wù)端推送等特性,這些特性使得 gRPC 在移動(dòng)端設(shè)備上更加省電和節(jié)省網(wǎng)絡(luò)流量;
  • 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一種語(yǔ)言無(wú)關(guān)的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 調(diào)用的高性能;
  • 安裝簡(jiǎn)單,擴(kuò)展方便(用該框架每秒可達(dá)到百萬(wàn)個(gè)RPC)。

2.3 gRPC 交互過(guò)程

image.png
  • 交換機(jī)在開(kāi)啟 gRPC 功能后充當(dāng) gRPC 客戶端的角色,采集服務(wù)器充當(dāng) gRPC 服務(wù)器角色;
  • 交換機(jī)會(huì)根據(jù)訂閱的事件構(gòu)建對(duì)應(yīng)數(shù)據(jù)的格式(GPB/JSON),通過(guò) Protocol Buffers 進(jìn)行編寫(xiě) proto 文件,交換機(jī)與服務(wù)器建立 gRPC 通道,通過(guò) gRPC 協(xié)議向服務(wù)器發(fā)送請(qǐng)求消息;
  • 服務(wù)器收到請(qǐng)求消息后,服務(wù)器會(huì)通過(guò) Protocol Buffers 解譯 proto 文件,還原出最先定義好格式的數(shù)據(jù)結(jié)構(gòu),進(jìn)行業(yè)務(wù)處理;
  • 數(shù)據(jù)處理完后,服務(wù)器需要使用 Protocol Buffers 重編譯應(yīng)答數(shù)據(jù),通過(guò) gRPC 協(xié)議向交換機(jī)發(fā)送應(yīng)答消息;
  • 交換機(jī)收到應(yīng)答消息后,結(jié)束本次的 gRPC 交互。

簡(jiǎn)單地說(shuō),gRPC 就是在客戶端和服務(wù)器端開(kāi)啟 gRPC 功能后建立連接,將設(shè)備上配置的訂閱數(shù)據(jù)推送給服務(wù)器端。

我們可以看到整個(gè)過(guò)程是需要用到 Protocol Buffers 將所需要處理數(shù)據(jù)的結(jié)構(gòu)化數(shù)據(jù)在 proto 文件中進(jìn)行定義。

2.4 Protocol Buffers

你可以理解 ProtoBuf 是一種更加靈活、高效的數(shù)據(jù)格式,與 XML、JSON 類似,在一些高性能且對(duì)響應(yīng)速度有要求的數(shù)據(jù)傳輸場(chǎng)景非常適用。

ProtoBuf 在 gRPC 的框架中主要有三個(gè)作用:定義數(shù)據(jù)結(jié)構(gòu)、定義服務(wù)接口,通過(guò)序列化和反序列化方式提升傳輸效率。

為什么 ProtoBuf 會(huì)提高傳輸效率呢?

我們知道使用 XML、JSON 進(jìn)行數(shù)據(jù)編譯時(shí),數(shù)據(jù)文本格式更容易閱讀,但進(jìn)行數(shù)據(jù)交換時(shí),設(shè)備就需要耗費(fèi)大量的 CPU 在 I/O 動(dòng)作上,自然會(huì)影響整個(gè)傳輸速率。

Protocol Buffers 不像前者,它會(huì)將字符串進(jìn)行序列化后再進(jìn)行傳輸,即二進(jìn)制數(shù)據(jù)。

image.png

可以看到其實(shí)兩者內(nèi)容相差不大,并且內(nèi)容非常直觀,但是 Protocol Buffers 編碼的內(nèi)容只是提供給操作者閱讀的,實(shí)際上傳輸?shù)牟⒉粫?huì)以這種文本形式,而是序列化后的二進(jìn)制數(shù)據(jù),字節(jié)數(shù)會(huì)比 JSON、XML 的字節(jié)數(shù)少很多,速率更快。

gPRC 如何支撐跨平臺(tái),多語(yǔ)言呢 ?

Protocol Buffers 自帶一個(gè)編譯器也是一個(gè)優(yōu)勢(shì)點(diǎn),前面提到的 proto 文件就是通過(guò)編譯器進(jìn)行編譯的,proto 文件需要編譯生成一個(gè)類似庫(kù)文件,基于庫(kù)文件才能真正開(kāi)發(fā)數(shù)據(jù)應(yīng)用。

具體用什么編程語(yǔ)言編譯生成這個(gè)庫(kù)文件呢?由于現(xiàn)網(wǎng)中負(fù)責(zé)網(wǎng)絡(luò)設(shè)備和服務(wù)器設(shè)備的運(yùn)維人員往往不是同一組人,運(yùn)維人員可能會(huì)習(xí)慣使用不同的編程語(yǔ)言進(jìn)行運(yùn)維開(kāi)發(fā),那么 Protocol Buffers 其中一個(gè)優(yōu)勢(shì)就能發(fā)揮出來(lái)——跨語(yǔ)言。

從上面的介紹,我們得出在編碼方面 Protocol Buffers 對(duì)比 JSON、XML 的優(yōu)點(diǎn):

  • 標(biāo)準(zhǔn)的 IDL 和 IDL 編譯器,這使得其對(duì)工程師非常友好;
  • 序列化數(shù)據(jù)非常簡(jiǎn)潔,緊湊,與 XML 相比,其序列化之后的數(shù)據(jù)量約為 1/3 到 1/10;
  • 解析速度非常快,比對(duì)應(yīng)的 XML 快約 20-100 倍;
  • 提供了非常友好的動(dòng)態(tài)庫(kù),使用非常簡(jiǎn)單,反序列化只需要一行代碼。

Protobuf 也有其局限性:

  • 由于 Protobuf 產(chǎn)生于 Google,所以目前其僅支持 Java、C++、Python 三種語(yǔ)言;
  • Protobuf 支持的數(shù)據(jù)類型相對(duì)較少,不支持常量類型;
  • 由于其設(shè)計(jì)的理念是純粹的展現(xiàn)層協(xié)議(Presentation Layer),目前并沒(méi)有一個(gè)專門(mén)支持 Protobuf 的 RPC 框架。

Protobuf 適用場(chǎng)景:

  • Protobuf 具有廣泛的用戶基礎(chǔ),空間開(kāi)銷小以及高解析性能是其亮點(diǎn),非常適合于公司內(nèi)部的對(duì)性能要求高的 RPC 調(diào)用;
  • 由于 Protobuf 提供了標(biāo)準(zhǔn)的 IDL 以及對(duì)應(yīng)的編譯器,其 IDL 文件是參與各方的非常強(qiáng)的業(yè)務(wù)約束;
  • Protobuf 與傳輸層無(wú)關(guān),采用 HTTP 具有良好的跨防火墻的訪問(wèn)屬性,所以 Protobuf 也適用于公司間對(duì)性能要求比較高的場(chǎng)景;
  • 由于其解析性能高,序列化后數(shù)據(jù)量相對(duì)少,非常適合應(yīng)用層對(duì)象的持久化場(chǎng)景;
  • 主要問(wèn)題在于其所支持的語(yǔ)言相對(duì)較少,另外由于沒(méi)有綁定的標(biāo)準(zhǔn)底層傳輸層協(xié)議,在公司間進(jìn)行傳輸層協(xié)議的調(diào)試工作相對(duì)麻煩。

2.5 基于 HTTP 2.0 標(biāo)準(zhǔn)設(shè)計(jì)

除了 Protocol Buffers 之外,從交互圖中和分層框架可以看到, gRPC 還有另外一個(gè)優(yōu)勢(shì)——它是基于 HTTP 2.0 協(xié)議的。

由于 gRPC 基于 HTTP 2.0 標(biāo)準(zhǔn)設(shè)計(jì),帶來(lái)了更多強(qiáng)大功能,如多路復(fù)用、二進(jìn)制幀、頭部壓縮、推送機(jī)制。

這些功能給設(shè)備帶來(lái)重大益處,如節(jié)省帶寬、降低 TCP 連接次數(shù)、節(jié)省 CPU 使用等,gRPC 既能夠在客戶端應(yīng)用,也能夠在服務(wù)器端應(yīng)用,從而以透明的方式實(shí)現(xiàn)兩端的通信和簡(jiǎn)化通信系統(tǒng)的構(gòu)建。

HTTP 1.X 定義了四種與服務(wù)器交互的方式,分別為 GET、POST、PUT、DELETE,這些在 HTTP 2.0 中均保留,我們看看 HTTP 2.0 的新特性:雙向流、多路復(fù)用、二進(jìn)制幀、頭部壓縮。

2.6 性能對(duì)比

與采用文本格式的 JSON 相比,采用二進(jìn)制格式的 protobuf 在速度上可以達(dá)到前者的 5 倍!

Auth0 網(wǎng)站所做的性能測(cè)試結(jié)果顯示,protobuf 和 JSON 的優(yōu)勢(shì)差異在 Java、Python 等環(huán)境中尤為明顯,下圖是 Auth0 在兩個(gè) Spring Boot 應(yīng)用程序間所做的對(duì)比測(cè)試結(jié)果。

image.png

結(jié)果顯示,protobuf 所需的請(qǐng)求時(shí)間最多只有 JSON 的 20% 左右,即速度是其 5 倍!

下面看一下性能和空間開(kāi)銷對(duì)比。

image.png
image.png

從上圖可得出如下結(jié)論:

  • XML序列化(Xstream)無(wú)論在性能和簡(jiǎn)潔性上比較差。
  • Thrift 與 Protobuf 相比在時(shí)空開(kāi)銷方面都有一定的劣勢(shì)。
  • Protobuf 和 Avro 在兩方面表現(xiàn)都非常優(yōu)越。

3. gRPC 實(shí)戰(zhàn)

3.1 項(xiàng)目結(jié)構(gòu)

我們先看一下項(xiàng)目結(jié)構(gòu):

image.png

3.2 生成 protobuf 文件

文件 helloworld.proto:

syntax = "proto3";option java_multiple_files = true;option java_package = "io.grpc.examples.helloworld";option java_outer_classname = "HelloWorldProto";option objc_class_prefix = "HLW";package helloworld;// The greeting service definition.service Greeter {  // Sends a greeting  rpc SayHello (HelloRequest) returns (HelloReply) {}}// The request message containing the user's name.message HelloRequest {  string name = 1;}// The response message containing the greetingsmessage HelloReply {  string message = 1;}

這里提供了一個(gè) SayHello() 方法,然后入?yún)?HelloRequest,返回值為 HelloReply,可以看到 proto 文件只定義了入?yún)⒑头祷刂档母袷?,以及調(diào)用的接口,至于接口內(nèi)部的實(shí)現(xiàn),該文件完全不用關(guān)心。

文件 pom.xml:

<?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">    <parent>        <artifactId>rpc-study</artifactId>        <groupId>org.example</groupId>        <version>1.0-SNAPSHOT</version>    </parent>    <modelVersion>4.0.0</modelVersion>    <artifactId>grpc-demo</artifactId>    <dependencies>        <dependency>            <groupId>io.grpc</groupId>            <artifactId>grpc-netty-shaded</artifactId>            <version>1.14.0</version>        </dependency>        <dependency>            <groupId>io.grpc</groupId>            <artifactId>grpc-protobuf</artifactId>            <version>1.14.0</version>        </dependency>        <dependency>            <groupId>io.grpc</groupId>            <artifactId>grpc-stub</artifactId>            <version>1.14.0</version>        </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.xolstice.maven.plugins</groupId>                <artifactId>protobuf-maven-plugin</artifactId>                <version>0.5.1</version>                <configuration>                    <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>                    <pluginId>grpc-java</pluginId>                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.14.0:exe:${os.detected.classifier}</pluginArtifact>                </configuration>                <executions>                    <execution>                        <goals>                            <goal>compile</goal>                            <goal>compile-custom</goal>                        </goals>                    </execution>                </executions>            </plugin>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-compiler-plugin</artifactId>                <configuration>                    <source>6</source>                    <target>6</target>                </configuration>            </plugin>        </plugins>    </build></project>

這里面的 build 其實(shí)是為了安裝 protobuf 插件,里面其實(shí)有 2 個(gè)插件我們需要用到,分別為 protobuf:compile 和 protobuf:compile-javanano,當(dāng)我們直接執(zhí)行時(shí),會(huì)生成左側(cè)文件,其中 GreeterGrpc 提供調(diào)用接口,Hello 開(kāi)頭的文件功能主要是對(duì)數(shù)據(jù)進(jìn)行序列化,然后處理入?yún)⒑头祷刂怠?/p>

可能有同學(xué)會(huì)問(wèn),你把文件生成到 target 中,我想放到 main.src 中,你可以把這些文件 copy 出來(lái),或者也可以通過(guò)工具生成:

下載 protoc.exe 工具 ,下載地址:
https://github.com/protocolbuffers/protobuf/releases

下載 protoc-gen-grpc 插件, 下載地址:
http://jcenter.bintray.com/io/grpc/protoc-gen-grpc-java/

image.png

3.3 服務(wù)端和客戶端

文件 HelloWorldClient.java:

public class HelloWorldClient {    private final ManagedChannel channel;    private final GreeterGrpc.GreeterBlockingStub blockingStub;    private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());    public HelloWorldClient(String host,int port){        channel = ManagedChannelBuilder.forAddress(host,port)                .usePlaintext(true)                .build();        blockingStub = GreeterGrpc.newBlockingStub(channel);    }    public void shutdown() throws InterruptedException {        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);    }    public  void greet(String name){        HelloRequest request = HelloRequest.newBuilder().setName(name).build();        HelloReply response;        try{            response = blockingStub.sayHello(request);        } catch (StatusRuntimeException e)        {            logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());            return;        }        logger.info("Message from gRPC-Server: "+response.getMessage());    }    public static void main(String[] args) throws InterruptedException {        HelloWorldClient client = new HelloWorldClient("127.0.0.1",50051);        try{            String user = "world";            if (args.length > 0){                user = args[0];            }            client.greet(user);        }finally {            client.shutdown();        }    }}

這個(gè)太簡(jiǎn)單了,就是連接服務(wù)端口,調(diào)用 sayHello() 方法。

文件 HelloWorldServer.java:

public class HelloWorldServer {    private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());    private int port = 50051;    private Server server;    private void start() throws IOException {        server = ServerBuilder.forPort(port)                .addService(new GreeterImpl())                .build()                .start();        logger.info("Server started, listening on " + port);        Runtime.getRuntime().addShutdownHook(new Thread() {            @Override            public void run() {                System.err.println("*** shutting down gRPC server since JVM is shutting down");                HelloWorldServer.this.stop();                System.err.println("*** server shut down");            }        });    }    private void stop() {        if (server != null) {            server.shutdown();        }    }    // block 一直到退出程序    private void blockUntilShutdown() throws InterruptedException {        if (server != null) {            server.awaitTermination();        }    }    public static void main(String[] args) throws IOException, InterruptedException {        final HelloWorldServer server = new HelloWorldServer();        server.start();        server.blockUntilShutdown();    }    // 實(shí)現(xiàn) 定義一個(gè)實(shí)現(xiàn)服務(wù)接口的類    private class GreeterImpl extends GreeterGrpc.GreeterImplBase {        @Override        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {            HelloReply reply = HelloReply.newBuilder().setMessage(("Hello " + req.getName())).build();            responseObserver.onNext(reply);            responseObserver.onCompleted();            System.out.println("Message from gRPC-Client:" + req.getName());            System.out.println("Message Response:" + reply.getMessage());        }    }}

主要是實(shí)現(xiàn) sayHello() 方法,里面對(duì)數(shù)據(jù)進(jìn)行了簡(jiǎn)單處理,入?yún)?“W orld”,返回的是 “Hello World”。

3.4 啟動(dòng)服務(wù)

先啟動(dòng) Server,返回如下:

image.png

再啟動(dòng) Client,返回如下:

image.png

同時(shí) Server返回如下:

image.png

3.5 項(xiàng)目代碼

Git 地址:https://github.com/lml200701158/rpc-study

4. 寫(xiě)在最后

這篇文章詳細(xì)講解了 RPC 和 gRPC,以及 gRPC 的應(yīng)用示例,非常全面,后面會(huì)再把 Thrift 整理出來(lái)。

這個(gè) Demo 看起來(lái)很簡(jiǎn)單,我 TM 居然搞了大半天,一開(kāi)始是因?yàn)椴恢佬枰獔?zhí)行 2 個(gè)不同的插件來(lái)生成 protobuf,以為只需要點(diǎn)擊 protobuf:compile 就可以,結(jié)果發(fā)現(xiàn) protobuf:compile-javanano 也需要點(diǎn)一下。

還有就是我自己喜歡作,感覺(jué)通過(guò)插件生成 protobuf 不完美,我想通過(guò)自己下載的插件,手動(dòng)生成 protobuf 文件,結(jié)果手動(dòng)生成的沒(méi)有搞定,自動(dòng)生成的方式也不可用,搞了半天才發(fā)現(xiàn)是緩存的問(wèn)題,最后直接執(zhí)行 “Invalidate Caches / Restart” 才搞定。

應(yīng)征了一句話“no zuo no die”,不過(guò)這個(gè)過(guò)程還是需要經(jīng)歷的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容