EC2实例根据标签进行自动扩缩容

背景

大部分业务的QPS都会存在周期性特征,比如:

  • 秒杀在秒杀开始的几分钟流量比较高,
  • 办公系统只在白天上班时间才会有流量
  • 自建电商,在直播开始的时候,比较多人购买商品

在这些场景下,需要在适当的时候,对系统进行扩缩容。

需求分析

假设系统都部署在AWS的EC2实例上,常规的扩缩容做法是,在需要的时候,在EC2列表中,手工选择实例进行启动或停止操作,比较繁琐。

故期望提供一种基于EC2标签的方式,对EC2进行批量控制。

方案

image-20220816172016179

  • 为了方便配置,根据扩容时长不同,做了两个不同的调度方案,整体架构是一样的

    • 短时间扩容,然后缩容,如:每天8点到19点整点扩容3分钟,然后缩容。只需要配置一个EventBridge
    • 长时间扩容,如:每天8点扩容,19点缩容。需要配置两个EventBridge,一个启动,一个关闭
  • 管理员配置基于crontab时间触发的EventBridge

  • EventBridge触发对应StepFunction的调用

  • StepFunction处理标签识别,并对EC2进行控制

长时间时间扩容方案

image-20220816172131859

  • 定义StepFunction的能力,支持调用方指定是启动还是停止
  • 获取EC2实例列表,传入参数 MaxResults 配置为100,这里假设EC2实例数量不超过100
  • 调用 filter_ec2_by_tag 函数获取实例Id列表
  • 根据EventBridge传入的Cmd参数执行启动或者停止实例操作

EventBridge配置

  • 启动 Cron 表达式

    1
    0 8 * * ? *

    启动 入参格式

1
2
3
4
{
"Label": "WorkTimeOnly",
"Cmd": "start"
}
  • 停止 Cron 表达式

    1
    0 20 * * ? *

    启动 入参格式

1
2
3
4
{
"Label": "WorkTimeOnly",
"Cmd": "stop"
}

filter_ec2_by_tag 函数代码

1
2
3
4
5
6
7
8
9
10
11
12
13
exports.handler = async (event) => {
var result = []
for (var i in event['Instances']["Instances"]) {
var instance = event['Instances']["Instances"][i];
for (var j in instance.Tags) {
var tag = instance.Tags[j];
if(tag.Value==event["Label"]){
result.push(instance.InstanceId)
}
}
}
return {"Cmd":event["Cmd"],"InstanceIds":result};
};

指定命令扩缩容状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
{
"Comment": "A description of my state machine",
"StartAt": "获取EC2实例列表",
"States": {
"获取EC2实例列表": {
"Type": "Task",
"Next": "获取Label对应的实例列表",
"Parameters": {
"MaxResults": 1000
},
"Resource": "arn:aws-cn:states:::aws-sdk:ec2:describeInstances",
"ResultPath": "$.Instances",
"ResultSelector": {
"Instances.$": "$..Instances[*]"
}
},
"获取Label对应的实例列表": {
"Type": "Task",
"Resource": "arn:aws-cn:states:::lambda:invoke",
"Parameters": {
"Payload.$": "$",
"FunctionName": "arn:aws-cn:lambda:cn-north-1:510374286132:function:filter_ec2_by_tag:$LATEST"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"Next": "判断命令",
"OutputPath": "$.Event",
"ResultSelector": {
"Event.$": "$.Payload"
},
"ResultPath": "$"
},
"判断命令": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.Cmd",
"StringEquals": "start",
"Next": "启动实例"
},
{
"Variable": "$.Cmd",
"StringEquals": "stop",
"Next": "停止实例"
}
],
"Default": "Pass"
},
"启动实例": {
"Type": "Task",
"End": true,
"Parameters": {
"InstanceIds.$": "$.InstanceIds"
},
"Resource": "arn:aws-cn:states:::aws-sdk:ec2:startInstances"
},
"停止实例": {
"Type": "Task",
"Parameters": {
"InstanceIds.$": "$.InstanceIds"
},
"Resource": "arn:aws-cn:states:::aws-sdk:ec2:stopInstances",
"End": true
},
"Pass": {
"Type": "Pass",
"End": true
}
}
}

