/** * Tencent is pleased to support the open source community by making MSEC available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the GNU General Public License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may * obtain a copy of the License at * * https://opensource.org/licenses/GPL-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ package beans.service; import beans.request.QueryRedisClusterDetailRequest; import beans.response.QueryRedisClusterDetailResponse; import beans.response.QuerySecondLevelServiceDetailResponse; import msec.org.DBUtil; import msec.org.JsonRPCHandler; import org.apache.log4j.Logger; import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisDataException; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class QueryRedisClusterDetail extends JsonRPCHandler { DBUtil util; HashMap<String,Integer> master_nodes; HashMap<String,Integer> slave_nodes; private void getStatus(String first_level_service_name, String second_level_service_name) throws SQLException { Logger logger = Logger.getLogger(QueryRedisClusterDetail.class); String sql = "select ip, port, group_id, master from t_service_info where first_level_service_name=? and second_level_service_name=?"; List<Object> params = new ArrayList<Object>(); params.add(first_level_service_name); params.add(second_level_service_name); ArrayList<Map<String, Object>> result_list = util.findModeResult(sql, params); for(int i = 0; i < result_list.size(); i++){ if(Boolean.parseBoolean(result_list.get(i).get("master").toString())) { master_nodes.put(result_list.get(i).get("ip").toString() + ":" + result_list.get(i).get("port").toString(), Integer.parseInt(result_list.get(i).get("group_id").toString())); } else slave_nodes.put(result_list.get(i).get("ip").toString() + ":" + result_list.get(i).get("port").toString(), Integer.parseInt(result_list.get(i).get("group_id").toString())); } logger.info(master_nodes); logger.info(slave_nodes); } private void updateStatus(String first_level_service_name, String second_level_service_name, String ip, int port, QueryRedisClusterDetailResponse.RTInfo info) throws SQLException { Logger logger = Logger.getLogger(QueryRedisClusterDetail.class); String sql = ""; List<Object> params = new ArrayList<Object>(); if(info.isOK()) { sql = "update t_service_info set master=?,status=? where first_level_service_name=? and second_level_service_name=? and ip=? and port=?"; params.add(info.isMaster()); params.add(info.getStatus()); } else { sql = "update t_service_info set status=? where first_level_service_name=? and second_level_service_name=? and ip=? and port=?"; params.add(info.getStatus()); } params.add(first_level_service_name); params.add(second_level_service_name); params.add(ip); params.add(port); int updNum = util.updateByPreparedStatement(sql, params); if (updNum != 1) { logger.error(String.format("updateStatus|%d", updNum)); } } private boolean updatePlan(String first_level_service_name, String second_level_service_name) throws SQLException { Logger logger = Logger.getLogger(QueryRedisClusterDetailResponse.class); String sql = "select distinct status from t_install_plan where plan_id=(select plan_id from t_second_level_service where first_level_service_name=? and second_level_service_name=?)"; List<Object> params = new ArrayList<Object>(); params.add(first_level_service_name); params.add(second_level_service_name); ArrayList<Map<String, Object>> status_infos = util.findModeResult(sql, params); if(status_infos.size() == 1 && status_infos.get(0).get("status").toString().startsWith("Done")) { //check if it is "Done" sql = "update t_second_level_service set plan_id=\"\" where first_level_service_name=? and second_level_service_name=?"; int addNum = util.updateByPreparedStatement(sql, params); if (addNum < 0) { throw new SQLException("Done update plan_id"); } logger.info("updatePlan_done"); return true; } else { for(int i = 0; i < status_infos.size(); i++) { if (status_infos.get(i).get("status").toString().startsWith("[ERROR]")) { sql = "update t_second_level_service set plan_id=\"\" where first_level_service_name=? and second_level_service_name=?"; int addNum = util.updateByPreparedStatement(sql, params); if (addNum < 0) { throw new SQLException("ERROR update plan_id"); } logger.info("updatePlan_ERROR"); return true; } } } return false; } private void updateMaster(String first_level_service_name, String second_level_service_name, String ip, int port, int group_id) throws SQLException { Logger logger = Logger.getLogger(QueryRedisClusterDetailResponse.class); String sql = "update t_service_info set master= case when ip=? and port=? then 1 else 0 end where first_level_service_name=? and second_level_service_name=? and group_id=?"; List<Object> params = new ArrayList<Object>(); params.add(ip); params.add(port); params.add(first_level_service_name); params.add(second_level_service_name); params.add(group_id); int updNum = util.updateByPreparedStatement(sql, params); if(updNum == 0) logger.error(String.format("updateMaster|%d", updNum)); } public boolean checkMaster(Jedis jedis) { for (String infoLine : jedis.clusterNodes().split("\n")) { if (infoLine.contains("myself")) { if(infoLine.contains("master")) return true; else return false; } } return false; } public QueryRedisClusterDetailResponse exec(QueryRedisClusterDetailRequest request) { Logger logger = Logger.getLogger(QueryRedisClusterDetail.class); QueryRedisClusterDetailResponse resp = new QueryRedisClusterDetailResponse(); String result = checkIdentity(); if (!result.equals("success")) { resp.setStatus(99); resp.setMessage(result); return resp; } boolean all_ok = true; int master_num = 0; util = new DBUtil(); if (util.getConnection() == null) { resp.setStatus(100); resp.setMessage("db connect failed!"); return resp; } master_nodes = new HashMap<>(); slave_nodes = new HashMap<>(); HashMap<String,Integer> master_ips = new HashMap<>(); HashMap<String, QueryRedisClusterDetailResponse.RTInfo> host_infos = new HashMap<>(); HashMap<String, Long> host_seqs = new HashMap<>(); ArrayList<String> masters_with_no_slaves = new ArrayList<>(); HashMap<String, QueryRedisClusterDetailResponse.RTInfo> info_map = new HashMap<>(); try { if(updatePlan(request.getFirst_level_service_name(), request.getSecond_level_service_name())) { resp.setMessage("refresh"); resp.setStatus(101); return resp; } getStatus(request.getFirst_level_service_name(), request.getSecond_level_service_name()); for (String host : request.getHosts()) { Jedis jedis = null; String[] ip_pair = host.split(":"); QueryRedisClusterDetailResponse.RTInfo rt_info = resp.new RTInfo(); try { jedis = new Jedis(ip_pair[0], Integer.parseInt(ip_pair[1])); String info = ""; try{ info = jedis.clusterInfo(); } catch (JedisDataException ex) { if(ex.getMessage().startsWith("LOADING")) { rt_info.setStatus("Syncing"); } else { rt_info.setStatus(ex.getMessage()); } host_infos.put(host, rt_info); continue; } if (!jedis.clusterInfo().split("\n")[0].contains("ok")) { rt_info.setStatus("Cluster state error"); } else { rt_info.setStatus("OK"); rt_info.setOK(true); rt_info.setMaster(checkMaster(jedis)); //if it is master.. if(rt_info.isMaster()) { //add master_ips int count = master_ips.containsKey(ip_pair[0]) ? master_ips.get(ip_pair[0]) : 0; master_ips.put(ip_pair[0], count + 1); master_num+=1; if(slave_nodes.containsKey(host)) { updateMaster(request.getFirst_level_service_name(), request.getSecond_level_service_name(), ip_pair[0], Integer.parseInt(ip_pair[1]), slave_nodes.get(host)); resp.setMessage("refresh"); resp.setStatus(101); return resp; } //get data seq String[] infos = jedis.info("replication").split("\r\n"); boolean has_slave = false; HashMap<String, Long> hs = new HashMap<>(); long master_seq = -1; for(String line: infos) { if(line.startsWith("slave")){ has_slave = true; String[] cols = line.split(","); if(cols.length >= 4) { String ip = cols[0].split("=")[1]; String port = cols[1].split("=")[1]; long seq = Long.parseLong(cols[3].split("=")[1]); hs.put(ip+":"+port, seq); } } else if (line.startsWith("master_repl_offset")) { master_seq = Long.parseLong(line.split(":")[1]); } } if(master_seq >= 0) { for(Map.Entry<String, Long> entry : hs.entrySet()) { host_seqs.put(entry.getKey(), master_seq - entry.getValue()); } } if(!has_slave) { masters_with_no_slaves.add(host); } } } host_infos.put(host, rt_info); } catch (JedisConnectionException e) { rt_info.setStatus("Connection lost"); host_infos.put(host, rt_info); } finally { if (jedis != null) jedis.close(); } } for (String host : request.getHosts()) { String[] ip_pair = host.split(":"); QueryRedisClusterDetailResponse.RTInfo rt_info = host_infos.get(host); if(rt_info != null) { if(host_seqs.containsKey(host)) rt_info.setSeq(host_seqs.get(host)); if(!rt_info.isOK()) { logger.info(String.format("host failed|%s", host)); all_ok = false; } info_map.put(host, rt_info); updateStatus(request.getFirst_level_service_name(), request.getSecond_level_service_name(), ip_pair[0], Integer.parseInt(ip_pair[1]), rt_info); } } } catch (Exception e) { e.printStackTrace(); logger.error(e.getMessage()); } finally { util.releaseConn(); } resp.setInfo_map(info_map); if(all_ok) { for (Map.Entry<String, Integer> entry : master_ips.entrySet()) { if (entry.getValue() * 2 >= master_num) { resp.setWarning(String.format("超过一半的主机部署在%s上,如果该IP出现问题,redis cluster将不能自动恢复。请尽快对该IP进行主从切换或对cluster进行扩容。", entry.getKey())); break; } } } else { if(masters_with_no_slaves.size() > 0) { String message = "下述主机只有一份拷贝,请尽快添加拷贝:"; for(String host : masters_with_no_slaves) { message+="<br/> "+host; } resp.setWarning(message); } } resp.setMessage("success"); resp.setStatus(0); return resp; } }