【腾讯云 TDSQL-C Serverless 产品体验】云函数+TDSQL-C Serverless:体验全栈Serverless的魅力

2023-11-15 10:39:10 浏览数 (2)

前言

最近在学习Serverless架构相关的知识,学习过程中发现一个有趣的现象:无论是教程示例,还是场景实例,Serverless架构中鲜有出现数据库的身影。各类文章所介绍的Serverless架构应用场景中,也几乎都是无需数据库的业务场景。在一些教程文章中,对于一些需要进行数据存储的场景,通常的做法是将数据存储在 JSON 文件中,然后上传到对象存储服务中,在搜索相关资料的过程中甚至还发现了SQLite 对象存储这种很硬核的数据存储方式,这些方法显然只能应对简单的数据存储。那么数据库作为互联网时代的基石,从单体架构到微服务架构,其都扮演着举足轻重的角色,为何偏偏在Serverless架构中存在感这么低呢?

从用户的角度来看,Serverless架构虽然有着免运维、弹性伸缩、按需付费等优点,但同时由于其本身构建复杂,扩展性不强,维护困难等缺点,用户一般只会用其来实现一些简单的业务,追求的是低成本、轻量级、无需维护长期运行的服务器。此时如果引入传统的数据库,整个架构就会重新变得厚重,用户看中的优势也被完全破坏了,违背了使用Serverless架构的初衷。

那么,有什么方案可以解决这个问题呢?答案就是让数据库也Serverless化,让数据库也具备免运维、弹性伸缩、按需付费等特点,这就是近两年比较火热的Serverless数据库。刚好在CSDN看到腾讯云 TDSQL-C Serverless 产品测评活动,可以免费体验腾讯云推出的Serverless数据库产品TDSQL-C Serverless。本篇博文就带大家一起,使用腾讯云云函数 TDSQL-C Serverless实现一个“时光邮局”,体验全栈Serverless的魅力。

一、TDSQL-C Serverless简介

TDSQL 是腾讯云自研的新一代云原生关系型数据库。融合了传统数据库、云计算与新硬件技术的优势,100% 兼容 MySQL,为用户提供极致弹性、高性能、高可用、高可靠、安全的数据库服务。实现超百万 QPS 的高吞吐、PB 级海量分布式智能存储、Serverless 秒级伸缩,助力企业加速完成数字化转型。

TDSQL-C Serverless 服务是腾讯云自研的新一代云原生关系型数据库 TDSQL-C MySQL 版的无服务器架构版,是全 Serverless 架构的云原生数据库。TDSQL-C Serverless 服务支持按实际计算和存储资源使用量收取费用,不用不付费,将腾讯云云原生技术普惠用户。其架构特点如下:

  • 按需启动,不需要时可关闭。
  • 自动扩展/收缩。
  • 缩放对应用程序无影响。

TDSQL-C Serverless 服务优势:

自动驾驶(Autopilot):数据库根据业务负载自动启动停止,无感扩缩容,扩缩容过程不会断开连接。

按使用计费(Utility Pricing):按实际使用的计算和存储量计费,不用不付费,按秒计量,按小时结算。

二、云函数 TDSQL-C Serverless实现“时光邮局”

1.购买TDSQL-C Serverless实例

TDSQL-C Serverless购买地址:https://buy.cloud.tencent.com/cynosdb,关键配置说明:

  • 实例形态选择Serverless
  • 网络选择:后续创建云函数时,需要选择与这里一致的VPC及子网
  • 算力配置:弹性伸缩的关键配置,与购买传统云数据库需要挑选固定规格不同的是,TDSQL-C Serverless只需要配置最小CCU和最大CCU即可。CCU(TDSQL-C Compute Unit)为 Serverless 的计算计费单位,1CCU约等于1C2G的计算资源。根据配置的CCU范围,TDSQL-C Serverless可以在这个区间内实现自动的弹性伸缩
  • 自动暂停:按需付费的关键配置,数据库在设定时间内无连接将自动进入暂停状态,暂停后计算将不再计费。当有连接访问时,系统会秒级自动启动处于暂停状态的数据库,用户不需设置重连机制。
  • 其他购买配置与传统数据库大同小异,根据自身需求配置即可

2.建库建表

通过DMC数据库管理工具可以快速的完成建库建表等操作,建表语句:

