
4.3 Serverless和数据库
数据库作为后端数据查询和落盘的服务,是Serverless架构中必不可少的一部分。本节通过介绍数据库服务中关系型数据库和非关系型数据库的概念及这两种数据库和FaaS服务的联动方案,为Serverless架构中需要连接数据库的场景提供参考。
4.3.1 基本概念
大多数软件架构都涉及数据的存储和查询,因此,数据库联动是Serverless架构不可或缺的一部分。数据库相关的知识广泛且复杂,本节只简要介绍典型的关系型数据库MySQL和非关系型数据库MongoDB,并以二者为例,阐述它们和FaaS的联动方式及适用场景。
当前软件产生的数据很大一部分由关系数据管理系统(RDBMS)处理,关系型数据库便于理解和维护,并且具有事务一致性,遵循ACID的原则,即原子性(Atomicity)、一致性(Consistency)、独立性(Isolation)和持久性(Durability)。这种特点使得关系型数据库能满足所有要求强一致性的场景,例如银行交易。但这些特性,也导致了关系型数据库在高并发读写、高扩展性的场景下不能很好的适配。此外,随着用户生成数据(UGD)和操作日志成倍增加,在很多场景下并不需要保持关系型数据库的事务一致性或读写实时性。此时非关系型数据库应运而生,可用于海量数据查询场景,支持高性能、高可用和弹性伸缩,并且基于BASE原则,即基本可用(Basically Available)、软状态/柔性事务(Soft-state)和最终一致性(Eventually Consistent)。
关系型数据库和非关系型数据库的功能和特性对比如表4-2所示。
表4-2 关系型和非关系型数据库对比

① CAP即一致性(Consistency)、数据可用性(Availability)、分区耐受性(Partition Tolerance)。CAP原理认为一个提供数据服务的存储系统无法同时完美满足一致性、数据可用性和分区耐受性这三个条件。
关系型数据库和非关系型数据库各有适用场景和典型的开源/商业化产品,在Serverless中,也需要基于这两种典型的数据库设计架构并提供服务。
4.3.2 数据库和FaaS的联动
为了确保数据库连接的安全性,在FaaS平台访问数据库时,需要配置私有网络VPC,将数据库和函数放置在相同的VPC内,可以通过私有网络安全连接云数据库。
1. 关系型数据库
FaaS访问关系型数据库时,在开通对应访问权限、配置网络后,可以通过对应的数据库客户端直接连接。代码清单4-5所示是一个基于Python连接MySQL数据库。
代码清单4-5 FaaS连接MySQL数据库示例
# -*- coding: utf8 -*- import datetime import pymysql.cursors import logging import sys import pytz # MySQL数据库账号信息,需要提前创建数据库,建议用环境变量方式传参 Host = '******' User = '****' Password = '****' Port = 63054 DB = u'SCF_Demo' logging.basicConfig(level=logging.INFO, stream=sys.stdout) logger = logging.getLogger() logger.setLevel(level=logging.INFO) #更改时区为北京时区 tz = pytz.timezone('Asia/Shanghai') g_connection = None g_connection_errno = 0 def connect_mysql(): global g_connection global g_connection_errno try: g_connection = pymysql.connect(host=Host, user=User, password=Password, port=Port, db=DB, charset='utf8', cursorclass=pymysql.cursors.DictCursor) except Exception as e: g_connection = None g_connection_errno = e[0] print("connect database error:%s"%e) print("connect database") connect_mysql() def main_handler(event, context): print('Start function') print("{%s}" % datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")) print("g_connection is %s" % g_connection) if not g_connection: connect_mysql() if not g_connection: return {"code": 409, "errorMsg": "internal error %s" % g_connection_errno} with g_connection.cursor() as cursor: sql = 'show databases' cursor.execute(sql) res = cursor.fetchall() print res sql = 'use %s'%DB cursor.execute(sql) #创建数据表 cursor.execute("DROP TABLE IF EXISTS Test") cursor.execute("CREATE TABLE Test (Msg TEXT NOT NULL,Time Datetime)") time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S") sql = "insert INTO Test ('Msg', 'Time') VALUES (%s, %s)" cursor.execute(sql, ("test", time)) g_connection.commit() sql = "select count(*) from Test" cursor.execute(sql) result = cursor.fetchall() print(result) cursor.close() print("{%s}" % datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")) return "test"
需要注意的是,一般在FaaS连接关系型数据库时,需要管理连接池,实现连接复用。
- 通过Redis进行数据缓存,从而有效控制实际的数据库连接。
- 调整数据库的超时时间,同时在代码中针对断开的连接做重连策略。
- 适当限制FaaS函数的并发,使其小于数据库承载的最大连接数,防止数据库高负载。
- 通过限制连接用户数、扩容并增加数据库max_connections等方式进行优化。
对于用户来说,最好的方案当然是不需要考虑数据库的连接管理,在数据库层也能够实现Serverless化。针对这个需求,AWS发布了Aurora Serverless for MySQL,实现了关系型数据库的弹性伸缩和按需付费(无请求时销毁数据库实例),并且支持HTTP方式的连接和访问,降低了数据库使用的门槛。
2. 非关系型数据库
在Serverless和非关系型数据库的联动中,AWS Lambda和DynamoDB的打通最为典型,其他云提供商也在逐步提供类似的服务,本节主要介绍这种连接及其开发模式。
DynamoDB是一个非关系型Key-Value键值存储数据库。它不会通过结构化、关系型的方式存储数据,而是用简单的Key-Value格式存储JSON对象。此外,DynamoDB是分布式数据库,因此提供了天然的冗余和备份能力。
在DynamoDB中,表(table)、数据(item)和属性(attributes)是三个核心概念。其中,每个表由一个或多个数据组成,每个数据又由一个或多个属性组成。每个表需要设置主键(primary key)作为索引,主键可以由单一的分区键(partition key)组成,也可以由分区键和排序键(sort key)组成,即复合主键。不管使用哪种方式,最后的组成的主键在一张表中必须是唯一的。DynamoDB中各个概念之间的关系如图4-15所示。

