Hello folks,今天我们介绍一下如何基于 Golang Web 应用程序进行分布式数据库操作,这里我们以 CockroachDB 开源数据库为例。
CockroachDB 是一款开源的分布式数据库,具有 NoSQL 对海量数据的存储管理能力,又保持了传统数据库支持的 ACID 和 SQL 等,还支持跨地域、去中心、高并发、多副本强一致和高可用等特性。支持 OLTP 场景,同时支持轻量级 OLAP 场景。
接下来,我们开始进行部署操作,首先我们检查一下当前的环境信息,具体如下所示:
[leonli@192 go ] % go version go version go1.18.1 darwin/arm64
为了方便起见,我们基于 Docker-compose 新建一个 CockroachD 集群,并将其运行起来,具体如下:
[leonli@192 CockroachDB ] % more docker-compose-cockroach.yml version: "3.5" # Reference: https://www.cockroachlabs.com/docs/v21.1/start-a-local-cluster-in-docker-linux services: # Node 1 (main) roach1: container_name: cockroach-db-1 image: cockroachdb/cockroach:v21.2.10 hostname: roach1 # Assign Container to a Network networks: - cockroach-net ports: - 26257:26257 # Cockroach Database port - 9090:8080 # Cockroach UI Panel port volumes: - ./cockroach-data/roach1:/cockroach/cockroach-data command: start --insecure --join=roach1,roach2,roach3 # Node 2 roach2: container_name: cockroach-db-2 image: cockroachdb/cockroach:v21.2.10 hostname: roach2 # Assign Container to a Network networks: - cockroach-net volumes: - ./cockroach-data/roach2:/cockroach/cockroach-data command: start --insecure --join=roach1,roach2,roach3 # Node 3 roach3: container_name: cockroach-db-3 image: cockroachdb/cockroach:v21.2.10 hostname: roach3 # Assign Container to a Network networks: - cockroach-net volumes: - ./cockroach-data/roach3:/cockroach/cockroach-data command: start --insecure --join=roach1,roach2,roach3 # First Time Initialization of Cockroach DB init-roach1: image: cockroachdb/cockroach:latest container_name: init-cockroach depends_on: - roach1 restart: "no" # Assign Container to a Network networks: - cockroach-net entrypoint: ["bash", "-c" ,"./cockroach init --insecure --host cockroach-db-1:26257 | wc || exit"] # Initialize a Network networks: cockroach-net: driver: bridge
[leonli@192 CockroachDB ] % docker-compose -f docker-compose-cockroach.yml up -d
然后,在浏览器中输入:http://localhost:9090/#/overview/list 进行访问,如下所示:
现在,我们进入 CockroachD 集群容器中,进行相关库表的创建,具体如下所示:
[leonli@192 CockroachDB ] % docker exec -it 05f9ff283fb8 bash [[root@roach1 cockroach]] # ls -l total 212444 -rwxr-xr-x 1 root root 217535728 May 2 17:51 cockroach drwxr-xr-x 24 root root 768 May 22 05:44 cockroach-data -rwxr-xr-x 1 root root 120 May 2 17:28 cockroach.sh
[[root@roach1 cockroach]] # cockroach sql --insecure # # Welcome to the CockroachDB SQL shell. # All statements must be terminated by a semicolon. # To exit, type: \q. # # Server version: CockroachDB CCL v21.2.10 (x86_64-unknown-linux-gnu, built 2022/05/02 17:38:58, go1.16.6) (same version as client) # Cluster ID: 369395e8-8b75-4d7a-9b5a-b7212c4313bb # # Enter \? for a brief introduction. # [root@:26257/defaultdb> show databases; database_name | owner | primary_region | regions | survival_goal ----------------+-------+----------------+---------+---------------- defaultdb | root | NULL | {} | NULL postgres | root | NULL | {} | NULL system | node | NULL | {} | NULL
[root@:26257/defaultdb> CREATE USER IF NOT EXISTS luga; CREATE ROLE Time: 276ms total (execution 272ms / network 3ms) [root@:26257/defaultdb> CREATE DATABASE books; CREATE DATABASE Time: 42ms total (execution 42ms / network 1ms) [root@:26257/defaultdb> GRANT ALL ON DATABASE books TO luga; GRANT Time: 139ms total (execution 138ms / network 1ms) [root@:26257/defaultdb> use books -> ; SET Time: 2ms total (execution 2ms / network 0ms) [root@:26257/books> show tables; SHOW TABLES 0 Time: 151ms total (execution 150ms / network 1ms) [root@:26257/books> CREATE TABLE tblbooks( id INT PRIMARY KEY, name VARCHAR, phone VARCHAR, email VARCHAR, stars INT, category VARCHAR ); CREATE TABLE Time: 28ms total (execution 28ms / network 1ms) [root@:26257/books> show tables; schema_name | table_name | type | owner | estimated_row_count | locality --------------+------------+-------+-------+---------------------+----------- public | tblbooks | table | root | 0 | NULL (1 row) Time: 120ms total (execution 119ms / network 1ms) [root@:26257/books> show tables; schema_name | table_name | type | owner | estimated_row_count | locality --------------+------------+-------+-------+---------------------+----------- public | tblbooks | table | root | 0 | NULL (1 row) Time: 75ms total (execution 74ms / network 1ms) [root@:26257/books> INSERT INTO tblbooks (id, name, phone, email, stars, category) VALUES (1,'Lucy','130-0000-0000','lucy13000000000@example.net',8,'life'), (2,'Green ','125-0456-0102','green1250456@example.com',1,'artity'), (3,'Linda','857-555-0182','linda857@example.com',3,'moon'), (4,'Wilium','999-555-000','wilium@example.com',5,'sunset'), (5,'Lily','234-2123-1231','lily1900@example.com',5,'work'), (6,'Luga','100-0555-0102','luga100@example.com',3,'golang'), (7,'Qiuchi','777-5555-0000','qiuchi777@example.com',3,'freedom'), (8,'walse','000-11111-22222','walse@example.com',5,'tutorial'), (9,'Jhons','444-4433-1213','jhons444@example.com',5,'project'), (10,'Leon','122-1214-5678','leon1221214@example.com',4,'confidence'); INSERT 10 Time: 30ms total (execution 30ms / network 1ms)
此時,通過 CockroachD 控制台,我們可以看到之前的相關操作信心,具體如下所示:
基于上述操作,我们新建了 books 数据库,并在此库中,新建 tblbooks 表,并对其进行用户角色、权限的配置。此时,我们开始着手于 Go 應用程序的編寫,首先,我們需要下載涉及的相關依賴,具體如下所示:
import ( "database/sql" "fmt" "net/http" "log" _ "github.com/lib/pq" )
下面,我們針對所依賴的代碼包進行簡要解釋,具體如下:
1、要在 Go 中使用 SQL 数据库和 SQL 等数据库,请使用 database/sql。
2、与 Print() 函数类似的是 fmt 函数。它格式化为 I/O。
3、使用 net/http 包对 HTTP 服务器和客户端进行 API 调用。
4、database/sql 需要用于 Go 的 Postgres 驱动程序包 “github.com/lib/pq” 。也可以通过在代码行开头使用下划线 _ 来加载它而不显示代码中的名称。
引入相關依賴包後,我們開始使用 struct 创建匹配数据库表的字段。通常情況下,使用基於 GoLang 的 struct 來创建与表 tbbook 相同的命名属性或字段。如下所示:
type Books struct { Id int Name string Phone string Email string Stars int Category string }
在成功导入依赖项,使用 GoLang 的 struct 命令创建匹配字段後。 接下来,让我们進行 CockroachDB 数据库連接操作。在 GoLang 語法結構中,通过设置“db”全局包级别变量来获取包内的全局使用情况。即:
var db *sql.DB
並使用 init() 函数進行快速无缝建立数据库连接,其最大好處在於:僅需調用一次。具體如下:
func init() { var err error connStr := "postgres://luga:password@localhost:26257/books?sslmode=disable" db, err = sql.Open("postgres", connStr) #訪問連接 if err != nil { panic(err) } if err = db.Ping(); err != nil { panic(err) } fmt.Println("Connected to the database") }
基於上述代碼,使用 db 检查任何引发的错误,以及 func main() 來验证数据库连接是否有效。上述代碼簡要描述了 GET /books 路由所需的所有初始化邏輯,現在,让我们为我们的 Web 应用程序編寫 booksIndex() HTTP 处理程序,即所謂的函數(方法)入口。具體如下所示:
func main(){ http.HandleFunc("/books", booksIndex) http.ListenAndServe(":9090", nil) }
当使用方法 http.HandleFunc() 向它传递第二个参数时,可以只传递函数的名称。一般情況下,需要通过消除括号将函数作为回调函数传递。
1、此處声明 HTTP 请求中的函数名称是 booksIndex。
2、“9090”為分配的監聽端口。
最後,我們來看一下最為核心的內容,具體如下所示:
func booksIndex(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { http.Error(w, http.StatusText(405), http.StatusMethodNotAllowed) return } rows, err := db.Query("SELECT * FROM tblbooks;") if err != nil { http.Error(w, http.StatusText(500), 500) return } defer rows.Close() bks := make([]*Book, 0) for rows.Next() { bk := new(Book) err := rows.Scan(&bk.Id, &bk.Name, &bk.Phone, &bk.Email, &bk.Stars, &bk.Category) if err != nil { http.Error(w, http.StatusText(500), 500) return } bks = append(bks, bk) } if err = rows.Err(); err != nil { http.Error(w, http.StatusText(500), 500) return } for _, bk := range bks { fmt.Fprintf(w, "%d %s %s %s %d %s\n", bk.Id, bk.Name, bk.Phone, bk.Email, bk.Stars, bk.Category) } }
我們對如上的代碼進行簡要的解釋,具體:
1、db.Query("SELECT * FROM tblbooks") ,基於此命令操作進行數據庫查詢,以返回相關結果。
2、 rows.Close() ,基於此命令延迟释放资源。
3、制作了一个切片并分配了 restos := make([]Restaurant, 0) 变量。
4、使用 rows.Next() 完成迭代。
5、使用 rows.Scan() 复制当前行的列。
基於上述的各階段描述,最終的代碼實現如下所示:
package main import ( "database/sql" "fmt" "net/http" "log" _ "github.com/lib/pq" ) type Book struct { Id int Name string Phone string Email string Stars int Category string } var db *sql.DB func init() { var err error db, err = sql.Open("postgres", "postgres://luga:password@127.0.0.1:26257/books?sslmode=disable") if err != nil { log.Fatal(err) } if err = db.Ping(); err != nil { log.Fatal(err) } } func main() { http.HandleFunc("/books", booksIndex) http.ListenAndServe(":9090", nil) } func booksIndex(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { http.Error(w, http.StatusText(405), http.StatusMethodNotAllowed) return } rows, err := db.Query("SELECT * FROM tblbook") if err != nil { http.Error(w, http.StatusText(500), 500) return } defer rows.Close() bks := make([]*Book, 0) for rows.Next() { bk := new(Book) err := rows.Scan(&bk.Id, &bk.Name, &bk.Phone, &bk.Email, &bk.Stars, &bk.Category) if err != nil { http.Error(w, http.StatusText(500), 500) return } bks = append(bks, bk) } if err = rows.Err(); err != nil { http.Error(w, http.StatusText(500), 500) return } for _, bk := range bks { fmt.Fprintf(w, "%d %s %s %s %d %s\n", bk.Id, bk.Name, bk.Phone, bk.Email, bk.Stars, bk.Category) } }
此時,我們在文件所在的目錄下執行如下命令:
[leonli@192 books ] % go run main.go [leonli@192 books ] %
其結果如下:
Lucy 130-0000-0000 lucy13000000000@example.net 8 life Green 125-0456-0102 green1250456@example.com 1 artity Linda 857-555-0182 linda857@example.com 3 moon Wilium 999-555-000 wilium@example.com 5 sunset Lily 234-2123-1231 lily1900@example.com 5 work Luga 100-0555-0102 luga100@example.com 3 golang Qiuchi 777-5555-0000 qiuchi777@example.com 3 freedom walse 000-11111-22222 walse@example.com 5 tutorial Jhons 444-4433-1213 jhons444@example.com 5 project Leon 122-1214-5678 leon1221214@example.com 4 confidence
當然,我們也可以通過 http://localhost:9090/books 進行訪問。基於上述結果,我們的操作到此結束。
至此,一个简单的 Demo 先解析到此为止,希望大家有所收获!
Adiós !