有一个表包含startIP,endIp以及与位置等相关的所有其他数据。
在应用程序启动时,缓存整个表数据以减少查询。现在当用户连接到应用程序时,我们得到了IP(即客户端IP),需要从缓存中找到Ip范围。
那么,什么是更好的缓存实现?
目前我的实施方式如下:
ConcurrentNavigableMap<String, ConcurrentNavigableMap<String, String>> cache;
外部地图键如下: startIP_endIP
即10.115.1.10_10.115.1.240
和外部地图值(内部地图)包含单行表格。
如上所述实现密钥有一个好处就是我可以使用 ConcurrentNavigableMap.subMap()方法来减少迭代。
查找以下方案
请在下面找到代码段:
System.out.println("==============================================");
System.out.println("clientIP: " + clientIP); // Client IP address
ConcurrentNavigableMap<String, Object> subMap = null;
String clientIPTokens[] = new String[4];
clientIPTokens[0] = clientIP; // 10.115.1.10
clientIPTokens[1] = clientIP.substring(0, clientIP.lastIndexOf(".")); // 10.115.1
clientIPTokens[2] = clientIP.substring(0, clientIP.indexOf(".", clientIP.indexOf(".")+1)); // 10.115
clientIPTokens[3] = clientIP.substring(0,clientIP.indexOf(".") ); //10
ConcurrentNavigableMap<String, Object> packageMap = null;
boolean isValid = false;
for(String tokens : clientIPTokens){
subMap = MapCache.getSubMapByPreffix(COLLECTION_NAME, tokens); // got submap
System.out.println("tokens: " + tokens + ", subMap: " + subMap);
if(subMap!=null && subMap.size()>0){
for(String key : subMap.keySet()){
isValid = checkIPInMap((ConcurrentNavigableMap<String, String>) subMap.get(key),clientIP);
if(isValid) // ip range found for the client ip (Got the single row)
break;
}
}
if(isValid)
break;
}
// When no match found need to iterate over whole cache
if(!isValid && MapCache.getMap()!=null && MapCache.getMap().containsKey(COLLECTION_NAME)){
packageMap = MapCache.getMap().get(COLLECTION_NAME);
isValid = checkIPInFullMap(packageMap, clientIP);
}
我在表格中有大约650个条目,所以我怎样才能更好地实现缓存或缓存密钥,以便更容易进行搜索。
因此,底线是:
我有一个IP和IP-Range列表(start-ip,end-ip)。并且需要找到它将落在哪个IP范围内。但必须遍历整个列表或(子列表)并需要检查
public static boolean isValidRange(String ipStart, String ipEnd,
String ipToCheck){
try {
long ipLo = ipToLong(InetAddress.getByName(ipStart));
long ipHi = ipToLong(InetAddress.getByName(ipEnd));
long ipToTest = ipToLong(InetAddress.getByName(ipToCheck));
return (ipToTest >= ipLo && ipToTest <= ipHi);
} catch (UnknownHostException e) {
//throw new Exception();
e.printStackTrace();
return false;
}
}
我相信这种逻辑必须要有所改善!!!! :)
答案 0 :(得分:0)
可以将IP地址作为数字进行比较(如果您的范围正确)。
尝试将ipStart和ipEnd转换为32位数字(我更喜欢它以避免签名的比较技巧)。为此数字添加一个开始和结束标记(如果使用long,则只需使用较低的数字位)并将其存储到任何类型的排序列表或集合中。
现在选择clientIP并将其转换为相同类型的数字。在列表中搜索两个最接近的数字,在客户端IP之上和之下,并检查开始/结束标志,以确保在开始和结束(范围内)之间失败,而不是结束和开始(范围之间)。
我认为这是您最快,最清晰的方法。
修改强>
是的,您的检查逻辑是正确的。 我为你的主要问题提出了整体解决方案 - 通过ip查找范围。
一些伪代码:
//setup:
TreeSet<Long> ranges = new TreeSet<>();
for(int i = 0; i < rangesCount; ++i) {
ranges.add(toLong(rangeStart[i]) << 1);
ranges.add((toLong(rangeEnd[i]) << 1) + 1);
}
//check:
long client = toLong(clientIp) << 1;
Long floorIp = ranges.floor(client);
Long ceilIp = ranges.ceiling(client);
if(floorIp == null || ceilIp == null) {
//outside any ranges
} else {
if(floorIp & 1 == 0 && ceilIp & 1 == 1) {
//in range floorIp-ceilIp
} else {
//between ranges
}
}
当然,范围不能以任何方式相交。
您还可以通过在ip和开始结束标记之间将其混合为长数来保持此结构中的范围编号。
long marker = (ip << 16 + (short) range_id) << 1 + startEndMark;
long floorIp = ranges.floor(clientIp << 17 + MAX_SHORT << 1);
long ceilIp = ranges.ceiling(clientIp << 17);
最后一行的小评论:
我们需要MAX_SHORT << 1
来正确处理间隔的开始边缘,因为
0xIP_IP_IP_IP_IDID_0 >= 0xIP_IP_IP_IP_0000_0
,但0xIP_IP_IP_IP_IDID_0 <= 0xIP_IP_IP_IP_FF_FF_0
。对于结束边缘:0xIP_IP_IP_IP_0000_0 <= 0xIP_IP_IP_IP_IDID_1
。
或者你可以将排序范围放到数组中,并使用数组索引作为范围数。
编辑2:
还有一种真正的Java方式:为IP和范围ID创建对象(以及开始标记等)。定义Comparator(或实现Comparable)并将此对象存储在有序集合或映射中,而不是位混合long。