图4-15 DynamoDB概念介绍
AWS Lambda可以通过配置数据库触发器的方式,一键打通DynamoDB,也可以在函数代码中实现数据库的增删改查。例如每次更新DynamoDB表时,该操作都可以作为事件触发Lambda函数,执行自定义逻辑。对应的触发事件格式如代码清单4-6所示。流事件可以同步触发函数进行数据库操作,也可以批量读取,进行数据库操作。AWS DynamoDB大大降低了开发人员对数据库运维和管理的依赖,进一步拓展了开发者的边界。
代码清单4-6 DynamoDB事件结构
{ "Records": [ { "eventID": "1", "eventVersion": "1.0", "dynamodb": { "Keys": { "Id": { "N": "101" } }, "NewImage": { "Message": { "S": "New item!" }, "Id": { "N": "101" } }, "StreamViewType": "NEW_AND_OLD_IMAGES", "SequenceNumber": "111", "SizeBytes": 26 }, "awsRegion": "us-west-2", "eventName": "INSERT", "eventSourceARN": eventsourcearn, "eventSource": "aws:dynamodb" }, { "eventID": "2", "eventVersion": "1.0", "dynamodb": { "OldImage": { "Message": { "S": "New item!" }, "Id": { "N": "101" } }, "SequenceNumber": "222", "Keys": { "Id": { "N": "101" } }, "SizeBytes": 59, "NewImage": { "Message": { "S": "This item has changed" }, "Id": { "N": "101" } }, "StreamViewType": "NEW_AND_OLD_IMAGES" }, "awsRegion": "us-west-2", "eventName": "MODIFY", "eventSourceARN": sourcearn, "eventSource": "aws:dynamodb" }