zoukankan      html  css  js  c++  java
  • 双机热备方案

    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 "";
            }
        }
    
    }
  • 相关阅读:
    MySQL DDL 在指定位置新增字段
    .NET平台常见技术框架整理汇总
    使用Linq求和方法Sum计算集合中多个元素和时应该注意的性能问题
    python时间操作
    django 利用原生sql操作数据库
    滑动验证码获取像素偏移量
    python opencv简单使用
    django通过旧数据库库生成models
    pandas 操作csv文件
    简单工厂模式(Simple Factory Pattern)
  • 原文地址:https://www.cnblogs.com/cnndevelop/p/14353441.html
Copyright © 2011-2022 走看看