0


Spring Boot集成kudu快速入门Demo

1.什么是kudu

在Kudu出现前,由于传统存储系统的局限性,对于数据的快速输入和分析还没有一个完美的解决方案,要么以缓慢的数据输入为代价实现快速分析,要么以缓慢的分析为代价实现数据快速输入。随着快速输入和分析场景越来越多,传统存储层的局限性越来越明显,Kudu应运而生,它的定位介于HDFS和HBase之间,将低延迟随机访问,逐行插入、更新和快速分析扫描融合到一个存储层中,是一个既支持随机读写又支持OLAP分析的存储引擎

kudu应用场景

  • 适用于那些既有随机访问,也有批量数据扫描的复合场景
  • 高计算量的场景
  • 使用了高性能的存储设备,包括使用更多的内存
  • 支持数据更新,避免数据反复迁移
  • 支持跨地域的实时数据备份和查询

架构

1、Table

表(Table)是数据库中用来存储数据的对象,是有结构的数据集合。kudu中的表具有schema和全局有序的primary key(主键)。kudu中一个table会被水平分成多个被称之为tablet的片段。

2、Tablet

一个 tablet 是一张 table连续的片段,tablet是kudu表的水平分区,类似于HBase的region。每个tablet存储着一定连续range的数据(key),且tablet两两间的range不会重叠。一张表的所有tablet包含了这张表的所有key空间 tablet 会冗余存储。放置到多个 tablet server上,并且在任何给定的时间点,其中一个副本被认为是leader tablet,其余的被认之为follower tablet。每个tablet都可以进行数据的读请求,但只有Leader tablet负责写数据请求

3、Tablet Server

tablet server负责数据存储,并提供数据读写服务 一个 tablet server 存储了table表的tablet,向kudu client 提供读取数据服务。对于给定的 tablet,一个tablet server 充当 leader,其他 tablet server 充当该 tablet 的 follower 副本 只有 leader服务写请求,然而 leader 或 followers 为每个服务提供读请求 。一个 tablet server 可以服务多个 tablets ,并且一个 tablet 可以被多个 tablet servers 服务着

4、Master Server

集群中负责集群管理、元数据管理等功能

2.环境安装

