双机热备方案

 

 

package com.cnn.service.HA;

import com.cnn.service.ScheduledExecutorServiceDelay.ScheduledExecutorServiceDelayTask;
import com.google.common.collect.ConcurrentHashMultiset;

import javassist.bytecode.analysis.Executor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import javax.net.ssl.*;
import java.io.IOException;
import java.net.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.*;
import java.util.logging.Handler;

/**
 * HACutover
 *
 * @author Administrator
 * @version 1.0
 * @project delay_queue
 * @title
 * @date 2021/1/10
 */
@Slf4j
public class HACutover {

    private static String LOCAL = "local";   //本地

    private static String FIRST = "first";   //第一个机房

    private static String SECOND = "second"; //第二个机房

    private static boolean status = false;   //状态

    private static int timeout = 3 * 1000;   //超时时间

    private static int queueSize = 24;       //队列长度

    private static int exception = 3;        //异常数量

    private static double exceptionPercent = 0.5;   //异常占比

    private static String SERVER_ROOM = "local";   //获取机房配置(这里可以用动态获取配置实现,根据机房不同获取不同的配置)

    private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    private static ConcurrentHashMap<String, String> avaiUrlMap = new ConcurrentHashMap<>();

    private static ConcurrentHashMap<String, LinkedBlockingDeque<UrlInfo>> queueMap = new ConcurrentHashMap<>();

    /**
     * 初始化配置
     */
    public HACutover() {
    }

    /**
     * 设置可用地址
     *
     * @param key
     * @param avaiUrl
     * @return
     */
    private static String setAvaiUrl(String key, String avaiUrl) {
        String check1 = avaiUrlMap.get(key);
        if (StringUtils.isEmpty(check1) || (StringUtils.isNotEmpty(check1) && !avaiUrl.equals(check1))) {
            synchronized (Handler.class) {
                String check2 = avaiUrlMap.get(key);
                if (StringUtils.isEmpty(check2) || (StringUtils.isNotEmpty(check1) && !avaiUrl.equals(check2))) {
                    avaiUrlMap.put(key, avaiUrl);
                }
            }
        }
        return avaiUrl;
    }

    /**
     * 获取可用地址
     * @param firstConfig    第一个机房
     * @param secondConfig   第二个机房
     * @return
     */
    public static String getAvaiUrl(String firstConfig, String secondConfig) {
        keepAliaved();
        String key = firstConfig + "%-%" + secondConfig;
        String avaiUrl = avaiUrlMap.get(key);
        if (StringUtils.isEmpty(avaiUrl)) {
            if (StringUtils.isNotEmpty(firstConfig) && StringUtils.isNotEmpty(secondConfig)) {
                if (FIRST.equals(SERVER_ROOM)) {
                    avaiUrl = setAvaiUrl(key, ConfigManager.get(firstConfig));
                }
                if (SECOND.equals(SERVER_ROOM)) {
                    avaiUrl = setAvaiUrl(key, ConfigManager.get(secondConfig));
                }
                //如果没有获取到机房配置,则默认取第一个机房的配置
                if (FIRST.equals(SERVER_ROOM)) {
                    avaiUrl = setAvaiUrl(key, ConfigManager.get(firstConfig));
                }
                return avaiUrl;
            } else {
                if (StringUtils.isNotEmpty(firstConfig)) {
                    return avaiUrl = ConfigManager.get(firstConfig);
                }
                if (StringUtils.isNotEmpty(secondConfig)) {
                    return avaiUrl = ConfigManager.get(secondConfig);
                }
            }
        }
        log.info("地址map信息:{}", avaiUrlMap);
        return avaiUrl;
    }

    /**
     * 保活
     */
    private static void keepAliaved() {
        if (!status) {
            executorService.scheduleAtFixedRate(() -> detect(), 0, 5, TimeUnit.SECONDS);
            status = true;
        } else {
            log.info("已经初始化过了");
        }
    }

