AutoScaling 與函數計算結合,賦予更豐富的彈性能力

目前,彈性伸縮服務已經接入了負載均衡(SLB)、云數據庫RDS 等云產品,但是暫未接入 云數據庫Redis,有時候我們可能會需要彈性伸縮服務在擴縮容的時候自動將擴縮容涉及到的 ECS 實例私網 IP 添加到 Redis 白名單或者從 Redis 白名單中移除。本文將給出上述場景的最佳實踐,向您介紹如何通過 AutoSclaing -> LifecycleHook -> MNS -> FC 的方式實現伸縮組發生擴容時自動將擴容出來的 ECS 實例私網 IP 添加到 Redis 白名單中,您可以在此基礎上,根據您的業務需求進行擴展。

函數計算(FC)簡介

阿里云函數計算是事件驅動的全托管計算服務。通過函數計算,您無需管理服務器等基礎設施,只需編寫代碼并上傳。函數計算會為您準備好計算資源,以彈性、可靠的方式運行您的代碼,并提供日志查詢、性能監控、報警等功能。借助于函數計算,您可以快速構建任何類型的應用和服務,無需管理和運維。而且,您只需要為代碼實際運行所消耗的資源付費,代碼未運行則不產生費用。更多關于函數計算的相關信息,您可以通過 函數計算官方文檔 進行了解。

消息服務(MNS)簡介

阿里云消息服務(Message Service,簡稱 MNS)是一種高效、可靠、安全、便捷、可彈性擴展的分布式消息服務。MNS能夠幫助應用開發者在他們應用的分布式組件上自由的傳遞數據、通知消息,構建松耦合系統。更多關于消息服務的相關信息,您可以通過 消息服務官方文檔 進行了解。

最佳實踐

前提條件

在進行以下操作前,您需要先開通 函數計算服務FC 、 消息服務MNS 、彈性伸縮服務AutoScaling,接下來配置我們需要用的 FC、MNS、AutoScaling 相關信息

配置 MNS

登錄 MNS控制臺,創建 MNS 主題(作為函數計算的觸發器),如下圖所示:

image

同樣的,創建 MNS 隊列,MNS 隊列作為函數計算執行結果接收器,隊列名稱會在代碼中進行配置。

配置 FC

登錄FC控制臺,新建服務,如下圖所示:

image

服務創建好以后,新增函數,如下圖所示:

image

點擊新增函數,彈出新建函數對話框,如下圖所示:

image

選擇函數語言,并選擇空白模板,跳轉到觸發器配置界面,如下圖所示:

image

配置好觸發器類型、觸發器名稱以及對應的 MNS 主題(MNS 主題與 FC 所屬的地域最好相同),點擊下一步,跳轉到基礎管理配置界面,如下圖所示:

image
image

所在服務默認會選擇當前服務,不用改變,填寫函數名稱,選擇運行環境,通過代碼包上傳的方式上傳提前測試好的 java jar包(即觸發函數計算時需要執行的運行的程序,本文最后會給出示例jar包),按照說明填寫好函數入口,點擊下一步,跳轉到模版授權管理界面,如下圖所示:

image
image

首先授予函數運行所需要的權限,授權時候應遵循權限最小化原則,防止權限過大,如上圖步驟1、2所示,再授予 MNS 觸發 FC 所需的權限,如上圖步驟3、4所示,最后點擊下一步,跳轉到信息核對界面,如下圖所示:

image

核對信息無誤,點擊創建,函數創建完成。

關于函數計算的配置過程,您可以通過 FC Hello World示例 進行了解。

創建云數據庫 Redis

登錄 Redis控制臺,選擇和 MNS 、FC 相同的地域,創建 Redis 實例。實例創建完以后,查看實例的白名單設置,如下圖所示:
image

配置 AutoScaling

登錄 彈性伸縮控制臺,創建好伸縮組以及伸縮配置以后,創建生命周期掛鉤(LifecycleHook),如下圖所示:

image

上圖中,在左側導航欄選擇生命周期掛鉤,點擊創建生命周期掛鉤按鈕,填寫名稱,選擇生命周期掛鉤對應的伸縮活動類型,配置生命周期掛鉤對應的 MNS 通知為 MNS 主題,并且選擇的主題為 FC 觸發器對應的主題,最后點擊創建按鈕,生命周期掛鉤函數創建完成,如下圖所示:

