Сокращение/перефразирование UUID

во-первых, хочу заверить, что осознаю тот факт, что перефразирование — это здравая тема. Однако я хотел бы услышать некоторые из ваших мнений, какой подход вы бы выбрали здесь.

Я создаю распределенное приложение, в котором узлы удаленно создают объекты, идентифицируемые UUID. В конце концов, все сущности должны быть собраны на выделенном стоковом узле, который хранит все сущности с помощью этих UUID.

Теперь я хочу создать дополнительные идентификаторы, более удобные для людей. Кодирование UUID в кодировке Base64 по-прежнему будет создавать идентификаторы с 22 символами, что не подходит для использования человеком. Поэтому мне нужно что-то вроде сервисов сокращения URL. Применение биективных функций не поможет, так как не снизит информативность. Конечно, я в курсе, что мне нужно потерять информацию, чтобы сократить идентификатор. И я также знаю, что любое сокращение информации хэша увеличит вероятность столкновения. Я застрял, как лучше всего сократить информацию, чтобы создать более короткие идентификаторы для людей.

Вот некоторые предварительные условия: Я предоставлю возможность отображать {UUID, сокращенный идентификатор} через мое хранилище данных. Я бы все же предпочел нецентрализованное решение. Мне, вероятно, никогда не понадобится больше, чем около миллиона идентификаторов (~ 2 ^ 20).

Вот мысли, которые я придумал до сих пор:

  • Идентификаторы с автоматическим увеличением: Если бы я использовал какой-либо идентификатор с автоматическим увеличением, я мог бы преобразовать этот идентификатор в запутанную строку и передать ее другим пользователям. Это был бы самый простой подход, и пока вокруг мало ключей, ключи не будут очень длинными. Однако мне пришлось бы ввести централизованную сущность, которую я действительно не хочу.
  • Сократите UUID: я мог бы просто взять некоторые биты исходного 128-битного UUID. Тогда я должен принять во внимание хотя бы версию UUID. Или что-то еще не так с этим?
  • Повторное хеширование UUID: я мог бы применить второй алгоритм хеширования к своему исходному UUID и сохранить сопоставление.

Есть ли другие подходы? Что выгодно?

Заранее спасибо!


person b_erb    schedule 12.02.2010    source источник


Ответы (4)


1) Чтобы сократить UUID, вы можете просто XOR верхней половины с нижней (и повторять, пока он не станет для вас достаточно коротким). Это позволит сохранить характеристики распределения. Как и любое решение, укорачивающее вывод, оно увеличивает вероятность коллизии из-за парадокса дня рождения.

2) XOR представляет собой тривиальный хэш, но так как не требуется дополнительного смешивания, все в порядке. Вы можете использовать CRC или некриптографический хеш для своего UUID, но я не верю, что это какое-то улучшение.

3) Если вы готовы принять некоторое централизованное управление, это не должно быть болезненным. Центральный орган может выделить каждому клиенту блоки адресного пространства среднего размера, после чего клиент может перебирать этот поддиапазон при назначении идентификаторов. Это гарантирует отсутствие коллизий, но также позволяет избежать двустороннего обхода для каждого идентификатора. Один из способов сделать это — использовать 32-битное целое число для идентификатора, выдавая 16-битный блок за раз. Другими словами, первому клиенту передается 0001, что позволяет использовать от 00010000 до 0001FFFF.

4) Вы можете вставить в базу данных с UUID, но также иметь поле идентификации. Это обеспечит альтернативный, более компактный уникальный идентификатор, который может быть ограничен 32-битным целым числом.