代码语言:javascript复制
CREATE TABLE `future_email` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date` date DEFAULT NULL,
  `email` varchar(50) DEFAULT '',
  `letter` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4

3.创建云函数

  • 关键配置:启用私有网络,保证可以内网访问TDSQL-C Serverless
  • 创建API网关触发器、定时触发器
  • 云函数代码
代码语言:javascript复制
'''
原作者:乂乂又又
原文链接:https://cloud.tencent.com/developer/article/1618588
修改说明:原文采用JSON文件 COS实现数据存储,本文修改为使用TDSQL-C Serverless作为数据存储方案
'''
# -*- coding: utf-8 -*-
import json
import datetime
import random
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr
import smtplib
import pymysql


# 配置TDSQL-C Serverless连接信息
host = '172.16.0.3'
port = 3306
user = 'root'
password = 'xxxxxxx'
database = 'test'

#配置发件邮箱
mail_host = "smtp.163.com"
mail_user = "xxxx@163.com"
mail_pass = "xxxxxxxxxxxx"
mail_port = 465

#smtp邮箱实例
smtpObj = smtplib.SMTP_SSL(mail_host, mail_port)

#获取所有信件
def getletters():
    db = pymysql.connect(host=host,port=port,user=user,password=password,database=database)
    cursor = db.cursor()
    # SQL 查询语句
    sql = "SELECT * FROM future_email WHERE date = %s"
    try:
        cursor.execute(sql, (today()))
        results = cursor.fetchall()
        data_list = []
        for row in results:
            data_list.append(list(row))
        return data_list
    except:
        print("Error: unable to fetch data")
    db.close()

#添加信件
def addletter(date, email, letter):
    db = pymysql.connect(host=host, port=port, user=user, password=password, database=database)
    cursor = db.cursor()

    # SQL 插入语句
    sql = "INSERT INTO future_email (date, email, letter) VALUES (%s, %s, %s)"
    try:
        cursor.execute(sql, (date, email, letter))
        db.commit()
        print("Data inserted successfully.")
    except pymysql.Error as e:
        print(f"MySQL Error {e.args[0]}: {e.args[1]}")
        db.rollback()
        return False
    db.close()
    return True

#删除信件
def delletter(id):
    db = pymysql.connect(host=host, port=port, user=user, password=password, database=database)
    cursor = db.cursor()
    # SQL 删除语句
    sql = "DELETE FROM future_email WHERE id = %s"
    try:
        cursor.execute(sql, (id))
        db.commit()
        print("Data deleted successfully.")
    except pymysql.Error as e:
        print(f"MySQL Error {e.args[0]}: {e.args[1]}")
        db.rollback()
    db.close()


# 获取今日日期
def today():
    return datetime.datetime.now().strftime("%Y-%m-%d")

# 根据时间生成uuid
def randomKey():
    return ''.join(random.sample('zyxwvutsrqponmlkjihgfedcba0123456789', 6))

# api网关回复消息格式化
def apiReply(reply, html=False, code=200):
    htmlStr = r'''<!DOCTYPE html>
<html lang="zh-cn">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <title>给未来的自己写封信</title>
    <style>
        html,
        body {
            padding: 0px;
            margin: 0px;
            height: 100vh;
        }
        
        .main {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
        }
        
        .main_phone {
            display: flex;
            flex-direction: column;
            justify-content: start;
            align-items: center;
        }
    </style>
</head>

<body id='body'>
    <div class="main" style="width: 80vw;">
        <div style="height: 5vh;"></div>
        <div id='letter_top'>
            <p style="text-align: center;">开始写信</p>
            <wired-textarea id="letter" style="height: 320px;width: 300px;" placeholder="此刻平静地写下一封信,给未来的自己一份温暖..." elevation="6" rows="14"></wired-textarea>
        </div>
        <div style="display: flex;align-items: center;justify-content: center;">
            <div id='letter_left'>
                <p style="text-align: center;">开始写信</p>
                <wired-textarea id="letter" style="height: 320px;width: 300px;" placeholder="此刻平静地写下一封信,给未来的自己一份温暖..." elevation="6" rows="14"></wired-textarea>
            </div>
            <div style="width: 16px;"></div>
            <div>
                <p style="text-align: center;">送信日期</p>
                <wired-calendar id="calendar"></wired-calendar>
            </div>
        </div>
        <wired-divider style="margin: 16px 0;"></wired-divider>
        <p id="hitokoto"></p>
        <div>
            <wired-input id="email" placeholder="收件邮箱"></wired-input>
            <wired-button onclick="send()">投递</wired-button>
        </div>
        <div style="height: 5vh;"></div>
    </div>
    <script>
        let datex = '';
        let myEmail = document.getElementById('email');
        let myLetter = document.getElementById('letter');
        let myCalendar = document.getElementById('calendar');

        let width =
            window.innerWidth ||
            document.documentElement.clientWidth ||
            document.body.clientWidth

        let height =
            window.innerHeight ||
            document.documentElement.clientHeight ||
            document.body.clientHeight

        let pc = width >= height

        let today = new Date();
        let info = today.toString().split(' ');
        let selected = `${info[1]} ${today.getDate()}, ${today.getFullYear()}`;

        document.getElementById('body').classList.add(pc ? 'main' : 'main_phone');
        if(pc){
            document.getElementById('letter_top').remove();
            document.getElementById('letter_left').style.display = 'block';
            myLetter = document.getElementById('letter');
        } else {
            document.getElementById('letter_top').style.display = 'block';
            document.getElementById('letter_left').remove();
            myLetter = document.getElementById('letter');
        }
        myCalendar.setAttribute("selected", selected);
        myCalendar.addEventListener('selected', () => {
            let selectedObject = myCalendar.value;
            let date = new Date(new Date().setDate(selectedObject.date.getDate()));
            datex = date.toISOString().substr(0, 10);
        });

        function send() {
            console.log(datex, myEmail.value, myLetter.value)
            if (datex.length < 1 || myEmail.value.length < 1 || myLetter.value.length < 1) {
                
                alert('信件内容、送信日期或投递邮箱不能为空');
                return;
            }
            fetch(window.location.href, {
                    method: 'POST',
                    body: JSON.stringify({
                        date: datex,
                        email: myEmail.value,
                        letter: myLetter.value
                    })
                }).then(res => res.json())
                .catch(error => console.error('Error:', error))
                .then(response => alert(response.ok ? '添加成功:)' : '添加失败:('));
        }
    </script>
    <script src="https://v1.hitokoto.cn/?encode=js&select=#hitokoto" defer></script>
    <script src="https://unpkg.com/wired-elements@2.0.5/lib/wired-elements-bundled.js"></script>
</body>

</html>'''
    return {
        "isBase64Encoded": False,
        "statusCode": code,
        "headers": {'Content-Type': 'text/html' if html else 'application/json', "Access-Control-Allow-Origin": "*"},
        "body": htmlStr if html else json.dumps(reply, ensure_ascii=False)
    }

#登陆邮箱
def loginEmail():
    try:
        smtpObj.login(mail_user, mail_pass)
        return True
    except smtplib.SMTPException as e:
        print(e)
        return False

#发送邮件
def sendEmail(letter):
    message = MIMEText(letter[3], 'plain', 'utf-8')
    message['From'] = formataddr(('时间邮局', mail_user))
    message['To'] = letter[2]
    message['Subject'] = '一封来自很久以前的信'
    try:
        smtpObj.sendmail(mail_user, letter[2], message.as_string())
        print("send email success")
        return True
    except smtplib.SMTPException as e:
        print(f"Send EMail Error {e.args[0]}: {e.args[1]}")
        return False

#每天定时检查需要发送的信件
def check_send_letters():
    loginEmail()
    letters = getletters()
    for letter in letters :
        if letter[1] == datetime.date.today():
            status = sendEmail(letter)
            if(status):
                delletter(letter[0])


def main_handler(event, context):
    if 'Time' in event.keys():  # 来自定时触发器
        check_send_letters()
        return
    if 'httpMethod' in event.keys():  # 来自api网关触发器
        if event['httpMethod'] == 'GET':
            return apiReply('', html=True)  # 返回网页
        if event['httpMethod'] == 'POST':  # 添加信件
            body = json.loads(event['body'])
            flag = addletter(body['date'], body['email'], body['letter'])
            return apiReply({
                'ok': True if flag else False,
                'message': '添加成功' if flag else '添加失败'
            })
    return apiReply('', html=True)

4.查看效果

  • 页面效果
  • TDSQL-C Serverless内存储的数据
  • 邮件效果

5.TDSQL-C Serverless状态以及账单

  • 当发生请求时的资源使用情况,可以清晰的看到TDSQL-C Serverless的自动启动过程
  • 根据购买时配置的自动暂停时间,10分钟后TDSQL-C Serverless已自动暂停
  • 账单

总结

TDSQL-C Serverless是一款完全符合Serverless特征的关系型数据库产品,无需运维,弹性伸缩,按需付费。有了它,数据库将不再是Serverless架构的“短板”,Serverless架构的落地场景也将不再局限于简单业务的处理,当遇到数据存储需求时,也不用再退而求其次的去使用JSON文件 对象存储的方案。

单从架构优势上来说,TDSQL-C Serverless的出现,打破了Serverless架构落地的最后一关,极大的丰富了Serverless架构的应用落地场景,用户可以体验到从前端、到后端、再到数据存储落地的全栈Serverless。

TDSQL-C Serverless继承了Serverless架构的优点的同时,不可避免的也会存在Serverless的一些缺点,最直观的一个缺点就是冷启动时间过长,云函数当前已经可以做到毫秒级的冷启动,但TDSQL-C Serverless的冷启动时长却还在秒级。希望TDSQL-C Serverless在后续的版本可以持续优化这个耗时,将腾讯云云原生技术普惠用户。

0 人点赞