文档中心 云函数 最佳实践 使用 SCF 连接数据库

使用 SCF 连接数据库

最近更新时间:2019-09-24 15:20:42

操作场景

如果您需要在云函数中使用关系型数据库,您可使用连接池或云函数团队提供的 SDK 来连接关系型数据库。连接池具备自动重连功能,可有效避免因云函数底层或者数据库释放连接,造成连接不可用的情况。

注意事项

由于云函数单实例同时处理的请求数均为1,以及为了防止连接数设置过大导致高并发下数据库连接耗尽,在使用连接池时,建议将最大连接数设置为1。

前提条件

已创建数据库。

说明:

此最佳实践适用于 MySQLCynosDBTDSQL。推荐使用腾讯云提供的 Serverless 数据库(CynosDB)。

使用原生代码连接数据库的操作步骤

Node.js 使用 mysql2 连接池示例

'use strict';

const DB_HOST       = process.env[`DB_HOST`]
const DB_PORT       = process.env[`DB_PORT`]
const DB_DATABASE   = process.env[`DB_DATABASE`]
const DB_USER       = process.env[`DB_USER`]
const DB_PASSWORD   = process.env[`DB_PASSWORD`]

const promisePool = require('mysql2').createPool({
  host              : DB_HOST,
  user              : DB_USER,
  port              : DB_PORT,
  password          : DB_PASSWORD,
  database          : DB_DATABASE,
  connectionLimit   : 1
}).promise();

exports.main_handler = async (event, context, callback) => {
  let result = await promisePool.query('select * from employee');
  console.log(result);
}

Npm 依赖如下:

"dependencies": {
    "mysql2": "^1.7.0"
}

Python 使用 pymysql 无连接池示例

说明:

pymysql 无连接池功能。

# -*- coding: utf8 -*-
from os import getenv

import pymysql
from pymysql.err import OperationalError

mysql_conn = None

def __get_cursor():
    try:
        return mysql_conn.cursor()
    except OperationalError:
        mysql_conn.ping(reconnect=True)
        return mysql_conn.cursor()

def main_handler(event, context):
    global mysql_conn
    if not mysql_conn:
        mysql_conn = pymysql.connect(
            host        = getenv('DB_HOST', '<YOUR DB HOST>'),
            port        = int(getenv('DB_PORT', '<YOUR DB PORT>')),
            user        = getenv('DB_USER', '<YOUR DB USER>'),
            password    = getenv('DB_PASSWORD', '<YOUR DB PASSWORD>'),
            db          = getenv('DB_DATABASE', '<YOUR DB DATABASE>'),
            charset     = 'utf8mb4',
            autocommit  = True
        )

    with __get_cursor() as cursor:
        cursor.execute('select * from employee')
        myresult = cursor.fetchall()
        for x in myresult:
            print(x)

PIP 依赖如下:

pymysql

Java 使用 Hikari 连接池示例

package example;

import com.qcloud.scf.runtime.Context;
import com.qcloud.services.scf.runtime.events.APIGatewayProxyRequestEvent;
import com.qcloud.services.scf.runtime.events.APIGatewayProxyResponseEvent;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class Http {
    private DataSource dataSource;

    public Http() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://" + System.getenv("DB_HOST") + ":"+ System.getenv("DB_PORT") + "/" + System.getenv("DB_DATABASE"));
        config.setUsername(System.getenv("DB_USER"));
        config.setPassword(System.getenv("DB_PASSWORD"));
        config.setDriverClassName("com.mysql.jdbc.Driver");
        config.setMaximumPoolSize(1);
        dataSource = new HikariDataSource(config);
    }

    public String mainHandler(APIGatewayProxyRequestEvent requestEvent, Context context) {
        System.out.println("start main handler");
        System.out.println("requestEvent: " + requestEvent);
        System.out.println("context: " + context);

        try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT * FROM employee")) {
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                System.out.println(rs.getInt("id"));
                System.out.println(rs.getString("first_name"));
                System.out.println(rs.getString("last_name"));
                System.out.println(rs.getString("address"));
                System.out.println(rs.getString("city"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent = new APIGatewayProxyResponseEvent();
        apiGatewayProxyResponseEvent.setBody("API GW Test Success");
        apiGatewayProxyResponseEvent.setIsBase64Encoded(false);
        apiGatewayProxyResponseEvent.setStatusCode(200);

        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "text");
        headers.put("Access-Control-Allow-Origin", "*");
        apiGatewayProxyResponseEvent.setHeaders(headers);

        return apiGatewayProxyResponseEvent.toString();
    }
}