    /**
     * 关闭探测
     */
    public static void closeDetect() {
        executorService.shutdownNow();
        status = false;
    }


    private static void detect() {
        log.info("开始探测!");
        try {
            avaiUrlMap.forEach((k, url) -> {
                LinkedBlockingDeque<UrlInfo> urlInfoQueue = queueMap.get(k);
                if (null != urlInfoQueue) {
                    boolean telnet = connectingAddress(url);
                    if (urlInfoQueue.size() >= queueSize) {
                        UrlInfo poll = urlInfoQueue.poll();//移除队列头部元素
                    }
                    UrlInfo urlInfo = new UrlInfo();
                    urlInfo.setStatus(telnet);
                    urlInfo.setTime(new Date());
                    urlInfoQueue.offer(urlInfo);
                    calculate(k, url, urlInfoQueue);
                } else {
                    LinkedBlockingDeque<UrlInfo> queue = new LinkedBlockingDeque<>(queueSize);
                    UrlInfo urlInfo = new UrlInfo();
                    urlInfo.setStatus(true);
                    urlInfo.setTime(new Date());
                    try {
                        queue.offer(urlInfo);
                        queueMap.put(k, queue);
                    } catch (Exception e) {
                        log.error("", e);
                    }
                }
            });
        } catch (Exception e) {
            log.error("定时任务执行出现异常:{}", e);
        }
    }


    /**
     * 计算异常比例,并切换域名,切换逻辑采用(5-3)
     *
     * @param k
     * @param url
     * @param urlinfoQueue
     */
    private static void calculate(String k, String url, LinkedBlockingDeque<UrlInfo> urlinfoQueue) {
        int exceptNum = 0;  //异常个数
        int contiExceptNum = 0;//连接异常个数
        if (null != urlinfoQueue && urlinfoQueue.size() >= exception) {
            Iterator iter = urlinfoQueue.iterator();
            while (iter.hasNext()) {
                UrlInfo urlInfo = (UrlInfo) iter.next();
                if (!urlInfo.status) {
                    exceptNum += 1;
                    contiExceptNum += 1;
                } else {
                    contiExceptNum = 0;
                }
                double percent = exceptNum / urlinfoQueue.size();
                if (contiExceptNum >= exception || percent >= exceptionPercent) {
                    cutover(k, url);
                    urlinfoQueue.clear();  //清空队列,避免重复触发切换条件
                }
            }
        }

    }


    /**
     * 切换域名
     *
     * @param k
     * @param url
     */
    private static void cutover(String k, String url) {
        String[] urlArray = k.split("%-%");
        for (int i = 0; i < urlArray.length; i++) {
            String currentConfig = urlArray[i];
            if (!(StringUtils.isNotEmpty(url) && url.equals(currentConfig))) {
                String currentUrl = ConfigManager.get(currentConfig);
                //先探测将要切换的地址是否正确,如果也是异常的则不作切换
                boolean connect = connectingAddress(currentUrl);
                log.info("自动切换地址检测:{}", connect);
                if (connect) {
                    log.info("自动切换连接地址:{}", currentUrl);
                    setAvaiUrl(k, currentUrl);
                }
            }
        }
    }


    /**
     * 探测地址连通性
     *
     * @param remoteInetAddr
     * @return
     */
    private static boolean connectingAddress(String remoteInetAddr) {
        String tempUrl = remoteInetAddr.substring(0, 5);  //取出地址前5位
        if (tempUrl.contains("http")) {  //判断传过来的地址中是否有http
            if (tempUrl.equals("https")) { //判断服务器是否是https协议
                try {
                    trustAllHttpsCertificates();  //当协议是https时
                } catch (Exception e) {
                    log.error("证书设置异常:{}", e);
                }
            }
            boolean telnet = false;
            if (remoteInetAddr.replace("://", "").contains(":")) {
                telnet = telnet(remoteInetAddr);
            } else {
                telnet = isConnServerByHttp(remoteInetAddr);
            }
            return telnet;
        } else {
            return isReachable(remoteInetAddr);
        }
    }


