【问题标题】:Consistent String#hash based only on the string's content一致的 String#hash 仅基于字符串的内容
【发布时间】:2011-09-26 02:02:01
【问题描述】:

目标:将服务器处理的每个 URL 映射到 0、1、2 或 3,尽可能均匀地分布。

虽然 ruby​​ 的 String#hash 方法的 documentation 表示它将“根据字符串的长度和内容返回一个哈希”,但这显然不是全部。给定字符串的哈希值在解释器的调用中不一致:

$ irb
ruby-1.9.2-p180 :001 > "foo".hash
 => 360517580588231756 
ruby-1.9.2-p180 :002 > ^D

$ irb
ruby-1.9.2-p180 :001 > "foo".hash
 => -2716152678666510148 

这意味着特定字符串的哈希值可能在服务器之间有所不同。 Rails 在内部使用String#hash 将URL 路径映射到四个资产主机之一(如果应用程序的asset_host 是so configured),但是由于跨机器不一致,此功能的效率要低得多;不同的服务器可能会将相同的 URL 映射到不同的资产主机,从而降低缓存的有效性、使天空阴云密布、过早冷却茶杯、玷污其他优秀程序员的声誉。

您能否建议一种替代散列函数,该函数可以有效且快速地在典型应用的 URL 空间中分发散列,最好是生成 Fixnum 的散列函数,因为最后我想将其映射到四个资产主机之一?

【问题讨论】:

标签: ruby ruby-on-rails-3 hash


【解决方案1】:

ruby 的摘要模块中有很多这样的功能:http://ruby-doc.org/stdlib/libdoc/digest/rdoc/index.html

简单示例:

require 'digest/sha1'
Digest::SHA1.hexdigest("some string")

【讨论】:

  • 是的,但是 SHA1 和 MD5 是不是矫枉过正?太慢了?
  • 我会说足够快。您可以检查 google 的 CityHash,它的目标是性能并最大限度地减少一般文本字符串中的冲突,但尝试 sha1/md5 并测试性能。
  • @RobDavis #hexdigest 中的“hex”表明输出是一个十六进制数,要将其转换为 int,您只需调用 to_i(16)
  • 我不知道您系统的限制,但 sha 一个真正解决您问题的方法,就像 md5 和 murmur hash 和 crc32 一样。不同之处在于您将 url 映射到的空间 - 只有 40 亿个 32 位数字,这对于我能想到的几乎所有问题都不够好。与其将自己限制在 32 位短整数上,不如尝试以更高的基数表示大整数 - base64/62 是用少数字符表示大整数的好方法。
【解决方案2】:

最简单(且一致)的方法可能是这样(而且速度很快):

"https://www.example.com/abc/def/123?hij=345".sum % 4

这总是会产生一个 0 - 3 的整数,速度非常快,并且分布应该相当好(尽管我实际上并没有对分布进行测试)。

【讨论】:

    【解决方案3】:

    有一个很小的图书馆xxHash:

    XXhash.xxh32('qwe') #=> 2396643526
    XXhash.xxh64('qwe') #=> 9343136760830690622
    

    也许它会有更多的冲突,但它比 SHA1 快 10 倍:

    Benchmark.bm do |x|
      n = 100_000
      str = 'qweqweqwe'
      x.report('xxhash32') { n.times { XXhash.xxh32(str) } }
      x.report('xxhash64') { n.times { XXhash.xxh64(str) } }
      x.report('hexadigest') { n.times { Digest::SHA1.hexdigest(str) } }
    end;1
    
    #       user     system      total        real
    # xxhash32  0.020000   0.000000   0.020000 (  0.021948)
    # xxhash64  0.040000   0.000000   0.040000 (  0.036340)
    # hexadigest  0.240000   0.030000   0.270000 (  0.276443)
    

    【讨论】:

    • 只是基准测试说明:在我的 i7 第 8 代联想笔记本电脑上,XXhash.xxh32(str) 是`0.016127`; xx64(str)0.020158)hexdigest 是 `0.212843)` 所以我会选择 xxh32
    【解决方案4】:

    你可以试试to_i(36)

    "Hash me please :(".to_i(36)
    => 807137
    

    【讨论】:

    • 虽然这似乎只看前四个字符:"Hash something else".to_i(36) 也会产生 807137
    • 它只在第一个空格之前有效。所以它可用于 URL。在 Fixnum 上有一个类似的方法叫做 to_s(36)。
    • 其实好像停在第一个斜线处。
    • 它将在第一个非字母数字字符处停止,因为您正在指定一个数字基数。你可以去掉这些字符,但是你可能不得不担心溢出(因为你基本上是在要求它解析一个巨大的数字)。
    • 这也不是均匀分布,而是根据最后一个字母的频率加权。例如。 “e”在英语中更常见,因此您将有更多请求发送到与 e 关联的服务器。
    猜你喜欢
    • 2018-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多