https://www.hellojava.com/a/76003.html
1)用ip地址字符串hash,显然太low
2)ip地址字符串转换为long,ipv4总共2^32个(4g),一个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;
}
其中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空间更小