博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AutoScaling 与函数计算结合,赋予更丰富的弹性能力
阅读量:6250 次
发布时间:2019-06-22

本文共 19668 字,大约阅读时间需要 65 分钟。

目前,弹性伸缩服务已经接入了负载均衡(SLB)、云数据库RDS 等云产品,但是暂未接入 ,有时候我们可能会需要弹性伸缩服务在扩缩容的时候自动将扩缩容涉及到的 ECS 实例私网 IP 添加到 Redis 白名单或者从 Redis 白名单中移除。本文将给出上述场景的最佳实践,向您介绍如何通过 AutoSclaing -> LifecycleHook -> MNS -> FC 的方式实现伸缩组发生扩容时自动将扩容出来的 ECS 实例私网 IP 添加到 Redis 白名单中,您可以在此基础上,根据您的业务需求进行扩展。

函数计算(FC)简介

阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传。函数计算会为您准备好计算资源,以弹性、可靠的方式运行您的代码,并提供日志查询、性能监控、报警等功能。借助于函数计算,您可以快速构建任何类型的应用和服务,无需管理和运维。而且,您只需要为代码实际运行所消耗的资源付费,代码未运行则不产生费用。更多关于函数计算的相关信息,您可以通过 进行了解。

消息服务(MNS)简介

阿里云消息服务(Message Service,简称 MNS)是一种高效、可靠、安全、便捷、可弹性扩展的分布式消息服务。MNS能够帮助应用开发者在他们应用的分布式组件上自由的传递数据、通知消息,构建松耦合系统。更多关于消息服务的相关信息,您可以通过 进行了解。

最佳实践

前提条件

在进行以下操作前,您需要先开通 、 、,接下来配置我们需要用的 FC、MNS、AutoScaling 相关信息

配置 MNS

登录 ,创建 MNS 主题(作为函数计算的触发器),如下图所示:

image

同样的,创建 MNS 队列,MNS 队列作为函数计算执行结果接收器,队列名称会在代码中进行配置。

配置 FC

登录,新建服务,如下图所示:

image

服务创建好以后,新增函数,如下图所示:

image

点击新增函数,弹出新建函数对话框,如下图所示:

image

选择函数语言,并选择空白模板,跳转到触发器配置界面,如下图所示:

image

配置好触发器类型、触发器名称以及对应的 MNS 主题(MNS 主题与 FC 所属的地域最好相同),点击下一步,跳转到基础管理配置界面,如下图所示:

image

image

所在服务默认会选择当前服务,不用改变,填写函数名称,选择运行环境,通过代码包上传的方式上传提前测试好的 java jar包(即触发函数计算时需要执行的运行的程序,本文最后会给出示例jar包),按照说明填写好函数入口,点击下一步,跳转到模版授权管理界面,如下图所示:

image

image

首先授予函数运行所需要的权限,授权时候应遵循权限最小化原则,防止权限过大,如上图步骤1、2所示,再授予 MNS 触发 FC 所需的权限,如上图步骤3、4所示,最后点击下一步,跳转到信息核对界面,如下图所示:

image

核对信息无误,点击创建,函数创建完成。

关于函数计算的配置过程,您可以通过 进行了解。

创建云数据库 Redis

登录 ,选择和 MNS 、FC 相同的地域,创建 Redis 实例。实例创建完以后,查看实例的白名单设置,如下图所示:

image

配置 AutoScaling

登录 ,创建好伸缩组以及伸缩配置以后,创建生命周期挂钩(LifecycleHook),如下图所示:

image

上图中,在左侧导航栏选择生命周期挂钩,点击创建生命周期挂钩按钮,填写名称,选择生命周期挂钩对应的伸缩活动类型,配置生命周期挂钩对应的 MNS 通知为 MNS 主题,并且选择的主题为 FC 触发器对应的主题,最后点击创建按钮,生命周期挂钩函数创建完成,如下图所示:

image

在伸缩组发生扩容伸缩活动时,实例创建完成并运行起来以后,生命周期挂钩会被触发,并发送伸缩活动相关信息到生命周期挂钩配置的 MNS 主题上,挂起当前的伸缩活动,直到生命周期挂钩超时或者被提前结束。生命周期挂钩活动结束以后,伸缩活动继续执行,扩容出来的 ECS 实例会被挂载到负载均衡实例上(如果伸缩组配置了负载均衡实例的话)。关于生命周期挂钩功能的详细说明,您可以通过云栖博客 进行详细了解。

触发扩容伸缩活动

首先,我们通过触发扩容伸缩活动的方式,创建 10 台 ECS 实例,对应的伸缩活动如下图所示:

