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

前言

最近在学习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

    image-20231029102723364

  • 网络选择:后续创建云函数时,需要选择与这里一致的VPC及子网

image-20231028161524766

  • 算力配置弹性伸缩的关键配置,与购买传统云数据库需要挑选固定规格不同的是,TDSQL-C Serverless只需要配置最小CCU和最大CCU即可。CCU(TDSQL-C Compute Unit)为 Serverless 的计算计费单位,1CCU约等于1C2G的计算资源。根据配置的CCU范围,TDSQL-C Serverless可以在这个区间内实现自动的弹性伸缩

    image-20231029101426407

  • 自动暂停按需付费的关键配置,数据库在设定时间内无连接将自动进入暂停状态,暂停后计算将不再计费。当有连接访问时,系统会秒级自动启动处于暂停状态的数据库,用户不需设置重连机制。

    image-20231029101854678

  • 其他购买配置与传统数据库大同小异,根据自身需求配置即可

2.建库建表

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

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

image-20231028161043146

image-20231028161008229

3.创建云函数

  • 关键配置:启用私有网络,保证可以内网访问TDSQL-C Serverless

    image-20231029103557696

  • 创建API网关触发器、定时触发器

    image-20231029130830755

    image-20231029130728945

  • 云函数代码

    '''
    原作者:乂乂又又
    原文链接: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 = "[email protected]"
    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 οnclick="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=%23hitokoto" defer></script>
        <script src="https://unpkg.com/[email protected]/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.查看效果

  • 页面效果

    image-20231029132531812

  • TDSQL-C Serverless内存储的数据

    image-20231029132800391

  • 邮件效果

    image-20231029133308390

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

  • 当发生请求时的资源使用情况,可以清晰的看到TDSQL-C Serverless的自动启动过程

    image-20231029133930698

  • 根据购买时配置的自动暂停时间,10分钟后TDSQL-C Serverless已自动暂停

    image-20231029134538516

  • 账单
    image-20231029161100548

总结

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

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

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

猜你喜欢

转载自blog.csdn.net/2301_79531419/article/details/134098427