使用 Cassandra 轉接程式連線至 Spanner

本頁面說明 Cassandra 介面卡,並解釋如何使用介面卡連線至 Spanner。

Cassandra 介面卡的設計是在與應用程式相同的機器上執行。這個介面卡會在 localhost 上公開端點,支援 Cassandra 查詢語言 (CQL) 連線通訊協定。這個服務會將 CQL 連線通訊協定轉換為 gRPC,也就是 Spanner 連線通訊協定。這個 Proxy 在本機執行後,Cassandra 用戶端就能連線至 Spanner 資料庫。

您可以透過下列方式啟動 Cassandra 介面卡:

  • 在 Go 應用程式中處理
  • 在 Java 應用程式中處理
  • 以獨立程序執行
  • 在 Docker 容器中

事前準備

啟動 Cassandra 介面卡前,請務必在執行 Cassandra 介面卡的機器上,使用使用者帳戶或服務帳戶進行驗證。如果您使用服務帳戶,必須知道 JSON 金鑰檔 (憑證檔案) 的位置。設定 GOOGLE_APPLICATION_CREDENTIALS 環境變數,指定憑證路徑。

如需詳細資訊,請參閱:

將 Cassandra 介面卡連線至應用程式

Cassandra 介面卡需要下列資訊:

  • 專案名稱
  • Spanner 執行個體名稱
  • 要連線的資料庫

如果您使用 Docker,則需要 JSON 格式憑證檔案 (金鑰檔案) 的路徑。

Java 處理序內

  1. 如果您使用服務帳戶進行驗證,請確保 GOOGLE_APPLICATION_CREDENTIALS 環境變數已設為憑證檔案的路徑。

  2. 如果是 Java 應用程式,只要將 google-cloud-spanner-cassandra 新增為專案的依附元件,即可直接將 Cassandra 介面卡連結至應用程式。

如果是 Maven,請在 <dependencies> 區段下方新增下列依附元件:

<dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-spanner-cassandra</artifactId>
    <version>0.4.0</version>
</dependency>

如果是 Gradle,請新增下列內容:

