Featured image of post Go与云函数(三) 连接至云数据库

Go与云函数(三) 连接至云数据库

上一章解决了程序的自动构建与部署问题,这一章需要解决数据持久化的问题——在我们的云函数与云数据库之间建立连接并进行操作。

腾讯云函数 数据库连接

在前面的文章中,我们实现了golang程序自动化构建并部署到腾讯云函数平台上,serverless服务是无状态的,只有在请求到达或者事件发生的时候才会对传入的信息做出处理,所以我们还需要想办法实现数据的持久化,可以考虑将数据存到数据库中或者是作为文件写入到对象储存。

这一章中,我们先从数据库开始,将这个云函数与数据库建立连接并尝试进行交互,我们的业务方案中直接使用了腾讯云的云mysql而非自建的方案。不得不说现在的PaaS平台提供了开发的各个阶段所需要的服务,原来各种复杂的运维操作都由云服务平台提供了解决方案,所以云计算时代的运维工作与传统的运维有着不小的差异。

vpc内网互联

需要注意的一点,腾讯云的云数据库是没有公网入口的,想要使用这个数据库我们需要将服务器/云函数和数据库加入同一个虚拟私有网络中(后文都用vpc代替),先不讨论跨地域vpc互联,通常来说一个vpc内的服务需要在同一个地域,比如都在上海或者都在香港。

官方文档已经有详细的流程介绍了,这里就不再一步一步记录,在购买云数据库后,同一个地域下会创建一个vpc。

编辑函数设置加入私有网络

经过测试,直接在网页上加入私有网络后,再通过serverless cli部署项目,之前加入的vpc依旧有效。当然,直接在serverless.yaml里面定义私有网络也是可以的。

最后在网页上创建一个数据库账号,并创建一个数据库并授权,在网页上通过可视化操作即可晚上上述步骤,还是很方便的。

image590e2fb6b68f367e.png

创建测试用数据库与表

连接数据库的测试

在之前的简易http服务器代码上做一些扩展(数据库部分的代码直接从网上找的,后续要用的话还是配合gorm之类的orm框架方便一些)

这里的代码很随意,之后也许会换一个完整一点的版本,不过其实从这里开始,与数据库的交互和传统的开发方式就没有区别了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package main

import (
	"fmt"
	"net/http"
	"os"
	"time"

	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

var (
	DB_HOST     string
	DB_USER     string
	DB_PASSWORD string
	DB_NAME     string
	DB          *sql.DB
)

func init() {
	// 数据库初始化
	DB_HOST = os.Getenv("DB_HOST")
	DB_USER = os.Getenv("DB_USER")
	DB_PASSWORD = os.Getenv("DB_PASSWORD")
	DB_NAME = os.Getenv("DB_NAME")
	DB, _ = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:3306)/%s", DB_USER, DB_PASSWORD, DB_HOST, DB_NAME))
	//设置数据库最大连接数
	DB.SetConnMaxLifetime(100)
	DB.SetMaxIdleConns(10)
	//验证连接
	if err := DB.Ping(); err != nil {
		fmt.Println("open database fail")
		return
	}
	fmt.Println("database connnect success")
}

func main() {
	http.HandleFunc("/", HelloServer)
	http.HandleFunc("/db", ConnectDBTest)
	http.ListenAndServe(":9000", nil)
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Helloserver接收到请求")
	fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}

type DB_Record struct {
	Id    int
	Name  string
	Mtime time.Time
}

func ConnectDBTest(w http.ResponseWriter, r *http.Request) {
	fmt.Println("数据库插入请求")
	name := r.URL.Query()["name"]
	if len(name) > 0 {
		var record = DB_Record{
			Name:  name[0],
			Mtime: time.Now(),
		}
		//开启事务
		tx, err := DB.Begin()
		if err != nil {
			fmt.Fprintln(w, "开启事务失败", err.Error())
			return
		}
		//准备sql语句
		stmt, _ := tx.Prepare("INSERT INTO test_table (`name`, `mtime`) VALUES (?, ?)")
		//将参数传递到sql语句中并且执行
		res, err := stmt.Exec(record.Name, record.Mtime)
		if err != nil {
			fmt.Fprintln(w, "事务执行失败", err.Error())
			return
		}
		//将事务提交
		tx.Commit()
		//获得上一个插入自增的id
		id, _ := res.LastInsertId()
		fmt.Fprintln(w, "成功记录name:", name[0], "id:", id)
	} 
}

成功构建并部署之后还需要配置一下环境变量,去网页上设置一下几个环境变量 必须在 serverless.yaml里面设置,不然部署后会覆盖网页上设置的环境变量。

1
2
3
4
	DB_HOST     vpc内部的ip
	DB_USER     数据库用户名
	DB_PASSWORD 数据库密码
	DB_NAME     数据库名

部署之后便能实现对数据库的访问和操作了。 插入数据后查看云数据库内容

另外需要注意的一些点:

在云函数 SCF 的运行机制下如何更加高效的管理数据库连接? SCF 的每个请求实际运行在一个容器上,容器在有连续请求的场景下可在一段时间内复用。数据库的连接创建建议在初始化容器的时候进行,即对应函数代码的全局部分。数据库连接建立后,在容器存在的时间段内均可以进行复用,在容器释放时会断开。避免在入口函数内部频繁进行数据库的连接和关闭,影响性能。为保证数据库的连接可用,可在入口函数内部进行连接检查。 建议使用数据库连接池进行容器数据库连接管理,最小连接数设置为1即可。

在函数高并发场景下如何进行数据库连接管理? 函数高并发场景下,并发数可能超过数据库最大连接数,可以参考如下方案进行处理:

增大数据库最大连接数。 设置函数最大独占并发配额,限制函数的并发数小于数据库最大连接数。 云数据库 MySQL 提供 数据库代理功能,通过代理集群中转访问数据库的主从节点,进行读写分离,将读请求转发至只读实例,降低主库的负载。

comments powered by Disqus
本站访客数:
Built with Hugo
主题 StackJimmy 设计