Implement AWS SQS and Lambda to decouple process flow

关于AWS SQS 和 Lamb function的实现

得力于最近项目上的需求,需要了解一下关于AWS SQS的使用方法。不多说,先上overview design:
在这里插入图片描述

使用场景:
这次的study主要是为了解决之前项目上的一些pain point. 当Batch jobs 被触发时,API 到 IBM Sterling之间会做很多处理来将documents 生成出来并且分发给不同的客户。 在这个模型里面,当Sterling 和 Batch在处理请求时,客户会反应说document没有收到, Ops team只有通过查看日志的方式在troubleshoot, 然而该系统下并没有日志系统做支撑,ops team 只能手动通过关键词去服务器查询日志找出问题。引入AWS SQS 的目的就是为了解决当我们的程序出现异常时,SQS 可以帮助我们log error, 并且提供retry mechanism.在这里插入图片描述Agenda:

  1. AWS SQS 介绍
  2. 如何使用AWS SQS
  3. 如何使用lambda function
  4. 如何查看日志
  5. 设置Dead loop queue 和 retry machanism
  6. AWS SQS 的一些经验分享
  7. 总结

AWS SQS 介绍
“Amazon Simple Queue Service (SQS)是一个完全托管的消息队列服务,它使您能够解耦和扩展微服务、分布式系统和无服务器应用程序。” 简单来说,SQS 就是一个云上的消息队列服务, 它可以帮助我们将请求存储在消息队列里然后触发我们的lambda function 来处理这些message, 这样做的目的就是为了解耦程序之间的关联,这样一来,我们的每个请求就不用等待后续的process跑完才return给用户,用户在这个过程中不需要等待就可以拿到一个success的结果,在后续的lambda function 会去执行剩下的代码。lambda function 是AWS 的另一个服务,我们可以将代码上传给lambda function, 让它帮我们执行我们需要跑的代码。
为了直观,这篇文章就不用代码演示如何创建Queue, 具体的代码可以参考官方github.

在这里插入图片描述
可以看到通过AWS Console,我们就可以完成创建队列,发送消息,删除消息等操作
在这里插入图片描述
Default Visibility Timeout: 当我们的队列里的message被处理时,Messages in flight 会记录有一条record正在被处理,当这个message被处理之后出现了异常, 那么这个message 会等待这个时间(Default Visibility Timeout) 之后,才会出现在Messages available里。设置这个时间是为了让我这条fail的message不会在我的lambda function没跑完之前被抓起来重跑。
Message Retention Period: message将会被delete如果过了这个期限message依然queuing.

Dead Letter Queue Settings
Dead Letter Queue: 需要创建一个新的queue 作为DLQ
Maximum Receives: retry的次数

当创建完Queue之后,我们可以试着发送一个Message在这里插入图片描述
在这里插入图片描述
Message Body: message 的内容
Message Attribute: 这里可以加入一些attr来区别不同的message,这里的attr主要是用在lambda function 里面。可以通过使用attr对不同的message 做不同的分类和处理。

当我创建一个Message之后,这个message 就很快进入了in flight, 在这里插入图片描述
这时候我们可以在CloudWatch 里面看到这条message被执行的log 如下,
在这里插入图片描述
这里我们的message 会被处理,是因为我们关联一个lambda function去处理这条message, 从Log里面可以看出,这条message 是fail的,因为1除以0是会报错的,这里让它出现异常,是为了体现retry之后,这条message 会进入Dead loop queue.

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190211190448138.png
你会看到这条Message queue在了Dead loop queue 里, 等待被其他程序处理。
同学肯定会好奇我的lambda function代码是什么样,如下:

public class LambdaFunctionHandler implements RequestHandler<Object, String> {
        @Override
        public String handleRequest(Object input, Context context) {
            context.getLogger().log("Input: " + input);
            String output = "Hello, " + input + "!";
            String indicator = input.toString();
            if(indicator.contains("fail")) {
            	System.out.println(1/0);
            }
            System.out.print(output);
            return output;
    }
}

这里的代码只是为了让程序出现异常,如果字段里面包含了"fail" 就去执行1/0 触发异常。

