发布于: Nov 30, 2022

【概要】本文提供一种跨区域形式的高可用性实现方法,保证能够在某一区域中的机器人或支持实现 API 不可用时,使用来自其他区域的资源以继续响应客户呼叫。

大家需要在主区域与辅助区域中创建相同的对话机器人。在本文中,我们将 us-east-1 作为主区域,us-west-2 作为辅助区域。接下来,先使用主区域 us-east-1 创建机器人:

  • 在 Amazon Lex 控制台上,点击 Create。
  • 在 Try a Sample 部分,选择 OrderFlowers,而后在 COPPA 中选择 No。
  • 其他设置项皆保留默认值,点击 Create。
  • 此机器人的创建与构建操作将自动进行。
  • 在机器人构建完成(约需 1 至 2 分钟)后,选择 Publish。
  • 创建一个别名,名称为 ver_one。对 us-west-2 区域重复上述步骤。现在,您已经在 us-east-1 与 us-west-2 中建立起能够正常运行的 Amazon Lex 机器人。

 

请确保您当前处于 us-east-1 区域内。

  • DynamoDB 控制台处,选择 Create。
  • 在 Table name 部分,输入 lexDR。
  • 在 Primary key 部分,输入 connectRegion 且类型为 String。
  • 其他各项保留默认值,而后选择 Create。
  • 在 Items 选项卡中,选择 Create item。
  • 将 connectRegion 的值设置为 us-east-1,而后 Append 一个类型为 String、名称为 lexRegion 的新列,并将其值设置为 us-east-1。
  • 点击 Save。

 

 

在此步骤中,我们将为两项 Lambda 函数创建一个 Amazon Web Services 身份与访问管理(Amazon Identity and Access Management,简称 IAM)角色。

  • 在 IAM 控制台上,点击 Access management 并选择 Policies。
  • 点击 Create Policy。
  • 点击 JSON。
  • 粘贴以下自定义 IAM 策略,此策略允许对 DynamoDB 表 lexDR 进行读取/写入访问。请将策略中的 “xxxxxxxxxxxx” 部分替换为您的 Amazon Web Services 账户编号。
{
	"Version": "2012-10-17",
	"Statement": [{
		"Sid": "VisualEditor0",
		"Effect": "Allow",
		"Action": ["dynamodb:GetItem", "dynamodb:UpdateItem"],
		"Resource": "arn:aws:dynamodb:us-east-1:xxxxxxxxxxxx:table/lexDR"
	}]
}
  • 点击 Review Policy。
  • 将其命名为 DynamoDBReadWrite,而后点击 Create Policy。
  • 在 IAM 控制台上,点击 Access management 下的 Roles ,而后点击 Create Role。
  • 为该服务选择 Lambda,而后点击 Next。
  • 附加以下权限策略:
    • AmazonLambdaBasicExecutionRole
    • AmazonLexRunBotsOnly
    • DynamoDBReadWrite
  • 点击 Next: Tags。接下来,点击 Next: Review 以跳过 Tags 页面。
  • 将角色命名为 lexDRRole,而后点击 Save。
 

我们首先需要创建一项 Lambda 函数,用于从 DynamoDB 表中读取记录,借此判断哪个 Amazon Lex 机器人与 Amazon Connect 实例处于同一区域当中。Amazon Connect 或者使用此机器人的应用程序后续将调用此函数。

  • 在 Lambda 控制台上,选择 Create function。
  • 在 Function name 部分,输入 lexDRGetRegion。
  • 在 Runtime 部分,选择 Python 3.8。
  • 在 Permissions 之下,选择 Use an existing role。
  • 选择角色 lexDRRole。
  • 选择 Create function。
  • 在 Lambda 代码编辑器中,输入以下代码(下载自 lexDRGetRegion.zip):
import json
import boto3
import os
import logging
dynamo_client=boto3.client('dynamodb')
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
 
def getCurrentPrimaryRegion(key):
    result = dynamo_client.get_item(
        TableName=os.environ['TABLE_NAME'],
        Key = { 
            "connectRegion": {"S": key } 
        }
    )
    logger.debug(result['Item']['lexRegion']['S'] )
    return result['Item']['lexRegion']['S'] 
 
def lambda_handler(event, context):
    logger.debug(event)
    region = event["Details"]["Parameters"]["region"]
    return {
        'statusCode': 200,
        'primaryCode': getCurrentPrimaryRegion(region)
    }
  • 在 Environment variables 部分,选择 Edit。
  • 添加一项环境变量,其中 Key 为 TABLE_NAME,Value 为 lexDR。
  • 点击 Save 以保存该环境变量。
  • 点击 Save 以保存该 Lambda 函数。

 

 

在 us-east-1 当中创建另一项 Lambda 函数,用以实现运行状况检查功能。

  • 在 Lambda 控制台上,选择 Create function。
  • 在 Function name 部分,输入 lexDRTest。
  • 在 Runtime 部分,选择 Python 3.8。
  • 在 Permissions 之下,选择 Use an existing role。
  • 选择 lexDRRole。
  • 选择 Create function。
  • 在 Lambda 代码编辑器中,输入以下代码(下载自 lexDRTest.zip):