image

在伸縮組發生擴容伸縮活動時,實例創建完成并運行起來以后,生命周期掛鉤會被觸發,并發送伸縮活動相關信息到生命周期掛鉤配置的 MNS 主題上,掛起當前的伸縮活動,直到生命周期掛鉤超時或者被提前結束。生命周期掛鉤活動結束以后,伸縮活動繼續執行,擴容出來的 ECS 實例會被掛載到負載均衡實例上(如果伸縮組配置了負載均衡實例的話)。關于生命周期掛鉤功能的詳細說明,您可以通過云棲博客 AutoScaling 生命周期掛鉤功能 進行詳細了解。

觸發擴容伸縮活動

首先,我們通過觸發擴容伸縮活動的方式,創建 10 臺 ECS 實例,對應的伸縮活動如下圖所示:

image

然后我們登錄 MNS控制臺,查看隊列接收到的 FC 執行結果消息,如下圖所示:

image

上述消息中 success 為 true,表示函數計算執行成功(即 ECS 實例私網 IP 添加到 Redis 白名單成功),消息體中還包括了當前生命周期掛鉤活動對應的 LifecycleHookId LifecycleActionToken 參數信息,您可以根據相關參數信息調用 CompleteLifecycleAction 接口提前結束生命周期活動。

最后,我們登錄 云數據庫Redis控制臺,查看當前的 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";@Overridepublic 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<MnsMessageModel>() {});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);//獲取本次伸縮活動對應實例的私網IPList<String> 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<String> 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<String> filterPrivateIpsForAppend(List<String> privateIps, IAcsClient client)throws ClientException {List<String> needAppendIps = new ArrayList<>();if (CollectionUtils.isEmpty(privateIps)) {return needAppendIps;}DescribeSecurityIpsRequest request = new DescribeSecurityIpsRequest();request.setInstanceId(REDIS_ID);DescribeSecurityIpsResponse response = client.getAcsResponse(request);List<DescribeSecurityIpsResponse.SecurityIpGroup> 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<String> 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<String> 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<String> getInstancesPrivateIps(List<String> instanceIds, IAcsClient client)throws Exception {List<String> 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 instancefor (int i = 1; i <= batchCount; i++) {int fromIndex = queryNumberPerTime * (i - 1);int toIndex = Math.min(queryNumberPerTime * i, size);List<String> subList = instanceIds.subList(fromIndex, toIndex);DescribeInstancesRequest request = new DescribeInstancesRequest();request.setInstanceIds(JSON.toJSONString(subList));DescribeInstancesResponse response = client.getAcsResponse(request);List<DescribeInstancesResponse.Instance> 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<String> privateIpAddress = vpcAttributes.getPrivateIpAddress();if (!CollectionUtils.isEmpty(privateIpAddress)) {privateIp = privateIpAddress.get(0);}}} else {List<String> 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;}@Overridepublic 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<String>      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<String> getInstanceIds() {return instanceIds;}public void setInstanceIds(List<String> 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 依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.aliyun.fc.wujin</groupId><artifactId>demo</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-ecs</artifactId><version>4.10.1</version></dependency><dependency><groupId>com.aliyun.fc.runtime</groupId><artifactId>fc-java-core</artifactId><version>1.0.0</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>3.2.6</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-r-kvstore</artifactId><version>2.0.3</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.25</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.2.5.RELEASE</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.2</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>com.springsource.org.apache.commons.lang</artifactId><version>2.6.0</version></dependency><dependency><groupId>com.aliyun.mns</groupId><artifactId>aliyun-sdk-mns</artifactId><version>1.1.8.4</version></dependency></dependencies><build><plugins><plugin><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><appendAssemblyId>false</appendAssemblyId> <!-- this is used for not append id to the jar name --></configuration><executions><execution><id>make-assembly</id> <!-- this is used for inheritance merges --><phase>package</phase> <!-- bind to the packaging phase --><goals><goal>single</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build></project>