短时间扩容方案

stepfunctions_graph

  • 获取EC2实例列表,传入参数 MaxResults 配置为100,这里假设EC2实例数量不超过100

  • 为了方便传参到下一个步骤,加了两个Pass组件做参数转换

  • 调用 get_ec2_instanceIds_by_tag 函数获取实例Id列表

    • 为了方便支持多标签,这里只判断Value相对,就会被匹配到
  • 支持启动指定实例id的EC2,然后等待指定N秒后,再停止实例

  • 这里管理员只需要配置EventBridge

EventBridge配置

  • Cron 表达式

    59 7-19 * * ? *

    因流量是整点开始,这里提前一分钟开始启动实例

  • 入参格式

1
2
3
4
{
"Label": "only_3minue",
"Seconds": 120
}

get_ec2_instanceIds_by_tag 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
exports.handler = async (event) => {
var result = []
for (var i in event['Instances']) {
var instance = event['Instances'][i];
for (var j in instance.Tags) {
var tag = instance.Tags[j];
if(tag.Value==event["Label"]){
result.push(instance.InstanceId)
}
}
}
return result;
};

短时间扩缩容状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
{
"Comment": "A description of my state machine",
"StartAt": "获取EC2实例列表",
"States": {
"获取EC2实例列表": {
"Type": "Task",
"Next": "实例列表参数转换",
"Parameters": {
"MaxResults": 1000
},
"Resource": "arn:aws-cn:states:::aws-sdk:ec2:describeInstances",
"ResultPath": "$.Instances",
"ResultSelector": {
"Instances.$": "$..Instances[*]"
}
},
"实例列表参数转换": {
"Type": "Pass",
"Next": "获取Label对应的实例列表",
"ResultPath": "$",
"Parameters": {
"Label.$": "$.Label",
"Seconds.$": "$.Seconds",
"Instances.$": "$.Instances.Instances"
}
},
"获取Label对应的实例列表": {
"Type": "Task",
"Resource": "arn:aws-cn:states:::lambda:invoke",
"Parameters": {
"Payload.$": "$",
"FunctionName": "arn:aws-cn:lambda:cn-north-1:510374286132:function:get_ec2_instanceIds_by_tag:$LATEST"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"Next": "实例Id列表参数转换",
"ResultPath": "$.InstanceIds",
"ResultSelector": {
"InstanceIds.$": "$.Payload"
}
},
"实例Id列表参数转换": {
"Type": "Pass",
"Next": "启动实例",
"Parameters": {
"Seconds.$": "$.Seconds",
"InstanceIds.$": "$.InstanceIds.InstanceIds"
},
"ResultPath": "$"
},
"启动实例": {
"Type": "Task",
"Parameters": {
"InstanceIds.$": "$.InstanceIds"
},
"Resource": "arn:aws-cn:states:::aws-sdk:ec2:startInstances",
"Next": "等待运行一段时间",
"ResultPath": "$.StartResult"
},
"等待运行一段时间": {
"Type": "Wait",
"Next": "停止实例",
"SecondsPath": "$.Seconds"
},
"停止实例": {
"Type": "Task",
"Parameters": {
"InstanceIds.$": "$.InstanceIds"
},
"Resource": "arn:aws-cn:states:::aws-sdk:ec2:stopInstances",
"End": true
}
}
}

运维人员操作说明

  • 先预创建备份EC2实例
  • 给需要扩缩容的EC2实例配置指定的标签,如 ScaleLabel=xxx
  • 按前面的说明配置EventBridge的cron表达式及调用入参

架构说明

  • 可运维
    • AWS完善的日志监控告警,方便服务调试,故障排查
    • 可视化运维配置
    • 可视化执行逻辑
  • 高可用
    • 依赖于AWS的高可用服务,自定义部分不存在性能问题
  • 可扩展
    • 以StepFunctions的方式进行实现,支持可视化调整业务,比如程序启动之后,通过sns通知干系人等
    • ec2:describeInstances 组件,在文档中支持Filter参数,可直接一步到位过滤出指定标签的实例,但在调试的过程中提示不支持Filter参数,后续aws支持了,可把参数给加上
    • 为了少写写代码,这里使用了ec2:describeInstances 组件,目前是查出所有实例,在少量实例的情况下可以接受。如果实例比较多,该组件可以方便的替换成lambda函数,调用EC2对象,通过Filter参数直径过滤出可用的实例
    • 短时扩容服务状态机,可支持后续业务代码,根据实际需求进行简单的调用,应用场景非常广泛
  • 低成本
    • 全流程使用Serverless,因调用量低,可以认为无任何云服务成本开销
  • 安全
    • 全链路通过IAM进行授权调用
    • 不对我暴露任何接口
  • 高性能
    • 不在本方案的考虑访问内

OSI模型图解-翻译

原文 https://www.lifewire.com/layers-of-the-osi-model-illustrated-818017

开放系统互联模型(OSI)定义了一个网络框架,通过一层控制下一层这样分层的方式来实现各种网络协议。它现在主要是作为一种教学工具被人们所广泛应用。它从概念上将计算机网络架构按照逻辑归类划分成7层。

低层处理电流信号,字节序列,以及这些数据在网络上的路由。高层从用户的角度来描述网络的请求、响应,数据的展示,网络协议。

OSI模型最早是作为建设网络系统的标准架构提出的,现今许多流行的网络技术都参考了OSI的分层设计思想。

物理层

作为第一层,OSI的物理层负责通过网络通信媒介从发送设备向接收设备高效传输电子数据(比特位)。

物理层图例,展示中继器,以太网电缆和集线器以及令牌环网络

属于第一层的技术包括以太网电缆以及集线器。转发器及中继器作为线缆连接起也是作用在物理层的标准网络设备。

在物理层,数据通过物理媒介支持的信号类型进行传输:电压、无线电频率或红外或普通光脉冲

数据链路层

数据链路层从物理层拿到数据,检测物理传输错误并将比特数据打包成数据帧。数据链路层还管理物理寻址方案,例如:以太网的MAC地址寻址,控制网络设备对物理介质的访问。

数据链路层图例:目标和源地址、媒体访问控制和帧页脚

数据链路层是OSI中最复杂的一层,因此,他经常被划分成更细的两个部分:媒介访问控制子层、逻辑链路控制子层。

网络层

网络层在数据链路层之上添加了路由的概念。当数据到达网络层时,每个帧中包含的源地址和目标地址都将被拆开检查,以确定数据是否已到达其最终目的地。如果数据已到达最终目的地,第3层将数据格式化为发送到传输层的数据包。否则,网络层更新目标地址并将帧向下推到较低层。

网络层图例

网络层维护逻辑地址以支持路由,例如维护接入该网络中的设备的IP地址。网络层还管理不同逻辑地址和物理地址之间的映射。在IPv4网络中,这种映射是通过地址解析协议(ARP)完成的;IPv6使用网络邻居发现协议(NDP)。

传输层

传输层通过网络连接传输数据。TCP(传输控制协议)和UDP(用户数据报协议)是第四层传输层网络协议最常见的示例。不同的传输协议可能支持一系列可选功能,包括错误恢复、流量控制和支持错误重传。

传输层图例

会话层

会话层管理启动和中断网络连接的事件序列和事件流。第5层,支持多种类型的连接,这些连接可以动态创建并在独立的网络上运行。

会话层图例,显示一系列公路立交桥

表示层

表示层的功能是OSI模型中最简单的。在第6层,它处理消息数据的语法处理,如格式转换和加密/解密,为其上的应用层提供支撑。

表示层的图例,看起来像电影放映机

应用层

应用层为最终用户应用程序提供网络服务。网络服务是处理用户数据的协议。例如,在web浏览器应用程序中,应用层协议HTTP打包了发送和接收网页内容所需的数据。该第七层向表示层提供并从中获取数据。

应用层图例,比如个人电脑上的一只猫的图像

记两次不同场景下遭遇的MySQL原数据锁

问题描述

环境:MySQL 5.7,一主两从,pt-online-schema-change改表

MDL 全称为 metadata lock,即元数据锁,一般也可称为字典锁。MDL 的主要作用是为了管理数据库对象的并发访问和确保元数据一致性。元数据锁适用对象包含:table、schema、procedures, functions, triggers, scheduled events、tablespaces

场景一

一年前,晚上11点多开始执行,6千万级别的两个表,相差几分钟开始进行字段变更。

看着主库变更完毕,就去睡觉了,但其中一个从库,执行到第二天上班还没完成。

上班的时候发现其中一个从库延迟了,直接将请求杀掉,从新执行变更,恢复正常。

之前从来没碰到过原数据锁的情况,一直比较信任pt-online-schema-change,表变更比较随意。两表同时变更,数据库压力大了,凌晨0点的时候,一堆查询走了从库,其中不乏一些慢查询,查show processlist,一堆Waiting for table metadata lock,出原数据锁,卡住了。

后来严格串行执行表变更,大表变更尽量挑低峰期执行,再也没碰到过原数据锁的问题。

阅读全文

几句话说明白十大排序算法原理

10大排序算法动态图

  • https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg
  • 冒泡排序

    • 学生乱排序
    • 老师从左到右一个个告诉学生:
      • 你往右,找到第一个比你高的人,停止,然后告诉他:
        • 你往右,找到第一个比你高的人,停止,然后告诉他:
          • 你往右,找到第一个比你高的人,停止,然后告诉他
            • ……
  • 插入排序

    • 类似于摸牌
    • 手上的牌是排好序的(0张和1张天然是排好序的)
    • 摸一张,就从左到右找到第一张比他大的,然后插入
    • 也可以从大往小比较
    • 这样等排摸完,就排好序了
    • 如果牌是正好按对的方向排好的,那几乎不用花时间
    • 如果是正好按反方向排好的,那每次都要花最多的时间去插入
  • 选择排序

    • 老师喜欢矮个的学生
    • 你们谁,最矮,过来
    • 你们谁,最矮,过来
    • 你们谁,最矮,过来
    • …..
  • 希尔排序法

    • 大家分成左右2队
    • 各自从1开始报数
    • 号码一样的,按上面从大到小比较的插入法排序下
    • 排好了是吧
    • 现在分成4队
    • 报数,同号的,按上面从大到小比较的插入法排序下
    • 现在分8队
    • …..
    • 每次队伍大概率是快排好了的
    • 所以插入效率会越来越高
  • 归并排序

    • 假设一个公司:
      • 有两个分公司
    • 每个分公司有两个大区经理
    • 每个大区经理管两个省份
    • 每个省份只在两个城市有团队
    • 每个团队有两个小组
    • 每个小组有两个导购
    • 现在想把公司的所有导购的业绩进行排行
    • 总共公司告诉分公司,你们先各自排好上报上来,然后他自己每次从两个排行里选绩效最好的就排好了
    • 分公司告诉大区经理,你们先各自排好上报上来,然后他自己每次从两个排行里选绩效最好的就排好了
    • ……
    • 市经理告诉小组长,你们先各自排好上报上来,然后他自己每次从两个排行里选绩效最好的就排好了
    • 小组长告诉导购,发现导购天然就是排好序的,每次从两个排行里选绩效最好的就排好了
    • 接着就是一路上报
  • 快速排序

    • 老师随便选了个同学,来比张XX高的去右边,比他矮的去左边
    • 然后两边的也随便选个同学,按一样的方式分下
    • 分到最后剩一个,自然是排好序的
  • 计数排序

    • 一个个看下价格
    • 好,最少21愿,最多29
    • 划分9个堆
    • 一个个往9个堆里丢
    • 从9个堆里,一个个拿出来,就排好多了
  • 桶排序

    • 预估下数据分布
    • 按数据访问划分多个桶
    • 将数据都丢到对应的同中
    • 每个桶单独进行插入排序
    • 按顺序把一个个桶里的数据取出来
    • 如果只有一个桶的话,退化为插入排序
    • 如果是N个桶的话,退化为计数排序
  • 基数排序

    • 补位到相同长度
    • 依次按最后一位把数据丢0-9这些桶里
    • 按顺序拿出来,再按倒数第二位把数据丢0-9这些桶里
    • 按顺序拿出来,再按倒数第三位把数据丢0-9这些桶里
    • …..
    • 知道最高位,再按倒数第三位把数据丢0-9这些桶里
    • 只有一位的话,退化为计数排序