image

然后我们登录 ,查看队列接收到的 FC 执行结果消息,如下图所示:

image

上述消息中 success 为 true,表示函数计算执行成功(即 ECS 实例私网 IP 添加到 Redis 白名单成功),消息体中还包括了当前生命周期挂钩活动对应的 LifecycleHookId LifecycleActionToken 参数信息,您可以根据相关参数信息调用 接口提前结束生命周期活动。

最后,我们登录 ,查看当前的 Redis 白名单信息,如下图所示:

image

从上图可以看出,弹性伸缩扩容活动创建出来的 ECS 实例私网 IP 成功添加到 Redis 白名单中。

至此,通过 AutoScaling -> LifecycleHook -> MNS -> FC 实现 Redis 白名单自动添加的过程结束,整体过程如下:

  1. 弹性伸缩组触发扩容伸缩活动,扩容 ECS 实例,扩容活动触发生命周期挂钩
  2. 生命周期挂钩将扩容活动挂起,同时发送消息到 MNS 主题
  3. MNS 主题接收到消息以后将消息作为输入信息触发 FC,FC 被触发以后执行预置业的 JAVA 函数
  4. JAVA 函数获取 FC 触发器的输入信息,信息中包括了本次伸缩活动对应的 ECS 实例 ID信息,通过接口获取 ECS 实例私网 IP 以后添加到 Redis default 分组白名单中
  5. 最后,函数执行结果发送到代码中配置好的 MNS 队列中

上述过程仅作为一个参考的 Demo,进一步实现自动化管理,还需要我们自己编程实现,如编程的方式消费 MNS 队列中的消息,获取执行结果与 LifecycleHookId LifecycleActionToken等参数信息提前结束生命周期挂钩活动等。

FC 预置 JAVA 代码解析

FC 预置函数为 JAVA 代码,通过 Maven 管理,对应的代码及依赖如下:

Example.java