采用docker-composer安装

  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. version: "3"
  18. services:
  19. kudu-master-1:
  20. image: apache/kudu:${KUDU_QUICKSTART_VERSION:-latest}
  21. ports:
  22. - "7051:7051"
  23. - "8051:8051"
  24. command: ["master"]
  25. volumes:
  26. - kudu-master-1:/var/lib/kudu
  27. environment:
  28. - KUDU_MASTERS=kudu-master-1:7051,kudu-master-2:7151,kudu-master-3:7251
  29. # TODO: Use `host.docker.internal` instead of KUDU_QUICKSTART_IP when it
  30. # works on Linux (https://github.com/docker/for-linux/issues/264)
  31. - >
  32. MASTER_ARGS=--fs_wal_dir=/var/lib/kudu/master
  33. --rpc_bind_addresses=0.0.0.0:7051
  34. --rpc_advertised_addresses=${KUDU_QUICKSTART_IP:?Please set KUDU_QUICKSTART_IP environment variable}:7051
  35. --webserver_port=8051
  36. --webserver_advertised_addresses=${KUDU_QUICKSTART_IP}:8051
  37. --webserver_doc_root=/opt/kudu/www
  38. --stderrthreshold=0
  39. --use_hybrid_clock=false
  40. --unlock_unsafe_flags=true
  41. kudu-master-2:
  42. image: apache/kudu:${KUDU_QUICKSTART_VERSION:-latest}
  43. ports:
  44. - "7151:7151"
  45. - "8151:8151"
  46. command: ["master"]
  47. volumes:
  48. - kudu-master-2:/var/lib/kudu
  49. environment:
  50. - KUDU_MASTERS=kudu-master-1:7051,kudu-master-2:7151,kudu-master-3:7251
  51. - >
  52. MASTER_ARGS=--fs_wal_dir=/var/lib/kudu/master
  53. --rpc_bind_addresses=0.0.0.0:7151
  54. --rpc_advertised_addresses=${KUDU_QUICKSTART_IP}:7151
  55. --webserver_port=8151
  56. --webserver_advertised_addresses=${KUDU_QUICKSTART_IP}:8151
  57. --webserver_doc_root=/opt/kudu/www
  58. --stderrthreshold=0
  59. --use_hybrid_clock=false
  60. --unlock_unsafe_flags=true
  61. kudu-master-3:
  62. image: apache/kudu:${KUDU_QUICKSTART_VERSION:-latest}
  63. ports:
  64. - "7251:7251"
  65. - "8251:8251"
  66. command: ["master"]
  67. volumes:
  68. - kudu-master-3:/var/lib/kudu
  69. environment:
  70. - KUDU_MASTERS=kudu-master-1:7051,kudu-master-2:7151,kudu-master-3:7251
  71. - >
  72. MASTER_ARGS=--fs_wal_dir=/var/lib/kudu/master
  73. --rpc_bind_addresses=0.0.0.0:7251
  74. --rpc_advertised_addresses=${KUDU_QUICKSTART_IP}:7251
  75. --webserver_port=8251
  76. --webserver_advertised_addresses=${KUDU_QUICKSTART_IP}:8251
  77. --webserver_doc_root=/opt/kudu/www
  78. --stderrthreshold=0
  79. --use_hybrid_clock=false
  80. --unlock_unsafe_flags=true
  81. kudu-tserver-1:
  82. image: apache/kudu:${KUDU_QUICKSTART_VERSION:-latest}
  83. depends_on:
  84. - kudu-master-1
  85. - kudu-master-2
  86. - kudu-master-3
  87. ports:
  88. - "7050:7050"
  89. - "8050:8050"
  90. command: ["tserver"]
  91. volumes:
  92. - kudu-tserver-1:/var/lib/kudu
  93. environment:
  94. - KUDU_MASTERS=kudu-master-1:7051,kudu-master-2:7151,kudu-master-3:7251
  95. - >
  96. TSERVER_ARGS=--fs_wal_dir=/var/lib/kudu/tserver
  97. --rpc_bind_addresses=0.0.0.0:7050
  98. --rpc_advertised_addresses=${KUDU_QUICKSTART_IP}:7050
  99. --webserver_port=8050
  100. --webserver_advertised_addresses=${KUDU_QUICKSTART_IP}:8050
  101. --webserver_doc_root=/opt/kudu/www
  102. --stderrthreshold=0
  103. --use_hybrid_clock=false
  104. --unlock_unsafe_flags=true
  105. kudu-tserver-2:
  106. image: apache/kudu:${KUDU_QUICKSTART_VERSION:-latest}
  107. depends_on:
  108. - kudu-master-1
  109. - kudu-master-2
  110. - kudu-master-3
  111. ports:
  112. - "7150:7150"
  113. - "8150:8150"
  114. command: ["tserver"]
  115. volumes:
  116. - kudu-tserver-2:/var/lib/kudu
  117. environment:
  118. - KUDU_MASTERS=kudu-master-1:7051,kudu-master-2:7151,kudu-master-3:7251
  119. - >
  120. TSERVER_ARGS=--fs_wal_dir=/var/lib/kudu/tserver
  121. --rpc_bind_addresses=0.0.0.0:7150
  122. --rpc_advertised_addresses=${KUDU_QUICKSTART_IP}:7150
  123. --webserver_port=8150
  124. --webserver_advertised_addresses=${KUDU_QUICKSTART_IP}:8150
  125. --webserver_doc_root=/opt/kudu/www
  126. --stderrthreshold=0
  127. --use_hybrid_clock=false
  128. --unlock_unsafe_flags=true
  129. kudu-tserver-3:
  130. image: apache/kudu:${KUDU_QUICKSTART_VERSION:-latest}
  131. depends_on:
  132. - kudu-master-1
  133. - kudu-master-2
  134. - kudu-master-3
  135. ports:
  136. - "7250:7250"
  137. - "8250:8250"
  138. command: ["tserver"]
  139. volumes:
  140. - kudu-tserver-3:/var/lib/kudu
  141. environment:
  142. - KUDU_MASTERS=kudu-master-1:7051,kudu-master-2:7151,kudu-master-3:7251
  143. - >
  144. TSERVER_ARGS=--fs_wal_dir=/var/lib/kudu/tserver
  145. --rpc_bind_addresses=0.0.0.0:7250
  146. --rpc_advertised_addresses=${KUDU_QUICKSTART_IP}:7250
  147. --webserver_port=8250
  148. --webserver_advertised_addresses=${KUDU_QUICKSTART_IP}:8250
  149. --webserver_doc_root=/opt/kudu/www
  150. --stderrthreshold=0
  151. --use_hybrid_clock=false
  152. --unlock_unsafe_flags=true
  153. kudu-tserver-4:
  154. image: apache/kudu:${KUDU_QUICKSTART_VERSION:-latest}
  155. depends_on:
  156. - kudu-master-1
  157. - kudu-master-2
  158. - kudu-master-3
  159. ports:
  160. - "7350:7350"
  161. - "8350:8350"
  162. command: ["tserver"]
  163. volumes:
  164. - kudu-tserver-4:/var/lib/kudu
  165. environment:
  166. - KUDU_MASTERS=kudu-master-1:7051,kudu-master-2:7151,kudu-master-3:7251
  167. - >
  168. TSERVER_ARGS=--fs_wal_dir=/var/lib/kudu/tserver
  169. --rpc_bind_addresses=0.0.0.0:7350
  170. --rpc_advertised_addresses=${KUDU_QUICKSTART_IP}:7350
  171. --webserver_port=8350
  172. --webserver_advertised_addresses=${KUDU_QUICKSTART_IP}:8350
  173. --webserver_doc_root=/opt/kudu/www
  174. --stderrthreshold=0
  175. --use_hybrid_clock=false
  176. --unlock_unsafe_flags=true
  177. kudu-tserver-5:
  178. image: apache/kudu:${KUDU_QUICKSTART_VERSION:-latest}
  179. depends_on:
  180. - kudu-master-1
  181. - kudu-master-2
  182. - kudu-master-3
  183. ports:
  184. - "7450:7450"
  185. - "8450:8450"
  186. command: ["tserver"]
  187. volumes:
  188. - kudu-tserver-5:/var/lib/kudu
  189. environment:
  190. - KUDU_MASTERS=kudu-master-1:7051,kudu-master-2:7151,kudu-master-3:7251
  191. - >
  192. TSERVER_ARGS=--fs_wal_dir=/var/lib/kudu/tserver
  193. --rpc_bind_addresses=0.0.0.0:7450
  194. --rpc_advertised_addresses=${KUDU_QUICKSTART_IP}:7450
  195. --webserver_port=8450
  196. --webserver_advertised_addresses=${KUDU_QUICKSTART_IP}:8450
  197. --webserver_doc_root=/opt/kudu/www
  198. --stderrthreshold=0
  199. --use_hybrid_clock=false
  200. --unlock_unsafe_flags=true
  201. volumes:
  202. kudu-master-1:
  203. kudu-master-2:
  204. kudu-master-3:
  205. kudu-tserver-1:
  206. kudu-tserver-2:
  207. kudu-tserver-3:
  208. kudu-tserver-4:
  209. kudu-tserver-5:

