package com.sohu.cache.redis.impl; import com.sohu.cache.constant.InstanceStatusEnum; import com.sohu.cache.dao.AppDao; import com.sohu.cache.dao.InstanceDao; import com.sohu.cache.dao.MachineDao; import com.sohu.cache.entity.AppDesc; import com.sohu.cache.entity.InstanceInfo; import com.sohu.cache.entity.MachineInfo; import com.sohu.cache.machine.MachineCenter; import com.sohu.cache.protocol.MachineProtocol; import com.sohu.cache.protocol.RedisProtocol; import com.sohu.cache.redis.RedisCenter; import com.sohu.cache.redis.RedisClusterNode; import com.sohu.cache.redis.RedisConfigTemplateService; import com.sohu.cache.redis.RedisDeployCenter; import com.sohu.cache.redis.enums.RedisConfigEnum; import com.sohu.cache.util.ConstUtils; import com.sohu.cache.util.IdempotentConfirmer; import com.sohu.cache.util.TypeUtil; import com.sohu.cache.web.enums.RedisOperateEnum; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisSentinelPool; import redis.clients.jedis.Protocol; import java.util.*; import java.util.concurrent.TimeUnit; /** * Created by yijunzhang on 14-8-25. */ public class RedisDeployCenterImpl implements RedisDeployCenter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private InstanceDao instanceDao; private MachineDao machineDao; private MachineCenter machineCenter; private RedisCenter redisCenter; private AppDao appDao; private RedisConfigTemplateService redisConfigTemplateService; @Override public boolean deployClusterInstance(long appId, List<RedisClusterNode> clusterNodes, int maxMemory) { if (!isExist(appId)) { return false; } AppDesc appDesc = appDao.getAppDescById(appId); String host = null; Integer port = null; Map<Jedis, Jedis> clusterMap = new LinkedHashMap<Jedis, Jedis>(); for (RedisClusterNode node : clusterNodes) { String masterHost = node.getMasterHost(); String slaveHost = node.getSlaveHost(); Integer masterPort = machineCenter.getAvailablePort(masterHost, ConstUtils.CACHE_TYPE_REDIS_CLUSTER); if (masterPort == null) { logger.error("masterHost={} getAvailablePort is null", masterHost); return false; } if (host == null || port == null) { host = masterHost; port = masterPort; } boolean isMasterRun = runInstance(appDesc, masterHost, masterPort, maxMemory, true); if (!isMasterRun) { return false; } if (StringUtils.isNotBlank(slaveHost)) { Integer slavePort = machineCenter.getAvailablePort(slaveHost, ConstUtils.CACHE_TYPE_REDIS_CLUSTER); if (slavePort == null) { logger.error("slavePort={} getAvailablePort is null", slavePort); return false; } boolean isSlaveRun = runInstance(appDesc, slaveHost, slavePort, maxMemory, true); if (!isSlaveRun) { return false; } clusterMap.put(redisCenter.getJedis(appId, masterHost, masterPort), redisCenter.getJedis(appId, slaveHost, slavePort)); } else { clusterMap.put(redisCenter.getJedis(appId, masterHost, masterPort), null); } } boolean isCluster; try { isCluster = startCluster(appId, clusterMap); if (!isCluster) { logger.error("startCluster create error!"); return false; } for (Map.Entry<Jedis, Jedis> entry : clusterMap.entrySet()) { Jedis master = entry.getKey(); Jedis slave = entry.getValue(); //保存实例信息 & 触发收集 saveInstance(appId, master.getClient().getHost(), master.getClient().getPort(), maxMemory, ConstUtils.CACHE_TYPE_REDIS_CLUSTER, ""); redisCenter.deployRedisCollection(appId, master.getClient().getHost(), master.getClient().getPort()); if (slave != null) { saveInstance(appId, slave.getClient().getHost(), slave.getClient().getPort(), maxMemory, ConstUtils.CACHE_TYPE_REDIS_CLUSTER, ""); redisCenter.deployRedisCollection(appId, slave.getClient().getHost(), slave.getClient().getPort()); } } } finally { //关闭jedis连接 for (Jedis master : clusterMap.keySet()) { master.close(); if (clusterMap.get(master) != null) { clusterMap.get(master).close(); } } } return true; } private boolean clusterMeet(Jedis jedis, long appId, String host, int port) { boolean isSingleNode = redisCenter.isSingleClusterNode(appId, host, port); if (!isSingleNode) { logger.error("{}:{} isNotSingleNode", host, port); return false; } else { logger.warn("{}:{} isSingleNode", host, port); } String response = jedis.clusterMeet(host, port); boolean isMeet = response != null && response.equalsIgnoreCase("OK"); if (!isMeet) { logger.error("{}:{} meet error", host, port); return false; } return true; } private boolean startCluster(final long appId, Map<Jedis, Jedis> clusterMap) { final Jedis jedis = new ArrayList<Jedis>(clusterMap.keySet()).get(0); //meet集群节点 for (final Jedis master : clusterMap.keySet()) { boolean isMeet = new IdempotentConfirmer() { @Override public boolean execute() { boolean isMeet = clusterMeet(jedis, appId, master.getClient().getHost(), master.getClient().getPort()); if (!isMeet) { return false; } return true; } }.run(); if (!isMeet) { return false; } final Jedis slave = clusterMap.get(master); if (slave != null) { isMeet = new IdempotentConfirmer() { @Override public boolean execute() { boolean isMeet = clusterMeet(jedis, appId, slave.getClient().getHost(), slave.getClient().getPort()); if (!isMeet) { return false; } return true; } }.run(); if (!isMeet) { return false; } } } int masterSize = clusterMap.size(); int perSize = (int) Math.ceil(16384 / masterSize); int index = 0; int masterIndex = 0; final ArrayList<Integer> slots = new ArrayList<Integer>(); List<Jedis> masters = new ArrayList<Jedis>(clusterMap.keySet()); //分配slot for (int slot = 0; slot <= 16383; slot++) { slots.add(slot); if (index++ >= perSize || slot == 16383) { final int[] slotArr = new int[slots.size()]; for (int i = 0; i < slotArr.length; i++) { slotArr[i] = slots.get(i); } final Jedis masterJedis = masters.get(masterIndex++); boolean isSlot = new IdempotentConfirmer() { @Override public boolean execute() { String response = masterJedis.clusterAddSlots(slotArr); boolean isSlot = response != null && response.equalsIgnoreCase("OK"); if (!isSlot) { return false; } return true; } }.run(); if (!isSlot) { logger.error("{}:{} set slots:{}", masterJedis.getClient().getHost(), masterJedis.getClient().getPort(), slots); return false; } slots.clear(); index = 0; } } //设置从节点 for (Jedis masterJedis : clusterMap.keySet()) { final Jedis slaveJedis = clusterMap.get(masterJedis); if (slaveJedis == null) { continue; } final String nodeId = getClusterNodeId(masterJedis); boolean isReplicate = new IdempotentConfirmer() { @Override public boolean execute() { try { //等待广播节点 TimeUnit.SECONDS.sleep(2); } catch (Exception e) { logger.error(e.getMessage(), e); } String response = null; try { response = slaveJedis.clusterReplicate(nodeId); } catch (Exception e) { logger.error(e.getMessage(), e); } boolean isReplicate = response != null && response.equalsIgnoreCase("OK"); if (!isReplicate) { try { //等待广播节点 TimeUnit.SECONDS.sleep(2); } catch (Exception e) { logger.error(e.getMessage(), e); } return false; } return true; } }.run(); if (!isReplicate) { logger.error("{}:{} set replicate:{}", slaveJedis.getClient().getHost(), slaveJedis.getClient().getPort()); return false; } } return true; } private String getClusterNodeId(Jedis jedis) { try { String infoOutput = jedis.clusterNodes(); for (String infoLine : infoOutput.split("\n")) { if (infoLine.contains("myself")) { return infoLine.split(" ")[0]; } } } catch (Exception e) { logger.error(e.getMessage(), e); } return null; } @Override public boolean deploySentinelInstance(long appId, String masterHost, String slaveHost, int maxMemory, List<String> sentinelList) { if (!isExist(appId)) { return false; } AppDesc appDesc = appDao.getAppDescById(appId); //获取端口 Integer masterPort = machineCenter.getAvailablePort(masterHost, ConstUtils.CACHE_REDIS_STANDALONE); if (masterPort == null) { logger.error("masterHost={} getAvailablePort is null", masterHost); return false; } Integer slavePort = machineCenter.getAvailablePort(slaveHost, ConstUtils.CACHE_REDIS_STANDALONE); if (slavePort == null) { logger.error("slaveHost={} getAvailablePort is null", slavePort); return false; } //运行实例 boolean isMasterRun = runInstance(appDesc, masterHost, masterPort, maxMemory, false); if (!isMasterRun) { return false; } boolean isSlaveRun = runInstance(appDesc, slaveHost, slavePort, maxMemory, false); if (!isSlaveRun) { return false; } //添加slaveof配置 boolean isSlave = slaveOf(appDesc.getAppId(), masterHost, masterPort, slaveHost, slavePort); if (!isSlave) { return false; } //运行sentinel实例组 boolean isRunSentinel = runSentinelGroup(appDesc, sentinelList, masterHost, masterPort, appId, appDesc.getPassword()); if (!isRunSentinel) { return false; } //写入instanceInfo 信息 saveInstance(appId, masterHost, masterPort, maxMemory, ConstUtils.CACHE_REDIS_STANDALONE, ""); saveInstance(appId, slaveHost, slavePort, maxMemory, ConstUtils.CACHE_REDIS_STANDALONE, ""); //启动监控trigger boolean isMasterDeploy = redisCenter.deployRedisCollection(appId, masterHost, masterPort); boolean isSlaveDeploy = redisCenter.deployRedisCollection(appId, slaveHost, slavePort); if (!isMasterDeploy) { logger.warn("host={},port={},isMasterDeploy=false", masterHost, masterPort); } if (!isSlaveDeploy) { logger.warn("host={},port={},isSlaveDeploy=false", slaveHost, slavePort); } return true; } @Override public boolean deployStandaloneInstance(long appId, String host, int maxMemory) { if (!isExist(appId)) { return false; } AppDesc appDesc = appDao.getAppDescById(appId); //获取端口 Integer port = machineCenter.getAvailablePort(host, ConstUtils.CACHE_REDIS_STANDALONE); if (port == null) { logger.error("masterHost={} getAvailablePort is null", host); return false; } //运行实例 boolean isMasterRun = runInstance(appDesc, host, port, maxMemory, false); if (!isMasterRun) { return false; } //写入instanceInfo 信息 saveInstance(appId, host, port, maxMemory, ConstUtils.CACHE_REDIS_STANDALONE, ""); //启动监控trigger boolean isMasterDeploy = redisCenter.deployRedisCollection(appId, host, port); if (!isMasterDeploy) { logger.warn("host={},port={},isMasterDeploy=false", host, port); } return true; } private InstanceInfo saveInstance(long appId, String host, int port, int maxMemory, int type, String cmd) { InstanceInfo instanceInfo = new InstanceInfo(); instanceInfo.setAppId(appId); MachineInfo machineInfo = machineDao.getMachineInfoByIp(host); instanceInfo.setHostId(machineInfo.getId()); instanceInfo.setConn(0); instanceInfo.setMem(maxMemory); instanceInfo.setStatus(InstanceStatusEnum.GOOD_STATUS.getStatus()); instanceInfo.setPort(port); instanceInfo.setType(type); instanceInfo.setCmd(cmd); instanceInfo.setIp(host); instanceDao.saveInstance(instanceInfo); return instanceInfo; } private boolean runSentinelGroup(AppDesc appDesc, List<String> sentinelList, String masterHost, int masterPort, long appId, String password) { for (String sentinelHost : sentinelList) { boolean isRun = runSentinel(appDesc, sentinelHost, getMasterName(masterHost, masterPort), masterHost, masterPort); if (!isRun) { return false; } } return true; } @Override public boolean createRunNode(AppDesc appDesc, String host, Integer port, int maxMemory, boolean isCluster) { return runInstance(appDesc, host, port, maxMemory, isCluster); } private boolean runInstance(AppDesc appDesc, String host, Integer port, int maxMemory, boolean isCluster) { long appId = appDesc.getAppId(); String password = appDesc.getPassword(); // 生成配置 List<String> configs = handleCommonConfig(port, maxMemory); if (isCluster) { configs.addAll(handleClusterConfig(port)); } if (StringUtils.isNotBlank(password)) { //加两个选项 configs.add(RedisConfigEnum.REQUIREPASS.getKey() + ConstUtils.SPACE + password); configs.add(RedisConfigEnum.MASTERAUTH.getKey() + ConstUtils.SPACE + password); } printConfig(configs); String fileName; String runShell; if (isCluster) { runShell = RedisProtocol.getRunShell(port, true); fileName = RedisProtocol.getConfig(port, true); } else { runShell = RedisProtocol.getRunShell(port, false); fileName = RedisProtocol.getConfig(port, false); } String pathFile = machineCenter.createRemoteFile(host, fileName, configs); if (StringUtils.isBlank(pathFile)) { logger.error("createFile={} error", pathFile); return false; } if (isCluster) { //删除cluster节点配置 String deleteNodeShell = String.format("rm -rf %s/nodes-%s.conf", MachineProtocol.DATA_DIR, port); String deleteNodeResult = machineCenter.executeShell(host, deleteNodeShell); if (!ConstUtils.INNER_ERROR.equals(deleteNodeResult)) { logger.warn("runDeleteNodeShell={} at host {}", deleteNodeShell, host); } } //启动实例 logger.info("masterShell:host={};shell={}", host, runShell); boolean isMasterShell = machineCenter.startProcessAtPort(host, port, runShell); if (!isMasterShell) { logger.error("runShell={} error,{}:{}", runShell, host, port); return false; } //验证实例 if (!redisCenter.isRun(appId, host, port)) { logger.error("host:{};port:{} not run", host, port); return false; } else { logger.warn("runInstance-fallback : redis-cli -h {} -p {} shutdown", host, port); } return true; } private boolean slaveOf(final long appId, final String masterHost, final int masterPort, final String slaveHost, final int slavePort) { final Jedis slave = redisCenter.getJedis(appId, slaveHost, slavePort, Protocol.DEFAULT_TIMEOUT * 3, Protocol.DEFAULT_TIMEOUT * 3); try { boolean isSlave = new IdempotentConfirmer() { @Override public boolean execute() { String result = slave.slaveof(masterHost, masterPort); return result != null && result.equalsIgnoreCase("OK"); } }.run(); if (!isSlave) { logger.error(String.format("modifyAppConfig:ip=%s,port=%s failed", slaveHost, slavePort)); return false; } redisCenter.configRewrite(appId, slaveHost, slavePort); } catch (Exception e) { logger.error(e.getMessage(), e); return false; } finally { if (slave != null) slave.close(); } return true; } private boolean runSentinel(AppDesc appDesc, String sentinelHost, String masterName, String masterHost, Integer masterPort) { //应用信息 long appId = appDesc.getAppId(); String password = appDesc.getPassword(); //启动sentinel实例 Integer sentinelPort = machineCenter.getAvailablePort(sentinelHost, ConstUtils.CACHE_REDIS_SENTINEL); if (sentinelPort == null) { logger.error("host={} getAvailablePort is null", sentinelHost); return false; } List<String> masterSentinelConfigs = handleSentinelConfig(masterName, masterHost, masterPort, sentinelPort); if (StringUtils.isNotBlank(password)) { masterSentinelConfigs.add("sentinel " + RedisConfigEnum.AUTH_PASS.getKey() + ConstUtils.SPACE + masterName + ConstUtils.SPACE + password); } printConfig(masterSentinelConfigs); String masterSentinelFileName = RedisProtocol.getConfig(sentinelPort, false); String sentinelPathFile = machineCenter .createRemoteFile(sentinelHost, masterSentinelFileName, masterSentinelConfigs); if (StringUtils.isBlank(sentinelPathFile)) { return false; } String sentinelShell = RedisProtocol.getSentinelShell(sentinelPort); logger.info("sentinelMasterShell:{}", sentinelShell); boolean isSentinelMasterShell = machineCenter.startProcessAtPort(sentinelHost, sentinelPort, sentinelShell); if (!isSentinelMasterShell) { logger.error("sentinelMasterShell={} error", sentinelShell); return false; } //验证实例 if (!redisCenter.isRun(sentinelHost, sentinelPort)) { logger.error("host:{};port:{} not run", sentinelHost, sentinelPort); return false; } else { logger.warn("runSentinel-fallback : redis-cli -h {} -p {} shutdown", sentinelHost, sentinelPort); } //save sentinel saveInstance(appId, sentinelHost, sentinelPort, 0, ConstUtils.CACHE_REDIS_SENTINEL, getMasterName(masterHost, masterPort)); return true; } /** * 获取redis 基础配置 * * @param port * @param maxMemory * @return */ public List<String> handleCommonConfig(int port, int maxMemory) { List<String> configs = null; try { configs = redisConfigTemplateService.handleCommonConfig(port, maxMemory); } catch (Exception e) { logger.error(e.getMessage(), e); } if (CollectionUtils.isEmpty(configs)) { configs = redisConfigTemplateService.handleCommonDefaultConfig(port, maxMemory); } return configs; } private List<String> handleSentinelConfig(String masterName, String host, int port, int sentinelPort) { List<String> configs = null; try { configs = redisConfigTemplateService.handleSentinelConfig(masterName, host, port, sentinelPort); } catch (Exception e) { logger.error(e.getMessage(), e); } if (CollectionUtils.isEmpty(configs)) { configs = redisConfigTemplateService.handleSentinelDefaultConfig(masterName, host, port, sentinelPort); } return configs; } private List<String> handleClusterConfig(int port) { List<String> configs = null; try { configs = redisConfigTemplateService.handleClusterConfig(port); } catch (Exception e) { logger.error(e.getMessage(), e); } if (CollectionUtils.isEmpty(configs)) { configs = redisConfigTemplateService.handleClusterDefaultConfig(port); } return configs; } private String getMasterName(String host, int port) { String masterSentinelName = String.format("sentinel-%s-%s", host, port); return masterSentinelName; } private void printConfig(List<String> masterConfigs) { logger.info("==================redis-{}-config==================", masterConfigs); for (String line : masterConfigs) { logger.info(line); } } private boolean isExist(long appId) { List<InstanceInfo> instanceInfos = instanceDao.getInstListByAppId(appId); if (instanceInfos != null && instanceInfos.size() > 0) { logger.error("appId={} instances is exist , instanceInfos={}", appId, instanceInfos); return false; } return true; } @Override public boolean modifyAppConfig(long appId, String parameter, String value) { List<InstanceInfo> list = instanceDao.getInstListByAppId(appId); if (list == null || list.isEmpty()) { logger.error(String.format("appId=%s no instances", appId)); return false; } for (InstanceInfo instance : list) { int type = instance.getType(); if (!TypeUtil.isRedisType(type)) { logger.error("appId={};type={};is not redisType", appId, type); return false; } //忽略sentinel if (TypeUtil.isRedisSentinel(type)) { continue; } //忽略下线 if (instance.isOffline()) { continue; } String host = instance.getIp(); int port = instance.getPort(); if (!modifyInstanceConfig(appId, host, port, parameter, value)) { return false; } } return true; } @Override public boolean modifyInstanceConfig(final long appId, final String host, final int port, final String parameter, final String value) { final Jedis jedis = redisCenter.getJedis(appId, host, port, 5000, 5000); try { boolean isConfig = new IdempotentConfirmer() { @Override public boolean execute() { boolean isRun = redisCenter.isRun(appId, host, port); if (!isRun) { logger.warn("modifyInstanceConfig{}:{} is shutdown", host, port); return true; } String result = jedis.configSet(parameter, value); boolean isConfig = result != null && result.equalsIgnoreCase("OK"); if (!isConfig) { logger.error(String.format("modifyConfigError:ip=%s,port=%s,result=%s", host, port, result)); return false; } return isConfig; } }.run(); boolean isRewrite = redisCenter.configRewrite(appId, host, port); if (!isRewrite) { logger.error("configRewrite={}:{} failed", host, port); } return isConfig; } catch (Exception e) { logger.error(e.getMessage(), e); return false; } finally { if (jedis != null) jedis.close(); } } @Override public boolean addSentinel(long appId, String sentinelHost) { AppDesc appDesc = appDao.getAppDescById(appId); JedisSentinelPool jedisSentinelPool = redisCenter.getJedisSentinelPool(appDesc); if (jedisSentinelPool == null) { return false; } List<InstanceInfo> instanceInfos = instanceDao.getInstListByAppId(appId); String masterName = null; for (Iterator<InstanceInfo> i = instanceInfos.iterator(); i.hasNext(); ) { InstanceInfo instanceInfo = i.next(); if (instanceInfo.getType() != ConstUtils.CACHE_REDIS_SENTINEL) { i.remove(); continue; } if (masterName == null && StringUtils.isNotBlank(instanceInfo.getCmd())) { masterName = instanceInfo.getCmd(); } } Jedis jedis = null; String masterHost = null; Integer masterPort = null; try { jedis = jedisSentinelPool.getResource(); masterHost = jedis.getClient().getHost(); masterPort = jedis.getClient().getPort(); } catch (Exception e) { logger.error(e.getMessage(), e); } finally { jedis.close(); jedisSentinelPool.destroy(); } boolean isRun = runSentinel(appDesc, sentinelHost, masterName, masterHost, masterPort); if (!isRun) { return false; } return true; } @Override public RedisOperateEnum addSlotsFailMaster(long appId, int lossSlotsInstanceId, String newMasterHost) throws Exception { // 1.参数、应用、实例信息确认 Assert.isTrue(appId > 0); Assert.isTrue(lossSlotsInstanceId > 0); Assert.isTrue(StringUtils.isNotBlank(newMasterHost)); AppDesc appDesc = appDao.getAppDescById(appId); Assert.isTrue(appDesc != null); int type = appDesc.getType(); if (!TypeUtil.isRedisCluster(type)) { logger.error("{} is not redis cluster type", appDesc); return RedisOperateEnum.FAIL; } //获取失联slots的实例信息 InstanceInfo lossSlotsInstanceInfo = instanceDao.getInstanceInfoById(lossSlotsInstanceId); Assert.isTrue(lossSlotsInstanceInfo != null); // 2.获取集群中一个健康的master作为clusterInfo Nodes的数据源 InstanceInfo sourceMasterInstance = redisCenter.getHealthyInstanceInfo(appId); // 并未找到一个合适的实例可以 if (sourceMasterInstance == null) { logger.warn("appId {} does not have right instance", appId); return RedisOperateEnum.FAIL; } // 3. 找到丢失的slots,如果没找到就说明集群正常,直接返回 String healthyMasterHost = sourceMasterInstance.getIp(); int healthyMasterPort = sourceMasterInstance.getPort(); int healthyMasterMem = sourceMasterInstance.getMem(); // 3.1 查看整个集群中是否有丢失的slots List<Integer> allLossSlots = redisCenter.getClusterLossSlots(appId, healthyMasterHost, healthyMasterPort); if (CollectionUtils.isEmpty(allLossSlots)) { logger.warn("appId {} all slots is regular and assigned", appId); return RedisOperateEnum.ALREADY_SUCCESS; } // 3.2 查看目标实例丢失slots List<Integer> clusterLossSlots = redisCenter.getInstanceSlots(appId, healthyMasterHost, healthyMasterPort, lossSlotsInstanceInfo.getIp(), lossSlotsInstanceInfo.getPort()); // 4.开启新的节点 // 4.1 从newMasterHost找到可用的端口newMasterPort final Integer newMasterPort = machineCenter.getAvailablePort(newMasterHost, ConstUtils.CACHE_TYPE_REDIS_CLUSTER); if (newMasterPort == null) { logger.error("host={} getAvailablePort is null", newMasterHost); return RedisOperateEnum.FAIL; } // 4.2 按照sourceMasterInstance的内存启动 boolean isRun = runInstance(appDesc, newMasterHost, newMasterPort, healthyMasterMem, true); if (!isRun) { logger.error("{}:{} is not run", newMasterHost, newMasterPort); return RedisOperateEnum.FAIL; } // 4.3 拷贝配置 boolean isCopy = copyCommonConfig(appId, healthyMasterHost, healthyMasterPort, newMasterHost, newMasterPort); if (!isCopy) { logger.error("{}:{} copy config {}:{} is error", healthyMasterHost, healthyMasterPort, newMasterHost, newMasterPort); return RedisOperateEnum.FAIL; } // 5. meet boolean isClusterMeet = false; Jedis sourceMasterJedis = null; try { sourceMasterJedis = redisCenter.getJedis(appId, healthyMasterHost, healthyMasterPort); isClusterMeet = clusterMeet(sourceMasterJedis, appId, newMasterHost, newMasterPort); if (!isClusterMeet) { logger.error("{}:{} cluster is failed", newMasterHost, newMasterPort); return RedisOperateEnum.FAIL; } } catch (Exception e) { logger.error(e.getMessage(), e); } finally { if (sourceMasterJedis != null) { sourceMasterJedis.close(); } } if (!isClusterMeet) { logger.warn("{}:{} meet {}:{} is fail", healthyMasterHost, healthyMasterPort, newMasterHost, newMasterPort); return RedisOperateEnum.FAIL; } // 6. 分配slots String addSlotsResult = ""; Jedis newMasterJedis = null; Jedis healthyMasterJedis = null; try { newMasterJedis = redisCenter.getJedis(appId, newMasterHost, newMasterPort, 5000, 5000); healthyMasterJedis = redisCenter.getJedis(appId, healthyMasterHost, healthyMasterPort, 5000, 5000); //获取新的补救节点的nodid final String nodeId = getClusterNodeId(newMasterJedis); for (Integer slot : clusterLossSlots) { addSlotsResult = healthyMasterJedis.clusterSetSlotNode(slot, nodeId); logger.warn("set slot {}, result is {}", slot, addSlotsResult); } } catch (Exception e) { logger.error(e.getMessage(), e); } finally { if (newMasterJedis != null) { newMasterJedis.close(); } if (healthyMasterJedis != null) { healthyMasterJedis.close(); } } if (!"OK".equalsIgnoreCase(addSlotsResult)) { logger.warn("{}:{} set slots faily", newMasterHost, newMasterPort); return RedisOperateEnum.FAIL; } // 7.保存实例信息、并开启收集信息 saveInstance(appId, newMasterHost, newMasterPort, healthyMasterMem, ConstUtils.CACHE_TYPE_REDIS_CLUSTER, ""); redisCenter.deployRedisCollection(appId, newMasterHost, newMasterPort); // 休息一段时间,同步clusterNodes信息 TimeUnit.SECONDS.sleep(2); // 8.最终打印出当前还没有补充的slots List<Integer> currentLossSlots = redisCenter.getClusterLossSlots(appId, newMasterHost, newMasterPort); logger.warn("appId {} failslots assigned unsuccessfully, lossslots is {}", appId, currentLossSlots); return RedisOperateEnum.OP_SUCCESS; } @Override public boolean addSlave(long appId, int instanceId, final String slaveHost) { Assert.isTrue(appId > 0); Assert.isTrue(instanceId > 0); Assert.isTrue(StringUtils.isNotBlank(slaveHost)); AppDesc appDesc = appDao.getAppDescById(appId); Assert.isTrue(appDesc != null); int type = appDesc.getType(); if (!TypeUtil.isRedisType(type)) { logger.error("{} is not redis type", appDesc); return false; } InstanceInfo instanceInfo = instanceDao.getInstanceInfoById(instanceId); Assert.isTrue(instanceInfo != null); String masterHost = instanceInfo.getIp(); int masterPort = instanceInfo.getPort(); final Integer slavePort = machineCenter.getAvailablePort(slaveHost, ConstUtils.CACHE_REDIS_STANDALONE); if (slavePort == null) { logger.error("host={} getAvailablePort is null", slaveHost); return false; } boolean isRun; if (TypeUtil.isRedisCluster(type)) { isRun = runInstance(appDesc, slaveHost, slavePort, instanceInfo.getMem(), true); } else { isRun = runInstance(appDesc, slaveHost, slavePort, instanceInfo.getMem(), false); } if (!isRun) { logger.error("{}:{} is not run", slaveHost, slavePort); return false; } boolean isCopy = copyCommonConfig(appId, masterHost, masterPort, slaveHost, slavePort); if (!isCopy) { logger.error("{}:{} copy config {}:{} is error", masterHost, masterPort, slaveHost, slavePort); return false; } if (TypeUtil.isRedisCluster(type)) { final Jedis masterJedis = redisCenter.getJedis(appId, masterHost, masterPort, Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT); final Jedis slaveJedis = redisCenter.getJedis(appId, slaveHost, slavePort, Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT); try { boolean isClusterMeet = clusterMeet(masterJedis, appId, slaveHost, slavePort); if (!isClusterMeet) { logger.error("{}:{} cluster is failed", slaveHost, slaveHost); return isClusterMeet; } final String nodeId = redisCenter.getNodeId(appId, masterHost, masterPort); if (StringUtils.isBlank(nodeId)) { logger.error("{}:{} getNodeId failed", masterHost, masterPort); return false; } boolean isClusterReplicate = new IdempotentConfirmer() { @Override public boolean execute() { try { //等待广播节点 TimeUnit.SECONDS.sleep(2); } catch (Exception e) { logger.error(e.getMessage(), e); } String response = slaveJedis.clusterReplicate(nodeId); logger.info("clusterReplicate-{}:{}={}", slaveHost, slavePort, response); return response != null && response.equalsIgnoreCase("OK"); } }.run(); if (!isClusterReplicate) { logger.error("{}:{} clusterReplicate {} is failed ", slaveHost, slavePort, nodeId); return false; } //保存配置 masterJedis.clusterSaveConfig(); slaveJedis.clusterSaveConfig(); redisCenter.configRewrite(appId, masterHost, masterPort); redisCenter.configRewrite(appId, slaveHost, slavePort); } finally { masterJedis.close(); slaveJedis.close(); } } else { boolean isSlave = slaveOf(appId, masterHost, masterPort, slaveHost, slavePort); if (!isSlave) { logger.error("{}:{} sync {}:{} is error", slaveHost, slavePort, masterHost, masterPort); return false; } } //写入instanceInfo 信息 if (TypeUtil.isRedisCluster(type)) { saveInstance(appId, slaveHost, slavePort, instanceInfo.getMem(), ConstUtils.CACHE_TYPE_REDIS_CLUSTER, ""); } else { saveInstance(appId, slaveHost, slavePort, instanceInfo.getMem(), ConstUtils.CACHE_REDIS_STANDALONE, ""); } //启动监控trigger boolean isDeploy = redisCenter.deployRedisCollection(appId, slaveHost, slavePort); if (!isDeploy) { logger.warn("host={},port={},isMasterDeploy=false", slaveHost, slavePort); } return true; } @Override public boolean sentinelFailover(final long appId) throws Exception { Assert.isTrue(appId > 0); AppDesc appDesc = appDao.getAppDescById(appId); Assert.isTrue(appDesc != null); int type = appDesc.getType(); if (!TypeUtil.isRedisSentinel(type)) { logger.warn("app={} is not sentinel", appDesc); return false; } final List<InstanceInfo> instanceList = instanceDao.getInstListByAppId(appId); if (instanceList == null || instanceList.isEmpty()) { logger.warn("app={} instances is empty"); return false; } for (InstanceInfo instanceInfo : instanceList) { int instanceType = instanceInfo.getType(); if (TypeUtil.isRedisSentinel(instanceType)) { final String host = instanceInfo.getIp(); final int port = instanceInfo.getPort(); final String masterName = instanceInfo.getCmd(); if (StringUtils.isBlank(masterName)) { logger.warn("{} cmd is null", instanceInfo); continue; } boolean isRun = redisCenter.isRun(host, port); if (!isRun) { logger.warn("{} is not run"); continue; } boolean isSentinelFailOver = new IdempotentConfirmer() { @Override public boolean execute() { Jedis jedis = redisCenter.getJedis(host, port); try { String response = jedis.sentinelFailover(masterName); return response != null && response.equalsIgnoreCase("OK"); } finally { jedis.close(); } } }.run(); if (!isSentinelFailOver) { logger.warn("{}:{} sentienl isSentinelFailOver error", host, port); return false; } else { logger.warn("SentinelFailOver done! "); break; } } } return true; } @Override public boolean clusterFailover(long appId, int slaveInstanceId) throws Exception { Assert.isTrue(appId > 0); Assert.isTrue(slaveInstanceId > 0); AppDesc appDesc = appDao.getAppDescById(appId); Assert.isTrue(appDesc != null); int type = appDesc.getType(); if (!TypeUtil.isRedisCluster(type)) { logger.error("{} is not redis type", appDesc); return false; } InstanceInfo instanceInfo = instanceDao.getInstanceInfoById(slaveInstanceId); Assert.isTrue(instanceInfo != null); String slaveHost = instanceInfo.getIp(); int slavePort = instanceInfo.getPort(); final Jedis slaveJedis = redisCenter.getJedis(appId, slaveHost, slavePort); boolean isClusterFailOver = new IdempotentConfirmer() { @Override public boolean execute() { String response = slaveJedis.clusterFailoverForce(); return response != null && response.equalsIgnoreCase("OK"); } }.run(); if (!isClusterFailOver) { logger.error("{}:{} clusterFailover failed", slaveHost, slavePort); return false; } else { logger.warn("{}:{} clusterFailover Done! ", slaveHost, slavePort); } return true; } /** * 拷贝redis配置 * * @param sourceHost * @param sourcePort * @param targetHost * @param targetPort * @return */ private boolean copyCommonConfig(long appId, String sourceHost, int sourcePort, String targetHost, int targetPort) { String[] compareConfigs = new String[] {"maxmemory-policy", "maxmemory", "cluster-node-timeout", "cluster-require-full-coverage", "repl-backlog-size", "appendonly", "hash-max-ziplist-entries", "hash-max-ziplist-value", "list-max-ziplist-entries", "list-max-ziplist-value", "set-max-intset-entries", "zset-max-ziplist-entries", "zset-max-ziplist-value"}; try { for (String config : compareConfigs) { String sourceValue = getConfigValue(appId, sourceHost, sourcePort, config); if (StringUtils.isBlank(sourceValue)) { continue; } String targetValue = getConfigValue(appId, targetHost, targetPort, config); if (StringUtils.isNotBlank(targetHost)) { if (!targetValue.equals(sourceValue)) { this.modifyInstanceConfig(appId, targetHost, targetPort, config, sourceValue); } } } return true; } catch (Exception e) { logger.error(e.getMessage(), e); return false; } } private String getConfigValue(long appId, String host, int port, String key) { Jedis jedis = redisCenter.getJedis(appId, host, port, Protocol.DEFAULT_TIMEOUT * 3, Protocol.DEFAULT_TIMEOUT * 3); try { List<String> values = jedis.configGet(key); if (values == null || values.size() < 1) { return null; } return values.get(1); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { jedis.close(); } } public void setInstanceDao(InstanceDao instanceDao) { this.instanceDao = instanceDao; } public void setMachineCenter(MachineCenter machineCenter) { this.machineCenter = machineCenter; } public void setMachineDao(MachineDao machineDao) { this.machineDao = machineDao; } public void setRedisCenter(RedisCenter redisCenter) { this.redisCenter = redisCenter; } public void setAppDao(AppDao appDao) { this.appDao = appDao; } public void setRedisConfigTemplateService(RedisConfigTemplateService redisConfigTemplateService) { this.redisConfigTemplateService = redisConfigTemplateService; } }