package fc;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.TypeReference;import com.aliyun.fc.runtime.Context;import com.aliyun.fc.runtime.StreamRequestHandler;import com.aliyun.mns.client.CloudAccount;import com.aliyun.mns.client.CloudQueue;import com.aliyun.mns.client.MNSClient;import com.aliyun.mns.model.Message;import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.ecs.model.v20140526.DescribeInstancesRequest;import com.aliyuncs.ecs.model.v20140526.DescribeInstancesResponse;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.profile.DefaultProfile;import com.aliyuncs.profile.IClientProfile;import com.aliyuncs.r_kvstore.model.v20150101.DescribeSecurityIpsRequest;import com.aliyuncs.r_kvstore.model.v20150101.DescribeSecurityIpsResponse;import com.aliyuncs.r_kvstore.model.v20150101.ModifySecurityIpsRequest;import model.FCResult;import model.HookModel;import model.MnsMessageModel;import org.apache.commons.codec.binary.Base64;import org.apache.commons.lang.StringUtils;import org.springframework.util.CollectionUtils;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.util.ArrayList;import java.util.Arrays;import java.util.List;public class Example implements StreamRequestHandler {    /**     * 专有网络类型,此参数不用变     */    private static final String  VPC_NETWORK                 = "vpc";    private static final String  CHAR_SET                    = "UTF-8";    /**     * 接收input数组大小,4096通常够用     */    private static final Integer MAX_BYTE_LENGTH             = 4096;    /**     * REDIS 白名单默认分组     */    private static final String  DEFAULT_SECURITY_GROUP_NAME = "default";    /**     * REDIS 修改白名单的模式     */    private static final String  MODIFY_MODE_APPEND          = "Append";    /**     * MNS 客户端发送消息地址     */    private static final String  MNS_END_POINT               = "http://%s.mns.%s.aliyuncs.com/";    /**     * 待添加的REDIS实例ID,根据个人情况替换     */    private static final String  REDIS_ID                    = "";    /**     * 接收本次函数计算执行结果的队列名称,根据个人情况替换     */    private static final String  QUEUE_NAME                  = "wujin-fc-callback";    /**     * 阿里云账号UID,根据跟人情况替换     */    private static final Long    USER_ID                     = 1111111111111111111L;    /**     * 伸缩组 MNS FC 所属的region,根据个人情况替换     */    private static final String  REGION_ID                   = "cn-hangzhou";    @Override    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) {        FCResult result = new FCResult();        String akId = context.getExecutionCredentials().getAccessKeyId();        String akSecret = context.getExecutionCredentials().getAccessKeySecret();        String securityToken = context.getExecutionCredentials().getSecurityToken();        try {            //获取MNS触发函数计算时输入的内容            String input = readInput(inputStream);            MnsMessageModel mnsMessageModel = JSON.parseObject(input,                    new TypeReference
() { }); if (mnsMessageModel == null) { result.setSuccess(false); result.setMessage("mnsMessageModel is null"); sendMns(akId, akSecret, securityToken, result.toString()); return; } HookModel contentModel = mnsMessageModel.getContent(); if (contentModel == null) { result.setSuccess(false); result.setMessage("contentModel is null"); sendMns(akId, akSecret, securityToken, result.toString()); return; } IAcsClient client = buildClient(akId, akSecret, securityToken); //获取本次伸缩活动对应实例的私网IP List
privateIps = getInstancesPrivateIps(contentModel.getInstanceIds(), client); if (CollectionUtils.isEmpty(privateIps)) { result.setSuccess(false); result.setMessage("privateIps is empty"); sendMns(akId, akSecret, securityToken, result.toString()); return; } List
needAppendIps = filterPrivateIpsForAppend(privateIps, client); if (!CollectionUtils.isEmpty(needAppendIps)) { modifySecurityIps(client, needAppendIps); result.setLifecycleHookId(contentModel.getLifecycleHookId()); result.setLifecycleActionToken(contentModel.getLifecycleActionToken()); sendMns(akId, akSecret, securityToken, result.toString()); } } catch (Exception ex) { result.setSuccess(false); result.setMessage(ex.getMessage()); sendMns(akId, akSecret, securityToken, result.toString()); } } /** * 构建请求 ECS Redis 接口客户端 * * @param akId * @param akSecret * @param securityToken * @return */ private IAcsClient buildClient(String akId, String akSecret, String securityToken) { IClientProfile clientProfile = DefaultProfile.getProfile(REGION_ID, akId, akSecret, securityToken); return new DefaultAcsClient(clientProfile); } /** * 将执行结果发送消息到MNS * * @param ak * @param aks * @param securityToken * @param msg */ private void sendMns(String ak, String aks, String securityToken, String msg) { MNSClient client = null; try { CloudAccount account = new CloudAccount(ak, aks, String.format(MNS_END_POINT, USER_ID, REGION_ID), securityToken); client = account.getMNSClient(); CloudQueue queue = client.getQueueRef(QUEUE_NAME); Message message = new Message(); message.setMessageBody(msg); queue.putMessage(message); } finally { if (client != null) { client.close(); } } } /** * 过滤出需要添加到redis的私网IP * * @param privateIps 过滤以前的私网IP * @param client * @return * @throws ClientException */ private List
filterPrivateIpsForAppend(List
privateIps, IAcsClient client) throws ClientException { List
needAppendIps = new ArrayList<>(); if (CollectionUtils.isEmpty(privateIps)) { return needAppendIps; } DescribeSecurityIpsRequest request = new DescribeSecurityIpsRequest(); request.setInstanceId(REDIS_ID); DescribeSecurityIpsResponse response = client.getAcsResponse(request); List
securityIpGroups = response .getSecurityIpGroups(); if (CollectionUtils.isEmpty(securityIpGroups)) { return privateIps; } for (DescribeSecurityIpsResponse.SecurityIpGroup securityIpGroup : securityIpGroups) { if (!securityIpGroup.getSecurityIpGroupName().equals(DEFAULT_SECURITY_GROUP_NAME)) { continue; } String securityIps = securityIpGroup.getSecurityIpList(); if (securityIps == null) { continue; } String[] securityIpList = securityIps.split(","); List
existIps = Arrays.asList(securityIpList); if (CollectionUtils.isEmpty(existIps)) { continue; } for (String ip : privateIps) { if (!existIps.contains(ip)) { needAppendIps.add(ip); } } } return privateIps; } /** * 修改REDIS实例DEFAULT分组私网IP白名单 * * @param client * @param needAppendIps * @throws ClientException */ private void modifySecurityIps(IAcsClient client, List
needAppendIps) throws ClientException { if (CollectionUtils.isEmpty(needAppendIps)) { return; } ModifySecurityIpsRequest request = new ModifySecurityIpsRequest(); request.setInstanceId(REDIS_ID); String ip = StringUtils.join(needAppendIps.toArray(), ","); request.setSecurityIps(ip); request.setSecurityIpGroupName(DEFAULT_SECURITY_GROUP_NAME); request.setModifyMode(MODIFY_MODE_APPEND); client.getAcsResponse(request); } /** * 获取输入,并base64解码 * * @param inputStream * @return * @throws IOException */ private String readInput(InputStream inputStream) throws IOException { try { byte[] bytes = new byte[MAX_BYTE_LENGTH]; int tmp; int len = 0; //循环读取所有内容 while ((tmp = inputStream.read()) != -1 && len < MAX_BYTE_LENGTH) { bytes[len] = (byte) tmp; len++; } inputStream.close(); byte[] act = new byte[len]; System.arraycopy(bytes, 0, act, 0, len); return new String(Base64.decodeBase64(act), CHAR_SET); } finally { inputStream.close(); } } /** * 获取实例列表对应的私网IP,并限制每次请求实例数量不超过100 * * @param instanceIds 实例列表 * @param client 请求客户端 * @return * @throws Exception */ public List
getInstancesPrivateIps(List
instanceIds, IAcsClient client) throws Exception { List
privateIps = new ArrayList<>(); if (CollectionUtils.isEmpty(instanceIds)) { return privateIps; } int size = instanceIds.size(); int queryNumberPerTime = 100; int batchCount = (int) Math.ceil((float) size / (float) queryNumberPerTime); //support 100 instance for (int i = 1; i <= batchCount; i++) { int fromIndex = queryNumberPerTime * (i - 1); int toIndex = Math.min(queryNumberPerTime * i, size); List
subList = instanceIds.subList(fromIndex, toIndex); DescribeInstancesRequest request = new DescribeInstancesRequest(); request.setInstanceIds(JSON.toJSONString(subList)); DescribeInstancesResponse response = client.getAcsResponse(request); List
instances = response.getInstances(); if (CollectionUtils.isEmpty(instances)) { continue; } for (DescribeInstancesResponse.Instance instance : instances) { String privateIp = getPrivateIp(instance); if (privateIp != null) { privateIps.add(privateIp); } } } return privateIps; } /** * 从 DescribeInstancesResponse.Instance 中解析出私网 IP * * @param instance DescribeInstancesResponse.Instance */ private String getPrivateIp(DescribeInstancesResponse.Instance instance) { String privateIp = null; if (VPC_NETWORK.equalsIgnoreCase(instance.getInstanceNetworkType())) { DescribeInstancesResponse.Instance.VpcAttributes vpcAttributes = instance .getVpcAttributes(); if (vpcAttributes != null) { List
privateIpAddress = vpcAttributes.getPrivateIpAddress(); if (!CollectionUtils.isEmpty(privateIpAddress)) { privateIp = privateIpAddress.get(0); } } } else { List
innerIpAddress = instance.getInnerIpAddress(); if (!CollectionUtils.isEmpty(innerIpAddress)) { privateIp = innerIpAddress.get(0); } } return privateIp; }}

