adamwiggins-redis-rb 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
 - data/README.markdown +34 -0
 - data/Rakefile +43 -0
 - data/bin/distredis +33 -0
 - data/examples/basic.rb +16 -0
 - data/examples/incr-decr.rb +18 -0
 - data/examples/list.rb +26 -0
 - data/examples/sets.rb +36 -0
 - data/examples/test_server.rb +13 -0
 - data/lib/dist_redis.rb +118 -0
 - data/lib/hash_ring.rb +127 -0
 - data/lib/pipeline.rb +22 -0
 - data/lib/redis.rb +299 -0
 - data/lib/redis/raketasks.rb +1 -0
 - data/spec/redis_spec.rb +462 -0
 - data/spec/spec_helper.rb +4 -0
 - data/tasks/redis.tasks.rb +126 -0
 - metadata +83 -0
 
    
        data/LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2009 Ezra Zygmuntowicz
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 4 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 5 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 6 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 7 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 8 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 9 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 12 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 15 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 16 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 17 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 18 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 19 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 20 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.markdown
    ADDED
    
    | 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # redis-rb
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            A ruby client library for the redis key value storage system.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ## Information about redis
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Redis is a key value store with some interesting features:
         
     | 
| 
      
 8 
     | 
    
         
            +
            1. It's fast.
         
     | 
| 
      
 9 
     | 
    
         
            +
            2. Keys are strings but values can have types of "NONE", "STRING", "LIST",  or "SET".  List's can be atomically push'd, pop'd, lpush'd, lpop'd and indexed.  This allows you to store things like lists of comments under one key while retaining the ability to append comments without reading and putting back the whole list.
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for more information.
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            ## Dependencies
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            1. rspec - 
         
     | 
| 
      
 16 
     | 
    
         
            +
            		sudo gem install rspec
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            2. redis - 
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            		rake redis:install
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            2. dtach - 
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            		rake dtach:install
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            3. git - git is the new black.
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            ## Setup
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            Use the tasks mentioned above (in Dependencies) to get your machine setup.
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            ## Examples
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            Check the examples/ directory.  *Note* you need to have redis-server running first.
         
     | 
    
        data/Rakefile
    ADDED
    
    | 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rake/gempackagetask'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rubygems/specification'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'spec/rake/spectask'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'tasks/redis.tasks'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            begin
         
     | 
| 
      
 9 
     | 
    
         
            +
              require 'jeweler'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              Jeweler::Tasks.new do |s|
         
     | 
| 
      
 12 
     | 
    
         
            +
                s.name = "redis-rb"
         
     | 
| 
      
 13 
     | 
    
         
            +
                s.summary = "Ruby client library for redis key value storage server"
         
     | 
| 
      
 14 
     | 
    
         
            +
                s.description = s.summary
         
     | 
| 
      
 15 
     | 
    
         
            +
                s.authors = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark', 'Brian McKinney', 'Salvatore Sanfilippo', 'Luca Guidi']
         
     | 
| 
      
 16 
     | 
    
         
            +
                s.email = "ez@engineyard.com"
         
     | 
| 
      
 17 
     | 
    
         
            +
                s.homepage = "http://github.com/ezmobius/redis-rb"
         
     | 
| 
      
 18 
     | 
    
         
            +
                s.has_rdoc = true
         
     | 
| 
      
 19 
     | 
    
         
            +
                s.extra_rdoc_files = ["LICENSE"]
         
     | 
| 
      
 20 
     | 
    
         
            +
                s.require_path = 'lib'
         
     | 
| 
      
 21 
     | 
    
         
            +
                s.autorequire = 'redis'
         
     | 
| 
      
 22 
     | 
    
         
            +
                s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,tasks,spec}/**/*")
         
     | 
| 
      
 23 
     | 
    
         
            +
                s.add_dependency "rspec"
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            rescue LoadError
         
     | 