    static HostnameVerifier hv = new HostnameVerifier() {
        @Override
        public boolean verify(String s, SSLSession sslSession) {
            return true;
        }
    };


    /**
     * IP地址是否可达,相当于Ping命令
     *
     * @param remoteInteAddr
     * @return
     */
    private static boolean isReachable(String remoteInteAddr) {
        boolean reachable = false;
        try {
            String host = getAddress(remoteInteAddr);
            InetAddress address = InetAddress.getByName(host);
            reachable = address.isReachable(timeout);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return reachable;
    }


    /**
     * 服务联通性
     *
     * @param serverUrl
     * @return
     */
    private static boolean isConnServerByHttp(String serverUrl) {
        boolean connFlag = false;
        URL url;
        HttpURLConnection conn = null;
        try {
            url = new URL(serverUrl);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(timeout);
            if (conn.getResponseCode() == 200) {  //如果连接成功则设置为true
                connFlag = true;
            }
        } catch (Exception e) {
            log.error("探测连接出现异常:{},异常信息:{}", serverUrl, e);
        } finally {
            conn.disconnect();
        }
        return connFlag;
    }


    /**
     * telnet查看地址是否可用
     *
     * @param serverUrl
     * @return
     */
    public static boolean telnet(String serverUrl) {
        Socket socket = new Socket();
        boolean isConnected = false;
        try {
            socket.connect(new InetSocketAddress(getAddress(serverUrl), getPort(serverUrl)), timeout);   //简历连接
            isConnected = socket.isConnected();   //通过现有方法查看连通状态
        } catch (IOException e) {
            isConnected = false;
        } finally {
            try {
                socket.close();  //关闭连接
            } catch (IOException e) {
                isConnected = false;
            }
        }
        return isConnected;
    }


    /**
     * 以下是Https适用
     *
     * @throws Exception
     */
    private static void trustAllHttpsCertificates() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[1];
        TrustManager tm = new miTM();
        trustAllCerts[0] = tm;
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }


    static class miTM implements TrustManager, X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(X509Certificate[] certs) {
            return true;
        }

        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            return;
        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            return;
        }
    }


    /**
     * 获取地址
     *
     * @param remoteInetAddr
     * @return
     */
    public static String getAddress(String remoteInetAddr) {
        if (remoteInetAddr.contains("://")) {
            remoteInetAddr = remoteInetAddr.substring(remoteInetAddr.indexOf("://")).replace("://", "");
        }
        if (remoteInetAddr.contains(":")) {
            remoteInetAddr = remoteInetAddr.substring(0, remoteInetAddr.indexOf(":"));
        }
        return remoteInetAddr;
    }

    /**
     * 获取端口
     *
     * @param remoteInetAddr
     * @return
     */
    public static int getPort(String remoteInetAddr) {
        remoteInetAddr = remoteInetAddr.replace("://", "");
        if (remoteInetAddr.contains(":")) {
            remoteInetAddr = remoteInetAddr.substring(remoteInetAddr.indexOf(":")).replace(":", "");
        }
        if (remoteInetAddr.contains("/")) {
            remoteInetAddr = remoteInetAddr.substring(0, remoteInetAddr.indexOf("/"));
        }
        if (StringUtils.isNotEmpty(remoteInetAddr)) {
            try {
                return Integer.valueOf(remoteInetAddr);
            } catch (NumberFormatException e) {
                log.error("获取端口异常:{}", e);
            }
        }
        return 80;
    }


    @Data
    static class UrlInfo {
        private boolean status;  //状态
        private Date time;       //入队列的时间
    }

    /**
     * 动态获取配置
     */
    static class ConfigManager {
        public static String get(String config) {
            return "";
        }
    }

}

 

posted @ 2021-01-31 18:15  大浪不惊涛  阅读(149)  评论(0编辑  收藏  举报