如何创建lambda function

在这里插入图片描述
在这里插入图片描述这里的role 需要去配置,因为AWS有自己的一套方法去管理权限,基本上就是点鼠标,让后把SQS, LAMBDA的权限都分配好就可以使用了。

创建一个S3 Bucket
这里的S3 Bucket是为了帮助我们把代码寄存在bucket里面,当lambda function被触发时,它会去bucket里面获取代码并执行代码。
在这里插入图片描述在这里插入图片描述

创建ACCESS KEY

当ACCESS KEY 和 S3 Bucket 创建完成之后, 剩下的就是将代码上传到S3 Bucket,然后让代码和我们的lambda function 关联起来,这里我们使用Eclipse做demo:

  1. 创建一个 AWS lambda Java Project
    在这里插入图片描述

  2. 找到LambdaFunctionHandler.java file, 然后把代码复制进去, save。

public class LambdaFunctionHandler implements RequestHandler<Object, String> {
        @Override
        public String handleRequest(Object input, Context context) {
            context.getLogger().log("Input: " + input);
            String output = "Hello, " + input + "!";
            String indicator = input.toString();
            if(indicator.contains("fail")) {
            	System.out.println(1/0);
            }
            System.out.print(output);
            return output;
    }
}
  1. 上传到S3 Bucket 并且测试代码:
    right click ->Upload function to AWS Lambda
    在这里插入图片描述
    这里我们要选好S3 Bucket是哪个然后完成
    在这里插入图片描述

完成后会看到我们最新upload的代码了:
在这里插入图片描述

测试function:
right click ->run function on AWS Lambda, 我们可以加入我们要测试的text 然后选择invoke

在这里插入图片描述
在这里插入图片描述
这样我们就完成了一次测试,并且成功将Eclipse里的代码上传到S3 Bucket里。

在这里插入图片描述
设置Dead loop queue 和 retry machanism

当我们的message 在SQS里面被process之后出现了异常,通常我们需要为lambda function 设置dead loop queue,让那些有问题的message在超过retry之后被send 去另外一个queue里面等待处理, 如上图所示的DeadLoopQueue

通过在lambda function里面配置debugging and error handling来设置DLQ
在这里插入图片描述

这样以来,那些failed 的record 在超过retry之后就会进入DLQ:
在这里插入图片描述

进入到DLQ 的message 通常有2种方式来处理:
a) 通过给DLQ配置lambda function, 当DLQ收到message之后立马开始执行lambda function, 具体做法和以上一致,通过eclipse上传代码到S3 Bucket

b) 通过配置WatchCloud Event 来对message 实现定时批处理
首先配置一个event source
在这里插入图片描述

然后关联这个event source 和 lambda function
在这里插入图片描述

为了处理queue里面的message, 我们需要让lambda function 去access 我们的DLQ, 以下是代码示例:
在这里插入图片描述

public class LambdaFunctionHandler implements RequestHandler<Object, String> {

