Serverless 是一种架构理念,具有自己的独特的优势和适用场景。本文以使用阿里云函数计算为例,构建一个简单具体的microservice为例,看看这种架构是如何达到快速开发和节约运维成本的。
应用场景1
某游戏公司刚开发完一个新的游戏,想要进行一些封闭测试,他们需要一个管理激活码的service来邀请有激活码的玩家来参与封闭测试,同时可能对积极参与封闭测试的玩家,等正式开服后,给予一定的礼包券码
应用场景2
某垂直领域的电商,刚刚起步,流量不是特别大,但发展势头不错。他们需要一个管理优惠码的service
针对上面所说的两个场景,无论是优惠码,激活码等相关码的管理,一般有如下四个:
- 生成码
- 使用码
- 验证码
- 删除码
传统方案
用户自己去架设服务器,配置数据库,再编写对应的服务,再配备相应的运维人员,总之,不管是硬件成本还是人力成本都不少。
serverless 方案
在本文中,我们运用阿里云API Gateway + 函数计算 + 表格存储(OTS),就能快速搭建这个服务,API Gateway 会自动 scale 去应对请求流量,同样函数计算也会根据流量自动 scale。开发方面,只需要实现好对应的逻辑函数即可。运维方面,省去了管理密匙、打安全补丁等工作,因为用户根本没有需要维护的机器。整个解决方案如下图:
从上图我们可以看出,主要有两个步骤:
- 函数计算作为 API 网关后端服务, 具体的教程可以参考官方教程和函数计算获取临时token
- 函数计算结合ots实现具体的逻辑,本文主要讲解这个过程, 并给出具体的代码。
具体步骤
1, 创建一个ots实例,并在实例中创建一张表;在本例中,是在华东2 region创建了code-ots
实例,并在该实例中创建了一张表code
, 主键是STRING类型
2, 创建对应的service,service下面创建4个函数,分别为gen_code, del_code, query_code, use_code, 具体的代码可以在本文最后附件下载,由于函数要访问ots,这边需要配置service可以读写ots的权限,相关授权教程可以参考函数计算实现流式处理大文件中的具体步骤第2步, 本文最后配置如下图,从下图可知,我们创建的service的服务角色是fc-ots-rw,该角色拥有对ots读写的权限。
具体function
1 生成码
- 配置event
{
"num":1000,
"start period":"2017-12-22 00:00:00",
"end period":"2017-12-28 00:00:00",
}
通过这个配置的event,设置的目标是产生1000个码, 码的有效期在2017-12-22 00:00:00
至 2017-12-28 00:00:00
,默认产生的状态都是UNACTIVED,未激活的
- code
# -*- coding: utf-8 -*-
import uuid
import json
import time
from tablestore import *
from tablestore.retry import WriteRetryPolicy
table_name = 'code' # 具体的table
ots_name = 'code-ots' # ots 实例
BATCH_NUM = 200
def batch_write_row(client, start, end, num):
put_row_items = []
for i in range(0, num):
uid_str = str(uuid.uuid1())
primary_key = [('uuid', uid_str)]
attribute_columns = [('start', start), ('end', end), ('status', 'UNACTIVED')]
row = Row(primary_key, attribute_columns)
condition = Condition(RowExistenceExpectation.EXPECT_NOT_EXIST)
item = PutRowItem(row, condition)
put_row_items.append(item)
request = BatchWriteRowRequest()
request.add(TableInBatchWriteRowItem(table_name, put_row_items))
result = client.batch_write_row(request)
succ, fail = result.get_put()
print ('check input table\'s put results: is_all_succeed={0}; succ_num={1}; \
fail_um={2}'.format(result.is_all_succeed(), len(succ), len(fail)))
for item in fail:
print ('Put failed, error code: %s, error message: %s' % (item.error_code, item.error_message))
def upload_ots(context, num, start, end):
endpoint = 'https://{}.cn-shanghai.ots-internal.aliyuncs.com'.format(ots_name)
creds = context.credentials
client = OTSClient(endpoint, creds.accessKeyId, creds.accessKeySecret, ots_name,
sts_token = creds.securityToken, retry_policy = WriteRetryPolicy())
while num > 0:
w_num = num
if num > BATCH_NUM:
w_num = BATCH_NUM
batch_write_row(client, start, end, w_num)
num = num - w_num
def handler(event, context):
evt = json.loads(event)
num = int(evt['num'])
start = evt['start period']
end = evt['end period']
start_t = time.mktime(time.strptime(start, "%Y-%m-%d %H:%M:%S"))
end_t = time.mktime(time.strptime(end, "%Y-%m-%d %H:%M:%S"))
print uuid.uuid1(), type(uuid.uuid1())
upload_ots(context, num, start_t, end_t)
return "ok"
2 使用码
- 配置event
{
"uuid":"254804e8-e707-11e7-9c21-0242ac110004"
}
假设使用码254804e8-e707-11e7-9c21-0242ac110004,只有表中存在这个码并且这个码是UNACTIVED
才返回SUCCESS,并且把该码的状态设置为ACTIVED
。其他情况,比如不存在这个码,或者是存在这个码但是已经被激活使用过,都返回FAIL
- code
# -*- coding: utf-8 -*-
from tablestore import *
from tablestore.retry import WriteRetryPolicy
import json
table_name = 'code'
ots_name = 'code-ots'
def update_row(client, uuid):
primary_key = [('uuid',uuid)]
update_of_attribute_columns = {
'PUT' : [('status','ACTIVED')],
}
row = Row(primary_key, update_of_attribute_columns)
condition = Condition(RowExistenceExpectation.EXPECT_EXIST, SingleColumnCondition("status", "UNACTIVED", ComparatorType.EQUAL))
try:
consumed, return_row = client.update_row(table_name, row, condition)
print ('Update succeed, consume %s write cu.' % consumed.write)
except Exception as e:
return 'FAILED'
return 'SUCCEED'
def handler(event, context):
endpoint = 'https://{}.cn-shanghai.ots-internal.aliyuncs.com'.format(ots_name)
creds = context.credentials
client = OTSClient(endpoint, creds.accessKeyId, creds.accessKeySecret, ots_name,
sts_token = creds.securityToken, retry_policy = WriteRetryPolicy())
evt = json.loads(event)
uuid = str(evt['uuid'])
return update_row(client, uuid)
3 查询码
- 配置event
{
"uuid":"254804e8-e707-11e7-9c21-0242ac110004"
}
假设查询码254804e8-e707-11e7-9c21-0242ac110004,只有表中不存在这个码返回NO EXISTED
, 存在的话,则返回表中记录的状态
- code
# -*- coding: utf-8 -*-
from tablestore import *
from tablestore.retry import WriteRetryPolicy
import time,json
table_name = 'code'
ots_name = 'code-ots'
def get_row(client, uuid):
primary_key = [('uuid', uuid)]
columns_to_get = []
cond = CompositeColumnCondition(LogicalOperator.OR)
cond.add_sub_condition(SingleColumnCondition("status", "UNACTIVED", ComparatorType.NOT_EQUAL))
cond.add_sub_condition(SingleColumnCondition("status", "UNACTIVED", ComparatorType.EQUAL))
consumed, return_row, next_token = client.get_row(table_name, primary_key, columns_to_get, cond, 1)
print ('Read succeed, consume %s read cu.' % consumed.read)
if return_row is None:
return "NO EXISTED"
status = "UNKNOWN"
for att in return_row.attribute_columns:
print ('name:%s\tvalue:%s\ttimestamp:%d' % (att[0], att[1], att[2]))
if att[0] == "status":
status = att[1]
if att[0] == "start":
start = att[1]
if att[0] == "end":
end = att[1]
current_time = time.time()
if current_time > end or current_time < start:
status = "TIMEINVALID"
return status
def handler(event, context):
endpoint = 'https://{}.cn-shanghai.ots-internal.aliyuncs.com'.format(ots_name)
creds = context.credentials
client = OTSClient(endpoint, creds.accessKeyId, creds.accessKeySecret, ots_name,
sts_token = creds.securityToken, retry_policy = WriteRetryPolicy())
evt = json.loads(event)
uuid = str(evt['uuid'])
return get_row(client, uuid)
4 删除码
- 配置event
{
"uuid":"254804e8-e707-11e7-9c21-0242ac110004"
}
假设删除码254804e8-e707-11e7-9c21-0242ac110004,不管表中是否存在这个码,只要表中没有这个码了就是成功删除,除非ots sdk delete_row抛出异常
- code
# -*- coding: utf-8 -*-
from tablestore import *
from tablestore.retry import WriteRetryPolicy
import json
table_name = 'code'
ots_name = 'code-ots'
def delete_row(client, uuid):
primary_key = [('uuid',uuid)]
row = Row(primary_key)
condition = Condition(RowExistenceExpectation.IGNORE, SingleColumnCondition("status", "", ComparatorType.NOT_EQUAL))
try:
consumed, return_row = client.delete_row(table_name, row, condition)
print ('Delete succeed, consume %s write cu.' % consumed.write)
except:
return 'FAILED'
return 'SUCCEED'
def handler(event, context):
endpoint = 'https://{}.cn-shanghai.ots-internal.aliyuncs.com'.format(ots_name)
creds = context.credentials
client = OTSClient(endpoint, creds.accessKeyId, creds.accessKeySecret, ots_name,
sts_token = creds.securityToken, retry_policy = WriteRetryPolicy())
evt = json.loads(event)
uuid = str(evt['uuid'])
return delete_row(client, uuid)