person Steven Sudit    schedule 12.02.2010
comment
@3: я привязан к UUID системой, используемой на распределенных узлах. И я не хочу снова добавлять свои собственные идентификаторы, поэтому я буду придерживаться UUID для хранения своих данных. Я просто хочу предоставить некоторые идентификаторы псевдонимов. - person b_erb; 12.02.2010
comment
Я добавлю (4), но я не уверен, что одобряю это. - person Steven Sudit; 12.02.2010
comment
@4: я планирую использовать CouchDB, которая не имеет функций автоинкрементной идентификации, а также по умолчанию использует UUID. Таким образом, дополнительный хэш, который я ищу, будет только дополнительным атрибутом для каждой записи и будет разрешен с использованием представления. - person b_erb; 12.02.2010
comment
Учитывая это, я не думаю, что (4) работает на вас. Является ли (1) достаточно хорошим? Имейте в виду, что парадокс дня рождения говорит, что 32 бита дают вам менее 64 КБ без коллизий. - person Steven Sudit; 12.02.2010
comment
@PartlyCloud - не могли бы вы предоставить пример кода, как это сделать? в основном для №1? пожалуйста? - person Pure.Krome; 22.02.2010
comment
@Pure: в этом нет ничего особенного. Главное использовать Guid.ToByteArray() для получения 16-байтового массива. Затем вы можете использовать оператор ^ для объединения байтов XOR. Если вам нужен 32-битный вывод, вам нужно объединить каждую группу из четырех входных байтов в один выходной байт. Я бы рекомендовал чередовать его так, чтобы первый выходной байт исходил из комбинации смещений 0, 4, 8 и 12. И так далее. - person Steven Sudit; 22.02.2010

Рассматривали ли вы возможность использования внешнего псевдонима, когда вы выбираете словарь понятных человеку терминов и используете их, чтобы сделать (части) UUID более читабельным (сравните с системами геокодирования, такими как What3Words):

de305d54-75b4-431b-adb2-eb6b9e546013

Использование словаря из 65536 слов может стать:

de305d54-zebra-stackoverflow-extraneous-eb6b9e546013

Маловероятно, что пользователи увидят коллизию мысленных хэшей (зебра дважды) с этими удобочитаемыми именами, и ваша база данных не увеличится в размере. Перевод биективный и чисто UI.

person Christopher Oezbek    schedule 28.01.2015

Всего пара вещей, которые приходят на ум:

Каков ваш вариант использования? Если вы беспокоитесь о том, что вы будете генерировать идентификаторы распределенным образом, одним из решений является присвоение каждой машине собственного уникального идентификатора int и использование его в качестве префикса или суффикса для его идентификаторов.

Это на самом деле не помогает, если, не имея центрального объекта, вы не имеете в виду ничего, что отслеживает идентификаторы даже локально. Вы можете позаимствовать страницу из самого UUID и использовать системное время в сочетании с идентификатором машины, назначенным, как указано выше. Это приведет вас к 64 битам + независимо от размера идентификатора вашей машины. По сути, это схема UUID V1, за исключением того, что вы используете что-то короче MAC-адреса для идентификатора машины. Учитывая, что вы знаете, что можете начать с даты >= 12 февраля 2010 г., вы можете сократить ее еще больше.

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

person Jim L    schedule 12.02.2010
comment
Пожалуйста, посмотрите мой первый комментарий к ответу Стивена, чтобы увидеть, что я привязан к UUID системой. - person b_erb; 12.02.2010
comment
Другое дело, что UUID обычно представляют собой хешированные версии значений, сгенерированных этим алгоритмом. - person Steven Sudit; 12.02.2010

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

Этот алгоритм не предназначен для обеспечения безопасности или эффективности, но должен сработать.

public class HashTools {

  final static String inputMapping = "0123456789ABCDEF";

  final static String[] outputMapping = new String[] {
      "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
      "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
  };

  /* Input: String - containing mostly letters / numbers
   * Output: <hashLength> String using 0-9,A-Z encoding
   */
  public static String simpleHash(String str, int hashLength) {
    StringBuilder hashStr = new StringBuilder(hashLength);
    String strUpper = str.toUpperCase();
    int[] hash = new int[hashLength];

    int i, j, num;
    for (i = 0; i < strUpper.length(); i++) {
      char strChar = strUpper.charAt(i);
      num = mapCharToInt(strChar);

      j = i % hashLength;
      hash[j] += num;
    }

    for (i = 0; i < hashLength; i++) {
      hashStr.append(mapIntToHashChar(hash[i]));
    }

    return hashStr.toString();
  }

  private static int mapCharToInt(char hexChar) {
    return inputMapping.indexOf(hexChar);
  }

  private static String mapIntToHashChar(int num) {
    return outputMapping[num % outputMapping.length];
  }
}
person Neromancer    schedule 01.09.2012