    @Override
    public String handleRequest(Object input, Context context) {
        context.getLogger().log("Input: " + input);
        String output = "Hello, " + input + "-";
        String indicator = input.toString();
        if(indicator.contains("fail")) {
        	System.out.println(1/0);
        }
        System.out.print(output);
        return output;
    }

}
package com.amazonaws.lambda.demo;
import com.amazonaws.client.builder.AdvancedConfig.Key;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.GetQueueAttributesRequest;
import com.amazonaws.services.sqs.model.Message;
import com.amazonaws.services.sqs.model.MessageAttributeValue;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.amazonaws.services.sqs.model.SendMessageRequest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DLQHandlerPullQueue {
	 private static final String QUEUE_NAME = "NTPDLQTest";
	 private static final AmazonSQS sqs = AmazonSQSClientBuilder.defaultClient();
	 private static final String queueUrl = sqs.getQueueUrl(QUEUE_NAME).getQueueUrl();
	 
	//To get attributes of queue
	public void ProcessSQSMessage() {
	        List<String> attributeNames = new ArrayList<String>();
	        attributeNames.add("All");
			GetQueueAttributesRequest gqar = new GetQueueAttributesRequest(queueUrl);
			gqar.setAttributeNames(attributeNames);
			Map<String, String> attrMap = new HashMap<String,String>(sqs.getQueueAttributes(gqar).getAttributes());
	        int messagesSize = Integer.parseInt(attrMap.get("ApproximateNumberOfMessages"));
	       //ApproximateNumberOfMessages is number of available message in Queue
	        System.out.println("Messages in the queue: " + messagesSize);
			//Print all attrs of queue
			for (Map.Entry<String, String> entry : attrMap.entrySet()) {
				System.out.println(entry.getKey() + "/" + entry.getValue());
			}
			
			
//			for (int i = 0; i < messagesSize; i++) {
//				ReceiveAllMessage();
//			}
			
			
			List<String> attrList = new ArrayList<String>();
			attrList.add("failA");
			attrList.add("failB");
			//attrList.add("failC");
			for (int i = 0; i < messagesSize; i++) {
				GetMessageByAttr(attrList);
			}
		}
		
	 
	//To receive all message in Queue
	public void ReceiveAllMessage() {
		List<Message> messages = sqs.receiveMessage(queueUrl).getMessages();
		System.out.println("test - ReceiveMessage");
        for (Message m : messages) {
            System.out.println(m.getBody());
        }
	}
	
	public void GetMessageByAttr(List<String> attrnameList) {
		final List<Message> messages = sqs.receiveMessage(
			    new ReceiveMessageRequest(queueUrl)
			        .withMessageAttributeNames(attrnameList)
			        .withMaxNumberOfMessages(Integer.valueOf("1"))
			).getMessages();
		System.out.println("Size = " + messages.size());

        for (Message m : messages) {
    		//Get message attrs
    		System.out.println(m.getMessageAttributes().toString());
            System.out.println(m.getBody());
        }
	}

	public void SendMessageToqueueWithAttr() {
		final Map<String, MessageAttributeValue> messageAttributes = new HashMap<>();
		messageAttributes.put("failcase", new MessageAttributeValue()
		        .withDataType("String")
		        .withStringValue("Jane"));
		
		final SendMessageRequest sendMessageRequest = new SendMessageRequest();
		sendMessageRequest.withMessageBody("fail: To test QueueAttr");
		sendMessageRequest.withQueueUrl(queueUrl);
		sendMessageRequest.withMessageAttributes(messageAttributes);
		sqs.sendMessage(sendMessageRequest);
		
	}
}

为了实现类似batch的效果,我们首先到去queue里面拿到所有的message的count, 然后使用for loop来完成对所有message的处理,因为ReceiveAllMessage() 这个每次去queue里面拿record的时候都是不一致的,例如Queue里面有A,B,C,D,E,for loop 5 之后拿出来的message可能是E-D-A-B-C, 每次的顺序都会不同。所以当我们的message在处理的时候fail, 我们需要设置Default Visibility Timeout时间,来让我们的message不会被瞬间设置会available, 否则的话,这条fail的message会重新被lambda function pick up 如此一来,就会有一条message被miss掉。 那么Default Visibility Timeout要怎么设置才可以避免这种情况发生?
Default Visibility Timeout = 12 hours
If lambda function triggered at 7:00 am,
If there are too much records to process, the batch will take 12 hours(max for 1 day)
7:00 PM, job stops and if the final message processing fail at 6:59pm, it will be shown in DLQ as available at 6:59am.

Default Visibility Timeout = 6 hours
If lambda function triggered at 7:00 am,
13:00pm, job completes. if the final message processing fail at 12:59pm. it will be shown in DLQ as available at 18:59am.

通过延长Default Visibility Timeout, 就可以避免message被重复pick up 然后process了。

