jruby-memcached 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +25 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +28 -0
- data/README.md +7 -0
- data/Rakefile +2 -0
- data/benchmark.rb +93 -0
- data/jruby_memcached.gemspec +21 -0
- data/lib/memcached/exceptions.rb +3 -0
- data/lib/memcached/version.rb +3 -0
- data/lib/memcached.rb +114 -0
- data/pom.xml +66 -0
- data/spec/memcached_spec.rb +114 -0
- data/spec/spec_helper.rb +12 -0
- data/src/main/java/net/spy/memcached/KetamaNodeLocator.java +165 -0
- data/src/main/java/net/spy/memcached/ReturnData.java +20 -0
- data/src/main/java/net/spy/memcached/transcoders/SimpleTranscoder.java +56 -0
- data/src/main/java/net/spy/memcached/util/DefaultKetamaNodeLocatorConfiguration.java +104 -0
- data/target/spymemcached-ext-0.0.1.jar +0 -0
- metadata +94 -0
data/.gitignore
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
coverage
|
6
|
+
InstalledFiles
|
7
|
+
lib/bundler/man
|
8
|
+
pkg
|
9
|
+
rdoc
|
10
|
+
spec/reports
|
11
|
+
test/tmp
|
12
|
+
test/version_tmp
|
13
|
+
tmp
|
14
|
+
|
15
|
+
# YARD artifacts
|
16
|
+
.yardoc
|
17
|
+
_yardoc
|
18
|
+
doc/
|
19
|
+
|
20
|
+
target/classes
|
21
|
+
target/maven-archiver
|
22
|
+
target/surefire
|
23
|
+
target/dependency-reduced-pom.xml
|
24
|
+
target/original-spymemcached-ext-0.0.1.jar
|
25
|
+
target/original-xmemcached-ext-0.0.1.jar
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
jruby-memcached (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
metaclass (0.0.1)
|
11
|
+
mocha (0.12.1)
|
12
|
+
metaclass (~> 0.0.1)
|
13
|
+
rspec (2.11.0)
|
14
|
+
rspec-core (~> 2.11.0)
|
15
|
+
rspec-expectations (~> 2.11.0)
|
16
|
+
rspec-mocks (~> 2.11.0)
|
17
|
+
rspec-core (2.11.1)
|
18
|
+
rspec-expectations (2.11.1)
|
19
|
+
diff-lcs (~> 1.1.3)
|
20
|
+
rspec-mocks (2.11.1)
|
21
|
+
|
22
|
+
PLATFORMS
|
23
|
+
java
|
24
|
+
|
25
|
+
DEPENDENCIES
|
26
|
+
jruby-memcached!
|
27
|
+
mocha
|
28
|
+
rspec
|
data/README.md
ADDED
data/Rakefile
ADDED
data/benchmark.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
3
|
+
JRUBY = defined?(JRUBY_VERSION)
|
4
|
+
|
5
|
+
if JRUBY
|
6
|
+
require 'lib/memcached'
|
7
|
+
else
|
8
|
+
require 'memcached'
|
9
|
+
end
|
10
|
+
require 'rubygems'
|
11
|
+
require 'dalli'
|
12
|
+
|
13
|
+
memcached = Memcached.new(['localhost:11211'])
|
14
|
+
dalli = Dalli::Client.new(['localhost:11211'])
|
15
|
+
|
16
|
+
3.to_i.times {
|
17
|
+
Benchmark.bm(30) {|bm|
|
18
|
+
if JRUBY
|
19
|
+
bm.report("jruby-memcached set") {
|
20
|
+
100_000.times { memcached.set('foo', 'bar').get }
|
21
|
+
}
|
22
|
+
bm.report("jruby-memcached get") {
|
23
|
+
100_000.times { memcached.get('foo') }
|
24
|
+
}
|
25
|
+
else
|
26
|
+
bm.report("memcached set") {
|
27
|
+
100_000.times { memcached.set('foo', 'bar') }
|
28
|
+
}
|
29
|
+
bm.report("memcached get") {
|
30
|
+
100_000.times { memcached.get('foo') }
|
31
|
+
}
|
32
|
+
end
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
3.times {
|
37
|
+
Benchmark.bm(30) {|bm|
|
38
|
+
bm.report("dalli set") {
|
39
|
+
100_000.times { dalli.set('foo', 'bar') }
|
40
|
+
}
|
41
|
+
bm.report("dalli get") {
|
42
|
+
100_000.times { dalli.get('foo') }
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
memcached.close
|
48
|
+
dalli.close
|
49
|
+
|
50
|
+
|
51
|
+
# MBP 2.8G i7
|
52
|
+
#
|
53
|
+
# ruby-1.9.3-p194
|
54
|
+
# ruby benchmark.rb
|
55
|
+
# user system total real
|
56
|
+
# memcached set 1.110000 1.010000 2.120000 ( 4.578121)
|
57
|
+
# memcached get 0.940000 0.960000 1.900000 ( 4.013941)
|
58
|
+
# user system total real
|
59
|
+
# memcached set 1.100000 1.010000 2.110000 ( 4.557462)
|
60
|
+
# memcached get 0.930000 0.950000 1.880000 ( 3.995192)
|
61
|
+
# user system total real
|
62
|
+
# memcached set 1.110000 1.020000 2.130000 ( 4.592509)
|
63
|
+
# memcached get 0.970000 1.000000 1.970000 ( 4.172170)
|
64
|
+
# user system total real
|
65
|
+
# dalli set 8.330000 1.570000 9.900000 ( 10.062159)
|
66
|
+
# dalli get 8.240000 1.630000 9.870000 ( 9.987921)
|
67
|
+
# user system total real
|
68
|
+
# dalli set 8.400000 1.580000 9.980000 ( 10.139169)
|
69
|
+
# dalli get 8.500000 1.680000 10.180000 ( 10.287153)
|
70
|
+
# user system total real
|
71
|
+
# dalli set 8.330000 1.560000 9.890000 ( 10.094499)
|
72
|
+
# dalli get 8.530000 1.680000 10.210000 ( 10.331083)
|
73
|
+
#
|
74
|
+
# jruby-1.6.7.2
|
75
|
+
# jruby --server -Ilib -S benchmark.rb
|
76
|
+
# user system total real
|
77
|
+
# jruby-memcached set 8.745000 0.000000 8.745000 ( 8.745000)
|
78
|
+
# jruby-memcached get 8.260000 0.000000 8.260000 ( 8.260000)
|
79
|
+
# user system total real
|
80
|
+
# jruby-memcached set 6.911000 0.000000 6.911000 ( 6.911000)
|
81
|
+
# jruby-memcached get 6.895000 0.000000 6.895000 ( 6.895000)
|
82
|
+
# user system total real
|
83
|
+
# jruby-memcached set 6.902000 0.000000 6.902000 ( 6.902000)
|
84
|
+
# jruby-memcached get 6.845000 0.000000 6.845000 ( 6.845000)
|
85
|
+
# user system total real
|
86
|
+
# dalli set 15.233000 0.000000 15.233000 ( 15.234000)
|
87
|
+
# dalli get 13.991000 0.000000 13.991000 ( 13.992000)
|
88
|
+
# user system total real
|
89
|
+
# dalli set 12.936000 0.000000 12.936000 ( 12.936000)
|
90
|
+
# dalli get 13.585000 0.000000 13.585000 ( 13.585000)
|
91
|
+
# user system total real
|
92
|
+
# dalli set 13.251000 0.000000 13.251000 ( 13.251000)
|
93
|
+
# dalli get 13.536000 0.000000 13.536000 ( 13.536000)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "memcached/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "jruby-memcached"
|
7
|
+
s.version = Memcached::VERSION
|
8
|
+
s.authors = ["Richard Huang"]
|
9
|
+
s.email = ["flyerhzm@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{jruby compatible memcached client}
|
12
|
+
s.description = %q{jruby memcacached client which is compatible with memcached gem}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency 'rspec'
|
20
|
+
s.add_development_dependency 'mocha'
|
21
|
+
end
|
data/lib/memcached.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'java'
|
2
|
+
require 'memcached/version'
|
3
|
+
require 'memcached/exceptions'
|
4
|
+
require File.join(File.dirname(__FILE__), '../target/spymemcached-ext-0.0.1.jar')
|
5
|
+
|
6
|
+
class Memcached
|
7
|
+
include_class 'java.net.InetSocketAddress'
|
8
|
+
include_class 'net.spy.memcached.MemcachedClient'
|
9
|
+
include_class 'net.spy.memcached.ConnectionFactoryBuilder'
|
10
|
+
include_class 'net.spy.memcached.ConnectionFactoryBuilder$Locator'
|
11
|
+
include_class 'net.spy.memcached.DefaultHashAlgorithm'
|
12
|
+
include_class 'net.spy.memcached.FailureMode'
|
13
|
+
include_class 'net.spy.memcached.transcoders.SimpleTranscoder'
|
14
|
+
include_class 'net.spy.memcached.AddrUtil'
|
15
|
+
|
16
|
+
FLAGS = 0x0
|
17
|
+
DEFAULTS = {
|
18
|
+
:default_ttl => 604800,
|
19
|
+
:exception_retry_limit => 5
|
20
|
+
}
|
21
|
+
|
22
|
+
attr_reader :options
|
23
|
+
attr_reader :default_ttl
|
24
|
+
|
25
|
+
def initialize(addresses, options={})
|
26
|
+
builder = ConnectionFactoryBuilder.new.
|
27
|
+
setLocatorType(Locator::CONSISTENT).
|
28
|
+
setHashAlg(DefaultHashAlgorithm::FNV1_32_HASH)
|
29
|
+
@client = MemcachedClient.new builder.build, AddrUtil.getAddresses(Array(addresses).join(' '))
|
30
|
+
|
31
|
+
@options = DEFAULTS.merge(options)
|
32
|
+
@default_ttl = @options[:default_ttl]
|
33
|
+
|
34
|
+
@simple_transcoder = SimpleTranscoder.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def set(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
|
38
|
+
with_retry do
|
39
|
+
value = encode(value, marshal, flags)
|
40
|
+
@simple_transcoder.setFlags(flags)
|
41
|
+
@client.set(key, ttl, value.to_java_bytes, @simple_transcoder)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
|
46
|
+
with_retry do
|
47
|
+
value = encode(value, marshal, flags)
|
48
|
+
@simple_transcoder.setFlags(flags)
|
49
|
+
if @client.add(key, ttl, value.to_java_bytes, @simple_transcoder).get === false
|
50
|
+
raise Memcached::NotStored
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def replace(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
|
56
|
+
with_retry do
|
57
|
+
value = encode(value, marshal, flags)
|
58
|
+
@simple_transcoder.setFlags(flags)
|
59
|
+
if @client.replace(key, ttl, value.to_java_bytes, @simple_transcoder).get === false
|
60
|
+
raise Memcached::NotStored
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete(key)
|
66
|
+
with_retry do
|
67
|
+
raise Memcached::NotFound if @client.delete(key).get === false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def get(key, marshal=true)
|
72
|
+
with_retry do
|
73
|
+
ret = @client.get(key, @simple_transcoder)
|
74
|
+
raise Memcached::NotFound if ret.nil?
|
75
|
+
flags, data = ret.flags, ret.data
|
76
|
+
value = String.from_java_bytes data
|
77
|
+
value = decode(value, marshal, flags)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def flush
|
82
|
+
@client.flush.get
|
83
|
+
end
|
84
|
+
|
85
|
+
def servers
|
86
|
+
@client.available_servers.map { |address| address.to_s.split("/").last }
|
87
|
+
end
|
88
|
+
|
89
|
+
def close
|
90
|
+
@client.shutdown
|
91
|
+
end
|
92
|
+
|
93
|
+
alias_method :quit, :close
|
94
|
+
|
95
|
+
private
|
96
|
+
def with_retry
|
97
|
+
begin
|
98
|
+
yield
|
99
|
+
rescue
|
100
|
+
tries ||= 0
|
101
|
+
raise unless tries < options[:exception_retry_limit]
|
102
|
+
tries += 1
|
103
|
+
retry
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def encode(value, marshal, flags)
|
108
|
+
marshal ? Marshal.dump(value) : value
|
109
|
+
end
|
110
|
+
|
111
|
+
def decode(value, marshal, flags)
|
112
|
+
marshal ? Marshal.load(value) : value
|
113
|
+
end
|
114
|
+
end
|
data/pom.xml
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
2
|
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
3
|
+
<modelVersion>4.0.0</modelVersion>
|
4
|
+
|
5
|
+
<groupId>net.spy</groupId>
|
6
|
+
<artifactId>spymemcached-ext</artifactId>
|
7
|
+
<version>0.0.1</version>
|
8
|
+
<packaging>jar</packaging>
|
9
|
+
|
10
|
+
<name>spymemcached-ext</name>
|
11
|
+
<url>http://maven.apache.org</url>
|
12
|
+
|
13
|
+
<properties>
|
14
|
+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
15
|
+
</properties>
|
16
|
+
|
17
|
+
<repositories>
|
18
|
+
<repository>
|
19
|
+
<id>spymemcached</id>
|
20
|
+
<name>spymemcached extention</name>
|
21
|
+
<layout>default</layout>
|
22
|
+
<url>http://files.couchbase.com/maven2/</url>
|
23
|
+
<snapshots>
|
24
|
+
<enabled>false</enabled>
|
25
|
+
</snapshots>
|
26
|
+
</repository>
|
27
|
+
</repositories>
|
28
|
+
|
29
|
+
<dependencies>
|
30
|
+
<dependency>
|
31
|
+
<groupId>org.jruby</groupId>
|
32
|
+
<artifactId>jruby</artifactId>
|
33
|
+
<version>1.6.7.2</version>
|
34
|
+
</dependency>
|
35
|
+
<dependency>
|
36
|
+
<groupId>spy</groupId>
|
37
|
+
<artifactId>spymemcached</artifactId>
|
38
|
+
<version>2.8.1</version>
|
39
|
+
</dependency>
|
40
|
+
</dependencies>
|
41
|
+
|
42
|
+
<build>
|
43
|
+
<plugins>
|
44
|
+
<plugin>
|
45
|
+
<groupId>org.apache.maven.plugins</groupId>
|
46
|
+
<artifactId>maven-shade-plugin</artifactId>
|
47
|
+
<version>1.4</version>
|
48
|
+
<executions>
|
49
|
+
<execution>
|
50
|
+
<phase>package</phase>
|
51
|
+
<goals>
|
52
|
+
<goal>shade</goal>
|
53
|
+
</goals>
|
54
|
+
<configuration>
|
55
|
+
<artifactSet>
|
56
|
+
<excludes>
|
57
|
+
<exclude>org.jruby:jruby</exclude>
|
58
|
+
</excludes>
|
59
|
+
</artifactSet>
|
60
|
+
</configuration>
|
61
|
+
</execution>
|
62
|
+
</executions>
|
63
|
+
</plugin>
|
64
|
+
</plugins>
|
65
|
+
</build>
|
66
|
+
</project>
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Memcached do
|
4
|
+
context "localhost" do
|
5
|
+
before :all do
|
6
|
+
@memcached = Memcached.new("127.0.0.1:11211")
|
7
|
+
end
|
8
|
+
|
9
|
+
after :all do
|
10
|
+
@memcached.close
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should initialize with options" do
|
14
|
+
@memcached.servers.should == ["127.0.0.1:11211"]
|
15
|
+
end
|
16
|
+
|
17
|
+
context "set/get" do
|
18
|
+
it "should set/get with plain text" do
|
19
|
+
@memcached.set "key", "value"
|
20
|
+
@memcached.get("key").should == "value"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should set/get with compressed text" do
|
24
|
+
@memcached.set "key", "x\234c?P?*?/?I\001\000\b8\002a"
|
25
|
+
@memcached.get("key").should == "x\234c?P?*?/?I\001\000\b8\002a"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "get" do
|
30
|
+
it "should get nil" do
|
31
|
+
@memcached.set "key", nil, 0
|
32
|
+
@memcached.get("key").should be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should get missing" do
|
36
|
+
@memcached.delete "key" rescue nil
|
37
|
+
lambda { @memcached.get "key" }.should raise_error(Memcached::NotFound)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "set" do
|
42
|
+
it "should set expiry" do
|
43
|
+
@memcached.set "key", "value", 1
|
44
|
+
@memcached.get("key").should == "value"
|
45
|
+
sleep 1
|
46
|
+
lambda { @memcached.get("key") }.should raise_error(Memcached::NotFound)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should retry when set failure" do
|
50
|
+
Java::NetSpyMemcached::MemcachedClient.any_instance.stubs(:set).raises(Memcached::NotStored)
|
51
|
+
Java::NetSpyMemcachedTranscoders::SimpleTranscoder.any_instance.expects(:setFlags).times(6)
|
52
|
+
lambda { @memcached.set "key", "value" }.should raise_error(Memcached::NotStored)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "add" do
|
57
|
+
it "should add new key" do
|
58
|
+
@memcached.delete "key" rescue nil
|
59
|
+
@memcached.add "key", "value"
|
60
|
+
@memcached.get("key").should == "value"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should not add existing key" do
|
64
|
+
@memcached.set "key", "value"
|
65
|
+
lambda { @memcached.add "key", "value" }.should raise_error(Memcached::NotStored)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should add expiry" do
|
69
|
+
@memcached.delete "key" rescue nil
|
70
|
+
@memcached.add "key", "value", 1
|
71
|
+
@memcached.get "key"
|
72
|
+
sleep 1
|
73
|
+
lambda { @memcached.get "key" }.should raise_error(Memcached::NotFound)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "replace" do
|
78
|
+
it "should replace existing key" do
|
79
|
+
@memcached.set "key", nil
|
80
|
+
@memcached.replace "key", "value"
|
81
|
+
@memcached.get("key").should == "value"
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should not replace with new key" do
|
85
|
+
@memcached.delete "key" rescue nil
|
86
|
+
lambda { @memcached.replace "key", "value" }.should raise_error(Memcached::NotStored)
|
87
|
+
lambda { @memcached.get "key" }.should raise_error(Memcached::NotFound)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "delete" do
|
92
|
+
it "should delete with existing key" do
|
93
|
+
@memcached.set "key", "value"
|
94
|
+
@memcached.delete "key"
|
95
|
+
lambda { @memcached.get "key" }.should raise_error(Memcached::NotFound)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should not delete with new key" do
|
99
|
+
@memcached.delete "key" rescue nil
|
100
|
+
lambda { @memcached.delete "key" }.should raise_error(Memcached::NotFound)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "flush" do
|
105
|
+
it "should flush all keys" do
|
106
|
+
@memcached.set "key1", "value2"
|
107
|
+
@memcached.set "key2", "value2"
|
108
|
+
@memcached.flush
|
109
|
+
lambda { @memcached.get "key1" }.should raise_error(Memcached::NotFound)
|
110
|
+
lambda { @memcached.get "key2" }.should raise_error(Memcached::NotFound)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rspec'
|
4
|
+
require 'rspec/autorun'
|
5
|
+
|
6
|
+
require 'memcached'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.mock_framework = :mocha
|
10
|
+
config.filter_run :focus => true
|
11
|
+
config.run_all_when_everything_filtered = true
|
12
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
package net.spy.memcached;
|
2
|
+
|
3
|
+
import java.util.ArrayList;
|
4
|
+
import java.util.Collection;
|
5
|
+
import java.util.Iterator;
|
6
|
+
import java.util.List;
|
7
|
+
import java.util.Map;
|
8
|
+
import java.util.SortedMap;
|
9
|
+
import java.util.TreeMap;
|
10
|
+
|
11
|
+
import net.spy.memcached.compat.SpyObject;
|
12
|
+
import net.spy.memcached.util.DefaultKetamaNodeLocatorConfiguration;
|
13
|
+
import net.spy.memcached.util.KetamaNodeLocatorConfiguration;
|
14
|
+
|
15
|
+
/**
|
16
|
+
* This is grabbed from spymemcached.
|
17
|
+
*
|
18
|
+
* hashAlg should only be used to get server by key.
|
19
|
+
*
|
20
|
+
*/
|
21
|
+
public final class KetamaNodeLocator extends SpyObject implements NodeLocator {
|
22
|
+
|
23
|
+
private volatile TreeMap<Long, MemcachedNode> ketamaNodes;
|
24
|
+
private final Collection<MemcachedNode> allNodes;
|
25
|
+
|
26
|
+
private final HashAlgorithm hashAlg;
|
27
|
+
private final KetamaNodeLocatorConfiguration config;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Create a new KetamaNodeLocator using specified nodes and the specifed hash
|
31
|
+
* algorithm.
|
32
|
+
*
|
33
|
+
* @param nodes The List of nodes to use in the Ketama consistent hash
|
34
|
+
* continuum
|
35
|
+
* @param alg The hash algorithm to use when choosing a node in the Ketama
|
36
|
+
* consistent hash continuum
|
37
|
+
*/
|
38
|
+
public KetamaNodeLocator(List<MemcachedNode> nodes, HashAlgorithm alg) {
|
39
|
+
this(nodes, alg, new DefaultKetamaNodeLocatorConfiguration());
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Create a new KetamaNodeLocator using specified nodes and the specifed hash
|
44
|
+
* algorithm and configuration.
|
45
|
+
*
|
46
|
+
* @param nodes The List of nodes to use in the Ketama consistent hash
|
47
|
+
* continuum
|
48
|
+
* @param alg The hash algorithm to use when choosing a node in the Ketama
|
49
|
+
* consistent hash continuum
|
50
|
+
* @param conf
|
51
|
+
*/
|
52
|
+
public KetamaNodeLocator(List<MemcachedNode> nodes, HashAlgorithm alg,
|
53
|
+
KetamaNodeLocatorConfiguration conf) {
|
54
|
+
super();
|
55
|
+
allNodes = nodes;
|
56
|
+
hashAlg = alg;
|
57
|
+
config = conf;
|
58
|
+
setKetamaNodes(nodes);
|
59
|
+
}
|
60
|
+
|
61
|
+
private KetamaNodeLocator(TreeMap<Long, MemcachedNode> smn,
|
62
|
+
Collection<MemcachedNode> an, HashAlgorithm alg,
|
63
|
+
KetamaNodeLocatorConfiguration conf) {
|
64
|
+
super();
|
65
|
+
ketamaNodes = smn;
|
66
|
+
allNodes = an;
|
67
|
+
hashAlg = alg;
|
68
|
+
config = conf;
|
69
|
+
}
|
70
|
+
|
71
|
+
public Collection<MemcachedNode> getAll() {
|
72
|
+
return allNodes;
|
73
|
+
}
|
74
|
+
|
75
|
+
public MemcachedNode getPrimary(final String k) {
|
76
|
+
MemcachedNode rv = getNodeForKey(hashAlg.hash(k));
|
77
|
+
assert rv != null : "Found no node for key " + k;
|
78
|
+
return rv;
|
79
|
+
}
|
80
|
+
|
81
|
+
long getMaxKey() {
|
82
|
+
return getKetamaNodes().lastKey();
|
83
|
+
}
|
84
|
+
|
85
|
+
MemcachedNode getNodeForKey(long hash) {
|
86
|
+
final MemcachedNode rv;
|
87
|
+
if (!ketamaNodes.containsKey(hash)) {
|
88
|
+
// Java 1.6 adds a ceilingKey method, but I'm still stuck in 1.5
|
89
|
+
// in a lot of places, so I'm doing this myself.
|
90
|
+
SortedMap<Long, MemcachedNode> tailMap = getKetamaNodes().tailMap(hash);
|
91
|
+
if (tailMap.isEmpty()) {
|
92
|
+
hash = getKetamaNodes().firstKey();
|
93
|
+
} else {
|
94
|
+
hash = tailMap.firstKey();
|
95
|
+
}
|
96
|
+
}
|
97
|
+
rv = getKetamaNodes().get(hash);
|
98
|
+
return rv;
|
99
|
+
}
|
100
|
+
|
101
|
+
public Iterator<MemcachedNode> getSequence(String k) {
|
102
|
+
// Seven searches gives us a 1 in 2^7 chance of hitting the
|
103
|
+
// same dead node all of the time.
|
104
|
+
return new KetamaIterator(k, 7, getKetamaNodes(), hashAlg);
|
105
|
+
}
|
106
|
+
|
107
|
+
public NodeLocator getReadonlyCopy() {
|
108
|
+
TreeMap<Long, MemcachedNode> smn =
|
109
|
+
new TreeMap<Long, MemcachedNode>(getKetamaNodes());
|
110
|
+
Collection<MemcachedNode> an =
|
111
|
+
new ArrayList<MemcachedNode>(allNodes.size());
|
112
|
+
|
113
|
+
// Rewrite the values a copy of the map.
|
114
|
+
for (Map.Entry<Long, MemcachedNode> me : smn.entrySet()) {
|
115
|
+
me.setValue(new MemcachedNodeROImpl(me.getValue()));
|
116
|
+
}
|
117
|
+
|
118
|
+
// Copy the allNodes collection.
|
119
|
+
for (MemcachedNode n : allNodes) {
|
120
|
+
an.add(new MemcachedNodeROImpl(n));
|
121
|
+
}
|
122
|
+
|
123
|
+
return new KetamaNodeLocator(smn, an, hashAlg, config);
|
124
|
+
}
|
125
|
+
|
126
|
+
@Override
|
127
|
+
public void updateLocator(List<MemcachedNode> nodes) {
|
128
|
+
setKetamaNodes(nodes);
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* @return the ketamaNodes
|
133
|
+
*/
|
134
|
+
protected TreeMap<Long, MemcachedNode> getKetamaNodes() {
|
135
|
+
return ketamaNodes;
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* Setup the KetamaNodeLocator with the list of nodes it should use.
|
140
|
+
*
|
141
|
+
* @param nodes a List of MemcachedNodes for this KetamaNodeLocator to use in
|
142
|
+
* its continuum
|
143
|
+
*/
|
144
|
+
protected void setKetamaNodes(List<MemcachedNode> nodes) {
|
145
|
+
TreeMap<Long, MemcachedNode> newNodeMap =
|
146
|
+
new TreeMap<Long, MemcachedNode>();
|
147
|
+
int numReps = config.getNodeRepetitions();
|
148
|
+
for (MemcachedNode node : nodes) {
|
149
|
+
for (int i = 0; i < numReps / 4; i++) {
|
150
|
+
byte[] digest =
|
151
|
+
DefaultHashAlgorithm.computeMd5(config.getKeyForNode(node, i));
|
152
|
+
for (int h = 0; h < 4; h++) {
|
153
|
+
Long k = ((long) (digest[3 + h * 4] & 0xFF) << 24)
|
154
|
+
| ((long) (digest[2 + h * 4] & 0xFF) << 16)
|
155
|
+
| ((long) (digest[1 + h * 4] & 0xFF) << 8)
|
156
|
+
| (digest[h * 4] & 0xFF);
|
157
|
+
newNodeMap.put(k, node);
|
158
|
+
getLogger().debug("Adding node %s in position %d", node, k);
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
assert newNodeMap.size() == numReps * nodes.size();
|
163
|
+
ketamaNodes = newNodeMap;
|
164
|
+
}
|
165
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
package net.spy.memcached;
|
2
|
+
|
3
|
+
public class ReturnData {
|
4
|
+
private int flags;
|
5
|
+
private byte[] data;
|
6
|
+
|
7
|
+
public ReturnData(int flags, byte[] data) {
|
8
|
+
this.flags = flags;
|
9
|
+
this.data = data;
|
10
|
+
}
|
11
|
+
|
12
|
+
|
13
|
+
public int getFlags() {
|
14
|
+
return flags;
|
15
|
+
}
|
16
|
+
|
17
|
+
public byte[] getData() {
|
18
|
+
return data;
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
package net.spy.memcached.transcoders;
|
2
|
+
|
3
|
+
import net.spy.memcached.CachedData;
|
4
|
+
import net.spy.memcached.ReturnData;
|
5
|
+
import net.spy.memcached.transcoders.Transcoder;
|
6
|
+
|
7
|
+
/**
|
8
|
+
*
|
9
|
+
* SimpleTranscoder didn't do any serializing/deserializing or compression/decompression.
|
10
|
+
* Ruby will convert object to string by Marshal and finally passing bytes[].
|
11
|
+
*
|
12
|
+
*/
|
13
|
+
public class SimpleTranscoder implements Transcoder<Object> {
|
14
|
+
private final int maxSize;
|
15
|
+
private int flags;
|
16
|
+
|
17
|
+
public SimpleTranscoder() {
|
18
|
+
this(CachedData.MAX_SIZE, 0);
|
19
|
+
}
|
20
|
+
|
21
|
+
public SimpleTranscoder(int flags) {
|
22
|
+
this(CachedData.MAX_SIZE, flags);
|
23
|
+
}
|
24
|
+
|
25
|
+
public SimpleTranscoder(int maxSize, int flags) {
|
26
|
+
this.maxSize = maxSize;
|
27
|
+
this.flags = flags;
|
28
|
+
}
|
29
|
+
|
30
|
+
public boolean asyncDecode(CachedData d) {
|
31
|
+
return false;
|
32
|
+
}
|
33
|
+
|
34
|
+
public CachedData encode(Object o) {
|
35
|
+
byte[] b = (byte[]) o;
|
36
|
+
return new CachedData(getFlags(), b, getMaxSize());
|
37
|
+
}
|
38
|
+
|
39
|
+
public Object decode(CachedData d) {
|
40
|
+
byte[] data = d.getData();
|
41
|
+
int flags = d.getFlags();
|
42
|
+
return new ReturnData(flags, data);
|
43
|
+
}
|
44
|
+
|
45
|
+
public int getMaxSize() {
|
46
|
+
return maxSize;
|
47
|
+
}
|
48
|
+
|
49
|
+
public int getFlags() {
|
50
|
+
return flags;
|
51
|
+
}
|
52
|
+
|
53
|
+
public void setFlags(int flags) {
|
54
|
+
this.flags = flags;
|
55
|
+
}
|
56
|
+
}
|
@@ -0,0 +1,104 @@
|
|
1
|
+
package net.spy.memcached.util;
|
2
|
+
|
3
|
+
import java.net.InetSocketAddress;
|
4
|
+
import java.util.HashMap;
|
5
|
+
import java.util.Map;
|
6
|
+
|
7
|
+
import net.spy.memcached.MemcachedNode;
|
8
|
+
|
9
|
+
/**
|
10
|
+
* This is grabbed from spymemcached.
|
11
|
+
*
|
12
|
+
* socket address in libmemcached 0.32 is
|
13
|
+
* 127.0.0.1-1 (port is 11211) or
|
14
|
+
* 127.0.0.1:43043-1 (port is not 11211)
|
15
|
+
*/
|
16
|
+
public class DefaultKetamaNodeLocatorConfiguration implements
|
17
|
+
KetamaNodeLocatorConfiguration {
|
18
|
+
|
19
|
+
private final int numReps = 160;
|
20
|
+
static final int DEFAULT_PORT = 11211;
|
21
|
+
|
22
|
+
// Internal lookup map to try to carry forward the optimisation that was
|
23
|
+
// previously in KetamaNodeLocator
|
24
|
+
protected Map<MemcachedNode, String> socketAddresses =
|
25
|
+
new HashMap<MemcachedNode, String>();
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Returns the socket address of a given MemcachedNode.
|
29
|
+
*
|
30
|
+
* @param node The node which we're interested in
|
31
|
+
* @return String the socket address of that node.
|
32
|
+
*/
|
33
|
+
protected String getSocketAddressForNode(MemcachedNode node) {
|
34
|
+
// Using the internal map retrieve the socket addresses
|
35
|
+
// for given nodes.
|
36
|
+
// I'm aware that this code is inherently thread-unsafe as
|
37
|
+
// I'm using a HashMap implementation of the map, but the worst
|
38
|
+
// case ( I believe) is we're slightly in-efficient when
|
39
|
+
// a node has never been seen before concurrently on two different
|
40
|
+
// threads, so it the socketaddress will be requested multiple times!
|
41
|
+
// all other cases should be as fast as possible.
|
42
|
+
String result = socketAddresses.get(node);
|
43
|
+
if (result == null) {
|
44
|
+
InetSocketAddress socketAddress = (InetSocketAddress) node.getSocketAddress();
|
45
|
+
if (socketAddress.getPort() == DEFAULT_PORT) {
|
46
|
+
result = socketAddress.getAddress().getHostAddress();
|
47
|
+
} else {
|
48
|
+
result = socketAddress.getAddress().getHostAddress() + ":" + socketAddress.getPort();
|
49
|
+
}
|
50
|
+
socketAddresses.put(node, result);
|
51
|
+
}
|
52
|
+
return result;
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Returns the number of discrete hashes that should be defined for each node
|
57
|
+
* in the continuum.
|
58
|
+
*
|
59
|
+
* @return NUM_REPS repetitions.
|
60
|
+
*/
|
61
|
+
public int getNodeRepetitions() {
|
62
|
+
return numReps;
|
63
|
+
}
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Returns a uniquely identifying key, suitable for hashing by the
|
67
|
+
* KetamaNodeLocator algorithm.
|
68
|
+
*
|
69
|
+
* <p>
|
70
|
+
* This default implementation uses the socket-address of the MemcachedNode
|
71
|
+
* and concatenates it with a hyphen directly against the repetition number
|
72
|
+
* for example a key for a particular server's first repetition may look like:
|
73
|
+
* <p>
|
74
|
+
*
|
75
|
+
* <p>
|
76
|
+
* <code>myhost/10.0.2.1-0</code>
|
77
|
+
* </p>
|
78
|
+
*
|
79
|
+
* <p>
|
80
|
+
* for the second repetition
|
81
|
+
* </p>
|
82
|
+
*
|
83
|
+
* <p>
|
84
|
+
* <code>myhost/10.0.2.1-1</code>
|
85
|
+
* </p>
|
86
|
+
*
|
87
|
+
* <p>
|
88
|
+
* for a server where reverse lookups are failing the returned keys may look
|
89
|
+
* like
|
90
|
+
* </p>
|
91
|
+
*
|
92
|
+
* <p>
|
93
|
+
* <code>/10.0.2.1-0</code> and <code>/10.0.2.1-1</code>
|
94
|
+
* </p>
|
95
|
+
*
|
96
|
+
* @param node The MemcachedNode to use to form the unique identifier
|
97
|
+
* @param repetition The repetition number for the particular node in question
|
98
|
+
* (0 is the first repetition)
|
99
|
+
* @return The key that represents the specific repetition of the node
|
100
|
+
*/
|
101
|
+
public String getKeyForNode(MemcachedNode node, int repetition) {
|
102
|
+
return getSocketAddressForNode(node) + "-" + repetition;
|
103
|
+
}
|
104
|
+
}
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jruby-memcached
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Richard Huang
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-07-24 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :development
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: mocha
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id002
|
37
|
+
description: jruby memcacached client which is compatible with memcached gem
|
38
|
+
email:
|
39
|
+
- flyerhzm@gmail.com
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- Gemfile
|
49
|
+
- Gemfile.lock
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- benchmark.rb
|
53
|
+
- jruby_memcached.gemspec
|
54
|
+
- lib/memcached.rb
|
55
|
+
- lib/memcached/exceptions.rb
|
56
|
+
- lib/memcached/version.rb
|
57
|
+
- pom.xml
|
58
|
+
- spec/memcached_spec.rb
|
59
|
+
- spec/spec_helper.rb
|
60
|
+
- src/main/java/net/spy/memcached/KetamaNodeLocator.java
|
61
|
+
- src/main/java/net/spy/memcached/ReturnData.java
|
62
|
+
- src/main/java/net/spy/memcached/transcoders/SimpleTranscoder.java
|
63
|
+
- src/main/java/net/spy/memcached/util/DefaultKetamaNodeLocatorConfiguration.java
|
64
|
+
- target/spymemcached-ext-0.0.1.jar
|
65
|
+
homepage: ""
|
66
|
+
licenses: []
|
67
|
+
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: "0"
|
85
|
+
requirements: []
|
86
|
+
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.24
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: jruby compatible memcached client
|
92
|
+
test_files:
|
93
|
+
- spec/memcached_spec.rb
|
94
|
+
- spec/spec_helper.rb
|