blockchain/internal/lib/cassandra/client.go

196 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cassandra
import (
"fmt"
"log"
"strconv"
"strings"
"time"
"github.com/gocql/gocql"
"github.com/scylladb/gocqlx/v3"
)
// cassandraConf 是初始化 CassandraDB 所需的內部設定(私有)
type cassandraConf struct {
Hosts []string // Cassandra 主機列表
Port int // 連線埠
Keyspace string // 預設使用的 Keyspace
Username string // 認證用戶名
Password string // 認證密碼
Consistency gocql.Consistency // 一致性級別
ConnectTimeoutSec int // 連線逾時秒數
NumConns int // 每個節點連線數
MaxRetries int // 重試次數
UseAuth bool // 是否使用帳號密碼驗證
RetryMin time.Duration // 重試間隔最小值
RetryMax time.Duration // 重試間隔最大值
ReconnectInitial time.Duration // 重連初始間隔
ReconnectMax time.Duration // 重連最大間隔
CQLVersion string // 執行連線的CQL 版本號
}
// CassandraDB 是封裝了 Cassandra 資料庫 session 的結構
type CassandraDB struct {
session gocqlx.Session
SaiSupported bool // 是否支援 sai
Version string // 資料庫版本
}
// NewCassandraDB 初始化並建立 Cassandra 資料庫連線使用預設設定並可透過Option修改
func NewCassandraDB(hosts []string, opts ...Option) (*CassandraDB, error) {
config := &cassandraConf{
Hosts: hosts,
Port: defaultPort,
Consistency: defaultConsistency,
ConnectTimeoutSec: defaultTimeoutSec,
NumConns: defaultNumConns,
MaxRetries: defaultMaxRetries,
RetryMin: defaultRetryMin,
RetryMax: defaultRetryMax,
ReconnectInitial: defaultReconnectInitial,
ReconnectMax: defaultReconnectMax,
CQLVersion: defaultCqlVersion,
}
// 套用Option設定選項
for _, opt := range opts {
opt(config)
}
// 建立連線設定
cluster := gocql.NewCluster(config.Hosts...)
cluster.Port = config.Port
cluster.Consistency = config.Consistency
cluster.Timeout = time.Duration(config.ConnectTimeoutSec) * time.Second
cluster.NumConns = config.NumConns
cluster.RetryPolicy = &gocql.ExponentialBackoffRetryPolicy{
NumRetries: config.MaxRetries,
Min: config.RetryMin,
Max: config.RetryMax,
}
cluster.ReconnectionPolicy = &gocql.ExponentialReconnectionPolicy{
MaxRetries: config.MaxRetries,
InitialInterval: config.ReconnectInitial,
MaxInterval: config.ReconnectMax,
}
var session *gocql.Session
var err error
for i := 1; i <= config.MaxRetries; i++ {
if session != nil {
session.Close()
}
session, err = cluster.CreateSession()
if err == nil {
break
}
waitInterval := cluster.ReconnectionPolicy.GetInterval(i)
log.Printf("[CassandraDB] Retry attempt #%d, waiting %s...", i, waitInterval)
time.Sleep(waitInterval)
}
if session == nil {
panic("failed to connect ....")
}
// 若有提供 Keyspace 則指定
if config.Keyspace != "" {
cluster.Keyspace = config.Keyspace
}
// 若啟用驗證則設定帳號密碼
if config.UseAuth {
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: config.Username,
Password: config.Password,
}
}
log.Printf("[CassandraDB] try to connect to Cassandra cluster %v, port: %d", config.Hosts, config.Port)
// 建立 Session
s, err := gocqlx.WrapSession(session, nil)
if err != nil {
return nil, fmt.Errorf("failed to connect to Cassandra cluster: %s", err)
}
log.Printf("[CassandraDB] success init Cassandra cluster")
db := &CassandraDB{
session: s,
}
version, err := db.getReleaseVersion()
if err != nil {
return nil, fmt.Errorf("failed to get DB version: %s", err)
}
db.Version = version
db.SaiSupported = isSAISupported(version)
return db, nil
}
// Close 關閉 Cassandra 資料庫連線
func (db *CassandraDB) Close() {
db.session.Close()
}
// GetSession 返回目前使用的 Cassandra Session
func (db *CassandraDB) GetSession() gocqlx.Session {
return db.session
}
// EnsureTable 確認並建立資料表
func (db *CassandraDB) EnsureTable(schema string) error {
return db.session.ExecStmt(schema)
}
func (db *CassandraDB) InitVersionSupport() error {
version, err := db.getReleaseVersion()
if err != nil {
return err
}
db.Version = version
db.SaiSupported = isSAISupported(version)
return nil
}
func (db *CassandraDB) getReleaseVersion() (string, error) {
var version string
stmt := "SELECT release_version FROM system.local"
err := db.GetSession().Query(stmt, []string{"release_version"}).Consistency(gocql.One).Scan(&version)
return version, err
}
func isSAISupported(version string) bool {
// 只要 major >=5 就支援
// 4.0.9+ 才有 SAI但不穩強烈建議 5.0+
parts := strings.Split(version, ".")
if len(parts) < 2 {
return false
}
major, _ := strconv.Atoi(parts[0])
minor, _ := strconv.Atoi(parts[1])
if major > 5 {
return true
}
if major == 5 {
return true
}
if major == 4 {
if minor > 0 { // 4.1.x、4.2.x 直接支援
return true
}
if minor == 0 {
patch := 0
if len(parts) >= 3 {
patch, _ = strconv.Atoi(parts[2])
}
if patch >= 9 {
return true
}
}
}
return false
}