总结:
以下摘自极客时间 胡忠想老师的课程:从零开始学习微服务
基于 RPC 通信的微服务架构,其特点是一个服务依赖于其他服务返回的结果,只有依赖服务执行成功并返回后,这个服务才算调用成功。这种架构适用于用户请求是读请求的情况,就像下图所描述的那样,比如微博用户的一次 Feed API 请求,会调用 Feed RPC 获取关注人微博,调用 Card RPC 获取微博中的视频、文章等多媒体卡片信息,还会调用 User RPC 获取关注人的昵称和粉丝数等个人详细信息,只有在这些信息都获取成功后,这次用户的 Feed API 请求才算调用成功。

在这里插入图片描述
而基于 MQ 消息队列通信的架构,其特点是服务之间的交互是通过消息发布与订阅的方式来完成的,一个服务往 MQ 消息队列发布消息,其他服务从 MQ 消息队列订阅消息并处理,发布消息的服务并不等待订阅消息服务处理的结果,而是直接返回调用成功。这种架构适用于用户请求是写请求的情况,就像下图所描述的那样,比如用户的写请求,无论是发博、评论还是赞都会首先调用 Feed API,然后 Feed API 将用户的写请求消息发布到 MQ 中,然后就返回给用户请求成功。如果是发博请求,发博服务就会从 MQ 中订阅到这条消息,然后更新用户发博列表的缓存和数据库;如果是评论请求,评论服务就会从 MQ 中订阅到这条消息,然后更新用户发出评论的缓存和数据库,以及评论对象收到评论的缓存和数据库;如果是赞请求,赞服务就会从 MQ 中订阅到这条消息,然后更新用户发出赞的缓存和数据库,以及赞对象收到的赞的缓存和数据库。这样设计的话,就把写请求的返回与具体执行请求的服务进行解耦,给用户的体验是写请求已经执行成功,不需要等待具体业务逻辑执行完成。
在这里插入图片描述
总结一下就是,基于 RPC 通信和基于 MQ 消息队列通信的方式都可以实现微服务的拆分,两者的使用场景不同,RPC 主要用于用户读请求的情况,MQ 主要用于用户写请求的情况。对于大部分互联网业务来说,读请求要远远大于写请求,所以针对读请求的基于 RPC 通信的微服务架构的讨论也更多一些,但并不代表基于 MQ 消息队列不能实现,而是要区分开它们不同的应用场景。
以上摘自极客时间 胡忠想老师的课程:从零开始学习微服务

我们所使用的SQS 也是为了解决应用之间解耦的问题,AWS SQS 的确是一个很好用的中间件,配合eclipse和lambda function 可以很容易的实现给应用的解耦,而且AWS 提供了clockwatch log 来帮忙我们track那些有问题的message。

Reference:
AWS Check source code for lambda:
https://s3.console.aws.amazon.com/s3/buckets/ntpsqstest/?region=ap-southeast-1&tab=overview#

AWS Check message log for lambda:
https://ap-southeast-1.console.aws.amazon.com/cloudwatch/home?region=ap-southeast-1

AWS Configuration for lambda:
https://ap-southeast-1.console.aws.amazon.com/lambda/home?region=ap-southeast-1#/functions/NTPDLQHandler?tab=graph

AWS SQS console:
https://console.aws.amazon.com/sqs/home?region=ap-southeast-1#queue-browser:selected=https://sqs.ap-southeast-1.amazonaws.com/408153411148/NTPDLQTest;prefix=

Sample Code Github:
https://github.com/awsdocs/aws-doc-sdk-examples/blob/master/java/example_code/sqs/src/main/java/aws/example/sqs/UsingQueues.java

AWS Lambda Adds Amazon Simple Queue Service to Supported Event Sources
https://aws.amazon.com/blogs/aws/aws-lambda-adds-amazon-simple-queue-service-to-supported-event-sources/

Tutorial: How to Create, Upload, and Invoke an AWS Lambda Function
https://docs.aws.amazon.com/toolkit-for-eclipse/v1/user-guide/lambda-tutorial.html

https://supsystic.com/documentation/id-secret-access-key-amazon-s3/

coding sample:
https://www.programcreek.com/java-api-examples/index.php?api=com.amazonaws.services.sqs.model.GetQueueAttributesRequest
https://www.javatips.net/api/com.amazonaws.services.sqs.model.receivemessagerequest

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值