Можете ли вы объяснить эти тревожные аномалии md5 и по модулю? - PullRequest
6 голосов
/ 29 марта 2011

Хорошо, название действительно очень субъективное.Но это как раз то, в чем проблема для меня.

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

Я также хочу убедиться, что один и тот же URL-адрес всегда обслуживается одним и тем же сервером.

Итак, я только что определил небольшую функцию, которая возвращает хост для использования путем хэширования URL-адреса запроса и вычисляет по модулю количество используемых серверов:

function pseudocode_statify($url) { // $url looks like /folder1/folder2/file.jpg
 return 'http://' . md5($url) % $num_of_servers .'.mydomain.com' . $url;
}

Сначала у меня было что-то вроде шестнадцатеричного декодирования и подстроки кпредотвратить переполнение на месте, но обнаружил, что он просто отлично работает, как описано выше.

Однако моя проблема заключается в том, что, если я запускаю следующий тестовый скрипт:

for($i=0;$i<100000;$i++) {
  $md5 = md5(uniqid($i).microtime().rand(1,999999999999));
  $result[$md5%2]++;
}

Я ожидал равномерного распределения,это означает, что $ result [0] будет около значения $ result [1];

Это не тот случай.

Ладно, ничего особенного, софар.Я бы просто принял тот факт, что md5 распределен не так равномерно, как я думал, и использовал бы другой алгоритм хеширования, например, sha1 или что-то в этом роде.

Но я попытался воспроизвести результаты и нашел образец, который не могуобъясните.

Соотношение всегда было около 2/1.На самом деле это соотношение всегда было что-то вроде 1 / 2,16 к 1 / 2,17

Пример вывода некоторых запусков приведенного выше сценария:

output was generated by: echo "ratio: ".$result[0]/$result[1]."\n";

ratio: 2.1757121534504
ratio: 2.1729411578062
ratio: 2.1726559360393
ratio: 2.1676895664225
ratio: 2.1667416128848
ratio: 2.1667115284133
ratio: 2.1677791605385
ratio: 2.1658969579688
ratio: 2.1668508131769
ratio: 2.1689292821741

Теперь странным было то, что отношение сумм% 2, равных 1 и сумм% 2, равных 0 , иногда чередуются!

for($j = 0; $j<100;$j++) {
    for($i=0;$i<100000;$i++) {
      $md5 = md5(uniqid($i).microtime().rand(1,999999999999));
      $result[$md5%2]++;
    }
var_dump($result);
}

Я запускал скрипт из командной строки два раза подряд и прерывал егопосле 3 прогонов получилось два выхода:

joe@joe-laptop:/home/flimmit/httpdocs$ php test.php
PHP Notice:  Undefined variable: result in /home/flimmit/httpdocs/test.php on line 6
PHP Notice:  Undefined offset: 0 in /home/flimmit/httpdocs/test.php on line 6
PHP Notice:  Undefined offset: 1 in /home/flimmit/httpdocs/test.php on line 6
array(2) {
  [0]=>
  int(68223)
  [1]=>
  int(31777)
}
array(2) {
  [0]=>
  int(136384)
  [1]=>
  int(63616)
}
array(2) {
  [0]=>
  int(204498)
  [1]=>
  int(95502)
}
^C
joe@joe-laptop:/home/flimmit/httpdocs$ php test.php
PHP Notice:  Undefined variable: result in /home/flimmit/httpdocs/test.php on line 6
PHP Notice:  Undefined offset: 1 in /home/flimmit/httpdocs/test.php on line 6
PHP Notice:  Undefined offset: 0 in /home/flimmit/httpdocs/test.php on line 6
array(2) {
  [1]=>
  int(31612)
  [0]=>
  int(68388)
}
array(2) {
  [1]=>
  int(63318)
  [0]=>
  int(136682)
}
array(2) {
  [1]=>
  int(94954)
  [0]=>
  int(205046)
}
^C
joe@joe-laptop:/home/flimmit/httpdocs$ 

Как видно из первого, первый результат результатов всегда выше, а во второй наоборот.тот же сценарий.

Странно то, что я могу ТОЛЬКО воспроизвести это поведение, когда я запускаю сценарий несколько раз.

Я написал этот небольшой сценарий, чтобы воспроизвести «свопинг» и сгенерировать достаточно данных измерения:

for($j = 0; $j<100;$j++) {
  for($i=0;$i<rand(1000,10000);$i++) {
    $md5 = md5(uniqid($i).microtime().rand(1,99999999));
    $result[$md5%2]++;
    }
    #var_dump($result);
    echo "ratio: ".$result[0]/$result[1]." ".(($result[0]<$result[1]) ? "A":"B")."\n";
    sleep(rand(2,5));
}

Но здесь Он печатает только b, а не A.Это заставило меня подумать, что в сценарии может быть семантическая ошибка, но я ее не нашел.

Я действительно застрял, и это меня очень беспокоит.

Итак, мои вопросы:

  • Можете ли вы порекомендовать любую литературу / веб-ссылки, где я мог бы прочитать немного больше о md5, включая дистрибутивы и т. Д.

  • Можете ли вы объяснить / воспроизвести поведение?У меня есть ошибка здесь?(на самом деле это очень вероятно, но я не могу его найти)

  • Можете ли вы порекомендовать какой-либо другой алгоритм, который подходит для моего варианта использования?Он не должен быть криптографическим или сильным, но быстрым, детерминированным и равномерно распределенным.

Ответы [ 3 ]

7 голосов
/ 29 марта 2011

Функция md5() возвращает строку , а не целое число.

Это означает, что эта строка будет введите по типу целое число по модулю;и поскольку эта строка будет содержать символы в диапазоне 0-9A-F, приведенные к целому числу, у вас есть:

  • 1 шанс из 16 получить 0
  • 9 шансов из16 из получения от 1 до 9
  • 6 шансов из 16 попадания между A и F - , которые будут приведены к 0


Например, это:

$a = md5('plop1');
var_dump($a, (int)$a);

$a = md5('plop2');
var_dump($a, (int)$a);

$a = md5('plop5');
var_dump($a, (int)$a);

Получит следующий вывод:

string 'ac4bf0e466417336599b72a8b2f595da' (length=32)
int 0

string 'ed91c463402dd797d0718350f5bd0acd' (length=32)
int 0

string '85782b3afb04072c1bf172a6a7e6bb5e' (length=32)
int 85782

Я позволю вам угадать возможное влияние, которое это может оказать на результат оператора по модулю; -)

1 голос
/ 29 марта 2011
for ($i = 0; $i < 10; $i++) {
    srand(crc32('test_url1'));
    echo rand().'<br />';
}
for ($i = 0; $i < 10; $i++) {
    srand(crc32('test_url2'));
    echo rand().'<br />';
}

добавьте диапазон в функцию rand, и у вас будет значение вашего сервера.

0 голосов
/ 30 марта 2011

"Исходным фоном является то, что я хочу равномерно распределять обращения к статическому веб-контенту по определенному количеству серверов кэширования."

Многие, многие балансировщики нагрузки уже делают это. Кальмар, Nginx, Лак, HAProxy ...

Пожалуйста, не пишите свой собственный балансировщик нагрузки на PHP. Пожалуйста. * * 1005

...