上述java文件中,Example.java 文件在包名為 fc 的目錄下,FCResult.java HookModel.java MnsMessageModel.java 三個文件在包名為 model 的目錄下,package fc 與 package model 處于同級目錄。
Example.java 文件需要根據實際情況對相關參數進行替換,QUEUE_NAME 參數定義了接收函數執行結果的 MNS 隊列,我們在 配置 MNS 章節已經提前創建好了。
參數替換完成以后,可以參考 FC Java 編程說明 重新打包并上傳您的 jar 包即可,上傳方法如下圖所示:
image

寫在最后

通過 AutoScaling -> LifecycleHook -> MNS -> FC 的方式,您可以具備更加豐富的彈性能力,從而更加靈活地管理您伸縮組內的資源。

上述代碼僅供參考,具體實現需要結合具體業務進行測試改造。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/388751.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/388751.shtml
英文地址,請注明出處:http://en.pswp.cn/news/388751.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

參考文獻_參考

參考文獻Recently, I am attracted by the news that Tanzania has attained lower middle income status under the World Bank’s classification, five years ahead of projection. Being curious on how they make the judgement, I take a look of the World Bank’s offi…

java語言靜態分析工具_PMD 6.16.0 發布,跨語言靜態代碼自動分析工具

PMD 6.16.0 發布了。PMD 是一個代碼分析器&#xff0c;能夠幫助發現常見的編程問題&#xff0c;比如未使用的變量、空的 catch 塊、不必要的對象創建等等。最初僅支持 Java 代碼&#xff0c;目前還可支持 JavaScript、Salesforce.com Apex 和 Visualforce、PLSQL、Apache Veloc…

B1922 [Sdoi2010]大陸爭霸 最短路

我一直都不會dij的堆優化&#xff0c;今天搞了一下。。。就是先弄一個優先隊列&#xff0c;存每個點的數據&#xff0c;然后這個題就加了一點不一樣的東西&#xff0c;每次的最短路算兩次&#xff0c;一次是自己的最短路&#xff0c;另一次是機關的最短路&#xff0c;兩者取最大…

WPF中的鼠標事件詳解

WPF中的鼠標事件詳解 Uielement和ContentElement都定義了十個以Mouse開頭的事件&#xff0c;8個以PreviewMouse開頭的事件&#xff0c;MouseMove,PreviewMouseMove,MouseEnter,Mouseleave的事件處理器類型都是MouseEventHandler類型。這些事件都具備對應得MouseEventargs對象。…

數據統計 測試方法_統計測試:了解如何為數據選擇最佳測試!

數據統計 測試方法This post is not meant for seasoned statisticians. This is geared towards data scientists and machine learning (ML) learners & practitioners, who like me, do not come from a statistical background.?他的職位是不是意味著經驗豐富的統計人…

前端介紹-35

前端介紹-35 # 前端## 一、什么是前端 前端即網站前臺部分&#xff0c;運行在PC端&#xff0c;移動端等瀏覽器上展現給用戶瀏覽的網頁。隨著互聯網技術的發展&#xff0c;HTML5&#xff0c;CSS3&#xff0c;前端框架的應用&#xff0c;跨平臺響應式網頁設計能夠適應各種屏幕…

spring的幾個通知(前置、后置、環繞、異常、最終)

