package com.sohu.cache.redis.impl;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.sohu.cache.async.AsyncService;
import com.sohu.cache.async.AsyncThreadPoolFactory;
import com.sohu.cache.async.KeyCallable;
import com.sohu.cache.constant.*;
import com.sohu.cache.dao.*;
import com.sohu.cache.entity.*;
import com.sohu.cache.machine.MachineCenter;
import com.sohu.cache.protocol.RedisProtocol;
import com.sohu.cache.redis.RedisCenter;
import com.sohu.cache.redis.enums.RedisReadOnlyCommandEnum;
import com.sohu.cache.schedule.SchedulerCenter;
import com.sohu.cache.stats.instance.InstanceStatsCenter;
import com.sohu.cache.util.*;
import com.sohu.cache.web.util.DateUtil;
import com.sohu.cache.web.vo.RedisSlowLog;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.quartz.JobKey;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.util.*;
import java.sql.Timestamp;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by yijunzhang on 14-6-10.
*/
public class RedisCenterImpl implements RedisCenter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public static final int REDIS_DEFAULT_TIME = 4000;
private SchedulerCenter schedulerCenter;
private AppStatsDao appStatsDao;
private AsyncService asyncService;
private InstanceDao instanceDao;
private InstanceStatsDao instanceStatsDao;
private InstanceStatsCenter instanceStatsCenter;
private MachineCenter machineCenter;
private volatile Map<String, JedisPool> jedisPoolMap = new HashMap<String, JedisPool>();
private final Lock lock = new ReentrantLock();
private AppDao appDao;
private AppAuditLogDao appAuditLogDao;
public static final String REDIS_SLOWLOG_POOL = "redis-slowlog-pool";
public void init() {
asyncService.assemblePool(getThreadPoolKey(), AsyncThreadPoolFactory.REDIS_SLOWLOG_THREAD_POOL);
}
private InstanceSlowLogDao instanceSlowLogDao;
@Override
public boolean deployRedisCollection(long appId, String host, int port) {
Assert.isTrue(appId > 0);
Assert.hasText(host);
Assert.isTrue(port > 0);
Map<String, Object> dataMap = new HashMap<String, Object>();
dataMap.put(ConstUtils.HOST_KEY, host);
dataMap.put(ConstUtils.PORT_KEY, port);
dataMap.put(ConstUtils.APP_KEY, appId);
JobKey jobKey = JobKey.jobKey(ConstUtils.REDIS_JOB_NAME, ConstUtils.REDIS_JOB_GROUP);
TriggerKey triggerKey = TriggerKey
.triggerKey(ObjectConvert.linkIpAndPort(host, port), ConstUtils.REDIS_TRIGGER_GROUP + appId);
return schedulerCenter
.deployJobByCron(jobKey, triggerKey, dataMap, ScheduleUtil.getMinuteCronByAppId(appId), false);
}
private JedisPool maintainJedisPool(String host, int port, String password) {
String hostAndPort = ObjectConvert.linkIpAndPort(host, port);
JedisPool jedisPool = jedisPoolMap.get(hostAndPort);
if (jedisPool == null) {
lock.lock();
try {
//double check
jedisPool = jedisPoolMap.get(hostAndPort);
if (jedisPool == null) {
try {
if (StringUtils.isNotBlank(password)) {
jedisPool = new JedisPool(new GenericObjectPoolConfig(), host, port, Protocol.DEFAULT_TIMEOUT, password);
} else {
jedisPool = new JedisPool(new GenericObjectPoolConfig(), host, port, Protocol.DEFAULT_TIMEOUT);
}
jedisPoolMap.put(hostAndPort, jedisPool);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
}
}
} finally {
lock.unlock();
}
}
return jedisPool;
}
@Override
public boolean unDeployRedisCollection(long appId, String host, int port) {
Assert.isTrue(appId > 0);
Assert.hasText(host);
Assert.isTrue(port > 0);
TriggerKey triggerKey = TriggerKey
.triggerKey(ObjectConvert.linkIpAndPort(host, port), ConstUtils.REDIS_TRIGGER_GROUP + appId);
Trigger trigger = schedulerCenter.getTrigger(triggerKey);
if (trigger == null) {
return true;
}
return schedulerCenter.unscheduleJob(triggerKey);
}
private String buildFutureKey(long appId, long collectTime, String host, int port) {
StringBuilder keyBuffer = new StringBuilder("redis-");
keyBuffer.append(collectTime);
keyBuffer.append("-");
keyBuffer.append(appId);
keyBuffer.append("-");
keyBuffer.append(host + ":" + port);
return keyBuffer.toString();
}
private class RedisKeyCallable extends KeyCallable<Boolean> {
private final long appId;
private final long collectTime;
private final String host;
private final int port;
private final Map<RedisConstant, Map<String, Object>> infoMap;
private RedisKeyCallable(long appId, long collectTime, String host, int port,
Map<RedisConstant, Map<String, Object>> infoMap) {
super(buildFutureKey(appId, collectTime, host, port));
this.appId = appId;
this.collectTime = collectTime;
this.host = host;
this.port = port;
this.infoMap = infoMap;
}
@Override
public Boolean execute() {
//比对currentInfoMap和lastInfoMap,计算差值
long lastCollectTime = ScheduleUtil.getLastCollectTime(collectTime);
Map<String, Object> lastInfoMap = instanceStatsCenter
.queryStandardInfoMap(lastCollectTime, host, port, ConstUtils.REDIS);
if (lastInfoMap == null || lastInfoMap.isEmpty()) {
logger.error("[redis-lastInfoMap] : lastCollectTime = {} appId={} host:port = {}:{} is null",
lastCollectTime, appId, host, port);
}
//基本统计累加差值
Table<RedisConstant, String, Long> baseDiffTable = getAccumulationDiff(infoMap, lastInfoMap);
fillAccumulationMap(infoMap, baseDiffTable);
//命令累加差值
Table<RedisConstant, String, Long> commandDiffTable = getCommandsDiff(infoMap, lastInfoMap);
fillAccumulationMap(infoMap, commandDiffTable);
Map<String, Object> currentInfoMap = new LinkedHashMap<String, Object>();
for (RedisConstant constant : infoMap.keySet()) {
currentInfoMap.put(constant.getValue(), infoMap.get(constant));
}
currentInfoMap.put(ConstUtils.COLLECT_TIME, collectTime);
instanceStatsCenter.saveStandardStats(currentInfoMap, host, port, ConstUtils.REDIS);
// 更新实例在db中的状态
InstanceStats instanceStats = getInstanceStats(appId, host, port, infoMap);
if (instanceStats != null) {
instanceStatsDao.updateInstanceStats(instanceStats);
}
boolean isMaster = isMaster(infoMap);
if (isMaster) {
Table<RedisConstant, String, Long> diffTable = HashBasedTable.create();
diffTable.putAll(baseDiffTable);
diffTable.putAll(commandDiffTable);
long allCommandCount = 0L;
//更新命令统计
List<AppCommandStats> commandStatsList = getCommandStatsList(appId, collectTime, diffTable);
for (AppCommandStats commandStats : commandStatsList) {
//排除无效命令且存储有累加的数据
if (RedisExcludeCommand.isExcludeCommand(commandStats.getCommandName())
|| commandStats.getCommandCount() <= 0L) {
continue;
}
allCommandCount += commandStats.getCommandCount();
try {
appStatsDao.mergeMinuteCommandStatus(commandStats);
appStatsDao.mergeHourCommandStatus(commandStats);
} catch (Exception e) {
logger.error(e.getMessage() + appId, e);
}
}
//写入app分钟统计
AppStats appStats = getAppStats(appId, collectTime, diffTable, infoMap);
try {
appStats.setCommandCount(allCommandCount);
appStatsDao.mergeMinuteAppStats(appStats);
appStatsDao.mergeHourAppStats(appStats);
} catch (Exception e) {
logger.error(e.getMessage() + appId, e);
}
logger.info("collect redis info done, appId: {}, instance: {}:{}, time: {}", appId, host, port,
collectTime);
}
return true;
}
}
@Override
public List<InstanceSlowLog> collectRedisSlowLog(long appId, long collectTime, String host, int port) {
Assert.isTrue(appId > 0);
Assert.hasText(host);
Assert.isTrue(port > 0);
InstanceInfo instanceInfo = instanceDao.getInstByIpAndPort(host, port);
//不存在实例/实例异常/下线
if (instanceInfo == null) {
return null;
}
if (TypeUtil.isRedisSentinel(instanceInfo.getType())) {
//忽略sentinel redis实例
return null;
}
// 从redis中获取慢查询日志
List<RedisSlowLog> redisLowLogList = getRedisSlowLogs(appId, host, port, 100);
if (CollectionUtils.isEmpty(redisLowLogList)) {
return Collections.emptyList();
}
// transfer
final List<InstanceSlowLog> instanceSlowLogList = new ArrayList<InstanceSlowLog>();
for (RedisSlowLog redisSlowLog : redisLowLogList) {
InstanceSlowLog instanceSlowLog = transferRedisSlowLogToInstance(redisSlowLog, instanceInfo);
if (instanceSlowLog == null) {
continue;
}
instanceSlowLogList.add(instanceSlowLog);
}
if (CollectionUtils.isEmpty(instanceSlowLogList)) {
return Collections.emptyList();
}
//处理
String key = getThreadPoolKey() + "_" + host + "_" + port;
boolean isOk = asyncService.submitFuture(getThreadPoolKey(), new KeyCallable<Boolean>(key) {
@Override
public Boolean execute() {
try {
instanceSlowLogDao.batchSave(instanceSlowLogList);
return true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
}
});
if (!isOk) {
logger.error("slowlog submitFuture failed,appId:{},collectTime:{},host:{},ip:{}", appId, collectTime, host, port);
}
return instanceSlowLogList;
}
private InstanceSlowLog transferRedisSlowLogToInstance(RedisSlowLog redisSlowLog, InstanceInfo instanceInfo) {
if (redisSlowLog == null) {
return null;
}
String command = redisSlowLog.getCommand();
long executionTime = redisSlowLog.getExecutionTime();
//如果command=BGREWRITEAOF并且小于50毫秒,则忽略
if (command.equalsIgnoreCase("BGREWRITEAOF") && executionTime < 50000) {
return null;
}
InstanceSlowLog instanceSlowLog = new InstanceSlowLog();
instanceSlowLog.setAppId(instanceInfo.getAppId());
instanceSlowLog.setCommand(redisSlowLog.getCommand());
instanceSlowLog.setCostTime((int) redisSlowLog.getExecutionTime());
instanceSlowLog.setCreateTime(new Timestamp(System.currentTimeMillis()));
instanceSlowLog.setExecuteTime(new Timestamp(redisSlowLog.getDate().getTime()));
instanceSlowLog.setInstanceId(instanceInfo.getId());
instanceSlowLog.setIp(instanceInfo.getIp());
instanceSlowLog.setPort(instanceInfo.getPort());
instanceSlowLog.setSlowLogId(redisSlowLog.getId());
return instanceSlowLog;
}
private String getThreadPoolKey() {
return REDIS_SLOWLOG_POOL;
}
@Override
public Map<RedisConstant, Map<String, Object>> collectRedisInfo(long appId, long collectTime, String host,
int port) {
Assert.isTrue(appId > 0);
Assert.hasText(host);
Assert.isTrue(port > 0);
long start = System.currentTimeMillis();
InstanceInfo instanceInfo = instanceDao.getInstByIpAndPort(host, port);
//不存在实例/实例异常/下线
if (instanceInfo == null) {
return null;
}
if (TypeUtil.isRedisSentinel(instanceInfo.getType())) {
//忽略sentinel redis实例
return null;
}
Map<RedisConstant, Map<String, Object>> infoMap = this.getInfoStats(appId, host, port);
if (infoMap == null || infoMap.isEmpty()) {
logger.error("appId:{},collectTime:{},host:{},ip:{} cost={} ms redis infoMap is null",
new Object[]{appId, collectTime, host, port, (System.currentTimeMillis() - start)});
return infoMap;
}
boolean isOk = asyncService.submitFuture(new RedisKeyCallable(appId, collectTime, host, port, infoMap));
if (!isOk) {
logger.error("submitFuture failed,appId:{},collectTime:{},host:{},ip:{} cost={} ms",
new Object[]{appId, collectTime, host, port, (System.currentTimeMillis() - start)});
}
return infoMap;
}
@Override
public Map<RedisConstant, Map<String, Object>> getInfoStats(final long appId, final String host, final int port) {
Map<RedisConstant, Map<String, Object>> infoMap = null;
final StringBuilder infoBuilder = new StringBuilder();
try {
boolean isOk = new IdempotentConfirmer() {
private int timeOutFactor = 1;
@Override
public boolean execute() {
Jedis jedis = null;
try {
jedis = getJedis(appId, host, port);
jedis.getClient().setConnectionTimeout(REDIS_DEFAULT_TIME * (timeOutFactor++));
jedis.getClient().setSoTimeout(REDIS_DEFAULT_TIME * (timeOutFactor++));
String info = jedis.info("all");
infoBuilder.append(info);
return StringUtils.isNotBlank(info);
} catch (Exception e) {
logger.warn("{}:{}, redis-getInfoStats errorMsg:{}", host, port, e.getMessage());
return false;
} finally {
if (jedis != null)
jedis.close();
}
}
}.run();
if (!isOk) {
return infoMap;
}
infoMap = processRedisStats(infoBuilder.toString());
} catch (Exception e) {
logger.error(e.getMessage() + " {}:{}", host, port, e);
}
if (infoMap == null || infoMap.isEmpty()) {
logger.error("host:{},ip:{} redis infoMap is null", host, port);
return infoMap;
}
return infoMap;
}
private void fillAccumulationMap(Map<RedisConstant, Map<String, Object>> infoMap,
Table<RedisConstant, String, Long> table) {
Map<String, Object> accMap = infoMap.get(RedisConstant.DIFF);
if (table == null || table.isEmpty()) {
return;
}
if (accMap == null) {
accMap = new LinkedHashMap<String, Object>();
infoMap.put(RedisConstant.DIFF, accMap);
}
for (RedisConstant constant : table.rowKeySet()) {
Map<String, Long> rowMap = table.row(constant);
accMap.putAll(rowMap);
}
}
/**
* 获取累加参数值
*
* @param currentInfoMap
* @return 累加差值map
*/
private Table<RedisConstant, String, Long> getAccumulationDiff(
Map<RedisConstant, Map<String, Object>> currentInfoMap,
Map<String, Object> lastInfoMap) {
//没有上一次统计快照,忽略差值统计
if (lastInfoMap == null || lastInfoMap.isEmpty()) {
return HashBasedTable.create();
}
Map<RedisAccumulation, Long> currentMap = new LinkedHashMap<RedisAccumulation, Long>();
for (RedisAccumulation acc : RedisAccumulation.values()) {
Long count = getCommonCount(currentInfoMap, acc.getConstant(), acc.getValue());
if (count != null) {
currentMap.put(acc, count);
}
}
Map<RedisAccumulation, Long> lastMap = new LinkedHashMap<RedisAccumulation, Long>();
for (RedisAccumulation acc : RedisAccumulation.values()) {
if (lastInfoMap != null) {
Long lastCount = getCommonCount(lastInfoMap, acc.getConstant(), acc.getValue());
if (lastCount != null) {
lastMap.put(acc, lastCount);
}
}
}
Table<RedisConstant, String, Long> resultTable = HashBasedTable.create();
for (RedisAccumulation key : currentMap.keySet()) {
Long value = MapUtils.getLong(currentMap, key, null);
Long lastValue = MapUtils.getLong(lastMap, key, null);
if (value == null || lastValue == null) {
//忽略
continue;
}
long diff = 0L;
if (value > lastValue) {
diff = value - lastValue;
}
resultTable.put(key.getConstant(), key.getValue(), diff);
}
return resultTable;
}
/**
* 获取命令差值统计
*
* @param currentInfoMap
* @param lastInfoMap
* @return 命令统计
*/
private Table<RedisConstant, String, Long> getCommandsDiff(Map<RedisConstant, Map<String, Object>> currentInfoMap,
Map<String, Object> lastInfoMap) {
//没有上一次统计快照,忽略差值统计
if (lastInfoMap == null || lastInfoMap.isEmpty()) {
return HashBasedTable.create();
}
Map<String, Object> map = currentInfoMap.get(RedisConstant.Commandstats);
Map<String, Long> currentMap = transferLongMap(map);
Map<String, Object> lastObjectMap;
if (lastInfoMap.get(RedisConstant.Commandstats.getValue()) == null) {
lastObjectMap = new HashMap<String, Object>();
} else {
lastObjectMap = (Map<String, Object>) lastInfoMap.get(RedisConstant.Commandstats.getValue());
}
Map<String, Long> lastMap = transferLongMap(lastObjectMap);
Table<RedisConstant, String, Long> resultTable = HashBasedTable.create();
for (String command : currentMap.keySet()) {
long lastCount = MapUtils.getLong(lastMap, command, 0L);
long currentCount = MapUtils.getLong(currentMap, command, 0L);
if (currentCount > lastCount) {
resultTable.put(RedisConstant.Commandstats, command, currentCount - lastCount);
}
}
return resultTable;
}
private AppStats getAppStats(long appId, long collectTime, Table<RedisConstant, String, Long> table,
Map<RedisConstant, Map<String, Object>> infoMap) {
AppStats appStats = new AppStats();
appStats.setAppId(appId);
appStats.setCollectTime(collectTime);
appStats.setModifyTime(new Date());
appStats.setUsedMemory(MapUtils.getLong(infoMap.get(RedisConstant.Memory), "used_memory", 0L));
appStats.setHits(MapUtils.getLong(table.row(RedisConstant.Stats), "keyspace_hits", 0L));
appStats.setMisses(MapUtils.getLong(table.row(RedisConstant.Stats), "keyspace_misses", 0L));
appStats.setEvictedKeys(MapUtils.getLong(table.row(RedisConstant.Stats), "evicted_keys", 0L));
appStats.setExpiredKeys(MapUtils.getLong(table.row(RedisConstant.Stats), "expired_keys", 0L));
appStats.setNetInputByte(MapUtils.getLong(table.row(RedisConstant.Stats), "total_net_input_bytes", 0L));
appStats.setNetOutputByte(MapUtils.getLong(table.row(RedisConstant.Stats), "total_net_output_bytes", 0L));
appStats.setConnectedClients(MapUtils.getIntValue(infoMap.get(RedisConstant.Clients), "connected_clients", 0));
appStats.setObjectSize(getObjectSize(infoMap));
return appStats;
}
private long getObjectSize(Map<RedisConstant, Map<String, Object>> currentInfoMap) {
Map<String, Object> sizeMap = currentInfoMap.get(RedisConstant.Keyspace);
if (sizeMap == null || sizeMap.isEmpty()) {
return 0L;
}
long result = 0L;
Map<String, Long> longSizeMap = transferLongMap(sizeMap);
for (String key : longSizeMap.keySet()) {
result += longSizeMap.get(key);
}
return result;
}
private Long getCommonCount(Map<?, ?> infoMap, RedisConstant redisConstant, String commond) {
Object constantObject =
infoMap.get(redisConstant) == null ? infoMap.get(redisConstant.getValue()) : infoMap.get(redisConstant);
if (constantObject != null && (constantObject instanceof Map)) {
Map constantMap = (Map) constantObject;
if (constantMap == null || constantMap.get(commond) == null) {
return null;
}
return MapUtils.getLongValue(constantMap, commond);
}
return null;
}
/**
* 转换redis 命令行统计结果
*
* @param commandMap
* @return
*/
private Map<String, Long> transferLongMap(Map<String, Object> commandMap) {
Map<String, Long> resultMap = new HashMap<String, Long>();
if (commandMap == null || commandMap.isEmpty()) {
return resultMap;
}
for (String key : commandMap.keySet()) {
if (commandMap.get(key) == null) {
continue;
}
String value = commandMap.get(key).toString();
String[] stats = value.split(",");
if (stats.length == 0) {
continue;
}
String[] calls = stats[0].split("=");
if (calls == null || calls.length < 2) {
continue;
}
long callCount = Long.valueOf(calls[1]);
resultMap.put(key, callCount);
}
return resultMap;
}
private List<AppCommandStats> getCommandStatsList(long appId, long collectTime,
Table<RedisConstant, String, Long> table) {
Map<String, Long> commandMap = table.row(RedisConstant.Commandstats);
List<AppCommandStats> list = new ArrayList<AppCommandStats>();
if (commandMap == null) {
return list;
}
for (String key : commandMap.keySet()) {
String commandName = key.replace("cmdstat_", "");
long callCount = MapUtils.getLong(commandMap, key, 0L);
if (callCount == 0L) {
continue;
}
AppCommandStats commandStats = new AppCommandStats();
commandStats.setAppId(appId);
commandStats.setCollectTime(collectTime);
commandStats.setCommandName(commandName);
commandStats.setCommandCount(callCount);
commandStats.setModifyTime(new Date());
list.add(commandStats);
}
return list;
}
/**
* 处理redis统计信息
*
* @param statResult 统计结果串
*/
private Map<RedisConstant, Map<String, Object>> processRedisStats(String statResult) {
Map<RedisConstant, Map<String, Object>> redisStatMap = new HashMap<RedisConstant, Map<String, Object>>();
String[] data = statResult.split("\r\n");
String key;
int i = 0;
int length = data.length;
while (i < length) {
if (data[i].contains("#")) {
int index = data[i].indexOf('#');
key = data[i].substring(index + 1);
++i;
RedisConstant redisConstant = RedisConstant.value(key.trim());
if (redisConstant == null) {
continue;
}
Map<String, Object> sectionMap = new LinkedHashMap<String, Object>();
while (i < length && data[i].contains(":")) {
String[] pair = data[i].split(":");
sectionMap.put(pair[0], pair[1]);
i++;
}
redisStatMap.put(redisConstant, sectionMap);
} else {
i++;
}
}
return redisStatMap;
}
/**
* 根据infoMap的结果判断实例的主从
*
* @param infoMap
* @return
*/
private Boolean isMaster(Map<RedisConstant, Map<String, Object>> infoMap) {
Map<String, Object> map = infoMap.get(RedisConstant.Replication);
if (map == null || map.get("role") == null) {
return null;
}
if (String.valueOf(map.get("role")).equals("master")) {
return true;
}
return false;
}
/**
* 根据ip和port判断某一个实例当前是主还是从
*
* @param ip ip
* @param port port
* @return 主返回true, 从返回false;
*/
@Override
public Boolean isMaster(long appId, String ip, int port) {
Jedis jedis = getJedis(appId, ip, port, REDIS_DEFAULT_TIME, REDIS_DEFAULT_TIME);
try {
String info = jedis.info("all");
Map<RedisConstant, Map<String, Object>> infoMap = processRedisStats(info);
return isMaster(infoMap);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return null;
} finally {
jedis.close();
}
}
@Override
public HostAndPort getMaster(String ip, int port, String password) {
JedisPool jedisPool = maintainJedisPool(ip, port, password);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String info = jedis.info(RedisConstant.Replication.getValue());
Map<RedisConstant, Map<String, Object>> infoMap = processRedisStats(info);
Map<String, Object> map = infoMap.get(RedisConstant.Replication);
if (map == null) {
return null;
}
String masterHost = MapUtils.getString(map, "master_host", null);
int masterPort = MapUtils.getInteger(map, "master_port", 0);
if (StringUtils.isNotBlank(masterHost) && masterPort > 0) {
return new HostAndPort(masterHost, masterPort);
}
return null;
} catch (Exception e) {
logger.error("{}:{} getMaster failed {}", ip, port, e.getMessage(), e);
return null;
} finally {
if (jedis != null)
jedis.close();
}
}
@Override
public boolean isRun(final String ip, final int port) {
return isRun(ip, port, null);
}
@Override
public boolean isRun(final long appId, final String ip, final int port) {
AppDesc appDesc = appDao.getAppDescById(appId);
return isRun(ip, port, appDesc.getPassword());
}
@Override
public boolean isRun(final String ip, final int port, final String password) {
boolean isRun = new IdempotentConfirmer() {
private int timeOutFactor = 1;
@Override
public boolean execute() {
Jedis jedis = null;
try {
jedis = getJedis(ip, port, password);
jedis.getClient().setConnectionTimeout(Protocol.DEFAULT_TIMEOUT * (timeOutFactor++));
jedis.getClient().setSoTimeout(Protocol.DEFAULT_TIMEOUT * (timeOutFactor++));
String pong = jedis.ping();
return pong != null && pong.equalsIgnoreCase("PONG");
} catch (JedisDataException e) {
String message = e.getMessage();
logger.warn(e.getMessage());
if (StringUtils.isNotBlank(message) && message.startsWith("LOADING")) {
return true;
}
return false;
} catch (Exception e) {
logger.warn("{}:{} error message is {} ", ip, port, e.getMessage());
return false;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}.run();
return isRun;
}
@Override
public boolean shutdown(long appId, String ip, int port) {
boolean isRun = isRun(appId, ip, port);
if (!isRun) {
return true;
}
final Jedis jedis = getJedis(appId, ip, port);
try {
//关闭实例节点
boolean isShutdown = new IdempotentConfirmer() {
@Override
public boolean execute() {
jedis.shutdown();
return true;
}
}.run();
if (!isShutdown) {
logger.error("{}:{} redis not shutdown!", ip, port);
}
return isShutdown;
} finally {
jedis.close();
}
}
@Override
public boolean shutdown(String ip, int port) {
boolean isRun = isRun(ip, port);
if (!isRun) {
return true;
}
final Jedis jedis = getJedis(ip, port);
try {
//关闭实例节点
boolean isShutdown = new IdempotentConfirmer() {
@Override
public boolean execute() {
jedis.shutdown();
return true;
}
}.run();
if (!isShutdown) {
logger.error("{}:{} redis not shutdown!", ip, port);
}
return isShutdown;
} finally {
jedis.close();
}
}
/**
* 返回当前实例的一些关键指标
*
* @param appId
* @param ip
* @param port
* @param infoMap
* @return
*/
public InstanceStats getInstanceStats(long appId, String ip, int port,
Map<RedisConstant, Map<String, Object>> infoMap) {
if (infoMap == null) {
return null;
}
// 查询最大内存限制
Long maxMemory = this.getRedisMaxMemory(appId, ip, port);
/**
* 将实例的一些关键指标返回
*/
InstanceStats instanceStats = new InstanceStats();
instanceStats.setAppId(appId);
InstanceInfo curInst = instanceDao.getInstByIpAndPort(ip, port);
if (curInst != null) {
instanceStats.setHostId(curInst.getHostId());
instanceStats.setInstId(curInst.getId());
} else {
logger.error("redis={}:{} not found", ip, port);
return null;
}
instanceStats.setIp(ip);
instanceStats.setPort(port);
if (maxMemory != null) {
instanceStats.setMaxMemory(maxMemory);
}
instanceStats.setUsedMemory(MapUtils.getLongValue(infoMap.get(RedisConstant.Memory), "used_memory", 0));
instanceStats.setHits(MapUtils.getLongValue(infoMap.get(RedisConstant.Stats), "keyspace_hits", 0));
instanceStats.setMisses(MapUtils.getLongValue(infoMap.get(RedisConstant.Stats), "keyspace_misses", 0));
instanceStats
.setCurrConnections(MapUtils.getIntValue(infoMap.get(RedisConstant.Clients), "connected_clients", 0));
instanceStats.setCurrItems(getObjectSize(infoMap));
instanceStats.setRole((byte) 1);
if (MapUtils.getString(infoMap.get(RedisConstant.Replication), "role").equals("slave")) {
instanceStats.setRole((byte) 2);
}
instanceStats.setModifyTime(new Timestamp(System.currentTimeMillis()));
instanceStats.setMemFragmentationRatio(MapUtils.getDoubleValue(infoMap.get(RedisConstant.Memory), "mem_fragmentation_ratio", 0.0));
instanceStats.setAofDelayedFsync(MapUtils.getIntValue(infoMap.get(RedisConstant.Persistence), "aof_delayed_fsync", 0));
return instanceStats;
}
@Override
public Long getRedisMaxMemory(final long appId, final String ip, final int port) {
final String key = "maxmemory";
final Map<String, Long> resultMap = new HashMap<String, Long>();
boolean isSuccess = new IdempotentConfirmer() {
private int timeOutFactor = 1;
@Override
public boolean execute() {
Jedis jedis = null;
try {
jedis = getJedis(appId, ip, port);
jedis.getClient().setConnectionTimeout(REDIS_DEFAULT_TIME * (timeOutFactor++));
jedis.getClient().setSoTimeout(REDIS_DEFAULT_TIME * (timeOutFactor++));
List<String> maxMemoryList = jedis.configGet(key); // 返回结果:list中是2个字符串,如:"maxmemory",
// "4096000000"
if (maxMemoryList != null && maxMemoryList.size() >= 2) {
resultMap.put(key, Long.valueOf(maxMemoryList.get(1)));
}
return MapUtils.isNotEmpty(resultMap);
} catch (Exception e) {
logger.warn("{}:{} errorMsg: {}", ip, port, e.getMessage());
return false;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}.run();
if (isSuccess) {
return MapUtils.getLong(resultMap, key);
} else {
logger.error("{}:{} getMaxMemory failed!", ip, port);
return null;
}
}
@Override
public String executeCommand(AppDesc appDesc, String command) {
//非测试应用只能执行白名单里面的命令
if (AppDescEnum.AppTest.NOT_TEST.getValue() == appDesc.getIsTest()) {
if (!RedisReadOnlyCommandEnum.contains(command)) {
return "online app only support read-only and safe command";
}
}
int type = appDesc.getType();
long appId = appDesc.getAppId();
if (type == ConstUtils.CACHE_REDIS_SENTINEL) {
JedisSentinelPool jedisSentinelPool = getJedisSentinelPool(appDesc);
if (jedisSentinelPool == null) {
return "sentinel can not execute ";
}
Jedis jedis = null;
try {
jedis = jedisSentinelPool.getResource();
String host = jedis.getClient().getHost();
int port = jedis.getClient().getPort();
return executeCommand(appId, host, port, command);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return "运行出错:" + e.getMessage();
} finally {
if (jedis != null) jedis.close();
jedisSentinelPool.destroy();
}
} else if (type == ConstUtils.CACHE_REDIS_STANDALONE) {
List<InstanceInfo> instanceList = instanceDao.getInstListByAppId(appId);
if (instanceList == null || instanceList.isEmpty()) {
return "应用没有运行的实例";
}
String host = null;
int port = 0;
for (InstanceInfo instanceInfo : instanceList) {
host = instanceInfo.getIp();
port = instanceInfo.getPort();
break;
}
try {
return executeCommand(appId, host, port, command);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return "运行出错:" + e.getMessage();
}
} else if (type == ConstUtils.CACHE_TYPE_REDIS_CLUSTER) {
List<InstanceInfo> instanceList = instanceDao.getInstListByAppId(appId);
if (instanceList == null || instanceList.isEmpty()) {
return "应用没有运行的实例";
}
Set<HostAndPort> clusterHosts = new LinkedHashSet<HostAndPort>();
for (InstanceInfo instance : instanceList) {
if (instance == null || instance.getStatus() == InstanceStatusEnum.OFFLINE_STATUS.getStatus()) {
continue;
}
clusterHosts.add(new HostAndPort(instance.getIp(), instance.getPort()));
}
if (clusterHosts.isEmpty()) {
return "no run instance";
}
String host = null;
int port = 0;
JedisCluster jedisCluster = new JedisCluster(clusterHosts, REDIS_DEFAULT_TIME);
try {
String commandKey = getCommandKey(command);
int slot;
if (StringUtils.isBlank(commandKey)) {
slot = 0;
} else {
slot = JedisClusterCRC16.getSlot(commandKey);
}
JedisPool jedisPool = jedisCluster.getConnectionHandler().getJedisPoolFromSlot(slot);
host = jedisPool.getHost();
port = jedisPool.getPort();
} finally {
jedisCluster.close();
}
try {
return executeCommand(appId, host, port, command);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return "运行出错:" + e.getMessage();
}
}
return "不支持应用类型";
}
private String getCommandKey(String command) {
String[] array = StringUtils.trim(command).split("\\s+");
if (array.length > 1) {
return array[1];
} else {
return null;
}
}
@Override
public String executeCommand(long appId, String host, int port, String command) {
AppDesc appDesc = appDao.getAppDescById(appId);
if (appDesc == null) {
return "not exist appId";
}
//非测试应用只能执行白名单里面的命令
if (AppDescEnum.AppTest.NOT_TEST.getValue() == appDesc.getIsTest()) {
if (!RedisReadOnlyCommandEnum.contains(command)) {
return "online app only support read-only and safe command ";
}
}
String password = appDesc.getPassword();
String shell = RedisProtocol.getExecuteCommandShell(host, port, password, command);
//记录客户端发送日志
logger.warn("executeRedisShell={}", shell);
return machineCenter.executeShell(host, shell);
}
@Override
public JedisSentinelPool getJedisSentinelPool(AppDesc appDesc) {
if (appDesc == null) {
logger.error("appDes is null");
return null;
}
if (appDesc.getType() != ConstUtils.CACHE_REDIS_SENTINEL) {
logger.error("type={} is not sentinel", appDesc.getType());
return null;
}
long appId = appDesc.getAppId();
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();
}
}
Set<String> sentinels = new HashSet<String>();
for (InstanceInfo instanceInfo : instanceInfos) {
sentinels.add(instanceInfo.getIp() + ":" + instanceInfo.getPort());
}
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels);
return jedisSentinelPool;
}
@Override
public Map<String, String> getRedisConfigList(int instanceId) {
if (instanceId <= 0) {
return Collections.emptyMap();
}
InstanceInfo instanceInfo = instanceDao.getInstanceInfoById(instanceId);
if (instanceInfo == null) {
return Collections.emptyMap();
}
if (TypeUtil.isRedisType(instanceInfo.getType())) {
Jedis jedis = null;
try {
jedis = getJedis(instanceInfo.getAppId(), instanceInfo.getIp(), instanceInfo.getPort(), REDIS_DEFAULT_TIME, REDIS_DEFAULT_TIME);
List<String> configs = jedis.configGet("*");
Map<String, String> configMap = new LinkedHashMap<String, String>();
for (int i = 0; i < configs.size(); i += 2) {
if (i < configs.size()) {
String key = configs.get(i);
String value = configs.get(i + 1);
if (StringUtils.isBlank(value)) {
continue;
}
configMap.put(key, value);
}
}
return configMap;
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
return Collections.emptyMap();
}
@Override
public List<RedisSlowLog> getRedisSlowLogs(int instanceId, int maxCount) {
if (instanceId <= 0) {
return Collections.emptyList();
}
InstanceInfo instanceInfo = instanceDao.getInstanceInfoById(instanceId);
if (instanceInfo == null) {
return Collections.emptyList();
}
if (TypeUtil.isRedisType(instanceInfo.getType())) {
return getRedisSlowLogs(instanceInfo.getAppId(), instanceInfo.getIp(), instanceInfo.getPort(), maxCount);
}
return Collections.emptyList();
}
private List<RedisSlowLog> getRedisSlowLogs(long appId, String host, int port, int maxCount) {
Jedis jedis = null;
try {
jedis = getJedis(appId, host, port, REDIS_DEFAULT_TIME, REDIS_DEFAULT_TIME);
List<RedisSlowLog> resultList = new ArrayList<RedisSlowLog>();
List<Slowlog> slowlogs = null;
if (maxCount > 0) {
slowlogs = jedis.slowlogGet(maxCount);
} else {
slowlogs = jedis.slowlogGet();
}
if (slowlogs != null && slowlogs.size() > 0) {
for (Slowlog sl : slowlogs) {
RedisSlowLog rs = new RedisSlowLog();
rs.setId(sl.getId());
rs.setExecutionTime(sl.getExecutionTime());
long time = sl.getTimeStamp() * 1000L;
rs.setDate(new Date(time));
rs.setTimeStamp(DateUtil.formatYYYYMMddHHMMSS(new Date(time)));
rs.setCommand(StringUtils.join(sl.getArgs(), " "));
resultList.add(rs);
}
}
return resultList;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return Collections.emptyList();
} finally {
if (jedis != null) {
jedis.close();
}
}
}
@Override
public boolean configRewrite(final long appId, final String host, final int port) {
return new IdempotentConfirmer() {
@Override
public boolean execute() {
Jedis jedis = getJedis(appId, host, port, REDIS_DEFAULT_TIME, REDIS_DEFAULT_TIME);
try {
String response = jedis.configRewrite();
return response != null && response.equalsIgnoreCase("OK");
} finally {
jedis.close();
}
}
}.run();
}
@Override
public boolean cleanAppData(AppDesc appDesc, AppUser appUser) {
if (appDesc == null) {
return false;
}
long appId = appDesc.getAppId();
// 线上应用不能清理数据
if (AppDescEnum.AppTest.IS_TEST.getValue() != appDesc.getIsTest()) {
logger.error("appId {} profile must be test", appId);
return false;
}
// 必须是redis应用
if (!TypeUtil.isRedisType(appDesc.getType())) {
logger.error("appId {} type must be redis", appId);
return false;
}
// 实例验证
List<InstanceInfo> instanceList = instanceDao.getInstListByAppId(appId);
if (CollectionUtils.isEmpty(instanceList)) {
logger.error("appId {} instanceList is empty", appId);
return false;
}
// 开始清除
for (InstanceInfo instance : instanceList) {
if (instance.getStatus() != InstanceStatusEnum.GOOD_STATUS.getStatus()) {
continue;
}
String host = instance.getIp();
int port = instance.getPort();
// master + 非sentinel节点
Boolean isMater = isMaster(appId, host, port);
if (isMater != null && isMater.equals(true) && !TypeUtil.isRedisSentinel(instance.getType())) {
Jedis jedis = getJedis(appId, host, port);
jedis.getClient().setConnectionTimeout(REDIS_DEFAULT_TIME);
jedis.getClient().setSoTimeout(30000);
try {
logger.warn("{}:{} start clear data", host, port);
long start = System.currentTimeMillis();
String result = jedis.flushAll();
if (!"ok".equalsIgnoreCase(result)) {
return false;
}
logger.warn("{}:{} finish clear data, cost time:{} ms", host, port,
(System.currentTimeMillis() - start));
} catch (Exception e) {
logger.error("clear redis: " + e.getMessage(), e);
return false;
} finally {
jedis.close();
}
}
}
//记录日志
AppAuditLog appAuditLog = AppAuditLog.generate(appDesc, appUser, 0L, AppAuditLogTypeEnum.APP_CLEAN_DATA);
appAuditLogDao.save(appAuditLog);
return true;
}
@Override
public boolean isSingleClusterNode(long appId, String host, int port) {
final Jedis jedis = getJedis(appId, host, port);
try {
String clusterNodes = jedis.clusterNodes();
if (StringUtils.isBlank(clusterNodes)) {
throw new RuntimeException(host + ":" + port + "clusterNodes is null");
}
String[] nodeInfos = clusterNodes.split("\n");
if (nodeInfos.length == 1) {
return true;
}
return false;
} finally {
jedis.close();
}
}
@Override
public List<String> getClientList(int instanceId) {
if (instanceId <= 0) {
return Collections.emptyList();
}
InstanceInfo instanceInfo = instanceDao.getInstanceInfoById(instanceId);
if (instanceInfo == null) {
return Collections.emptyList();
}
if (TypeUtil.isRedisType(instanceInfo.getType())) {
Jedis jedis = null;
try {
jedis = getJedis(instanceInfo.getAppId(), instanceInfo.getIp(), instanceInfo.getPort(), REDIS_DEFAULT_TIME, REDIS_DEFAULT_TIME);
jedis.clientList();
List<String> resultList = new ArrayList<String>();
String clientList = jedis.clientList();
if (StringUtils.isNotBlank(clientList)) {
String[] array = clientList.split("\n");
resultList.addAll(Arrays.asList(array));
}
return resultList;
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
return Collections.emptyList();
}
@Override
public Map<String, String> getClusterLossSlots(long appId) {
// 1.从应用中获取一个健康的主节点
InstanceInfo sourceMasterInstance = getHealthyInstanceInfo(appId);
if (sourceMasterInstance == null) {
return Collections.emptyMap();
}
// 2. 获取所有slot和节点的对应关系
Map<Integer, String> slotHostPortMap = getSlotsHostPortMap(appId, sourceMasterInstance.getIp(), sourceMasterInstance.getPort());
// 3. 获取集群中失联的slot
List<Integer> lossSlotList = getClusterLossSlots(appId, sourceMasterInstance.getIp(), sourceMasterInstance.getPort());
// 3.1 将失联的slot列表组装成Map<String host:port,List<Integer> lossSlotList>
Map<String, List<Integer>> hostPortSlotMap = new HashMap<String, List<Integer>>();
if (CollectionUtils.isNotEmpty(lossSlotList)) {
for (Integer lossSlot : lossSlotList) {
String key = slotHostPortMap.get(lossSlot);
if (hostPortSlotMap.containsKey(key)) {
hostPortSlotMap.get(key).add(lossSlot);
} else {
List<Integer> list = new ArrayList<Integer>();
list.add(lossSlot);
hostPortSlotMap.put(key, list);
}
}
}
// 3.2 hostPortSlotMap组装成Map<String host:port,String startSlot-endSlot>
Map<String, String> slotSegmentsMap = new HashMap<String, String>();
for (Entry<String, List<Integer>> entry : hostPortSlotMap.entrySet()) {
List<Integer> list = entry.getValue();
List<String> slotSegments = new ArrayList<String>();
int min = list.get(0);
int max = min;
for (int i = 1; i < list.size(); i++) {
int temp = list.get(i);
if (temp == max + 1) {
max = temp;
} else {
slotSegments.add(String.valueOf(min) + "-" + String.valueOf(max));
min = temp;
max = temp;
}
}
slotSegments.add(String.valueOf(min) + "-" + String.valueOf(max));
slotSegmentsMap.put(entry.getKey(), slotSegments.toString());
}
return slotSegmentsMap;
}
/**
* 从一个应用中获取一个健康的主节点
*
* @param appId
* @return
*/
public InstanceInfo getHealthyInstanceInfo(long appId) {
InstanceInfo sourceMasterInstance = null;
List<InstanceInfo> appInstanceInfoList = instanceDao.getInstListByAppId(appId);
if (CollectionUtils.isEmpty(appInstanceInfoList)) {
logger.error("appId {} has not instances", appId);
return null;
}
for (InstanceInfo instanceInfo : appInstanceInfoList) {
int instanceType = instanceInfo.getType();
if (!TypeUtil.isRedisCluster(instanceType)) {
continue;
}
final String host = instanceInfo.getIp();
final int port = instanceInfo.getPort();
if (instanceInfo.getStatus() != InstanceStatusEnum.GOOD_STATUS.getStatus()) {
continue;
}
boolean isRun = isRun(appId, host, port);
if (!isRun) {
logger.warn("{}:{} is not run", host, port);
continue;
}
boolean isMaster = isMaster(appId, host, port);
if (!isMaster) {
logger.warn("{}:{} is not master", host, port);
continue;
}
sourceMasterInstance = instanceInfo;
break;
}
return sourceMasterInstance;
}
/**
* clusterslots命令拼接成Map<Integer slot, String host:port>
*
* @param host
* @param port
* @return
*/
private Map<Integer, String> getSlotsHostPortMap(long appId, String host, int port) {
Map<Integer, String> slotHostPortMap = new HashMap<Integer, String>();
Jedis jedis = null;
try {
jedis = getJedis(appId, host, port);
List<Object> slots = jedis.clusterSlots();
for (Object slotInfoObj : slots) {
List<Object> slotInfo = (List<Object>) slotInfoObj;
if (slotInfo.size() <= 2) {
continue;
}
List<Integer> slotNums = getAssignedSlotArray(slotInfo);
// hostInfos
List<Object> hostInfos = (List<Object>) slotInfo.get(2);
if (hostInfos.size() <= 0) {
continue;
}
HostAndPort targetNode = generateHostAndPort(hostInfos);
for (Integer slot : slotNums) {
slotHostPortMap.put(slot, targetNode.getHost() + ":" + targetNode.getPort());
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return slotHostPortMap;
}
private HostAndPort generateHostAndPort(List<Object> hostInfos) {
return new HostAndPort(SafeEncoder.encode((byte[]) hostInfos.get(0)),
((Long) hostInfos.get(1)).intValue());
}
private List<Integer> getAssignedSlotArray(List<Object> slotInfo) {
List<Integer> slotNums = new ArrayList<Integer>();
for (int slot = ((Long) slotInfo.get(0)).intValue(); slot <= ((Long) slotInfo.get(1))
.intValue(); slot++) {
slotNums.add(slot);
}
return slotNums;
}
@Override
public List<Integer> getClusterLossSlots(long appId, String host, int port) {
InstanceInfo instanceInfo = instanceDao.getAllInstByIpAndPort(host, port);
if (instanceInfo == null) {
logger.warn("{}:{} instanceInfo is null", host, port);
return Collections.emptyList();
}
if (!TypeUtil.isRedisCluster(instanceInfo.getType())) {
logger.warn("{}:{} is not rediscluster type", host, port);
return Collections.emptyList();
}
List<Integer> clusterLossSlots = new ArrayList<Integer>();
Jedis jedis = null;
try {
jedis = getJedis(appId, host, port, REDIS_DEFAULT_TIME, REDIS_DEFAULT_TIME);
String clusterNodes = jedis.clusterNodes();
if (StringUtils.isBlank(clusterNodes)) {
throw new RuntimeException(host + ":" + port + "clusterNodes is null");
}
Set<Integer> allSlots = new LinkedHashSet<Integer>();
for (int i = 0; i <= 16383; i++) {
allSlots.add(i);
}
// 解析
ClusterNodeInformationParser nodeInfoParser = new ClusterNodeInformationParser();
for (String nodeInfo : clusterNodes.split("\n")) {
if (StringUtils.isNotBlank(nodeInfo) && !nodeInfo.contains("disconnected")) {
ClusterNodeInformation clusterNodeInfo = nodeInfoParser.parse(nodeInfo, new HostAndPort(host, port));
List<Integer> availableSlots = clusterNodeInfo.getAvailableSlots();
for (Integer slot : availableSlots) {
allSlots.remove(slot);
}
}
}
clusterLossSlots = new ArrayList<Integer>(allSlots);
} catch (Exception e) {
logger.error("getClusterLossSlots: " + e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return clusterLossSlots;
}
@Override
public List<Integer> getInstanceSlots(long appId, String healthHost, int healthPort, String lossSlotsHost, int lossSlotsPort) {
InstanceInfo instanceInfo = instanceDao.getAllInstByIpAndPort(healthHost, healthPort);
if (instanceInfo == null) {
logger.warn("{}:{} instanceInfo is null", healthHost, healthPort);
return Collections.emptyList();
}
if (!TypeUtil.isRedisCluster(instanceInfo.getType())) {
logger.warn("{}:{} is not rediscluster type", healthHost, healthPort);
return Collections.emptyList();
}
List<Integer> clusterLossSlots = new ArrayList<Integer>();
Jedis jedis = null;
try {
jedis = getJedis(appId, healthHost, healthPort, REDIS_DEFAULT_TIME, REDIS_DEFAULT_TIME);
String clusterNodes = jedis.clusterNodes();
if (StringUtils.isBlank(clusterNodes)) {
throw new RuntimeException(healthHost + ":" + healthPort + "clusterNodes is null");
}
// 解析
ClusterNodeInformationParser nodeInfoParser = new ClusterNodeInformationParser();
for (String nodeInfo : clusterNodes.split("\n")) {
if (StringUtils.isNotBlank(nodeInfo) && nodeInfo.contains("disconnected") && nodeInfo.contains(lossSlotsHost + ":" + lossSlotsPort)) {
ClusterNodeInformation clusterNodeInfo = nodeInfoParser.parse(nodeInfo, new HostAndPort(healthHost, healthPort));
clusterLossSlots = clusterNodeInfo.getAvailableSlots();
}
}
} catch (Exception e) {
logger.error("getClusterLossSlots: " + e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return clusterLossSlots;
}
public void destory() {
for (JedisPool jedisPool : jedisPoolMap.values()) {
jedisPool.destroy();
}
}
@Override
public boolean deployRedisSlowLogCollection(long appId, String host, int port) {
Assert.isTrue(appId > 0);
Assert.hasText(host);
Assert.isTrue(port > 0);
Map<String, Object> dataMap = new HashMap<String, Object>();
dataMap.put(ConstUtils.HOST_KEY, host);
dataMap.put(ConstUtils.PORT_KEY, port);
dataMap.put(ConstUtils.APP_KEY, appId);
JobKey jobKey = JobKey.jobKey(ConstUtils.REDIS_SLOWLOG_JOB_NAME, ConstUtils.REDIS_SLOWLOG_JOB_GROUP);
TriggerKey triggerKey = TriggerKey.triggerKey(ObjectConvert.linkIpAndPort(host, port), ConstUtils.REDIS_SLOWLOG_TRIGGER_GROUP + appId);
boolean result = schedulerCenter.deployJobByCron(jobKey, triggerKey, dataMap, ScheduleUtil.getRedisSlowLogCron(appId), false);
return result;
}
@Override
public boolean unDeployRedisSlowLogCollection(long appId, String host, int port) {
Assert.isTrue(appId > 0);
Assert.hasText(host);
Assert.isTrue(port > 0);
TriggerKey triggerKey = TriggerKey.triggerKey(ObjectConvert.linkIpAndPort(host, port), ConstUtils.REDIS_SLOWLOG_TRIGGER_GROUP + appId);
Trigger trigger = schedulerCenter.getTrigger(triggerKey);
if (trigger == null) {
return true;
}
return schedulerCenter.unscheduleJob(triggerKey);
}
@Override
public List<InstanceSlowLog> getInstanceSlowLogByAppId(long appId) {
try {
return instanceSlowLogDao.getByAppId(appId);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return Collections.emptyList();
}
}
@Override
public List<InstanceSlowLog> getInstanceSlowLogByAppId(long appId, Date startDate, Date endDate) {
try {
return instanceSlowLogDao.search(appId, startDate, endDate);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return Collections.emptyList();
}
}
@Override
public Map<String, Long> getInstanceSlowLogCountMapByAppId(Long appId, Date startDate, Date endDate) {
try {
List<Map<String, Object>> list = instanceSlowLogDao.getInstanceSlowLogCountMapByAppId(appId, startDate, endDate);
if (CollectionUtils.isEmpty(list)) {
return Collections.emptyMap();
}
Map<String, Long> resultMap = new LinkedHashMap<String, Long>();
for (Map<String, Object> map : list) {
long count = MapUtils.getLongValue(map, "count");
String hostPort = MapUtils.getString(map, "hostPort");
if (StringUtils.isNotBlank(hostPort)) {
resultMap.put(hostPort, count);
}
}
return resultMap;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return Collections.emptyMap();
}
}
@Override
public Map<String, InstanceSlotModel> getClusterSlotsMap(long appId) {
AppDesc appDesc = appDao.getAppDescById(appId);
if (!TypeUtil.isRedisCluster(appDesc.getType())) {
return Collections.emptyMap();
}
// 最终结果
Map<String, InstanceSlotModel> resultMap = new HashMap<String, InstanceSlotModel>();
// 找到一个运行的节点用来执行cluster slots
List<InstanceInfo> instanceList = instanceDao.getInstListByAppId(appId);
String host = null;
int port = 0;
for (InstanceInfo instanceInfo : instanceList) {
if (instanceInfo.isOffline()) {
continue;
}
host = instanceInfo.getIp();
port = instanceInfo.getPort();
boolean isRun = isRun(appId, host, port);
if (isRun) {
break;
}
}
if (StringUtils.isBlank(host) || port <= 0) {
return Collections.emptyMap();
}
// 获取cluster slots
List<Object> clusterSlotList = null;
Jedis jedis = null;
try {
jedis = getJedis(appId, host, port);
clusterSlotList = jedis.clusterSlots();
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (jedis != null)
jedis.close();
}
if (clusterSlotList == null || clusterSlotList.size() == 0) {
return Collections.emptyMap();
}
//clusterSlotList形如:
// [0, 1, [[B@5caf905d, 6380], [[B@27716f4, 6379]]
// [3, 4096, [[B@8efb846, 6380], [[B@2a84aee7, 6379]]
// [12291, 16383, [[B@a09ee92, 6383], [[B@30f39991, 6382]]
// [2, 2, [[B@452b3a41, 6381], [[B@4a574795, 6382]]
// [8194, 12290, [[B@f6f4d33, 6381], [[B@23fc625e, 6382]]
// [4097, 8193, [[B@3f99bd52, 6380], [[B@4f023edb, 6381]]
for (Object clusterSlotObj : clusterSlotList) {
List<Object> slotInfoList = (List<Object>) clusterSlotObj;
if (slotInfoList.size() <= 2) {
continue;
}
//获取slot的start到end相关
int startSlot = ((Long) slotInfoList.get(0)).intValue();
int endSlot = ((Long) slotInfoList.get(1)).intValue();
String slotDistribute = getStartToEndSlotDistribute(startSlot, endSlot);
List<Integer> slotList = getStartToEndSlotList(startSlot, endSlot);
List<Object> masterInfoList = (List<Object>) slotInfoList.get(2);
String tempHost = SafeEncoder.encode((byte[]) masterInfoList.get(0));
int tempPort = ((Long) masterInfoList.get(1)).intValue();
String hostPort = tempHost + ":" + tempPort;
if (resultMap.containsKey(hostPort)) {
InstanceSlotModel instanceSlotModel = resultMap.get(hostPort);
instanceSlotModel.getSlotDistributeList().add(slotDistribute);
instanceSlotModel.getSlotList().addAll(slotList);
} else {
InstanceSlotModel instanceSlotModel = new InstanceSlotModel();
instanceSlotModel.setHost(tempHost);
instanceSlotModel.setPort(tempPort);
List<String> slotDistributeList = new ArrayList<String>();
slotDistributeList.add(slotDistribute);
instanceSlotModel.setSlotDistributeList(slotDistributeList);
instanceSlotModel.setSlotList(slotList);
resultMap.put(hostPort, instanceSlotModel);
}
}
return resultMap;
}
/**
* 获取slot列表
* @param startSlot
* @param endSlot
* @return
*/
private List<Integer> getStartToEndSlotList(int startSlot, int endSlot) {
List<Integer> slotList = new ArrayList<Integer>();
if (startSlot == endSlot) {
slotList.add(startSlot);
} else {
for (int i = startSlot; i <= endSlot; i++) {
slotList.add(i);
}
}
return slotList;
}
/**
* 0,4096 0-4096
* 2,2 2-2
* @param slotInfo
* @return
*/
private String getStartToEndSlotDistribute(int startSlot, int endSlot) {
if (startSlot == endSlot) {
return String.valueOf(startSlot);
} else {
return startSlot + "-" + endSlot;
}
}
@Override
public String getRedisVersion(long appId, String ip, int port) {
Map<RedisConstant, Map<String, Object>> infoAllMap = getInfoStats(appId, ip, port);
if (MapUtils.isEmpty(infoAllMap)) {
return null;
}
Map<String, Object> serverMap = infoAllMap.get(RedisConstant.Server);
if (MapUtils.isEmpty(serverMap)) {
return null;
}
return MapUtils.getString(serverMap, "redis_version");
}
@Override
public String getNodeId(long appId, String ip, int port) {
final Jedis jedis = getJedis(appId, ip, port);
try {
final StringBuilder clusterNodes = new StringBuilder();
boolean isGetNodes = new IdempotentConfirmer() {
@Override
public boolean execute() {
String nodes = jedis.clusterNodes();
if (nodes != null && nodes.length() > 0) {
clusterNodes.append(nodes);
return true;
}
return false;
}
}.run();
if (!isGetNodes) {
logger.error("{}:{} clusterNodes failed", jedis.getClient().getHost(), jedis.getClient().getPort());
return null;
}
for (String infoLine : clusterNodes.toString().split("\n")) {
if (infoLine.contains("myself")) {
String nodeId = infoLine.split(" ")[0];
return nodeId;
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (jedis != null)
jedis.close();
}
return null;
}
public void setSchedulerCenter(SchedulerCenter schedulerCenter) {
this.schedulerCenter = schedulerCenter;
}
public void setInstanceStatsCenter(InstanceStatsCenter instanceStatsCenter) {
this.instanceStatsCenter = instanceStatsCenter;
}
public void setAppStatsDao(AppStatsDao appStatsDao) {
this.appStatsDao = appStatsDao;
}
public void setAsyncService(AsyncService asyncService) {
this.asyncService = asyncService;
}
public void setInstanceDao(InstanceDao instanceDao) {
this.instanceDao = instanceDao;
}
public void setInstanceStatsDao(InstanceStatsDao instanceStatsDao) {
this.instanceStatsDao = instanceStatsDao;
}
public void setMachineCenter(MachineCenter machineCenter) {
this.machineCenter = machineCenter;
}
public void setAppDao(AppDao appDao) {
this.appDao = appDao;
}
public void setAppAuditLogDao(AppAuditLogDao appAuditLogDao) {
this.appAuditLogDao = appAuditLogDao;
}
public void setInstanceSlowLogDao(InstanceSlowLogDao instanceSlowLogDao) {
this.instanceSlowLogDao = instanceSlowLogDao;
}
@Override
public Jedis getJedis(long appId, String host, int port) {
return getJedis(appId, host, port, Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT);
}
@Override
public Jedis getJedis(long appId, String host, int port, int connectionTimeout, int soTimeout) {
AppDesc appDesc = appDao.getAppDescById(appId);
String password = appDesc.getPassword();
Jedis jedis = new Jedis(host, port);
jedis.getClient().setConnectionTimeout(connectionTimeout);
jedis.getClient().setSoTimeout(soTimeout);
if (StringUtils.isNotBlank(password)) {
jedis.auth(password);
}
return jedis;
}
@Override
public Jedis getJedis(String host, int port, String password) {
Jedis jedis = new Jedis(host, port);
jedis.getClient().setConnectionTimeout(Protocol.DEFAULT_TIMEOUT);
jedis.getClient().setSoTimeout(Protocol.DEFAULT_TIMEOUT);
if (StringUtils.isNotBlank(password)) {
jedis.auth(password);
}
return jedis;
}
@Override
public Jedis getJedis(String host, int port) {
return getJedis(host, port, null);
}
}