代码之家  ›  专栏  ›  技术社区  ›  Gdeglin

在Ruby进程之间使用大型数据对象

  •  6
  • Gdeglin  · 技术社区  · 14 年前

    我有一个Ruby哈希,如果使用marshal.dump写入一个文件,它将达到大约10兆字节。gzip压缩后,大约有500千字节。

    在Ruby中,迭代和修改这个哈希非常快(一毫秒的分数)。即使是复制也非常快。

    问题是我需要在RubyonRails进程之间共享这个哈希中的数据。为了使用Rails缓存(File_Store或Memcached)进行此操作,我需要首先对文件进行marshal.dump,但是在序列化文件时,这会导致1000毫秒的延迟,而在序列化文件时会导致400毫秒的延迟。

    理想情况下,我希望能够在不到100毫秒的时间内保存和加载每个进程中的哈希。

    一个想法是生成一个新的Ruby进程来保存这个哈希,它为其他进程提供一个API来修改或处理其中的数据,但是我希望避免这样做,除非我确定没有其他方法可以快速共享这个对象。

    是否有一种方法可以更直接地在进程之间共享此哈希,而无需对其进行序列化或反序列化?

    下面是我用来生成散列的代码,与我正在使用的代码类似:

    @a = []
    0.upto(500) do |r|
      @a[r] = []
      0.upto(10_000) do |c|
        if rand(10) == 0 
          @a[r][c] = 1 # 10% chance of being 1
        else
          @a[r][c] = 0
        end
      end
    end
    
    @c = Marshal.dump(@a) # 1000 milliseconds
    Marshal.load(@c) # 400 milliseconds
    

    更新:

    由于我最初的问题没有得到多少回答,我假设没有像我希望的那样简单的解决方案。

    目前我正在考虑两种选择:

    1. 创建一个Sinatra应用程序,用一个API来存储这个哈希,以修改/访问它。
    2. 创建一个C应用程序来执行与1相同的操作,但速度要快得多。

    我的问题范围增加了,使得散列值可能比我原来的示例大。因此2可能是必要的。但我不知道从何处开始编写一个公开适当API的C应用程序。

    一个关于如何最好地实施1或2的良好演练可能会获得最佳答案学分。

    更新2

    最后,我把它作为一个用Ruby1.9编写的独立应用程序来实现,它有一个DRB接口来与应用程序实例通信。我使用守护进程gem在Web服务器启动时生成DRB实例。在启动时,DRB应用程序从数据库加载必要的数据,然后与客户机通信以返回结果并保持最新。它现在的生产运行得相当好。谢谢你的帮助!

    6 回复  |  直到 14 年前
        1
  •  3
  •   Marc-André Lafortune    14 年前

    Sinatra应用程序可以工作,但与DRB服务相比,un序列化和HTML解析可能会影响性能。

    下面是一个例子,基于您在相关问题中的示例。我使用的是散列而不是数组,因此您可以将用户ID用作索引。这样就不需要同时保留感兴趣的表和服务器上的用户ID表。请注意,与您的示例相比,利息表是“转置的”,这是您无论如何都想要它的方式,因此可以在一次调用中更新它。

    # server.rb
    require 'drb'
    
    class InterestServer < Hash
      include DRbUndumped # don't send the data over!
    
      def closest(cur_user_id)
        cur_interests = fetch(cur_user_id)
        selected_interests = cur_interests.each_index.select{|i| cur_interests[i]}
    
        scores = map do |user_id, interests|
          nb_match = selected_interests.count{|i| interests[i] }
          [nb_match, user_id]
        end
        scores.sort!
      end
    end
    
    DRb.start_service nil, InterestServer.new
    puts DRb.uri
    
    DRb.thread.join
    
    
    # client.rb
    
    uri = ARGV.shift
    require 'drb'
    DRb.start_service
    interest_server = DRbObject.new nil, uri
    
    
    USERS_COUNT = 10_000
    INTERESTS_COUNT = 500
    
    # Mock users
    users = Array.new(USERS_COUNT) { {:id => rand(100000)+100000} }
    
    # Initial send over user interests
    users.each do |user|
      interest_server[user[:id]] = Array.new(INTERESTS_COUNT) { rand(10) == 0 }
    end
    
    # query at will
    puts interest_server.closest(users.first[:id]).inspect
    
    # update, say there's a new user:
    new_user = {:id => 42}
    users << new_user
    # This guy is interested in everything!
    interest_server[new_user[:id]] = Array.new(INTERESTS_COUNT) { true } 
    
    puts interest_server.closest(users.first[:id])[-2,2].inspect
    # Will output our first user and this new user which both match perfectly
    

    要在终端中运行,请启动服务器并将输出作为参数提供给客户端:

    $ ruby server.rb
    druby://mal.lan:51630
    
    $ ruby client.rb druby://mal.lan:51630
    [[0, 100035], ...]
    
    [[45, 42], [45, 178902]]
    
        2
  •  2
  •   ndp    14 年前

    也许这太明显了,但是如果您牺牲一点对散列成员的访问速度,传统的数据库将为您提供对值的更稳定的时间访问。您可以从那里开始,然后添加缓存,看看是否可以从中获得足够的速度。这将比使用Sinatra或其他工具简单一点。

        3
  •  0
  •   Zepplock    14 年前

    小心memcache,它有一些对象大小限制(2MB左右)

    要尝试的一件事是使用MongoDB作为存储。它非常快,您可以将几乎所有的数据结构映射到其中。

        4
  •  0
  •   Judson    14 年前

    如果在方法调用中包装您的怪物散列是明智的,您可以简单地使用drb呈现它-启动一个小的守护进程,以散列作为前端对象启动一个drb服务器-其他进程可以使用相当于rpc的量对其进行查询。

    更重要的是,有没有其他方法来解决你的问题?不知道你想做什么,很难说是肯定的-但也许一个特里亚,或布卢姆过滤器会工作?或者,即使是一个接口很好的位域也可能为您节省相当多的空间。

        5
  •  0
  •   Mike Buckbee    14 年前

    是否考虑增大memcache最大对象大小?

    版本大于1.4.2

    memcached -I 11m #giving yourself an extra MB in space
    

    或者在以前的版本中更改slabs.c中的power_块的值并重新编译。

        6
  •  0
  •   Sixty4Bit    14 年前

    将数据存储在memcache中而不是将哈希存储在memcache中怎么样?使用上面的代码:

    @a = []
    0.upto(500) do |r|
      @a[r] = []
      0.upto(10_000) do |c|
        key = "#{r}:#{c}"
        if rand(10) == 0 
          Cache.set(key, 1) # 10% chance of being 1
        else 
          Cache.set(key, 0)
        end
      end
    end
    

    这将是快速的,您不必担心序列化,并且您的所有系统都可以访问它。我在一篇关于访问数据的评论中问到,你必须要有创造性,但这应该很容易做到。