import json
import boto3
import sys
import os

dynamo_client = boto3.client('dynamodb')
primaryRegion = os.environ['PRIMARY_REGION']
secondaryRegion = os.environ['SECONDARY_REGION']
tableName = os.environ['TABLE_NAME']
primaryRegion_client = boto3.client('lex-runtime',region_name=primaryRegion)
secondaryRegion_client = boto3.client('lex-runtime',region_name=secondaryRegion)

def getCurrentPrimaryRegion():
    result = dynamo_client.get_item(
        TableName=tableName,
        Key={  
            'connectRegion': {'S': primaryRegion}  
        }
    )
    return result['Item']['lexRegion']['S'] 
    
def updateTable(region):
    result = dynamo_client.update_item( 
        TableName= tableName,
        Key={  
            'connectRegion': {'S': primaryRegion } 
        },  
        UpdateExpression='set lexRegion = :region',
        ExpressionAttributeValues={
        ':region': {'S':region}
        }
    )
    
#SEND MESSAGE/PUT SESSION ENV VA
def put_session(botname, botalias, user, region):
    print(region,botname, botalias)
    client = primaryRegion_client
    if region == secondaryRegion:
        client = secondaryRegion_client
    try:
        response = client.put_session(botName=botname, botAlias=botalias, userId=user)
        if (response['ResponseMetadata'] and response['ResponseMetadata']['HTTPStatusCode'] and response['ResponseMetadata']['HTTPStatusCode'] != 200) or (not response['sessionId']):  
            return 501
        else:
            if getCurrentPrimaryRegion != region:
                updateTable(region)
        return 200
    except:
        print('ERROR: {}',sys.exc_info()[0])
        return 501

def send_message(botname, botalias, user, region):
    print(region,botname, botalias)
    client = primaryRegion_client
    if region == secondaryRegion:
        client = secondaryRegion_client
    try:
        message = os.environ['SAMPLE_UTTERANCE']
        expectedOutput = os.environ['EXPECTED_RESPONSE']
        response = client.post_text(botName=botname, botAlias=botalias, userId=user, inputText=message)
        if response['message']!=expectedOutput:
            print('ERROR: Expected_Response=Success, Response_Received='+response['message'])
            return 500
        else:
            if getCurrentPrimaryRegion != region:
                updateTable(region)
            return 200
    except:
        print('ERROR: {}',sys.exc_info()[0])
        return 501

def lambda_handler(event, context):
    print(event)
    botName = os.environ['BOTNAME']
    botAlias = os.environ['BOT_ALIAS']
    testUser = os.environ['TEST_USER']
    testMethod = os.environ['TEST_METHOD']
    if testMethod == 'send_message':
        primaryRegion_response = send_message(botName, botAlias, testUser, primaryRegion)
    else:
        primaryRegion_response = put_session(botName, botAlias, testUser, primaryRegion)
    if primaryRegion_response != 501:
        primaryRegion_client.delete_session(botName=botName, botAlias=botAlias, userId=testUser)
    if primaryRegion_response != 200:
        if testMethod == 'send_message':
            secondaryRegion_response = send_message(botName, botAlias, testUser, secondaryRegion)
        else:
            secondaryRegion_response = put_session(botName, botAlias, testUser, secondaryRegion)
        if secondaryRegion_response != 501:
            secondaryRegion_client.delete_session(botName=botName, botAlias=botAlias, userId=testUser)
        if secondaryRegion_response != 200:
            updateTable('err')
    #deleteSessions(botName, botAlias, testUser)
    return {'statusCode': 200,'body': 'Success'}
  • 在 Environment variables 部分,选择 Edit,而后添加以下环境变量:
    • BOTNAME – OrderFlowers
    • BOT_ALIAS – ver_one
    • SAMPLE_UTTERANCE – I would like to order some flowers. (向机器人发送的示例话语。)
    • EXPECTED_RESPONSE – What type of flowers would you like to order? (机器人在收到以上示例话语后应做出的预期响应。) 
    • PRIMARY_REGION – us-east-1
    • SECONDARY_REGION – us-west-2
    • TABLE_NAME – lexDR
    • TEST_METHOD – put_session or send_message
      • send_message : 此方法将调用 Lex 运行时函数 postText,该函数将提取语音并将其映射至训练得出的某一 intent。postText 将测试 Lex 的自然语言理解能力,每项请求的使用成本为 0.00075 美元,几乎可以忽略不计。
      • put_session: 此方法将调用 Lex 运行时函数 put_session,该函数为用户创建一个新的会话。put_session 不会测试 Lex 的自然语言理解能力。
    • TEST_USER – test
  • 点击 Save 以保存此环境变量。
  • 在 Basic Settings section 当中,将 Timeout 的值更新为 15 秒。
  • 点击 Save 以保存此 Lambda 函数。

 

 