| 
      
 26 
     | 
    
         
            +
              puts "Jewler is not installed, gem publishing tasks will not be available"
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            ###############################
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            task :default => :spec
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            desc "Run specs"
         
     | 
| 
      
 34 
     | 
    
         
            +
            Spec::Rake::SpecTask.new do |t|
         
     | 
| 
      
 35 
     | 
    
         
            +
              t.spec_files = FileList['spec/**/*_spec.rb']
         
     | 
| 
      
 36 
     | 
    
         
            +
              t.spec_opts = %w(-fs --color)
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            desc "Run all examples with RCov"
         
     | 
| 
      
 40 
     | 
    
         
            +
            Spec::Rake::SpecTask.new(:rcov) do |t|
         
     | 
| 
      
 41 
     | 
    
         
            +
              t.spec_files = FileList['spec/**/*_spec.rb']
         
     | 
| 
      
 42 
     | 
    
         
            +
              t.rcov = true
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
    
        data/bin/distredis
    ADDED
    
    | 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class RedisCluster
         
     | 
| 
      
 4 
     | 
    
         
            +
              
         
     | 
| 
      
 5 
     | 
    
         
            +
              def initialize(opts={})
         
     | 
| 
      
 6 
     | 
    
         
            +
                opts = {:port => 6379, :host => 'localhost', :basedir => "#{Dir.pwd}/rdsrv" }.merge(opts)
         
     | 
| 
      
 7 
     | 
    
         
            +
                FileUtils.mkdir_p opts[:basedir]
         
     | 
| 
      
 8 
     | 
    
         
            +
                opts[:size].times do |i|
         
     | 
| 
      
 9 
     | 
    
         
            +
                  port = opts[:port] + i
         
     | 
| 
      
 10 
     | 
    
         
            +
                  FileUtils.mkdir_p "#{opts[:basedir]}/#{port}"
         
     | 
| 
      
 11 
     | 
    
         
            +
                  File.open("#{opts[:basedir]}/#{port}.conf", 'w'){|f| f.write(make_config(port, "#{opts[:basedir]}/#{port}", "#{opts[:basedir]}/#{port}.log"))}
         
     | 