代码中涉及到的 Model 文件

FCResult.java

package model;import com.alibaba.fastjson.JSON;public class FCResult {  private boolean success = true;  private String  lifecycleHookId;  private String  lifecycleActionToken;  private String  message;  public boolean isSuccess() {      return success;  }  public void setSuccess(boolean success) {      this.success = success;  }  public String getLifecycleHookId() {      return lifecycleHookId;  }  public void setLifecycleHookId(String lifecycleHookId) {      this.lifecycleHookId = lifecycleHookId;  }  public String getLifecycleActionToken() {      return lifecycleActionToken;  }  public void setLifecycleActionToken(String lifecycleActionToken) {      this.lifecycleActionToken = lifecycleActionToken;  }  public String getMessage() {      return message;  }  public void setMessage(String message) {      this.message = message;  }  @Override  public String toString() {      return JSON.toJSONString(this);  }}

HookModel.java

package model;import java.util.List;public class HookModel {    private String            lifecycleHookId;    private String            lifecycleActionToken;    private String            lifecycleHookName;    private String            scalingGroupId;    private String            scalingGroupName;    private String            lifecycleTransition;    private String            defaultResult;    private String            requestId;    private String            scalingActivityId;    private List
instanceIds; public String getLifecycleHookId() { return lifecycleHookId; } public void setLifecycleHookId(String lifecycleHookId) { this.lifecycleHookId = lifecycleHookId; } public String getLifecycleActionToken() { return lifecycleActionToken; } public void setLifecycleActionToken(String lifecycleActionToken) { this.lifecycleActionToken = lifecycleActionToken; } public String getLifecycleHookName() { return lifecycleHookName; } public void setLifecycleHookName(String lifecycleHookName) { this.lifecycleHookName = lifecycleHookName; } public String getScalingGroupId() { return scalingGroupId; } public void setScalingGroupId(String scalingGroupId) { this.scalingGroupId = scalingGroupId; } public String getScalingGroupName() { return scalingGroupName; } public void setScalingGroupName(String scalingGroupName) { this.scalingGroupName = scalingGroupName; } public String getLifecycleTransition() { return lifecycleTransition; } public void setLifecycleTransition(String lifecycleTransition) { this.lifecycleTransition = lifecycleTransition; } public String getDefaultResult() { return defaultResult; } public void setDefaultResult(String defaultResult) { this.defaultResult = defaultResult; } public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } public String getScalingActivityId() { return scalingActivityId; } public void setScalingActivityId(String scalingActivityId) { this.scalingActivityId = scalingActivityId; } public List
getInstanceIds() { return instanceIds; } public void setInstanceIds(List
instanceIds) { this.instanceIds = instanceIds; }}