为了每 5 分钟触发一次运行状况检查函数,我们需要创建一条 Amazon CloudWatch 规则。

  • 在 CloudWatch 控制台的 Events 之下,选择 Rules。
  • 选择 Create rule。
  • 在 Event Source 之下,将选项切换为 Schedule。
  • 将 Fixed rate of 设置为 5 minutes。
  • 在 Targets 之下,选择 Add target。
  • 选择目标 Lambda function。
  • 在 Function 部分,选择 lexDRTest。
  • 在 Configure input 之下,选择 Constant (JSON text),而后输入 {}
  • 选择 Configure details。
  • 在 Rule definition 之下的 Name 部分,输入 lexHealthCheckRule。
  • 选择 Create rule。

现在,您应该已经建立起 lexHealthCheckRule CloudWatch 规则,能够每 5 分钟调用一次 lexDRTest 函数。这项操作将检查您主机器人的运行状况是否正常,并将结果对应更新至 DynamoDB 表。

 

现在,我们需要创建一个 Amazon Connect 实例,借此在创建 lexDRTest 函数的同一区域之内测试机器人的多区域模式。

  • 如果您还没有 Amazon Connect 实例,请首先创建一个。
  • 在 Amazon Connect 控制台上,选择作为 Amazon Connect 传输流目标的实例别名。
  • 选择 Contact flows。
  • 在 Amazon Lex 之下,从 us-east-1 区域中选择 OrderFlowers 机器人,而后点击 Add Lex Bot。
  • 在 us-west-2 区域中选择 OrderFlowers 机器人,而后点击 Add Lex Bot。
  • 在 Amazon Lambda 之下,选择 lexDRGetRegion 并点击 Add Lambda Function。
  • 点击左侧面板中的 Overview 再点击登录链接,借此登录至您的 Amazon Connect 实例。
  • 点击左侧面板中的 Routing,而后点击下拉菜单中的 Contact Flows。
  • 点击 Create Contact Flow 按钮。
  • 点击 Save 按钮旁的向下箭头按钮,再点击 Import Flow。
  • 下载联系流程 Flower DR Flow。在 Import Flow 对话框中上传此文件。
  • 在 Contact Flow 中,点击 Inovke Amazon Lambda Function 部分,借此在屏幕右侧打开一个属性面板。
  • 选择 lexDRGetRegion 并点击 Save。
  • 点击 Publish 按钮,发布当前联系流程。
 

接下来,我们需要将电话号码关联至联系流程,借此调用并测试 OrderFlowers 机器人。

  • 点击左侧导航栏中的 Routing 选项。
  • 点击 Phone Numbers。
  • 点击 Claim Number。
  • 选择您的国家代码并选择电话号码。
  • 在 Contact flow/IVR 选择框中,选择我们在之前步骤中导入的联系流程 Flower DR Flow 。
  • 等待几分钟,而后呼叫该号码以与 OrderFlowers 机器人交互。
 

要测试这套解决方案,您可以执行以下操作以模拟 us-east-1 区域发生故障的场景:

  • 在 us-east-1 区域中打开 Amazon Lex 控制台。
  • 选择 OrderFlowers 机器人。
  • 点击 Settings。
  • 删除机器人别名 ver_one

在下一次进行运行状况检查时,解决方案将尝试与 us-east-1 区域的 Lex 机器人进行通信。由于机器人别名不存在,因此无法获得成功响应。因此,本示例随后会呼叫辅助区域 us-west-2 并收到成功响应。在收到响应后,示例将使用 us-west-2 更新 lexDR 以及 DynamoDB 表中的 lexRegion 列。

接下来,所有指向 us-east-1 区域内 Connect 的后续呼叫都将与 us-west-2 区域内的 Lex 机器人进行实际交互。通过这一自动切换,可以证明当前架构模式确实能够在发生服务故障时保障业务连续性。

在删除机器人别名到下一次运行状况检查之间的时段内,对 Amazon Connect 的呼叫都将失败。但在运行状况检查之后,系统将自动实现业务连续性保障。因此,每一轮运行状况检查之间的间隔越短,则停机时间越短。您可以通过编辑 Amazon CloudWatch 规则 lexHealthCheckRule 以调整每次运行状况检查之间的间隔时长。

要让 us-east-1 区域再次通过运行状况检查,请在 us-east-1 中重新创建 OrderFlowers 机器人的别名 ver_one。

 

为了避免产生不必要的额外成本,请删除本示例中创建的所有资源。

  • 创建在 us-east-1 与 us-west-2 当中的 Amazon Lex 机器人OrderFlowers
  • CloudWatch 规则 lexHealthCheckRule
  • DynamoDB 表 lexDR
  • Lambda 函数 lexDRTest 与 lexDRGetRegion
  • IAM 角色 lexDRRole
  • 联系流程 Flower DR Flow
 

配合 Amazon Lex 提供的自助服务,Amazon Connect 将帮助您轻松创建便捷直观的客户服务体验。本文提供一种跨区域形式的高可用性实现方法,保证能够在某一区域中的机器人或支持实现 API 不可用时,使用来自其他区域的资源以继续响应客户呼叫。

 

相关文章