| 
      
 12 
     | 
    
         
            +
                  system(%Q{#{File.join(File.expand_path(File.dirname(__FILE__)), "../redis/redis-server #{opts[:basedir]}/#{port}.conf &" )}})
         
     | 
| 
      
 13 
     | 
    
         
            +
                end  
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              def make_config(port=6379, data=port, logfile='stdout', loglevel='debug')
         
     | 
| 
      
 17 
     | 
    
         
            +
                config = %Q{
         
     | 
| 
      
 18 
     | 
    
         
            +
            timeout 300
         
     | 
| 
      
 19 
     | 
    
         
            +
            save 900 1
         
     | 
| 
      
 20 
     | 
    
         
            +
            save 300 10
         
     | 
| 
      
 21 
     | 
    
         
            +
            save 60 10000
         
     | 
| 
      
 22 
     | 
    
         
            +
            dir #{data}
         
     | 
| 
      
 23 
     | 
    
         
            +
            loglevel #{loglevel}
         
     | 
| 
      
 24 
     | 
    
         
            +
            logfile #{logfile}
         
     | 
| 
      
 25 
     | 
    
         
            +
            databases 16
         
     | 
| 
      
 26 
     | 
    
         
            +
            port #{port}
         
     | 
| 
      
 27 
     | 
    
         
            +
                }
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
              
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            RedisCluster.new :size => 4
         
     | 
    
        data/examples/basic.rb
    ADDED
    
    
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'redis'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            r = Redis.new
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            puts
         
     | 
| 
      
 7 
     | 
    
         
            +
            p 'incr'
         
     | 
| 
      
 8 
     | 
    
         
            +
            r.delete 'counter'
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            p r.incr('counter')
         
     | 
| 
      
 11 
     | 
    
         
            +
            p r.incr('counter')
         
     | 
| 
      
 12 
     | 
    
         
            +
            p r.incr('counter')
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            puts
         
     | 
| 
      
 15 
     | 
    
         
            +
            p 'decr'
         
     | 
| 
      
 16 
     | 
    
         
            +
            p r.decr('counter')
         
     | 
| 
      
 17 
     | 
    
         
            +
            p r.decr('counter')
         
     | 
| 
      
 18 
     | 
    
         
            +
            p r.decr('counter')
         
     | 
    
        data/examples/list.rb
    ADDED
    
    | 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'redis'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            r = Redis.new
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            r.delete 'logs'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            puts
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            p "pushing log messages into a LIST"
         
     | 
| 
      
 11 
     | 
    
         
            +
            r.push_tail 'logs', 'some log message'
         
     | 
| 
      
 12 
     | 
    
         
            +
            r.push_tail 'logs', 'another log message'
         
     | 
| 
      
 13 
     | 
    
         
            +
            r.push_tail 'logs', 'yet another log message'
         
     | 
| 
      
 14 
     | 
    
         
            +
            r.push_tail 'logs', 'also another log message'
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            puts
         
     | 
| 
      
 17 
     | 
    
         
            +
            p 'contents of logs LIST'
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            p r.list_range('logs', 0, -1)
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            puts
         
     | 
| 
      
 22 
     | 
    
         
            +
            p 'Trim logs LIST to last 2 elements(easy circular buffer)'
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            r.list_trim('logs', -2, -1)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            p r.list_range('logs', 0, -1)
         
     | 
    
        data/examples/sets.rb
    ADDED
    
    | 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'redis'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            r = Redis.new
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            r.delete 'foo-tags'
         
     | 
| 
      
 7 
     | 
    
         
            +
            r.delete 'bar-tags'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            puts
         
     | 
| 
      
 10 
     | 
    
         
            +
            p "create a set of tags on foo-tags"
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            r.set_add 'foo-tags', 'one'
         
     | 
| 
      
 13 
     | 
    
         
            +
            r.set_add 'foo-tags', 'two'
         
     | 
| 
      
 14 
     | 
    
         
            +
            r.set_add 'foo-tags', 'three'
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            puts
         
     | 
| 
      
 17 
     | 
    
         
            +
            p "create a set of tags on bar-tags"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            r.set_add 'bar-tags', 'three'
         
     | 
| 
      
 20 
     | 
    
         
            +
            r.set_add 'bar-tags', 'four'
         
     | 
| 
      
 21 
     | 
    
         
            +
            r.set_add 'bar-tags', 'five'
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            puts
         
     | 
| 
      
 24 
     | 
    
         
            +
            p 'foo-tags'
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            p r.set_members('foo-tags')
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            puts
         
     | 
| 
      
 29 
     | 
    
         
            +
            p 'bar-tags'
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            p r.set_members('bar-tags')
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            puts
         
     | 
| 
      
 34 
     | 
    
         
            +
            p 'intersection of foo-tags and bar-tags'
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            p r.set_intersect('foo-tags', 'bar-tags')
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'socket'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'pp'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require File.join(File.dirname(__FILE__), '../lib/redis')
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            #require File.join(File.dirname(__FILE__), '../lib/server')
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            #r = Redis.new
         
     | 
| 
      
 9 
     | 
    
         
            +
            #loop do
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            #    puts "--------------------------------------"
         
     | 
| 
      
 12 
     | 
    
         
            +
            #  sleep 12
         
     | 
| 
      
 13 
     | 
    
         
            +
            #end
         
     | 
    
        data/lib/dist_redis.rb
    ADDED
    
    | 
         @@ -0,0 +1,118 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'redis'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'hash_ring'
         
     | 
| 
      
 3 
     | 
    
         
            +
            class DistRedis
         
     | 
| 
      
 4 
     | 
    
         
            +
              attr_reader :ring
         
     | 
| 
      
 5 
     | 
    
         
            +
              def initialize(opts={})
         
     | 
| 
      
 6 
     | 
    
         
            +
                hosts = []
         
     | 
| 
      
 7 
     | 
    
         
            +
                
         
     | 
| 
      
 8 
     | 
    
         
            +
                db = opts[:db] || nil
         
     | 
| 
      
 9 
     | 
    
         
            +
                timeout = opts[:timeout] || nil 
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                raise Error, "No hosts given" unless opts[:hosts]
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                opts[:hosts].each do |h|
         
     | 
| 
      
 14 
     | 
    
         
            +
                  host, port = h.split(':')
         
     | 
| 
      
 15 
     | 
    
         
            +
                  hosts << Redis.new(:host => host, :port => port, :db => db, :timeout => timeout, :db => db)
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                @ring = HashRing.new hosts 
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
              
         
     | 
| 
      
 21 
     | 
    
         
            +
              def node_for_key(key)
         
     | 
| 
      
 22 
     | 
    
         
            +
                if key =~ /\{(.*)?\}/
         
     | 
| 
      
 23 
     | 
    
         
            +
                  key = $1
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
                @ring.get_node(key)
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
              
         
     | 
| 
      
 28 
     | 
    
         
            +
              def add_server(server)
         
     | 
| 
      
 29 
     | 
    
         
            +
                server, port = server.split(':')
         
     | 
| 
      
 30 
     | 
    
         
            +
                @ring.add_node Redis.new(:host => server, :port => port)
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
              
         
     | 
| 
      
 33 
     | 
    
         
            +
              def method_missing(sym, *args, &blk)
         
     | 
| 
      
 34 
     | 
    
         
            +
                if redis = node_for_key(args.first.to_s)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  redis.send sym, *args, &blk
         
     | 
| 
      
 36 
     | 
    
         
            +
                else
         
     | 
| 
      
 37 
     | 
    
         
            +
                  super
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
              
         
     | 
| 
      
 41 
     | 
    
         
            +
              def keys(glob)
         
     | 
| 
      
 42 
     | 
    
         
            +
                keyz = []
         
     | 
| 
      
 43 
     | 
    
         
            +
                @ring.nodes.each do |red|
         
     | 
| 
      
 44 
     | 
    
         
            +
                  keyz.concat red.keys(glob)
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
                keyz
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
              
         
     | 
| 
      
 49 
     | 
    
         
            +
              def save
         
     | 
| 
      
 50 
     | 
    
         
            +
                @ring.nodes.each do |red|
         
     | 
| 
      
 51 
     | 
    
         
            +
                  red.save
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
              
         
     | 
| 
      
 55 
     | 
    
         
            +
              def bgsave
         
     | 
| 
      
 56 
     | 
    
         
            +
                @ring.nodes.each do |red|
         
     | 
| 
      
 57 
     | 
    
         
            +
                  red.bgsave
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
              
         
     | 
| 
      
 61 
     | 
    
         
            +
              def quit
         
     | 
| 
      
 62 
     | 
    
         
            +
                @ring.nodes.each do |red|
         
     | 
| 
      
 63 
     | 
    
         
            +
                  red.quit
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
              
         
     | 
| 
      
 67 
     | 
    
         
            +
              def delete_cloud!
         
     | 
| 
      
 68 
     | 
    
         
            +
                @ring.nodes.each do |red|
         
     | 
| 
      
 69 
     | 
    
         
            +
                  red.keys("*").each do |key|
         
     | 
| 
      
 70 
     | 
    
         
            +
                    red.delete key
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end  
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
              
         
     | 
| 
      
 75 
     | 
    
         
            +
            end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
            if __FILE__ == $0
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
            r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
         
     | 
| 
      
 81 
     | 
    
         
            +
              r['urmom'] = 'urmom'
         
     | 
| 
      
 82 
     | 
    
         
            +
              r['urdad'] = 'urdad'
         
     | 
| 
      
 83 
     | 
    
         
            +
              r['urmom1'] = 'urmom1'
         
     | 
| 
      
 84 
     | 
    
         
            +
              r['urdad1'] = 'urdad1'
         
     | 
| 
      
 85 
     | 
    
         
            +
              r['urmom2'] = 'urmom2'
         
     | 
| 
      
 86 
     | 
    
         
            +
              r['urdad2'] = 'urdad2'
         
     | 
| 
      
 87 
     | 
    
         
            +
              r['urmom3'] = 'urmom3'
         
     | 
| 
      
 88 
     | 
    
         
            +
              r['urdad3'] = 'urdad3'
         
     | 
| 
      
 89 
     | 
    
         
            +
              p r['urmom']
         
     | 
| 
      
 90 
     | 
    
         
            +
              p r['urdad']
         
     | 
| 
      
 91 
     | 
    
         
            +
              p r['urmom1']
         
     | 
| 
      
 92 
     | 
    
         
            +
              p r['urdad1']
         
     | 
| 
      
 93 
     | 
    
         
            +
              p r['urmom2']
         
     | 
| 
      
 94 
     | 
    
         
            +
              p r['urdad2']
         
     | 
| 
      
 95 
     | 
    
         
            +
              p r['urmom3']
         
     | 
| 
      
 96 
     | 
    
         
            +
              p r['urdad3']
         
     | 
| 
      
 97 
     | 
    
         
            +
              
         
     | 
| 
      
 98 
     | 
    
         
            +
              r.push_tail 'listor', 'foo1'
         
     | 
| 
      
 99 
     | 
    
         
            +
              r.push_tail 'listor', 'foo2'
         
     | 
| 
      
 100 
     | 
    
         
            +
              r.push_tail 'listor', 'foo3'
         
     | 
| 
      
 101 
     | 
    
         
            +
              r.push_tail 'listor', 'foo4'
         
     | 
| 
      
 102 
     | 
    
         
            +
              r.push_tail 'listor', 'foo5'
         
     | 
| 
      
 103 
     | 
    
         
            +
              
         
     | 
| 
      
 104 
     | 
    
         
            +
              p r.pop_tail('listor')
         
     | 
| 
      
 105 
     | 
    
         
            +
              p r.pop_tail('listor')
         
     | 
| 
      
 106 
     | 
    
         
            +
              p r.pop_tail('listor')
         
     | 
| 
      
 107 
     | 
    
         
            +
              p r.pop_tail('listor')
         
     | 
| 
      
 108 
     | 
    
         
            +
              p r.pop_tail('listor')
         
     | 
| 
      
 109 
     | 
    
         
            +
              
         
     | 
| 
      
 110 
     | 
    
         
            +
              puts "key distribution:"
         
     | 
| 
      
 111 
     | 
    
         
            +
              
         
     | 
| 
      
 112 
     | 
    
         
            +
              r.ring.nodes.each do |red|
         
     | 
| 
      
 113 
     | 
    
         
            +
                p [red.port, red.keys("*")]
         
     | 
| 
      
 114 
     | 
    
         
            +
              end
         
     | 
| 
      
 115 
     | 
    
         
            +
              r.delete_cloud!
         
     | 
| 
      
 116 
     | 
    
         
            +
              p r.keys('*')
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/hash_ring.rb
    ADDED
    
    | 
         @@ -0,0 +1,127 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'zlib'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class HashRing
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              POINTS_PER_SERVER = 160 # this is the default in libmemcached
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              attr_reader :ring, :sorted_keys, :replicas, :nodes
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              # nodes is a list of objects that have a proper to_s representation.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # replicas indicates how many virtual points should be used pr. node,
         
     | 
| 
      
 11 
     | 
    
         
            +
              # replicas are required to improve the distribution.
         
     | 
| 
      
 12 
     | 
    
         
            +
              def initialize(nodes=[], replicas=POINTS_PER_SERVER)
         
     | 
| 
      
 13 
     | 
    
         
            +
                @replicas = replicas
         
     | 
| 
      
 14 
     | 
    
         
            +
                @ring = {}
         
     | 
| 
      
 15 
     | 
    
         
            +
                @nodes = []
         
     | 
| 
      
 16 
     | 
    
         
            +
                @sorted_keys = []
         
     | 
| 
      
 17 
     | 
    
         
            +
                nodes.each do |node|
         
     | 
| 
      
 18 
     | 
    
         
            +
                  add_node(node)
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
              
         
     | 
| 
      
 22 
     | 
    
         
            +
              # Adds a `node` to the hash ring (including a number of replicas).
         
     | 
| 
      
 23 
     | 
    
         
            +
              def add_node(node)
         
     | 
| 
      
 24 
     | 
    
         
            +
                @nodes << node
         
     | 
| 
      
 25 
     | 
    
         
            +
                @replicas.times do |i|
         
     | 
| 
      
 26 
     | 
    
         
            +
                  key = Zlib.crc32("#{node}:#{i}")
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @ring[key] = node
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @sorted_keys << key
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
                @sorted_keys.sort!
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
              
         
     | 
| 
      
 33 
     | 
    
         
            +
              def remove_node(node)
         
     | 
| 
      
 34 
     | 
    
         
            +
                @replicas.times do |i|
         
     | 
| 
      
 35 
     | 
    
         
            +
                  key = Zlib.crc32("#{node}:#{count}")
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @ring.delete(key)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @sorted_keys.reject! {|k| k == key}
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
              
         
     | 
| 
      
 41 
     | 
    
         
            +
              # get the node in the hash ring for this key
         
     | 
| 
      
 42 
     | 
    
         
            +
              def get_node(key)
         
     | 
| 
      
 43 
     | 
    
         
            +
                get_node_pos(key)[0]
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
              
         
     | 
| 
      
 46 
     | 
    
         
            +
              def get_node_pos(key)
         
     | 
| 
      
 47 
     | 
    
         
            +
                return [nil,nil] if @ring.size == 0
         
     | 
| 
      
 48 
     | 
    
         
            +
                crc = Zlib.crc32(key)
         
     | 
| 
      
 49 
     | 
    
         
            +
                idx = HashRing.binary_search(@sorted_keys, crc)
         
     | 
| 
      
 50 
     | 
    
         
            +
                return [@ring[@sorted_keys[idx]], idx]
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
              
         
     | 
| 
      
 53 
     | 
    
         
            +
              def iter_nodes(key)
         
     | 
| 
      
 54 
     | 
    
         
            +
                return [nil,nil] if @ring.size == 0
         
     | 
| 
      
 55 
     | 
    
         
            +
                node, pos = get_node_pos(key)
         
     | 
| 
      
 56 
     | 
    
         
            +
                @sorted_keys[pos..-1].each do |k|
         
     | 
| 
      
 57 
     | 
    
         
            +
                  yield @ring[k]
         
     | 
| 
      
 58 
     | 
    
         
            +
                end  
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
              
         
     | 
| 
      
 61 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                # gem install RubyInline to use this code
         
     | 
| 
      
 64 
     | 
    
         
            +
                # Native extension to perform the binary search within the hashring.
         
     | 
| 
      
 65 
     | 
    
         
            +
                # There's a pure ruby version below so this is purely optional
         
     | 
| 
      
 66 
     | 
    
         
            +
                # for performance.  In testing 20k gets and sets, the native
         
     | 
| 
      
 67 
     | 
    
         
            +
                # binary search shaved about 12% off the runtime (9sec -> 8sec).
         
     | 
| 
      
 68 
     | 
    
         
            +
                begin
         
     | 
| 
      
 69 
     | 
    
         
            +
                  require 'inline'
         
     | 
| 
      
 70 
     | 
    
         
            +
                  inline do |builder|
         
     | 
| 
      
 71 
     | 
    
         
            +
                    builder.c <<-EOM
         
     | 
| 
      
 72 
     | 
    
         
            +
                    int binary_search(VALUE ary, unsigned int r) {
         
     | 
| 
      
 73 
     | 
    
         
            +
                        int upper = RARRAY_LEN(ary) - 1;
         
     | 
| 
      
 74 
     | 
    
         
            +
                        int lower = 0;
         
     | 
| 
      
 75 
     | 
    
         
            +
                        int idx = 0;
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                        while (lower <= upper) {
         
     | 
| 
      
 78 
     | 
    
         
            +
                            idx = (lower + upper) / 2;
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                            VALUE continuumValue = RARRAY_PTR(ary)[idx];
         
     | 
| 
      
 81 
     | 
    
         
            +
                            unsigned int l = NUM2UINT(continuumValue);
         
     | 
| 
      
 82 
     | 
    
         
            +
                            if (l == r) {
         
     | 
| 
      
 83 
     | 
    
         
            +
                                return idx;
         
     | 
| 
      
 84 
     | 
    
         
            +
                            }
         
     | 
| 
      
 85 
     | 
    
         
            +
                            else if (l > r) {
         
     | 
| 
      
 86 
     | 
    
         
            +
                                upper = idx - 1;
         
     | 
| 
      
 87 
     | 
    
         
            +
                            }
         
     | 
| 
      
 88 
     | 
    
         
            +
                            else {
         
     | 
| 
      
 89 
     | 
    
         
            +
                                lower = idx + 1;
         
     | 
| 
      
 90 
     | 
    
         
            +
                            }
         
     | 
| 
      
 91 
     | 
    
         
            +
                        }
         
     | 
| 
      
 92 
     | 
    
         
            +
                        return upper;
         
     | 
| 
      
 93 
     | 
    
         
            +
                    }
         
     | 
| 
      
 94 
     | 
    
         
            +
                    EOM
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
                rescue Exception => e
         
     | 
| 
      
 97 
     | 
    
         
            +
                  # Find the closest index in HashRing with value <= the given value
         
     | 
| 
      
 98 
     | 
    
         
            +
                  def binary_search(ary, value, &block)
         
     | 
| 
      
 99 
     | 
    
         
            +
                    upper = ary.size - 1
         
     | 
| 
      
 100 
     | 
    
         
            +
                    lower = 0
         
     | 
| 
      
 101 
     | 
    
         
            +
                    idx = 0
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                    while(lower <= upper) do
         
     | 
| 
      
 104 
     | 
    
         
            +
                      idx = (lower + upper) / 2
         
     | 
| 
      
 105 
     | 
    
         
            +
                      comp = ary[idx] <=> value
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                      if comp == 0
         
     | 
| 
      
 108 
     | 
    
         
            +
                        return idx
         
     | 
| 
      
 109 
     | 
    
         
            +
                      elsif comp > 0
         
     | 
| 
      
 110 
     | 
    
         
            +
                        upper = idx - 1
         
     | 
| 
      
 111 
     | 
    
         
            +
                      else
         
     | 
| 
      
 112 
     | 
    
         
            +
                        lower = idx + 1
         
     | 
| 
      
 113 
     | 
    
         
            +
                      end
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
                    return upper
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
              end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
            end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            # ring = HashRing.new ['server1', 'server2', 'server3']
         
     | 
| 
      
 124 
     | 
    
         
            +
            # p ring
         
     | 
| 
      
 125 
     | 
    
         
            +
            # #
         
     | 
| 
      
 126 
     | 
    
         
            +
            # p ring.get_node "kjhjkjlkjlkkh"
         
     | 
| 
      
 127 
     | 
    
         
            +
            # 
         
     |