1、沒有異常的 2、有異常的 1、被代理類接口Person.java 1 package com.xiaostudy;2 3 /**4 * desc 被代理類接口5 * 6 * author xiaostudy7 *8 */9 public interface Person { 10 11 public void add(); 12 public void update(); 13 public void delete();…

每個Power BI開發人員的Power Query提示

If someone asks you to define the Power Query, what should you say? If you’ve ever worked with Power BI, there is no chance that you haven’t used Power Query, even if you weren’t aware of it. Therefore, one could easily say that Power Query is the “he…

c# PDF 轉換成圖片

1.新建項目 2.新增一個新文件夾“lib”&#xff08;主要是為了存放引用的dll&#xff09; 3.將“gsdll32.dll 、PDFLibNet.dll 、PDFView.dll”3個dll添加到文件夾中 4.項目添加“PDFLibNet.dll 、PDFView.dll”2個類庫的引用&#xff0c;并將gsdll32.dll 拷貝到項目生產根…

java finally在return_Java finally語句到底是在return之前還是之后執行?

點擊上方“方志朋”&#xff0c;選擇“置頂或者星標”你的關注意義重大&#xff01;網上有很多人探討Java中異常捕獲機制try...catch...finally塊中的finally語句是不是一定會被執行&#xff1f;很多人都說不是&#xff0c;當然他們的回答是正確的&#xff0c;經過我試驗&#…

oracle 死鎖

為什么80%的碼農都做不了架構師&#xff1f;>>> ORA-01013: user requested cancel of current operation 轉載于:https://my.oschina.net/8808/blog/2994537

面試題:二叉樹的深度

題目描述&#xff1a;輸入一棵二叉樹&#xff0c;求該樹的深度。從根結點到葉結點依次經過的結點&#xff08;含根、葉結點&#xff09;形成樹的一條路徑&#xff0c;最長路徑的長度為樹的深度。 思路&#xff1a;遞歸 //遞歸 public class Solution {public int TreeDepth(Tre…

a/b測試_如何進行A / B測試?

a/b測試The idea of A/B testing is to present different content to different variants (user groups), gather their reactions and user behaviour and use the results to build product or marketing strategies in the future.A / B測試的想法是將不同的內容呈現給不同…

hibernate h2變mysql_struts2-hibernate-mysql開發案例 -解道Jdon

Hibernate專題struts2-hibernate-mysql開發案例與源碼源碼下載本案例展示使用Struts2&#xff0c;Hibernate和MySQL數據庫開發一個個人音樂管理器Web應用程序。&#xff0c;可將您的音樂收藏添加到數據庫中。功能有&#xff1a;顯示一個添加記錄的表單和所有的音樂收藏的列表。…

P5024 保衛王國

傳送門 我現在還是不明白為什么NOIPd2t3會是一道動態dp…… 首先關于動態dp可以看這里 然后這里就是把把矩陣給改一改&#xff0c;改成這個形式\[\left[dp_{i-1,0},dp_{i-1,1}\right]\times \left[\begin{matrix}\infty&ldp_{i,1}\\ldp_{i,0}&ldp_{i,1}\end{matrix}\ri…

提取圖像感興趣區域_從圖像中提取感興趣區域

提取圖像感興趣區域Welcome to the second post in this series where we talk about extracting regions of interest (ROI) from images using OpenCV and Python.歡迎來到本系列的第二篇文章&#xff0c;我們討論使用OpenCV和Python從圖像中提取感興趣區域(ROI)。 As a rec…

解決java compiler level does not match the version of the installed java project facet

ava compiler level does not match the version of the installed java project facet錯誤的解決 因工作的關系&#xff0c;Eclipse開發的Java項目拷來拷去&#xff0c;有時候會報一個很奇怪的錯誤。明明源碼一模一樣&#xff0c;為什么項目復制到另一臺機器上&#xff0c;就會…

php模板如何使用,ThinkPHP如何使用模板

到目前為止&#xff0c;我們只是使用了控制器和模型&#xff0c;還沒有接觸視圖&#xff0c;下面來給上面的應用添加視圖模板。首先我們修改下 Action 的 index 操作方法&#xff0c;添加模板賦值和渲染模板操作。PHP代碼classIndexActionextendsAction{publicfunctionindex(){…

理解Windows窗體和WPF中的跨線程調用

你曾開發過Windows窗體程序&#xff0c;可能會注意到有時事件處理程序將拋出InvalidOperationException異常&#xff0c;信息為“ 跨線程調用非法&#xff1a;在非創建控件的線程上訪問該控件”。這種Windows窗體應用程序中 跨線程調用時的一個最為奇怪的行為就是&#xff0c;有…

什么是嵌入式系統

在我們的日常生活中&#xff0c;我們經常使用許多使用嵌入式系統技術設計的電氣和電子電路和套件。計算機&#xff0c;手機&#xff0c;平板&#xff0c;筆記本電腦&#xff0c;數字電子系統以及其他電子和電子設備都是使用嵌入式系統設計的。 什么是嵌入式系統&#xff1f;將硬…