package com.sohu.cache.stats.app.impl; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import com.sohu.cache.async.NamedThreadFactory; import com.sohu.cache.constant.AppAuditLogTypeEnum; import com.sohu.cache.constant.AppAuditType; import com.sohu.cache.constant.AppCheckEnum; import com.sohu.cache.constant.AppStatusEnum; import com.sohu.cache.constant.DataFormatCheckResult; import com.sohu.cache.constant.HorizontalResult; import com.sohu.cache.constant.InstanceStatusEnum; import com.sohu.cache.dao.AppAuditDao; import com.sohu.cache.dao.AppAuditLogDao; import com.sohu.cache.dao.AppDao; import com.sohu.cache.dao.InstanceDao; import com.sohu.cache.entity.AppAudit; import com.sohu.cache.entity.AppAuditLog; import com.sohu.cache.entity.AppDesc; import com.sohu.cache.entity.AppUser; import com.sohu.cache.entity.InstanceInfo; import com.sohu.cache.entity.InstanceSlotModel; import com.sohu.cache.entity.MachineInfo; import com.sohu.cache.machine.MachineCenter; import com.sohu.cache.redis.RedisCenter; import com.sohu.cache.redis.RedisClusterNode; import com.sohu.cache.redis.RedisClusterReshard; import com.sohu.cache.redis.RedisDeployCenter; import com.sohu.cache.redis.ReshardProcess; import com.sohu.cache.stats.app.AppDeployCenter; import com.sohu.cache.util.ConstUtils; import com.sohu.cache.util.TypeUtil; import com.sohu.cache.web.service.AppService; import com.sohu.cache.web.util.AppEmailUtil; import redis.clients.jedis.HostAndPort; /** * Created by yijunzhang on 14-10-20. */ public class AppDeployCenterImpl implements AppDeployCenter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private AppService appService; private RedisDeployCenter redisDeployCenter; private RedisCenter redisCenter; private AppEmailUtil appEmailUtil; private AppAuditDao appAuditDao; private MachineCenter machineCenter; private InstanceDao instanceDao; private AppAuditLogDao appAuditLogDao; private AppDao appDao; private ConcurrentMap<Long, ReshardProcess> processMap = new ConcurrentSkipListMap<Long, ReshardProcess>(); private ExecutorService processThreadPool = new ThreadPoolExecutor(0, 256, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), new NamedThreadFactory("redis-cluster-reshard", false)); @Override public boolean createApp(AppDesc appDesc, AppUser appUser, String memSize) { try { appService.save(appDesc); // 保存应用和用户的关系 appService.saveAppToUser(appDesc.getAppId(), appDesc.getUserId()); // 更新appKey long appId = appDesc.getAppId(); appService.updateAppKey(appId); // 保存应用审批信息 AppAudit appAudit = new AppAudit(); appAudit.setAppId(appId); appAudit.setUserId(appUser.getId()); appAudit.setUserName(appUser.getName()); appAudit.setModifyTime(new Date()); appAudit.setParam1(memSize); appAudit.setParam2(appDesc.getTypeDesc()); appAudit.setInfo("类型:" + appDesc.getTypeDesc() + ";初始申请空间:" + memSize); appAudit.setStatus(AppCheckEnum.APP_WATING_CHECK.value()); appAudit.setType(AppAuditType.APP_AUDIT.getValue()); appAuditDao.insertAppAudit(appAudit); // 发邮件 appEmailUtil.noticeAppResult(appDesc, appAudit); // 保存申请日志 AppAuditLog appAuditLog = AppAuditLog.generate(appDesc, appUser, appAudit.getId(), AppAuditLogTypeEnum.APP_DESC_APPLY); if (appAuditLog != null) { appAuditLogDao.save(appAuditLog); } return true; } catch (Exception e) { logger.error(e.getMessage(), e); return false; } } @Override public DataFormatCheckResult checkAppDeployDetail(Long appAuditId, String appDeployText) { if (appAuditId == null) { logger.error("appAuditId is null"); return DataFormatCheckResult.fail("审核id不能为空!"); } if (StringUtils.isBlank(appDeployText)) { logger.error("appDeployText is null"); return DataFormatCheckResult.fail("部署节点列表不能为空!"); } String[] nodeInfoList = appDeployText.split(ConstUtils.NEXT_LINE); if (nodeInfoList == null || nodeInfoList.length == 0) { logger.error("nodeInfoList is null"); return DataFormatCheckResult.fail("部署节点列表不能为空!"); } AppAudit appAudit = appAuditDao.getAppAudit(appAuditId); if (appAudit == null) { logger.error("appAudit:id={} is not exist", appAuditId); return DataFormatCheckResult.fail(String.format("审核id=%s不存在", appAuditId)); } long appId = appAudit.getAppId(); AppDesc appDesc = appService.getByAppId(appId); if (appDesc == null) { logger.error("appDesc:id={} is not exist"); return DataFormatCheckResult.fail(String.format("appId=%s不存在", appId)); } int type = appDesc.getType(); //检查每一行 for (String nodeInfo : nodeInfoList) { nodeInfo = StringUtils.trim(nodeInfo); if (StringUtils.isBlank(nodeInfo)) { return DataFormatCheckResult.fail(String.format("部署列表%s中存在空行", appDeployText)); } String[] array = nodeInfo.split(ConstUtils.COLON); if (array == null || array.length == 0) { return DataFormatCheckResult.fail(String.format("部署列表%s中存在空行", appDeployText)); } String masterHost = null; String memSize = null; String slaveHost = null; if (TypeUtil.isRedisCluster(type)) { if (array.length == 2) { masterHost = array[0]; memSize = array[1]; } else if (array.length == 3) { masterHost = array[0]; memSize = array[1]; slaveHost = array[2]; } else { return DataFormatCheckResult.fail(String.format("部署列表中%s, 格式错误!", nodeInfo)); } } else if (TypeUtil.isRedisSentinel(type)) { if (array.length == 3) { masterHost = array[0]; memSize = array[1]; slaveHost = array[2]; } else if (array.length == 1) { masterHost = array[0]; } else { return DataFormatCheckResult.fail(String.format("部署列表中%s, 格式错误!", nodeInfo)); } } else if (TypeUtil.isRedisStandalone(type)) { if (array.length == 2) { masterHost = array[0]; memSize = array[1]; } else { return DataFormatCheckResult.fail(String.format("部署列表中%s, 格式错误!", nodeInfo)); } } if (!checkHostExist(masterHost)) { return DataFormatCheckResult.fail(String.format("%s中的ip=%s不存在,请在机器管理中添加!", nodeInfo, masterHost)); } if (StringUtils.isNotBlank(memSize) && !NumberUtils.isDigits(memSize)) { return DataFormatCheckResult.fail(String.format("%s中的中的memSize=%s不是整数!", nodeInfo, memSize)); } if (StringUtils.isNotBlank(slaveHost) && !checkHostExist(slaveHost)) { return DataFormatCheckResult.fail(String.format("%s中的ip=%s不存在,请在机器管理中添加!", nodeInfo, slaveHost)); } } //检查sentinel类型:数据节点一行,sentinel节点多行 if (TypeUtil.isRedisSentinel(type)) { return checkSentinelAppDeploy(nodeInfoList); //检查单点类型:只能有一行数据节点 } else if (TypeUtil.isRedisStandalone(type)) { return checkStandaloneAppDeploy(nodeInfoList); } return DataFormatCheckResult.success("应用部署格式正确,可以开始部署了!"); } /** * 检查单点格式 * @param nodeInfoList * @return */ private DataFormatCheckResult checkStandaloneAppDeploy(String[] nodeInfoList) { int redisLineNum = 0; for (String nodeInfo : nodeInfoList) { nodeInfo = StringUtils.trim(nodeInfo); String[] array = nodeInfo.split(ConstUtils.COLON); if (array.length == 2) { redisLineNum++; } } // redis节点只有一行 if (redisLineNum != 1) { return DataFormatCheckResult.fail("应用部署格式错误, Standalone格式必须是一行masterIp:memSize(M)"); } return DataFormatCheckResult.success("应用部署格式正确,可以开始部署了!"); } /** * 检查redis sentinel格式 * @param nodeInfoList * @return */ private DataFormatCheckResult checkSentinelAppDeploy(String[] nodeInfoList) { int redisLineNum = 0; int sentinelLineNum = 0; for (String nodeInfo : nodeInfoList) { nodeInfo = StringUtils.trim(nodeInfo); String[] array = nodeInfo.split(ConstUtils.COLON); if (array.length == 3) { redisLineNum++; } else if (array.length == 1) { sentinelLineNum++; } } // redis节点只有redisLineMustNum行 final int redisLineMustNum = 1; if (redisLineNum < redisLineMustNum) { return DataFormatCheckResult.fail("应用部署格式错误, Sentinel应用中必须有Redis数据节点!"); } else if (redisLineNum > redisLineMustNum) { return DataFormatCheckResult.fail("应用部署格式错误, Sentinel应用中Redis数据节点只能有一行!"); } // sentinel节点至少有sentinelLessNum个 final int sentinelLessNum = 3; if (sentinelLineNum < sentinelLessNum) { return DataFormatCheckResult.fail("应用部署格式错误, Sentinel应用中Sentinel节点至少要有" + sentinelLessNum + "个!"); } return DataFormatCheckResult.success("应用部署格式正确,可以开始部署了!"); } /** * 查看host是否存在 * @param host * @return */ private boolean checkHostExist(String host) { try { MachineInfo machineInfo = machineCenter.getMachineInfoByIp(host); if (machineInfo == null) { return false; } if (machineInfo.isOffline()) { logger.warn("host {} is offline", host); return false; } } catch (Exception e) { logger.error(e.getMessage(), e); return false; } return true; } @Override public boolean allocateResourceApp(Long appAuditId, List<String> nodeInfoList, AppUser auditUser) { if (appAuditId == null || appAuditId <= 0L) { logger.error("appAuditId is null"); return false; } if (nodeInfoList == null || nodeInfoList.isEmpty()) { logger.error("nodeInfoList is null"); return false; } AppAudit appAudit = appAuditDao.getAppAudit(appAuditId); if (appAudit == null) { logger.error("appAudit:id={} is not exist", appAuditId); return false; } long appId = appAudit.getAppId(); AppDesc appDesc = appService.getByAppId(appId); if (appDesc == null) { logger.error("appDesc:id={} is not exist"); return false; } int type = appDesc.getType(); List<String[]> nodes = new ArrayList<String[]>(); for (String nodeInfo : nodeInfoList) { nodeInfo = StringUtils.trim(nodeInfo); if (StringUtils.isBlank(nodeInfo)) { continue; } String[] array = nodeInfo.split(":"); // if (array.length < 2) { // logger.error("error nodeInfo:{}", Arrays.toString(array)); // continue; // } nodes.add(array); } boolean isAudited = false; if (TypeUtil.isRedisType(type)) { if (TypeUtil.isRedisCluster(type)) { isAudited = deployCluster(appId, nodes); } else if (nodes.size() > 0) { if (TypeUtil.isRedisSentinel(type)) { isAudited = deploySentinel(appId, nodes); } else { isAudited = deployStandalone(appId, nodes.get(0)); } } else { logger.error("nodeInfoList={} is error"); } } else { logger.error("unknown type : {}", type); return false; } //审核通过 if (isAudited) { // 改变审核状态 appAuditDao.updateAppAudit(appAudit.getId(), AppCheckEnum.APP_ALLOCATE_RESOURCE.value()); } return true; } @Override public boolean offLineApp(Long appId) { Assert.isTrue(appId != null && appId > 0L); AppDesc appDesc = appService.getByAppId(appId); if (appDesc == null) { logger.error("appId={} not exist"); return false; } List<InstanceInfo> instanceInfos = instanceDao.getInstListByAppId(appId); int type = appDesc.getType(); if (instanceInfos != null) { for (InstanceInfo instanceInfo : instanceInfos) { final String ip = instanceInfo.getIp(); final int port = instanceInfo.getPort(); if (TypeUtil.isRedisType(type)) { //取消收集 redisCenter.unDeployRedisCollection(appId, ip, port); redisCenter.unDeployRedisSlowLogCollection(appId, ip, port); boolean isShutdown = redisCenter.shutdown(appId, ip, port); if (!isShutdown) { logger.error("{}:{} redis not shutdown!", ip, port); return false; } } //更新实例下线 instanceInfo.setStatus(InstanceStatusEnum.OFFLINE_STATUS.getStatus()); instanceDao.update(instanceInfo); } } //更新应用信息 appDesc.setStatus(AppStatusEnum.STATUS_OFFLINE.getStatus()); appService.update(appDesc); return true; } @Override public boolean modifyAppConfig(Long appId, Long appAuditId, String key, String value) { Assert.isTrue(appId != null && appId > 0L); Assert.isTrue(appAuditId != null && appAuditId > 0L); Assert.isTrue(StringUtils.isNotBlank(key)); Assert.isTrue(StringUtils.isNotBlank(value)); boolean isModify = redisDeployCenter.modifyAppConfig(appId, key, value); if (isModify) { // 改变审核状态 appAuditDao.updateAppAudit(appAuditId, AppCheckEnum.APP_ALLOCATE_RESOURCE.value()); } return isModify; } private boolean deploySentinel(long appId, List<String[]> nodes) { //数据节点 String[] dataNodeInfo = nodes.get(0); String master = dataNodeInfo[0]; int memory = NumberUtils.createInteger(dataNodeInfo[1]); String slave = dataNodeInfo[2]; // sentinel节点 List<String> sentinelList = new ArrayList<String>(); if (nodes.size() < 2) { logger.error("sentinelList is none,don't generate sentinel app!"); return false; } // sentinel节点 for (int i = 1; i < nodes.size(); i++) { String[] nodeInfo = nodes.get(i); if (nodeInfo.length == 0 || StringUtils.isBlank(nodeInfo[0])) { logger.error("sentinel line {} may be empty", i); return false; } sentinelList.add(nodeInfo[0]); } return redisDeployCenter.deploySentinelInstance(appId, master, slave, memory, sentinelList); } private boolean deployCluster(long appId, List<String[]> nodes) { List<RedisClusterNode> clusterNodes = new ArrayList<RedisClusterNode>(); int maxMemory = 0; for (String[] array : nodes) { String master = array[0]; int memory = NumberUtils.createInteger(array[1]); String slave = null; if (array.length > 2) { slave = array[2]; } RedisClusterNode node = new RedisClusterNode(master, slave); maxMemory = memory; clusterNodes.add(node); } return redisDeployCenter.deployClusterInstance(appId, clusterNodes, maxMemory); } private boolean deployStandalone(long appId, String[] nodeInfo) { String host = nodeInfo[0]; int memory = NumberUtils.createInteger(nodeInfo[1]); return redisDeployCenter.deployStandaloneInstance(appId, host, memory); } @Override public boolean verticalExpansion(Long appId, Long appAuditId, final int memory) { Assert.isTrue(appId != null && appId > 0L); Assert.isTrue(appAuditId != null && appAuditId > 0L); Assert.isTrue(memory > 0); boolean isInProcess = isInProcess(appId); if (isInProcess) { return false; } AppDesc appDesc = appService.getByAppId(appId); Assert.isTrue(appDesc != null); int type = appDesc.getType(); if (!TypeUtil.isRedisType(type)) { logger.error("appId={};type={} is not redis!", appDesc, type); return false; } List<InstanceInfo> instanceInfos = instanceDao.getInstListByAppId(appId); if (instanceInfos == null || instanceInfos.isEmpty()) { logger.error("instanceInfos is null"); return false; } for (InstanceInfo instanceInfo : instanceInfos) { int instanceType = instanceInfo.getType(); if (TypeUtil.isRedisSentinel(instanceType)) { continue; } // 下线实例不做操作 if (instanceInfo.isOffline()) { continue; } String host = instanceInfo.getIp(); int port = instanceInfo.getPort(); final long maxMemoryBytes = Long.valueOf(memory) * 1024 * 1024; boolean isConfig = redisDeployCenter.modifyInstanceConfig(appId, host, port, "maxmemory", String.valueOf(maxMemoryBytes)); if (!isConfig) { logger.error("{}:{} set maxMemory error", host, port); return false; } //更新instanceInfo配置 instanceInfo.setMem(memory); instanceDao.update(instanceInfo); } // 改变审核状态 appAuditDao.updateAppAudit(appAuditId, AppCheckEnum.APP_ALLOCATE_RESOURCE.value()); return true; } @Override public boolean addHorizontalNodes(Long appId, String masterHost, String slaveHost, int memory) { AppDesc appDesc = appDao.getAppDescById(appId); //1. 寻找主从节点的可用端口 Integer masterPort = machineCenter.getAvailablePort(masterHost, ConstUtils.CACHE_TYPE_REDIS_CLUSTER); if (masterPort == null) { logger.error("master host={} getAvailablePort is null", masterHost); return false; } Integer slavePort = 0; boolean hasSlave = StringUtils.isNotBlank(slaveHost); if (hasSlave) { slavePort = machineCenter.getAvailablePort(slaveHost, ConstUtils.CACHE_TYPE_REDIS_CLUSTER); if (slavePort == null) { logger.error("slave host={} getAvailablePort is null", slaveHost); return false; } } //2. 启动主从节点 boolean isMasterCreate = redisDeployCenter.createRunNode(appDesc, masterHost, masterPort, memory, true); if (!isMasterCreate) { logger.error("createRunNode master failed {}:{}", masterHost, masterPort); return false; } if (hasSlave) { //运行节点 boolean isSlaveCreate = redisDeployCenter.createRunNode(appDesc, slaveHost, slavePort, memory, true); if (!isSlaveCreate) { logger.error("createRunNode slave failed {}:{}", slaveHost, slavePort); return false; } } //3. 获取应用下有效节点 Set<HostAndPort> clusterHosts = getEffectiveInstanceList(appId); //4. 添加新节点: meet,复制,不做slot分配 RedisClusterReshard clusterReshard = new RedisClusterReshard(clusterHosts, redisCenter); boolean joinCluster = clusterReshard.joinCluster(appId, masterHost, masterPort, slaveHost, slavePort); if (joinCluster) { //5. 保存实例,开启统计功能 saveInstance(appId, masterHost, masterPort, memory); redisCenter.deployRedisCollection(appId, masterHost, masterPort); if (hasSlave) { saveInstance(appId, slaveHost, slavePort, memory); redisCenter.deployRedisCollection(appId, slaveHost, slavePort); } } return joinCluster; } @Override public boolean cleanAppData(long appId, AppUser appUser) { try { AppDesc appDesc = appDao.getAppDescById(appId); if (appDesc == null) { return false; } if (TypeUtil.isRedisType(appDesc.getType())) { return redisCenter.cleanAppData(appDesc, appUser); } else { return false; } } catch (Exception e) { logger.error(e.getMessage(), e); return false; } } private boolean isInProcess(Long appId) { ReshardProcess process = processMap.get(appId); if (process != null && process.getStatus() == 0) { logger.warn("appId={} is inProcess", appId); return true; } else { return false; } } @Override public ConcurrentMap<Long, ReshardProcess> getHorizontalProcess() { return processMap; } private InstanceInfo saveInstance(long appId, String host, int port, int maxMemory) { InstanceInfo instanceInfo = new InstanceInfo(); instanceInfo.setAppId(appId); MachineInfo machineInfo = machineCenter.getMachineInfoByIp(host); instanceInfo.setHostId(machineInfo.getId()); instanceInfo.setConn(0); instanceInfo.setMem(maxMemory); instanceInfo.setStatus(InstanceStatusEnum.GOOD_STATUS.getStatus()); instanceInfo.setPort(port); instanceInfo.setType(ConstUtils.CACHE_TYPE_REDIS_CLUSTER); instanceInfo.setCmd(""); instanceInfo.setIp(host); instanceDao.saveInstance(instanceInfo); return instanceInfo; } @Override public HorizontalResult checkHorizontal(long appId, long appAuditId, long sourceId, long targetId, int startSlot, int endSlot, int migrateType) { // 0.当前应用正在迁移 boolean isInProcess = isInProcess(appId); if (isInProcess) { return HorizontalResult.fail(String.format("appId=%s正在迁移!", appId)); } // 1.应用信息 AppDesc appDesc = appService.getByAppId(appId); if (appDesc == null) { return HorizontalResult.fail("应用信息为空"); } // 2.0 源实例ID不能等于目标实例ID if (sourceId == targetId) { return HorizontalResult.fail(String.format("源实例ID=%s不能等于目标实例ID=%s", sourceId, targetId)); } // 2.1 源实例信息 InstanceInfo sourceInstanceInfo = instanceDao.getInstanceInfoById(sourceId); if (sourceInstanceInfo == null) { return HorizontalResult.fail(String.format("源实例id=%s为空", sourceId)); } // 2.2 对比源实例的appId是否正确 long sourceAppId = sourceInstanceInfo.getAppId(); if (sourceAppId != appId) { return HorizontalResult.fail(String.format("源实例id=%s不属于appId=%s", sourceId, appId)); } // 2.3 源实例是否在线 boolean sourceIsRun = redisCenter.isRun(appId, sourceInstanceInfo.getIp(), sourceInstanceInfo.getPort()); if (!sourceIsRun) { return HorizontalResult.fail(String.format("源实例%s必须运行中", sourceInstanceInfo.getHostPort())); } // 2.4必须是master节点 boolean sourceIsMaster = redisCenter.isMaster(appId, sourceInstanceInfo.getIp(), sourceInstanceInfo.getPort()); if (!sourceIsMaster) { return HorizontalResult.fail(String.format("源实例%s必须是主节点", sourceInstanceInfo.getHostPort())); } // 3.1 目标实例信息 InstanceInfo targetInstanceInfo = instanceDao.getInstanceInfoById(targetId); if (targetInstanceInfo == null) { return HorizontalResult.fail(String.format("目标实例id=%s为空", targetId)); } // 3.2 对比目标实例的appId是否正确 long targetAppId = targetInstanceInfo.getAppId(); if (targetAppId != appId) { return HorizontalResult.fail(String.format("目标实例id=%s不属于appId=%s", targetId, appId)); } // 3.3 目标实例是否在线 boolean targetIsRun = redisCenter.isRun(appId, targetInstanceInfo.getIp(), targetInstanceInfo.getPort()); if (!targetIsRun) { return HorizontalResult.fail(String.format("目标实例%s必须运行中", targetInstanceInfo.getHostPort())); } // 3.4 必须是master节点 boolean targetIsMaster = redisCenter.isMaster(appId, targetInstanceInfo.getIp(), targetInstanceInfo.getPort()); if (!targetIsMaster) { return HorizontalResult.fail(String.format("目标实例%s必须是主节点", targetInstanceInfo.getHostPort())); } // 4.startSlot和endSlot是否在源实例中 // 4.1 判断数值 int maxSlot = 16383; if (startSlot < 0 || startSlot > maxSlot) { return HorizontalResult.fail(String.format("startSlot=%s必须在0-%s", startSlot, maxSlot)); } if (endSlot < 0 || endSlot > maxSlot) { return HorizontalResult.fail(String.format("endSlot=%s必须在0-%s", endSlot, maxSlot)); } if (startSlot > endSlot) { return HorizontalResult.fail("startSlot不能大于endSlot"); } // 4.2 判断startSlot和endSlot属于sourceId // 获取所有slot分布 Map<String, InstanceSlotModel> clusterSlotsMap = redisCenter.getClusterSlotsMap(appId); if (MapUtils.isEmpty(clusterSlotsMap)) { return HorizontalResult.fail("无法获取slot分布!"); } // 获取源实例负责的slot String sourceHostPort = sourceInstanceInfo.getHostPort(); InstanceSlotModel instanceSlotModel = clusterSlotsMap.get(sourceHostPort); if (instanceSlotModel == null || CollectionUtils.isEmpty(instanceSlotModel.getSlotList())) { return HorizontalResult.fail("源实例上没有slot!"); } List<Integer> slotList = instanceSlotModel.getSlotList(); for (int i = startSlot; i <= endSlot; i++) { if (!slotList.contains(i)) { return HorizontalResult.fail(String.format("源实例没有包含尽startSlot=%s到endSlot=%s", startSlot, endSlot)); } } //5.是否支持批量,版本要大于等于3.0.6 String sourceRedisVersion = redisCenter.getRedisVersion(sourceAppId, sourceInstanceInfo.getIp(), sourceInstanceInfo.getPort()); if (StringUtils.isBlank(sourceRedisVersion)) { return HorizontalResult.fail(String.format("源实例%s版本为空", sourceInstanceInfo.getHostPort())); } String targetRedisVersion = redisCenter.getRedisVersion(targetAppId, targetInstanceInfo.getIp(), targetInstanceInfo.getPort()); if (StringUtils.isBlank(targetRedisVersion)) { return HorizontalResult.fail(String.format("目标实例%s版本为空", targetInstanceInfo.getHostPort())); } RedisVersion sourceRedisVersionModel = getRedisVersion(sourceRedisVersion); //选择了批量,但是当前版本不支持pipeline if (migrateType == 1 && !sourceRedisVersionModel.isSupportPipelineMigrate()) { return HorizontalResult.fail(String.format("源实例%s版本为%s,不支持pipeline migrate!", sourceInstanceInfo.getHostPort(), sourceRedisVersion)); } RedisVersion targetRedisVersionModel = getRedisVersion(targetRedisVersion); //选择了批量,但是当前版本不支持pipeline if (migrateType == 1 && !targetRedisVersionModel.isSupportPipelineMigrate()) { return HorizontalResult.fail(String.format("目标实例%s版本为%s,不支持pipeline migrate!", targetInstanceInfo.getHostPort(), targetRedisVersion)); } return HorizontalResult.checkSuccess(); } private RedisVersion getRedisVersion(String redisVersion) { String[] versionArr = redisVersion.split("\\."); if (versionArr.length == 1) { return new RedisVersion(NumberUtils.toInt(versionArr[0]), 0, 0); } else if (versionArr.length == 2) { return new RedisVersion(NumberUtils.toInt(versionArr[0]), NumberUtils.toInt(versionArr[1]), 0); } else if (versionArr.length >= 3) { return new RedisVersion(NumberUtils.toInt(versionArr[0]), NumberUtils.toInt(versionArr[1]), NumberUtils.toInt(versionArr[2])); } return null; } private class RedisVersion { int majorVersion; int minorVersion; int patchVersion; public RedisVersion(int majorVersion, int minorVersion, int patchVersion) { super(); this.majorVersion = majorVersion; this.minorVersion = minorVersion; this.patchVersion = patchVersion; } /** * 大于等于3.0.6 * @return */ public boolean isSupportPipelineMigrate() { if (majorVersion < 3) { return false; } else if (majorVersion == 3) { if (minorVersion > 0) { return true; } else { return patchVersion >= 6; } } else { return true; } } @Override public String toString() { return "RedisVersion [majorVersion=" + majorVersion + ", minorVersion=" + minorVersion + ", patchVersion=" + patchVersion + "]"; } } /** * 获取应用下有效节点 * @param appId * @return */ private Set<HostAndPort> getEffectiveInstanceList(long appId) { Set<HostAndPort> clusterHosts = new HashSet<HostAndPort>(); //全部节点 List<InstanceInfo> instanceInfos = instanceDao.getInstListByAppId(appId); for (InstanceInfo instance : instanceInfos) { if (instance.isOffline()) { continue; } clusterHosts.add(new HostAndPort(instance.getIp(), instance.getPort())); } return clusterHosts; } @Override public HorizontalResult startHorizontal(final long appId, final long appAuditId, long sourceId, final long targetId, final int startSlot, final int endSlot, final int migrateType) { final InstanceInfo sourceInstanceInfo = instanceDao.getInstanceInfoById(sourceId); final InstanceInfo targetInstanceInfo = instanceDao.getInstanceInfoById(targetId); processThreadPool.execute(new Runnable() { @Override public void run() { //所有节点用户clustersetslot Set<HostAndPort> clusterHosts = getEffectiveInstanceList(appId); RedisClusterReshard clusterReshard = new RedisClusterReshard(clusterHosts, redisCenter); //添加进度 processMap.put(appId, clusterReshard.getReshardProcess()); boolean joinCluster = clusterReshard.migrateSlot(appId, sourceInstanceInfo, targetInstanceInfo, startSlot, endSlot, migrateType == 1); logger.warn("async:appId={} joinCluster={} done result={}", appId, joinCluster, clusterReshard.getReshardProcess()); if (joinCluster) { // 改变审核状态 appAuditDao.updateAppAudit(appAuditId, AppCheckEnum.APP_ALLOCATE_RESOURCE.value()); if (targetInstanceInfo != null && targetInstanceInfo.getStatus() != InstanceStatusEnum.GOOD_STATUS.getStatus()) { targetInstanceInfo.setStatus(InstanceStatusEnum.GOOD_STATUS.getStatus()); instanceDao.update(targetInstanceInfo); } } } }); logger.warn("reshard appId={} instance={}:{} deploy done", appId, targetInstanceInfo.getIp(), targetInstanceInfo.getPort()); return HorizontalResult.scaleSuccess(); } @Override public DataFormatCheckResult checkHorizontalNodes(Long appAuditId, String masterSizeSlave) { if (appAuditId == null) { logger.error("appAuditId is null"); return DataFormatCheckResult.fail("审核id不能为空!"); } if (StringUtils.isBlank(masterSizeSlave)) { logger.error("masterSizeSlave is null"); return DataFormatCheckResult.fail("添加节点不能为空!"); } AppAudit appAudit = appAuditDao.getAppAudit(appAuditId); if (appAudit == null) { logger.error("appAudit:id={} is not exist", appAuditId); return DataFormatCheckResult.fail(String.format("审核id=%s不存在", appAuditId)); } long appId = appAudit.getAppId(); AppDesc appDesc = appService.getByAppId(appId); if (appDesc == null) { logger.error("appDesc:id={} is not exist"); return DataFormatCheckResult.fail(String.format("appId=%s不存在", appId)); } //节点数组 master:memSize:slave String[] array = masterSizeSlave.split(ConstUtils.COLON); if (array == null || array.length == 0) { return DataFormatCheckResult.fail(String.format("添加节点%s格式错误", masterSizeSlave)); } //检查格式 String masterHost = null; String memSize = null; String slaveHost = null; if (array.length == 2) { masterHost = array[0]; memSize = array[1]; } else if (array.length == 3) { masterHost = array[0]; memSize = array[1]; slaveHost = array[2]; } else { return DataFormatCheckResult.fail(String.format("添加节点%s, 格式错误!", masterSizeSlave)); } //检查主节点机器是否存在 if (!checkHostExist(masterHost)) { return DataFormatCheckResult.fail(String.format("%s中的ip=%s不存在,请在机器管理中添加!", masterSizeSlave, masterHost)); } //检查memSize格式 if (StringUtils.isNotBlank(memSize) && !NumberUtils.isDigits(memSize)) { return DataFormatCheckResult.fail(String.format("%s中的中的memSize=%s不是整数!", masterSizeSlave, memSize)); } //检查从节点格式 if (StringUtils.isNotBlank(slaveHost) && !checkHostExist(slaveHost)) { return DataFormatCheckResult.fail(String.format("%s中的ip=%s不存在,请在机器管理中添加!", masterSizeSlave, slaveHost)); } return DataFormatCheckResult.success("添加节点格式正确,可以开始部署了!"); } public void setAppService(AppService appService) { this.appService = appService; } public void setRedisDeployCenter(RedisDeployCenter redisDeployCenter) { this.redisDeployCenter = redisDeployCenter; } public void setAppEmailUtil(AppEmailUtil appEmailUtil) { this.appEmailUtil = appEmailUtil; } public void setAppAuditDao(AppAuditDao appAuditDao) { this.appAuditDao = appAuditDao; } public void setInstanceDao(InstanceDao instanceDao) { this.instanceDao = instanceDao; } public void setRedisCenter(RedisCenter redisCenter) { this.redisCenter = redisCenter; } public void setMachineCenter(MachineCenter machineCenter) { this.machineCenter = machineCenter; } public void setAppAuditLogDao(AppAuditLogDao appAuditLogDao) { this.appAuditLogDao = appAuditLogDao; } public void setAppDao(AppDao appDao) { this.appDao = appDao; } }