ip白名单算法(pdd活跃用户)

https://www.hellojava.com/a/76003.html

 

1)用ip地址字符串hash,显然太low

2)ip地址字符串转换为long(int存不了那么多,unsigned int 可以),ipv4总共2^32个(4g=2^2*2^10*2^10*2^10=4*1024^3),一个bit存一个ip地址,4g个bit=0.5g个字节

故建立长度为0.5g的byte数组,接下去要确定的是白ip所在的位置

所在数组索引=ip/8

在byte中位索引=ip%8=ip&(8-1)

    /**
     * ipv4总共2^32个(4g)
     * 一个bit存一个ip地址,4g个bit=0.5g个字节
     */
    private static byte [] pool = new byte[512*1024*1024];

    // 0-255 <子ip字符串,long> 缓存
    private static ConcurrentHashMap<String, Long> cache = new ConcurrentHashMap<>(256);

    public static void add(String ip) {
        long ipLong = ipToLong(ip);
        long index = ipLong / 8;
        long pos = ipLong & (8-1);相当于ipLong % 8
        pool[(int)index] |= 1 << pos;
    }

    public static boolean check(String ip) {
        long ipLong = ipToLong(ip);
        long index = ipLong / 8;
        long pos = ipLong & (8-1);
        return (pool[(int)index] & (1<<pos)) > 0;
    }

 

为什么hashmap以2的倍数作为桶的长度,同时以2作为扩容倍数

其中ip字符串转数字涉及到一个缓存,如"192"直接缓存,下一次不需要再转数字

 如:

192.168.1.3

为3232235779,

byte index = 404029472.375

bit index = 3

 

    public static void add(String ip) {
        long ipLong = ipToLong(ip);
    //    System.out.println(ipLong);
        long index = ipLong / 8;
    //    System.out.println(index);
        long pos = ipLong & (8-1);
    //    System.out.println(1 << pos);
        pool[(int)index] |= 1 << pos;
    }

 

程序输出:

3232235779
404029472

8 二进制为 00001000

 

对该方式进行压力测试,CPU密集型,开4个线程,每个线程跑200w次

    public static void test() {
        check("192.168.1.3");
        check("192.168.1.4");
    }

    // 4并发数 缓存3s执行完,不缓存5s,qps约2百万
    // 隔壁需要6s
    public static void main(String [] f) throws InterruptedException {
        String ip = "192.168.1.3";
        add(ip);
        System.out.println(check(ip));
        System.out.println(check("192.168.2.3"));

        String ip1= "192.168.1.3";
        IPFiliterTest.addWhiteIpAddress(ip1);

        final int threadCount = 4;
        CountDownLatch countDownLatchMain = new CountDownLatch(threadCount);
        CountDownLatch countDownLatchSub = new CountDownLatch(1);
        

        for(int i=0; i<threadCount; ++i) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        countDownLatchSub.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    for(int j=0; j<1000000; ++j)
                        test();
                    //IPFiliterTest.test();
                    countDownLatchMain.countDown();

                }
            }).start();
        }
        System.out.println(System.currentTimeMillis());
        countDownLatchSub.countDown();
        countDownLatchMain.await();
        System.out.println(System.currentTimeMillis());
    }

 

结果:

总共800w次在4s中完成(3s为缓存,5s为不缓存),qps=200w

使用原作者的需要6s

10线程5.8s,总共2000w次调用,qps=333w

100线程40.8s,总共2亿次调用,qps=490w

 

使用benchmark

4线程和10线程,一次预热3次迭代都是290w*2=580w左右的qps

 4thr.  IpPool.test  thrpt    3  2908446.729 ± 178674.949  ops/s

10 thr IpPool.test  thrpt    3  2860266.544 ± 505878.277  ops/s

 

该算法使用内存至少512m,稍显大,可以使用外部存储集群,比如redis bit结构,程序分片

总共4g个比特,分为4片,每片1g个bit 128m内存,那么

服务器索引=x/1g

路由后的ip=x%1g=x&(2^30-1)

 

这种方式也可以用于活跃用户统计

(拼多多面试真题:如何用Redis统计独立用户访问量https://blog.csdn.net/weixin_38405253/article/details/92285696),

设20亿用户,每个用户一个bit,总共需要20亿/8个字节=238M内存

缺点就是如果用户稀疏,会浪费内存,对uid hash空间更小

posted on 2019-10-24 14:28  silyvin  阅读(929)  评论(0编辑  收藏  举报