MnsMessageModel.java

package model;public class MnsMessageModel {    private String    userId;    private String    regionId;    private String    resourceArn;    private HookModel content;    public String getUserId() {        return userId;    }    public void setUserId(String userId) {        this.userId = userId;    }    public String getRegionId() {        return regionId;    }    public void setRegionId(String regionId) {        this.regionId = regionId;    }    public String getResourceArn() {        return resourceArn;    }    public void setResourceArn(String resourceArn) {        this.resourceArn = resourceArn;    }    public HookModel getContent() {        return content;    }    public void setContent(HookModel content) {        this.content = content;    }}

Maven 依赖

4.0.0
com.aliyun.fc.wujin
demo
1.0-SNAPSHOT
com.aliyun
aliyun-java-sdk-ecs
4.10.1
com.aliyun.fc.runtime
fc-java-core
1.0.0
com.aliyun
aliyun-java-sdk-core
3.2.6
com.aliyun
aliyun-java-sdk-r-kvstore
2.0.3
com.alibaba
fastjson
1.2.25
org.springframework
spring-context
4.2.5.RELEASE
org.apache.httpcomponents
httpclient
4.5.2
org.apache.commons
com.springsource.org.apache.commons.lang
2.6.0
com.aliyun.mns
aliyun-sdk-mns
1.1.8.4
maven-assembly-plugin
3.1.0
jar-with-dependencies
false
make-assembly
package
single
org.apache.maven.plugins
maven-compiler-plugin
1.8
1.8

上述java文件中,Example.java 文件在包名为 fc 的目录下,FCResult.java HookModel.java MnsMessageModel.java 三个文件在包名为 model 的目录下,package fc 与 package model 处于同级目录。

Example.java 文件需要根据实际情况对相关参数进行替换,QUEUE_NAME 参数定义了接收函数执行结果的 MNS 队列,我们在 章节已经提前创建好了。
参数替换完成以后,可以参考 重新打包并上传您的 jar 包即可,上传方法如下图所示:
image

写在最后

通过 AutoScaling -> LifecycleHook -> MNS -> FC 的方式,您可以具备更加丰富的弹性能力,从而更加灵活地管理您伸缩组内的资源。

上述代码仅供参考,具体实现需要结合具体业务进行测试改造。

转载地址:http://mcgia.baihongyu.com/

你可能感兴趣的文章
区块链凉了?我们来看一些真问题
查看>>
美国Gazelle秒杀国内二手交易网站,是如何赢的?
查看>>
敲黑板!原子变量与内存模型是什么鬼!
查看>>
Adobe将机器学习融入邮件营销平台,满足客户个性化需求
查看>>
Waymo称已拿到关键性证据,案件审讯或推迟
查看>>
比特人生|信仰下的欲望、疯狂与迷惘
查看>>
从CES 2017看今年智能汽车发展趋势之一:车联网有望率先实现
查看>>
VR将用于“换头术”,VR会是医疗保健的未来吗?
查看>>
中小企业跨境贸易报告:新外贸有这四大特征
查看>>
PostgreSQL远程连接配置管理/账号密码分配(解决:致命错误: 用户 "postgres" Ident 认证失败)...
查看>>
codeforces B. Pasha and String(贪心)
查看>>
小议西安软件外包产业联盟
查看>>
无人驾驶如果投入市场,可能最先出现在日本
查看>>
n个结点,不同形态的二叉树(数目+生成)
查看>>
SpringMVC解析2-ContextLoaderListener
查看>>
Hadoop 调试第一个mapreduce程序过程详细记录总结
查看>>
任务调度-java普通工程通过Timer实现
查看>>
<img>标签的src=""空字符会出现的情况
查看>>
poj2513Colored Sticks(无向图的欧拉回路)
查看>>
两家ADAS路测大战,谁将成为最终的“汽车之眼”?
查看>>