• Java自定义DNS解析器负载均衡实现


    在上文Java自定义DNS解析器实践中,我们没有讲到org.apache.http.conn.DnsResolver具体如何实现负载均衡,今天我们就分享一下,负载均衡的具体实现。

    InMemoryDnsResolver被淘汰

    首先上期文章提到的org.apache.http.impl.conn.InMemoryDnsResolver类是无法实现负载均衡的,原因是这个实现类是将hostIP存在一个java.util.concurrent.ConcurrentHashMap中,然后解析的时候从java.util.concurrent.ConcurrentHashMap根据host获取到IP的,所以无法进行负载均衡。

    使用的Demo如下:

        /**
         * 重写Java自定义DNS解析器,非负载均衡
         *
         * @return
         */
        private static DnsResolver getDnsResolver2() {
            InMemoryDnsResolver dnsResolver = new InMemoryDnsResolver();
    
            try {
                logger.warn("调用一次");
                dnsResolver.add("fun.tester", InetAddress.getByName("127.0.0.1"));
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return dnsResolver;
    

    其中org.apache.http.impl.conn.InMemoryDnsResolver#add方法源码如下:

        public void add(String host, InetAddress... ips) {
            Args.notNull(host, "Host name");
            Args.notNull(ips, "Array of IP addresses");
            this.dnsMap.put(host, ips);
        }
    

    然后我们看一下org.apache.http.impl.conn.InMemoryDnsResolver#dnsMap相关初始化代码:

        /**
         * In-memory collection that will hold the associations between a host name
         * and an array of InetAddress instances.
         */
        private final Map<String, InetAddress[]> dnsMap;
    
        /**
         * Builds a DNS resolver that will resolve the host names against a
         * collection held in-memory.
         */
        public InMemoryDnsResolver() {
            dnsMap = new ConcurrentHashMap<String, InetAddress[]>();
        }
    
    

    SystemDefaultDnsResolver

    最终我放弃了自定义的org.apache.http.conn.DnsResolver接口的方案,选择了org.apache.http.impl.conn.SystemDefaultDnsResolver重写resolve方法的方案,具体实现如下:

        /**
         * 重写Java自定义DNS解析器,负载均衡
         *
         * @return
         */
        private static DnsResolver getDnsResolver() {
            return new SystemDefaultDnsResolver() {
                @Override
                public InetAddress[] resolve(final String host) throws UnknownHostException {
                    if (host.equalsIgnoreCase("fun.tester")) {
                        return new InetAddress[]{SourceCode.random(ips)};
                    } else {
                        return super.resolve(host);
                    }
                }
            };
        }
    

    其中ips是全局的静态变量,初始化方法如下:

        /**
         * 初始化DNS配置IP
         *
         * @return
         */
        private static List<InetAddress> getAddress() {
            try {
    
                return Arrays.asList(
                        InetAddress.getByName("127.0.0.1"),
                        InetAddress.getByName("0.0.0.0")
                );
            } catch (Exception e) {
                FailException.fail("DNS IP解析失败!");
            }
            return null;
        }
    

    PS:如果你选择使用了自定义的DNS解析器,那么系统hosts配置的功能就会失效,所以谨慎使用。

    测试

    为了验证结果,我对com.funtester.httpclient.ClientManage#getDnsResolver方法进行了改造,每次获取到IP的时候我都打印出来。

        /**
         * 重写Java自定义DNS解析器,负载均衡
         *
         * @return
         */
        private static DnsResolver getDnsResolver() {
            return new SystemDefaultDnsResolver() {
                @Override
                public InetAddress[] resolve(final String host) throws UnknownHostException {
                    if (host.equalsIgnoreCase("fun.tester")) {
                        InetAddress random = SourceCode.random(ips);
                        logger.info(random);
                        return new InetAddress[]{random};
                    } else {
                        return super.resolve(host);
                    }
                }
            };
        }
    

    单线程

    下面看我的测试,首先分享测试用例:

        public static void main(String[] args) {
            String url = "http://fun.tester:12345/"
            def get = getHttpGet(url)
            def test = {
                getHttpResponse(get)
            }
            10.times {
                test()
            }
        }
    

    控制台输出:

    INFO-> 13.691 main 
      ###### #     #  #    # ####### ######  #####  ####### ######  #####
      #      #     #  ##   #    #    #       #         #    #       #    #
      ####   #     #  # #  #    #    ####    #####     #    ####    #####
      #      #     #  #  # #    #    #            #    #    #       #   #
      #       #####   #    #    #    ######  #####     #    ######  #    #
    
    INFO-> 14.408 main /0.0.0.0
    INFO-> 14.460 main 请求uri:http://fun.tester:12345/ , 耗时:451 ms , HTTPcode: 200
    INFO-> 14.462 main 请求uri:http://fun.tester:12345/ , 耗时:2 ms , HTTPcode: 200
    ****省略多余的内容****
    

    可以看出,单线程请求HTTP服务,DNS只会解析一次,经过多次尝试,解析的IP会在设定的两个IP之间随机出现,但这明显不符合我们的需求。

    多线程

    测试用例如下:

        public static void main(String[] args) {
            String url = "http://fun.tester:12345/"
            def get = getHttpGet(url)
            def test = {
                fun {
                    getHttpResponse(get)
                }
            }
            10.times {
                test()
            }
        }
    

    控制台输出:

    INFO-> 03.636 main 
      ###### #     #  #    # ####### ######  #####  ####### ######  #####
      #      #     #  ##   #    #    #       #         #    #       #    #
      ####   #     #  # #  #    #    ####    #####     #    ####    #####
      #      #     #  #  # #    #    #            #    #    #       #   #
      #       #####   #    #    #    ######  #####     #    ######  #    #
    
    INFO-> 04.581 Deamon 守护线程开启!
    INFO-> 04.843 F-6  /0.0.0.0
    INFO-> 04.843 F-4  /127.0.0.1
    INFO-> 04.843 F-7  /0.0.0.0
    INFO-> 04.844 F-2  /0.0.0.0
    INFO-> 04.844 F-10 /0.0.0.0
    INFO-> 04.844 F-1  /0.0.0.0
    INFO-> 04.844 F-5  /127.0.0.1
    INFO-> 04.844 F-3  /127.0.0.1
    INFO-> 04.844 F-8  /0.0.0.0
    INFO-> 04.844 F-9  /127.0.0.1
    
    INFO-> 04.903 F-7  请求uri:http://fun.tester:12345/ , 耗时:309 ms , HTTPcode: 200
    INFO-> 04.903 F-3  请求uri:http://fun.tester:12345/ , 耗时:309 ms , HTTPcode: 200
    INFO-> 04.903 F-2  请求uri:http://fun.tester:12345/ , 耗时:309 ms , HTTPcode: 200
    ****省略多余的内容****
    
    

    这下我们就能看出每个线程都执行了一次org.apache.http.impl.conn.SystemDefaultDnsResolver#resolve方法,获取到了IP也是随机的,而且每次请求的耗时都是比较长的。这里让我心生疑惑,相当于每个线程请求都是重新重建了连接,于是就有了下面的测试。

    单个连接

    这里我把HttpClient的连接池的最大连接数改成了1: public static int MAX_PER_ROUTE_CONNECTION = 1;或者 public static int MAX_TOTAL_CONNECTION = 1;,这个之前分享过,这里不多讲了,上用例:

    用例同多线程用例
    

    控制台输出:

    INFO-> 02.928 main 
      ###### #     #  #    # ####### ######  #####  ####### ######  #####
      #      #     #  ##   #    #    #       #         #    #       #    #
      ####   #     #  # #  #    #    ####    #####     #    ####    #####
      #      #     #  #  # #    #    #            #    #    #       #   #
      #       #####   #    #    #    ######  #####     #    ######  #    #
    
    INFO-> 03.648 Deamon 守护线程开启!
    INFO-> 03.910 F-5  /0.0.0.0
    INFO-> 03.961 F-6  请求uri:http://fun.tester:12345/ , 耗时:299 ms , HTTPcode: 200
    INFO-> 03.961 F-5  请求uri:http://fun.tester:12345/ , 耗时:299 ms , HTTPcode: 200
    INFO-> 03.961 F-4  请求uri:http://fun.tester:12345/ , 耗时:300 ms , HTTPcode: 200
    INFO-> 03.961 F-7  请求uri:http://fun.tester:12345/ , 耗时:300 ms , HTTPcode: 200
    INFO-> 03.961 F-3  请求uri:http://fun.tester:12345/ , 耗时:300 ms , HTTPcode: 200
    INFO-> 03.961 F-2  请求uri:http://fun.tester:12345/ , 耗时:300 ms , HTTPcode: 200
    INFO-> 03.961 F-1  请求uri:http://fun.tester:12345/ , 耗时:300 ms , HTTPcode: 200
    INFO-> 03.961 F-9  请求uri:http://fun.tester:12345/ , 耗时:300 ms , HTTPcode: 200
    INFO-> 03.961 F-8  请求uri:http://fun.tester:12345/ , 耗时:300 ms , HTTPcode: 200
    INFO-> 03.961 F-10 请求uri:http://fun.tester:12345/ , 耗时:300 ms , HTTPcode: 200
    WARN-> 04.673 Deamon 异步线程池关闭!
    

    这里看到虽然我起了10个线程分别执行请求,但是每个请求的耗时都是非常长的,但是只有F-5这个线程执行了一次org.apache.http.impl.conn.SystemDefaultDnsResolver#resolve方法,由于HttpClient只有一个连接。所以应当是每个连接创建的时候会调用org.apache.http.impl.conn.SystemDefaultDnsResolver#resolve方法,而每个线程请求耗时比较高,原因是因为每个线程去获取到链接资源之后,会重新进行建联的过程导致的。

    实践出真知,奇怪的知识又增加了。

    Have Fun ~ Tester !

  • 相关阅读:
    微信小程序在sublime开发代码高亮显示
    CSS之flex兼容
    本地存储(2)
    IE浏览器兼容性问题解决方案
    设计一套方案,解决不同浏览器的兼容问题(2)
    Webpack, 现在最流行的模块打包工具.压缩打包
    Linux环境下安装配置Node.js
    阿里云服务器 linux 怎么安装php(PHPSTUDY)开发环境
    函数与闭包
    内建的控制结构
  • 原文地址:https://www.cnblogs.com/FunTester/p/15880427.html
Copyright © 2020-2023  润新知