windows set env

  1. $env:KUDU_QUICKSTART_VERSION = "1.12.0"
  2. $env:KUDU_QUICKSTART_IP= "10.11.68.77"
  3. Get-ChildItem Env:

linux set env

  1. export KUDU_QUICKSTART_VERSION="1.12.0"
  2. export KUDU_QUICKSTART_IP=$(ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}' | tail -1)

run

  1. docker-compose -f docker/quickstart.yml up -d

stop

  1. docker-compose -f docker/quickstart.yml down
  1. 访问http://localhost:8051/

3.代码工程

实验目的

实现java对kudu的创建表 ,插入,查询,修改操作

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springboot-demo</artifactId>
  7. <groupId>com.et</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>kudu</artifactId>
  12. <properties>
  13. <maven.compiler.source>17</maven.compiler.source>
  14. <maven.compiler.target>17</maven.compiler.target>
  15. <kudu-version>1.12.0</kudu-version>
  16. </properties>
  17. <dependencies>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-web</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-autoconfigure</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-test</artifactId>
  29. <scope>test</scope>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.apache.kudu</groupId>
  33. <artifactId>kudu-client</artifactId>
  34. <version>${kudu-version}</version>
  35. </dependency>
  36. <!-- For logging messages. -->
  37. <dependency>
  38. <groupId>org.slf4j</groupId>
  39. <artifactId>slf4j-simple</artifactId>
  40. <version>1.7.30</version>
  41. </dependency>
  42. <dependency>
  43. <groupId>org.apache.kudu</groupId>
  44. <artifactId>kudu-test-utils</artifactId>
  45. <version>${kudu-version}</version>
  46. <scope>test</scope>
  47. </dependency>
  48. </dependencies>
  49. </project>