dependencies {
    implementation 'com.google.cloud:google-cloud-spanner-cassandra:0.4.0'
}

  1. 修改CqlSession建立程式碼。請改用 SpannerCqlSessionBuilder,並提供 Spanner 資料庫 URI: CqlSessionBuilder
    
    import com.datastax.oss.driver.api.core.CqlSession;
    import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
    import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
    import com.datastax.oss.driver.api.core.cql.ResultSet;
    import com.datastax.oss.driver.api.core.cql.Row;
    import com.google.cloud.spanner.adapter.SpannerCqlSession;
    import java.net.InetSocketAddress;
    import java.time.Duration;
    import java.util.Random;
    
    // This sample assumes your spanner database <my_db> contains a table <users>
    // with the following schema:
    
    // CREATE TABLE users (
    //  id        INT64          OPTIONS (cassandra_type = 'int'),
    //  active    BOOL           OPTIONS (cassandra_type = 'boolean'),
    //  username  STRING(MAX)    OPTIONS (cassandra_type = 'text'),
    // ) PRIMARY KEY (id);
    
    class QuickStartSample {
    
      public static void main(String[] args) {
    
        // TODO(developer): Replace these variables before running the sample.
        final String projectId = "my-gcp-project";
        final String instanceId = "my-spanner-instance";
        final String databaseId = "my_db";
    
        final String databaseUri =
            String.format("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseId);
    
        try (CqlSession session =
            SpannerCqlSession.builder() // `SpannerCqlSession` instead of `CqlSession`
                .setDatabaseUri(databaseUri) // Set spanner database URI.
                .addContactPoint(new InetSocketAddress("localhost", 9042))
                .withLocalDatacenter("datacenter1")
                .withKeyspace(databaseId) // Keyspace name should be the same as spanner database name
                .withConfigLoader(
                    DriverConfigLoader.programmaticBuilder()
                        .withString(DefaultDriverOption.PROTOCOL_VERSION, "V4")
                        .withDuration(
                            DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, Duration.ofSeconds(5))
                        .build())
                .build()) {
    
          final int randomUserId = new Random().nextInt(Integer.MAX_VALUE);
    
          System.out.printf("Inserting user with ID: %d%n", randomUserId);
    
          // INSERT data
          session.execute(
              "INSERT INTO users (id, active, username) VALUES (?, ?, ?)",
              randomUserId,
              true,
              "John Doe");
    
          System.out.printf("Successfully inserted user: %d%n", randomUserId);
          System.out.printf("Querying user: %d%n", randomUserId);
    
          // SELECT data
          ResultSet rs =
              session.execute("SELECT id, active, username FROM users WHERE id = ?", randomUserId);
    
          // Get the first row from the result set
          Row row = rs.one();
    
          System.out.printf(
              "%d %b %s%n", row.getInt("id"), row.getBoolean("active"), row.getString("username"));
    
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
    

改為在程序內執行

如果是 Go 應用程式,您需要在叢集初始化檔案中進行一行變更,才能整合 Spanner Cassandra Go 用戶端。然後直接將 Cassandra 介面卡連結至應用程式。

  1. 在 Go 應用程式中,從 Spanner Cassandra Go 用戶端匯入介面的 spanner 套件。
import spanner "github.com/googleapis/go-spanner-cassandra/cassandra/gocql"
  1. 修改叢集建立程式碼,使用 spanner.NewCluster 而非 gocql.NewCluster,並提供 Spanner 資料庫 URI:
    import (
    	"fmt"
    	"io"
    	"math"
    	"math/rand/v2"
    	"time"
    
    	spanner "github.com/googleapis/go-spanner-cassandra/cassandra/gocql"
    )
    
    // This sample assumes your spanner database <your_db> contains a table <users>
    // with the following schema:
    //
    // CREATE TABLE users (
    //	id   	 	INT64          OPTIONS (cassandra_type = 'int'),
    //	active    	BOOL           OPTIONS (cassandra_type = 'boolean'),
    //	username  	STRING(MAX)    OPTIONS (cassandra_type = 'text'),
    // ) PRIMARY KEY (id);
    
    func quickStart(databaseURI string, w io.Writer) error {
    	opts := &spanner.Options{
    		DatabaseUri: databaseURI,
    	}
    	cluster := spanner.NewCluster(opts)
    	if cluster == nil {
    		return fmt.Errorf("failed to create cluster")
    	}
    	defer spanner.CloseCluster(cluster)
    
    	// You can still configure your cluster as usual after connecting to your
    	// spanner database
    	cluster.Timeout = 5 * time.Second
    	cluster.Keyspace = "your_db_name"
    
    	session, err := cluster.CreateSession()
    
    	if err != nil {
    		return err
    	}
    
    	randomUserId := rand.IntN(math.MaxInt32)
    	if err = session.Query("INSERT INTO users (id, active, username) VALUES (?, ?, ?)",
    			       randomUserId, true, "John Doe").
    		Exec(); err != nil {
    		return err
    	}
    
    	var id int
    	var active bool
    	var username string
    	if err = session.Query("SELECT id, active, username FROM users WHERE id = ?",
    			       randomUserId).
    		Scan(&id, &active, &username); err != nil {
    		return err
    	}
    	fmt.Fprintf(w, "%d %v %s\n", id, active, username)
    	return nil
    }

連線至 Spanner 資料庫後,即可照常設定叢集。

獨立式

  1. 複製存放區:
git clone https://github.com/googleapis/go-spanner-cassandra.git
cd go-spanner-cassandra
  1. 執行 cassandra_launcher.go 並加上必要的 -db 旗標:
go run cassandra_launcher.go \
-db "projects/my_project/instances/my_instance/databases/my_database"
  1. 請將 -db 改成您的 Spanner 資料庫 URI。

Docker

使用下列指令啟動 Cassandra 介面卡。

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
docker run -d -p 9042:9042 \
-e GOOGLE_APPLICATION_CREDENTIALS \
-v ${GOOGLE_APPLICATION_CREDENTIALS}:${GOOGLE_APPLICATION_CREDENTIALS}:ro \
gcr.io/cloud-spanner-adapter/cassandra-adapter \
-db DATABASE_URI

以下列出 Spanner Cassandra Adapter 最常用的啟動選項:

  • -db <DatabaseUri>

Spanner 資料庫 URI (必要)。這會指定用戶端連線的 Spanner 資料庫。例如 projects/YOUR_PROJECT/instances/YOUR_INSTANCE/databases/YOUR_DATABASE

  • -tcp <TCPEndpoint>

用戶端 Proxy 接聽程式位址。這會定義 TCP 端點,用戶端會在此接聽傳入的 Cassandra 用戶端連線。預設值:localhost:9042

  • -grpc-channels <NumGrpcChannels>

連線至 Spanner 時要使用的 gRPC 管道數量。預設值:4

舉例來說,下列指令會使用應用程式憑證,在通訊埠 9042 上啟動 Cassandra 介面卡,並將介面卡連線至 projects/my_project/instances/my_instance/databases/my_database 資料庫:

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
docker run -d -p 9042:9042 \
-e GOOGLE_APPLICATION_CREDENTIALS \
-v ${GOOGLE_APPLICATION_CREDENTIALS}:${GOOGLE_APPLICATION_CREDENTIALS}:ro \
gcr.io/cloud-spanner-adapter/cassandra-adapter \
-db projects/my_project/instances/my_instance/databases/my_database

建議

下列建議有助於提升 Cassandra 介面卡的體驗。這些建議著重於 Java,特別是 Java 4 版的 Cassandra 用戶端驅動程式

增加要求逾時

與預設的兩秒值相比,要求逾時時間設為五秒以上,可提供更優異的 Cassandra 介面卡體驗。

# Sample application.conf: increases request timeout to five seconds
datastax-java-driver {
  basic {
    request {
      timeout = 5 seconds
    }
  }
}

調整連線集區

連線數量上限和每個連線/主機的並行要求數量上限預設設定,適用於開發、測試,以及低用量的實際工作環境或預備環境。不過,我們建議您增加這些值,因為 Cassandra 介面卡會偽裝成單一節點,不像 Cassandra 叢集內的節點集區。

調高這些值可讓用戶端與 Cassandra 介面之間建立更多並行連線。這可避免在負載過重時,連線集區耗盡。

# Sample application.conf: increases maximum number of requests that can be
# executed concurrently on a connection
advanced.connection {
  max-requests-per-connection = 32000
  pool {
    local.size = 10
  }
}

調整 gRPC 管道

Spanner 用戶端會使用 gRPC 管道進行通訊。一個 gRPC 管道大致相當於一個 TCP 連線。一個 gRPC 管道最多可處理 100 項並行要求。也就是說,應用程式需要的 gRPC 管道數量至少要等於應用程式執行的並行要求數量除以 100。

停用權杖感知路由

使用 Cassandra 介面卡時,如果駕駛人使用權杖感知負載平衡,可能會列印警告訊息,或無法正常運作。由於 Cassandra 介面卡會偽裝成單一節點,因此 Cassandra 介面卡不一定能與權杖感知驅動程式搭配運作,因為這類驅動程式會預期叢集中至少有複製因數數量的節點。部分驅動程式可能會列印警告 (可忽略),並回退至輪詢平衡政策等項目,其他驅動程式則可能會因錯誤而失敗。如果驅動程式發生錯誤而失敗,您必須停用權杖感知功能,或設定循環負載平衡政策。

# Sample application.conf: disables token-aware routing
metadata {
  token-map {
    enabled = false
  }
}

將通訊協定版本釘選為 V4

Cassandra Adapter 與任何符合 CQL 二進位 v4 連線通訊協定的開放原始碼 Apache Cassandra 用戶端驅動程式相容。請務必將 PROTOCOL_VERSION 釘選至 V4,否則可能會看到連線錯誤。

# Sample application.conf: overrides protocol version to V4
datastax-java-driver {
  advanced.protocol.version = V4
}

後續步驟