Hello folks,今天我们介绍一下如何基于 Golang Web 应用程序进行分布式数据库操作,这里我们以 CockroachDB 开源数据库为例。
CockroachDB 是一款开源的分布式数据库,具有 NoSQL 对海量数据的存储管理能力,又保持了传统数据库支持的 ACID 和 SQL 等,还支持跨地域、去中心、高并发、多副本强一致和高可用等特性。支持 OLTP 场景,同时支持轻量级 OLAP 场景。
接下来,我们开始进行部署操作,首先我们检查一下当前的环境信息,具体如下所示:
代码语言:javascript复制[leonli@192 go ] % go version
go version go1.18.1 darwin/arm64
为了方便起见,我们基于 Docker-compose 新建一个 CockroachD 集群,并将其运行起来,具体如下:
代码语言:javascript复制[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
代码语言:javascript复制[leonli@192 CockroachDB ] % docker-compose -f docker-compose-cockroach.yml up -d
然后,在浏览器中输入:http://localhost:9090/#/overview/list 进行访问,如下所示:
现在,我们进入 CockroachD 集群容器中,进行相关库表的创建,具体如下所示:
代码语言:javascript复制[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
代码语言:javascript复制[[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
代码语言:javascript复制[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 應用程序的編寫,首先,我們需要下載涉及的相關依賴,具體如下所示:
代码语言:javascript复制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 相同的命名属性或字段。如下所示:
代码语言:javascript复制type Books struct {
Id int
Name string
Phone string
Email string
Stars int
Category string
}
在成功导入依赖项,使用 GoLang 的 struct 命令创建匹配字段後。 接下来,让我们進行 CockroachDB 数据库連接操作。在 GoLang 語法結構中,通过设置“db”全局包级别变量来获取包内的全局使用情况。即:
代码语言:javascript复制var db *sql.DB
並使用 init() 函数進行快速无缝建立数据库连接,其最大好處在於:僅需調用一次。具體如下:
代码语言:javascript复制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 处理程序,即所謂的函數(方法)入口。具體如下所示:
代码语言:javascript复制func main(){
http.HandleFunc("/books", booksIndex)
http.ListenAndServe(":9090", nil)
}
当使用方法 http.HandleFunc() 向它传递第二个参数时,可以只传递函数的名称。一般情況下,需要通过消除括号将函数作为回调函数传递。
1、此處声明 HTTP 请求中的函数名称是 booksIndex。
2、“9090”為分配的監聽端口。
最後,我們來看一下最為核心的內容,具體如下所示:
代码语言:javascript复制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 %sn", 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() 复制当前行的列。
基於上述的各階段描述,最終的代碼實現如下所示:
代码语言:javascript复制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 %sn", bk.Id, bk.Name, bk.Phone, bk.Email, bk.Stars, bk.Category)
}
}
此時,我們在文件所在的目錄下執行如下命令:
代码语言:javascript复制[leonli@192 books ] % go run main.go
[leonli@192 books ] %
其結果如下:
代码语言:javascript复制 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 !
- EOF -