example.java

  1. // Licensed to the Apache Software Foundation (ASF) under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. The ASF licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing,
  12. // software distributed under the License is distributed on an
  13. // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. // KIND, either express or implied. See the License for the
  15. // specific language governing permissions and limitations
  16. // under the License.
  17. package com.et.kudu;
  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import org.apache.kudu.ColumnSchema;
  21. import org.apache.kudu.Schema;
  22. import org.apache.kudu.Type;
  23. import org.apache.kudu.client.AlterTableOptions;
  24. import org.apache.kudu.client.CreateTableOptions;
  25. import org.apache.kudu.client.Insert;
  26. import org.apache.kudu.client.KuduClient;
  27. import org.apache.kudu.client.KuduException;
  28. import org.apache.kudu.client.KuduPredicate;
  29. import org.apache.kudu.client.KuduPredicate.ComparisonOp;
  30. import org.apache.kudu.client.KuduScanner;
  31. import org.apache.kudu.client.KuduSession;
  32. import org.apache.kudu.client.KuduTable;
  33. import org.apache.kudu.client.PartialRow;
  34. import org.apache.kudu.client.RowResult;
  35. import org.apache.kudu.client.RowResultIterator;
  36. import org.apache.kudu.client.SessionConfiguration.FlushMode;
  37. /*
  38. * A simple example of using the synchronous Kudu Java client to
  39. * - Create a table.
  40. * - Insert rows.
  41. * - Alter a table.
  42. * - Scan rows.
  43. * - Delete a table.
  44. */
  45. public class Example {
  46. private static final Double DEFAULT_DOUBLE = 12.345;
  47. private static final String KUDU_MASTERS = System.getProperty("kuduMasters", "localhost:7051");
  48. static void createExampleTable(KuduClient client, String tableName) throws KuduException {
  49. // Set up a simple schema.
  50. List<ColumnSchema> columns = new ArrayList<>(2);
  51. columns.add(new ColumnSchema.ColumnSchemaBuilder("key", Type.INT32)
  52. .key(true)
  53. .build());
  54. columns.add(new ColumnSchema.ColumnSchemaBuilder("value", Type.STRING).nullable(true)
  55. .build());
  56. Schema schema = new Schema(columns);
  57. // Set up the partition schema, which distributes rows to different tablets by hash.
  58. // Kudu also supports partitioning by key range. Hash and range partitioning can be combined.
  59. // For more information, see http://kudu.apache.org/docs/schema_design.html.
  60. CreateTableOptions cto = new CreateTableOptions();
  61. List<String> hashKeys = new ArrayList<>(1);
  62. hashKeys.add("key");
  63. int numBuckets = 8;
  64. cto.addHashPartitions(hashKeys, numBuckets);
  65. // Create the table.
  66. client.createTable(tableName, schema, cto);
  67. System.out.println("Created table " + tableName);
  68. }
  69. static void insertRows(KuduClient client, String tableName, int numRows) throws KuduException {
  70. // Open the newly-created table and create a KuduSession.
  71. KuduTable table = client.openTable(tableName);
  72. KuduSession session = client.newSession();
  73. session.setFlushMode(FlushMode.AUTO_FLUSH_BACKGROUND);
  74. for (int i = 0; i < numRows; i++) {
  75. Insert insert = table.newInsert();
  76. PartialRow row = insert.getRow();
  77. row.addInt("key", i);
  78. // Make even-keyed row have a null 'value'.
  79. if (i % 2 == 0) {
  80. row.setNull("value");
  81. } else {
  82. row.addString("value", "value " + i);
  83. }
  84. session.apply(insert);
  85. }
  86. // Call session.close() to end the session and ensure the rows are
  87. // flushed and errors are returned.
  88. // You can also call session.flush() to do the same without ending the session.
  89. // When flushing in AUTO_FLUSH_BACKGROUND mode (the mode recommended
  90. // for most workloads, you must check the pending errors as shown below, since
  91. // write operations are flushed to Kudu in background threads.
  92. session.close();
  93. if (session.countPendingErrors() != 0) {
  94. System.out.println("errors inserting rows");
  95. org.apache.kudu.client.RowErrorsAndOverflowStatus roStatus = session.getPendingErrors();
  96. org.apache.kudu.client.RowError[] errs = roStatus.getRowErrors();
  97. int numErrs = Math.min(errs.length, 5);
  98. System.out.println("there were errors inserting rows to Kudu");
  99. System.out.println("the first few errors follow:");
  100. for (int i = 0; i < numErrs; i++) {
  101. System.out.println(errs[i]);
  102. }
  103. if (roStatus.isOverflowed()) {
  104. System.out.println("error buffer overflowed: some errors were discarded");
  105. }
  106. throw new RuntimeException("error inserting rows to Kudu");
  107. }
  108. System.out.println("Inserted " + numRows + " rows");
  109. }
  110. static void scanTableAndCheckResults(KuduClient client, String tableName, int numRows) throws KuduException {
  111. KuduTable table = client.openTable(tableName);
  112. Schema schema = table.getSchema();
  113. // Scan with a predicate on the 'key' column, returning the 'value' and "added" columns.
  114. List<String> projectColumns = new ArrayList<>(2);
  115. projectColumns.add("key");
  116. projectColumns.add("value");
  117. projectColumns.add("added");
  118. int lowerBound = 0;
  119. KuduPredicate lowerPred = KuduPredicate.newComparisonPredicate(
  120. schema.getColumn("key"),
  121. ComparisonOp.GREATER_EQUAL,
  122. lowerBound);
  123. int upperBound = numRows / 2;
  124. KuduPredicate upperPred = KuduPredicate.newComparisonPredicate(
  125. schema.getColumn("key"),
  126. ComparisonOp.LESS,
  127. upperBound);
  128. KuduScanner scanner = client.newScannerBuilder(table)
  129. .setProjectedColumnNames(projectColumns)
  130. .addPredicate(lowerPred)
  131. .addPredicate(upperPred)
  132. .build();
  133. // Check the correct number of values and null values are returned, and
  134. // that the default value was set for the new column on each row.
  135. // Note: scanning a hash-partitioned table will not return results in primary key order.
  136. int resultCount = 0;
  137. int nullCount = 0;
  138. while (scanner.hasMoreRows()) {
  139. RowResultIterator results = scanner.nextRows();
  140. while (results.hasNext()) {
  141. RowResult result = results.next();
  142. if (result.isNull("value")) {
  143. nullCount++;
  144. }
  145. double added = result.getDouble("added");
  146. if (added != DEFAULT_DOUBLE) {
  147. throw new RuntimeException("expected added=" + DEFAULT_DOUBLE +
  148. " but got added= " + added);
  149. }
  150. resultCount++;
  151. }
  152. }
  153. int expectedResultCount = upperBound - lowerBound;
  154. if (resultCount != expectedResultCount) {
  155. throw new RuntimeException("scan error: expected " + expectedResultCount +
  156. " results but got " + resultCount + " results");
  157. }
  158. int expectedNullCount = expectedResultCount / 2 + (numRows % 2 == 0 ? 1 : 0);
  159. if (nullCount != expectedNullCount) {
  160. throw new RuntimeException("scan error: expected " + expectedNullCount +
  161. " rows with value=null but found " + nullCount);
  162. }
  163. System.out.println("Scanned some rows and checked the results");
  164. }
  165. public static void main(String[] args) {
  166. System.out.println("-----------------------------------------------");
  167. System.out.println("Will try to connect to Kudu master(s) at " + KUDU_MASTERS);
  168. System.out.println("Run with -DkuduMasters=master-0:port,master-1:port,... to override.");
  169. System.out.println("-----------------------------------------------");
  170. String tableName = "java_example-" + System.currentTimeMillis();
  171. KuduClient client = new KuduClient.KuduClientBuilder(KUDU_MASTERS).build();
  172. try {
  173. createExampleTable(client, tableName);
  174. int numRows = 150;
  175. insertRows(client, tableName, numRows);
  176. // Alter the table, adding a column with a default value.
  177. // Note: after altering the table, the table needs to be re-opened.
  178. AlterTableOptions ato = new AlterTableOptions();
  179. ato.addColumn("added", org.apache.kudu.Type.DOUBLE, DEFAULT_DOUBLE);
  180. client.alterTable(tableName, ato);
  181. System.out.println("Altered the table");
  182. scanTableAndCheckResults(client, tableName, numRows);
  183. } catch (Exception e) {
  184. e.printStackTrace();
  185. } finally {
  186. try {
  187. client.deleteTable(tableName);
  188. System.out.println("Deleted the table");
  189. } catch (Exception e) {
  190. e.printStackTrace();
  191. } finally {
  192. try {
  193. client.shutdown();
  194. } catch (Exception e) {
  195. e.printStackTrace();
  196. }
  197. }
  198. }
  199. }
  200. }

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.

4.测试

测试创建表

  1. @Test
  2. public void testCreateExampleTable() throws KuduException {
  3. String tableName = "test_create_example";
  4. Example.createExampleTable(client, tableName);
  5. assertTrue(client.tableExists(tableName));
  6. }

插入数据

  1. @Test
  2. public void testInsertRows() throws KuduException {
  3. String tableName = "test_create_example";
  4. // Example.insertRows(client,tableName,100);
  5. System.out.println(client.getTableStatistics(tableName).getLiveRowCount());
  6. }

dashboard可以看到我们创建的表和记录条数

5.引用

  • Apache Kudu - Fast Analytics on Fast Data
  • Spring Boot集成kudu快速入门Demo | Harries Blog™

本文转载自: https://blog.csdn.net/dot_life/article/details/140505216
版权归原作者 HBLOGA 所有, 如有侵权,请联系我们删除。

“Spring Boot集成kudu快速入门Demo”的评论:

还没有评论