Maven 依赖如下:

<dependencies>
    <dependency>
        <groupId>com.tencentcloudapi</groupId>
        <artifactId>scf-java-events</artifactId>
        <version>0.0.1</version>
    </dependency>
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>3.2.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.11</version>
    </dependency>
</dependencies>

配置环境变量和私有网络

MySQL 配置环境变量和私有网络

  1. 登录 MySQL 控制台,单击已创建的 MySQL 数据库 ID。
  2. 在数据库详情页,获取该数据库的内网地址所属网络。如下图所示:

    如果您使用自建的MySQL实例,请根据实际情况填写内网地址所属网络

  3. 登录 云函数控制台,单击左侧导航栏中的【函数服务】。
  4. 单击需连接数据库的函数 ID,进入该函数的“函数配置”页面,参考以下信息进行配置。
    • 新增环境变量参考以下表格填写。如下图所示:
      key value
      DB_PASSWORD 数据库密码
      DB_USER 数据库用户名
      DB_HOST 数据库地址
      DB_PORT 数据库端口
      DB_DATABASE 数据库名
    • 开启内网访问,并选择和数据库相同的私有网络和子网。如下图所示:

CynosDB 配置环境变量和私有网络

  1. 登录 CynosDB 控制台,单击已创建的 CynosDB 数据库 ID。
  2. 在数据库详情页,获取该数据库的内网地址所属网络。如下图所示:
  3. 登录 云函数控制台,单击左侧导航栏中的【函数服务】。
  4. 单击需连接数据库的函数 ID,进入该函数的“函数配置”页面,参考以下信息进行配置。
    • 新增环境变量参考以下表格填写。如下图所示:
      key value
      DB_PASSWORD 数据库密码
      DB_USER 数据库用户名
      DB_HOST 数据库地址
      DB_PORT 数据库端口
      DB_DATABASE 数据库名
    • 开启内网访问,并选择和数据库相同的私有网络和子网。如下图所示:

使用 Serverless DB SDK 连接数据库的操作步骤

为了方便使用,云函数团队将上述 Node.js 和 Python 连接池最佳实践封装成了 Serverless DB SDK,并且内置到了运行时中,不需要在依赖文件中导入依赖。支持 MySQL,CynosDB,TDSQL 等 MySQL 协议的数据库。

Serverless DB SDK 具备以下特点:

  • 自动从环境变量初始化数据库客户端。
  • SDK 会在全局维护一个数据库长连接,并处理连接中断后的重连。
  • 云函数团队会持续关注 issue,确保获得连接即可用,不需要关注数据库连接。

Node.js SDK

'use strict';
const database = require('scf-nodejs-serverlessdb-sdk').database;

exports.main_handler = async (event, context, callback) => {
  let connection = await database().connection();
  let result = await connection.queryAsync('select * from name');
  console.log(result);
}

Python SDK

from serverless_db_sdk import database

def main_handler(event, context):
    print('Start Serverlsess DB SDK function')

    connection = database().connection(autocommit=False)
    cursor = connection.cursor()

    cursor.execute('SELECT * FROM name')
    myresult = cursor.fetchall()

    for x in myresult:
        print(x)

配置环境变量和私有网络

若您使用 Serverless DB SDK,请按照以下步骤进行配置:

  1. 登录 云函数控制台,单击左侧导航栏中的【函数服务】。
  2. 单击需连接数据库的函数 ID,进入该函数的“函数配置”页面,参考以下信息进行配置。
    • 新增环境变量,请参考以下表格填写,如下图所示:
      • 环境变量 key 格式为DB_{引用}_XXX,您可通过 mysql.database(引用).connection() 获得已初始化的数据库连接。
      • 若您设置添加环境变量 DB_DEFAULTDB1,则 mysql.database() 默认使用 DB1,否则需要指定引用 mysql.database("DB1")
      key value 是否可选
      DB_DB1_HOST DB1 实例的地址。
      DB_DB1_PORT DB1 实例的端口。
      DB_DB1_USER DB1 实例的用户名。
      DB_DB1_PASSWORD DB1 实例的密码。
      DB_DB1_DATABASE DB1 实例的数据库。
      DB_DEFAULT 本示例中为 “DB1”。
    • 开启内网访问,并选择和数据库相同的私有网络和子网。如下图所示: