Структура данных для хранения диапазонов IP-адресов, обеспечивающая быстрый поиск данного IP-адреса (Java)

Я пытаюсь придумать эффективную структуру данных для представления диапазонов IP-адресов, как описано чуть ниже. Я знаю, что то, что я хочу попробовать, довольно легко возможно, я просто не могу понять это.

Итак, допустим, у меня есть сотни отдельных диапазонов IP-адресов в формате 1.1.1.0 - 1.1.2.255 или в каком-либо другом формате (но не в формате CIDR, таком как 1.1.1.0/24).

Различные диапазоны не являются последовательными, поэтому между концом одного и началом следующего могут быть миллионы IP-адресов. При желании они могли бы / вместо этого были бы представлены в целочисленном формате (например, 16843008–16843519 в этом примере).

Не было бы известного наложения IP-адресов в другие диапазоны.

По сути, эти диапазоны представляют собой сетевые блоки ASN, если вам интересно. И мне нужно создать инструмент, чтобы определить, попадает ли какой-либо данный IP в один из этих диапазонов, но инструмент должен быть достаточно быстрым (в идеале менее 0,5 секунды).

Теперь, если у меня есть сотни или тысячи этих диапазонов, которые охватывают миллионы IP-адресов, и я хочу определить, находится ли данный IP-адрес в одном из диапазонов (или нет), что будет самым быстрым способом, при этом не слишком много памяти интенсивный?

Я могу придумать несколько вариантов:

  • Создайте HashSet, который содержит каждый IP-адрес из всех диапазонов, и просто сделайте для него contains (ip). Я ожидал, что там будет около 50 миллионов IP-адресов. Быстро, но кажется немного расточительным с точки зрения памяти?

  • Имейте TreeMap, ключ которого является начальным IP-адресом каждого диапазона, а значение - конечным IP-адресом. Пройдите по дереву и проверьте для каждого ключа, если тестовый IP-адрес больше, чем этот ключ, но меньше, чем следующий ключ. Если это так, то исследуйте значение (то есть конечный IP-адрес диапазона), и если IP-адрес меньше значения карты, тогда IP-адрес находится в диапазоне - если нет, нет смысла продолжать и можно предположить, что IP-адрес не в любом из диапазонов. Может ли, вероятно, бинарный поиск по ключам дерева быстрее прийти к выводу, а не проверка по порядку?

  • Другая идея - иметь HashMap, ключами которого будут все возможные подсети во всех диапазонах (я понимаю, что их будет много), например «123.123.123, 123.123.124, 123.123.125, 211.211.211, 211.211.215. "и т. д. Затем, если меня попросят проверить IP 123.123.124.144, я сначала смогу посмотреть, является ли его подсеть (123.123.124) ключом на карте. Значение карты может быть настраиваемым объектом, содержащим начальный и конечный IP-адреса диапазона, связанного с этой конкретной подсетью. Затем вы можете просто использовать это, чтобы проверить, подходит ли полный IP-адрес диапазону. Этот специальный объект будет совместно использоваться многими записями на карте, поскольку очевидно, что в данном диапазоне может быть много подсетей.

Итак, какие-нибудь мысли / идеи / мнения? Я чувствую, что моя вторая идея может быть хорошим способом пойти? Спасибо за информацию ... очень рад услышать ваши идеи!


person ConorD55    schedule 16.08.2014    source источник
comment
Чем больше я думал об этом, тем больше понимал, что проблему довольно легко решить. Просто сохраните массивный HashMap, где ключи - это все возможные подсети из каждого диапазона. У него максимальный размер 1764705, но для меня он будет только около 5% от этого - так что он не слишком большой. Я забыл, что каждый IP-адрес в подсети может обрабатываться одинаково, поскольку ASN не могут быть разбиты меньше, чем на уровне подсети.   -  person ConorD55    schedule 19.08.2014


Ответы (4)


Если диапазоны не содержат поддиапазонов, вы можете проверить guava RangeSet.
https://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#RangeSet
На самом деле я не анализировал временную и пространственную сложность RangeSet, но RangeSet, похоже, удовлетворяет ваши требование вполне хорошо.

person Fuqiang Jiang    schedule 16.08.2014

Я использую дерево AVL с диапазоном IP-адресов в качестве значения узла и подходящей функцией сравнения. (Если диапазон равен a..b (a ‹= b), при сравнении двух диапазонов r1 и r2: r1‹ r2, если r1.b ‹r2.a; r1" == "r2, если r1.a> = r2. a и r1.b ‹= r2.b; r1> r2, если r1.a> r2.b. Таким образом," == "означает, что r1 равно или входит в r2.)

Если у вас нет совпадений, этого достаточно. Если у вас есть перекрытия (как у меня, но я обрабатываю сетевые префиксы), вы получите деревья AVL, вложенные в деревья AVL.

Когда вы говорите, что нет перекрытия сетевых блоков ASN, я предполагаю, что если ASN имеет делегированный ему / yy, вы разбиваете родительский / xx на отдельные, но непрерывные сетевые блоки.

Поскольку ваш список сетевых блоков не так часто меняется, вам, вероятно, не нужно дерево AVL. Вы можете просто отсортировать сетевые блоки и попасть в список с помощью двоичного кода. Если вам нужно что-то, что работает быстрее, чем двоичное дерево / чоп, у вас может быть вспомогательный набор указателей в двоичный фрагмент, используя ms-байт начала просматриваемого диапазона, чтобы идентифицировать первый и последний диапазоны, которые стоит посмотреть в.

person Community    schedule 17.08.2014

Это структура, которую я использую. Давайте возьмем другую таблицу, в данном случае таблицу locations, чтобы увидеть назначение и использование диапазонов IP-адресов в реальной ситуации.

--
-- Table structure for table `locations`
--
CREATE TABLE IF NOT EXISTS `locations` (
  `location_id` int(10) unsigned NOT NULL,
  `parent_id` int(10) unsigned NOT NULL,
  `location_name` varchar(64) NOT NULL,
  PRIMARY KEY (`location_id`),
  KEY `parent_id` (`parent_id`)
);

--
-- Table structure for table `locations_to_ip_ranges`
--
CREATE TABLE IF NOT EXISTS `locations_to_ip_ranges` (
  `l_ip_r` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `location_id` int(10) unsigned NOT NULL,
  `starting_ip` varchar(45) NOT NULL,
  `ending_ip` varchar(45) NOT NULL,
  `starting_cidr` int(10) unsigned NOT NULL,
  `ending_cidr` int(10) unsigned NOT NULL,
  PRIMARY KEY (`l_ip_r`),
  KEY `location_id` (`location_id`)
);

Вот несколько записей из второй таблицы

l_ip_r  location_id starting_ip     ending_ip        starting_cidr  ending_cidr
-------------------------------------------------------------------------------
94005   47          217.147.0.0     217.147.15.255   3650289664     3650293759
94004   47          217.146.32.0    217.146.47.255   3650232320     3650236415
94003   47          217.145.144.0   217.145.159.255  3650195456     3650199551
94002   47          217.145.16.0    217.145.31.255   3650162688     3650166783
94001   47          217.144.176.0   217.144.191.255  3650138112     3650142207

Следующая полезная функция для преобразования IP-адреса в номер CIDR. Он написан на PHP, но я считаю, что его будет легко преобразовать в Java. Используемая здесь функция explode() разделяет строку в соответствии с заданным разделителем.

function ip_address_to_cidr($ip_address){
    $ips = explode(".", $ip_address);
    return ($ips[3] + $ips[2] * 256 + $ips[1] * 65536 + $ips[0] * 16777216);
}

Итак, если вы хотите получить страну для данного IP-адреса, у вас будет что-то вроде этого

// call the ip_ip_address_to_cidr function passing the remote_address as an argument
$cidr = ip_address_to_cidr($_SERVER['REMOTE_ADDR']);

// pass the returned $cidr to the following query and get the location_id
$set = mysql_query("
    SELECT location_id 
    FROM locations_to_ip_ranges
    WHERE " . $cidr . " BETWEEN starting_cidr AND ending_cidr
");
$row = mysql_fetch_object($set);
echo $row->location_id 

Поскольку мне нужна многоязычная поддержка, есть еще одна таблица locations_to_languages, которую я не упомянул, чтобы она оставалась простой и понятной. В настоящее время эти таблицы содержат десятки миллионов данных, и у меня нет проблем с производительностью.

Боковое примечание: я давно не использую Java, поэтому приведенный выше фрагмент написан на PHP, но я считаю, что будет несложно понять логику и при необходимости преобразовать ее в Java.

person hex494D49    schedule 16.08.2014
comment
Привет, конечно, это можно было бы очень легко сделать на уровне базы данных, но это слишком медленно и проходит через слишком много данных. Я ищу подходящую структуру данных для поиска в памяти, не просматривая все. Я не ищу схему базы данных. Однако спасибо за подробный ответ! - person ConorD55; 16.08.2014

Мы можем использовать btrees, с помощью которых мы можем сопоставить IP-адрес в первичной памяти, а затем сопоставить их с вторичной памятью. Как мы знаем, нам нужно хранить довольно большое количество IP-адресов, мы не можем хранить их в первичной памяти, как есть, лучше, если бы мы использовали btree. Поскольку это похоже на принцип хеширования, также эффективное использование памяти.

person Rohit Nunna    schedule 15.08.2015