jruby-memcached 0.1.0
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/.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
|