xunch 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/lib/xunch.rb +25 -0
  3. data/lib/xunch/cache/cache.rb +88 -0
  4. data/lib/xunch/cache/cache_builder.rb +68 -0
  5. data/lib/xunch/cache/field_object_cache.rb +120 -0
  6. data/lib/xunch/cache/list_field_object_cache.rb +63 -0
  7. data/lib/xunch/cache/list_object_cache.rb +59 -0
  8. data/lib/xunch/cache/object_cache.rb +63 -0
  9. data/lib/xunch/codec/codec.rb +31 -0
  10. data/lib/xunch/codec/hash_codec.rb +98 -0
  11. data/lib/xunch/codec/json_codec.rb +81 -0
  12. data/lib/xunch/shard/redis.rb +270 -0
  13. data/lib/xunch/shard/shard_info.rb +37 -0
  14. data/lib/xunch/shard/shard_redis.rb +267 -0
  15. data/lib/xunch/shard/sharded.rb +50 -0
  16. data/lib/xunch/utils/exceptions.rb +11 -0
  17. data/lib/xunch/utils/nginx_cache_helper.rb +52 -0
  18. data/lib/xunch/utils/rb_tree.rb +634 -0
  19. data/lib/xunch/utils/rb_tree_node.rb +67 -0
  20. data/lib/xunch/utils/types.rb +8 -0
  21. data/lib/xunch/utils/utils.rb +24 -0
  22. data/test/benchmark_test.rb +68 -0
  23. data/test/cache_builder_test.rb +28 -0
  24. data/test/cache_object.rb +120 -0
  25. data/test/consistency_hash_test.rb +31 -0
  26. data/test/field_object_cache_test.rb +430 -0
  27. data/test/hash_codec_test.rb +57 -0
  28. data/test/json_codec_test.rb +57 -0
  29. data/test/list_field_object_cache_test.rb +211 -0
  30. data/test/list_object_cache_test.rb +211 -0
  31. data/test/nginx_cache_helper_test.rb +45 -0
  32. data/test/object_cache_test.rb +322 -0
  33. data/test/rb_tree_test.rb +48 -0
  34. data/test/redis_benchmark_test.rb +54 -0
  35. data/test/redis_test.rb +58 -0
  36. data/test/running_test.rb +212 -0
  37. data/test/test.rb +176 -0
  38. data/test/track_record_origin.rb +58 -0
  39. metadata +125 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 11095eadd1ac6cf93128811cd1bab2d818e345b1
4
+ data.tar.gz: 8e60835af4879fd33dfa4fe7cfc67d009548d5c2
5
+ SHA512:
6
+ metadata.gz: 6b7ca3c02e48930d5d7b551a8393c677493d3ac74ba6dc69fbf54249233ff2804d62f353e93324b62449b255b76635e39d25a8a4ba98dca17ee3a697e9d934fa
7
+ data.tar.gz: fed73c21df8039c4622c9ca91328f66aa5cc933f61a2230af0bd5c870804b41a1e502abeee45f875652168bcb04158947c2a2c1a6f734e183351d69f8e1041a3
@@ -0,0 +1,25 @@
1
+ require 'yajl'
2
+ require 'date'
3
+ require 'bigdecimal'
4
+ require 'murmurhash'
5
+ require 'redis'
6
+ require 'yaml'
7
+ require 'xunch/utils/rb_tree'
8
+ require 'xunch/utils/rb_tree_node'
9
+ require 'xunch/utils/exceptions'
10
+ require 'xunch/utils/types'
11
+ require 'xunch/utils/utils'
12
+ require 'xunch/utils/nginx_cache_helper'
13
+ require 'xunch/shard/shard_info'
14
+ require 'xunch/shard/sharded'
15
+ require 'xunch/shard/shard_redis'
16
+ require 'xunch/shard/redis'
17
+ require 'xunch/codec/codec'
18
+ require 'xunch/codec/json_codec'
19
+ require 'xunch/codec/hash_codec'
20
+ require 'xunch/cache/cache'
21
+ require 'xunch/cache/object_cache'
22
+ require 'xunch/cache/field_object_cache'
23
+ require 'xunch/cache/list_field_object_cache'
24
+ require 'xunch/cache/list_object_cache'
25
+ require 'xunch/cache/cache_builder'
@@ -0,0 +1,88 @@
1
+ module Xunch
2
+ class Cache
3
+
4
+ def initialize(options, shard_infos)
5
+ @options = initialize_options(options)
6
+ @shard_redis = ShardRedis.new(@options[:regex],shard_infos)
7
+ end
8
+
9
+ def evict(key)
10
+ @shard_redis.del(assembleKey(key))
11
+ end
12
+
13
+ def batch_evict(keys)
14
+ new_keys = []
15
+ keys.each { |key|
16
+ new_keys.push assembleKey(key)
17
+ }
18
+ @shard_redis.batch_del(new_keys)
19
+ end
20
+
21
+ def destroy
22
+ @shard_redis.destroy
23
+ end
24
+
25
+ protected
26
+ def getCacheObjectKey(value)
27
+ key = value.send(@options[:key_field_name])
28
+ raise ArgumentError("value #{value} key field can not be nil.") unless key != nil
29
+ key
30
+ end
31
+
32
+ def assembleKey(key)
33
+ name = @options[:name]
34
+ name + "_".concat(key.to_s).concat("_").concat(@options[:version].to_s)
35
+ end
36
+
37
+ def assembleTempKey(key)
38
+ name = @options[:name]
39
+ name + "_".concat(key.to_s).concat("_").concat(@options[:version].to_s).concat("_temp")
40
+ end
41
+
42
+ def initialize_options(options)
43
+ use_options = {}
44
+ expire_time = 60 * 60 * 24 * 1000
45
+ if(options["expire_time"] != nil)
46
+ expire_time = options["expire_time"]
47
+ end
48
+ use_options.store(:expire_time, expire_time)
49
+
50
+ if(options["cache_class"] != nil)
51
+ cache_class = eval(options["cache_class"])
52
+ elsif options["type"] != CacheType::LISTOBJECT && options["type"] != CacheType::LISTFIELDOBJECT
53
+ raise ArgumentError, "cache_class is nil"
54
+ end
55
+ use_options.store(:cache_class, cache_class)
56
+
57
+ version = 1
58
+ if(options["version"] != nil)
59
+ version = options["version"]
60
+ use_options.store(:version, version)
61
+ end
62
+
63
+ if(options["name"] != nil)
64
+ name = options["name"]
65
+ use_options.store(:name, name)
66
+ end
67
+
68
+ if(options["key_field_name"] != nil)
69
+ key_field_name = options["key_field_name"]
70
+ use_options.store(:key_field_name, key_field_name)
71
+ end
72
+
73
+ if(options["timeout"] != nil)
74
+ timeout = options["timeout"]
75
+ use_options.store(:timeout, timeout)
76
+ end
77
+
78
+ regex = '[0-9]+'
79
+ if(options["regex"] != nil)
80
+ regex = options["regex"]
81
+ end
82
+ use_options.store(:regex, regex)
83
+
84
+ return use_options
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,68 @@
1
+ module Xunch
2
+ class CacheBuilder
3
+ # every cache config hava attributes below:
4
+ # ['type'] => the cache type, have object|field_object|list_object
5
+ # ['name'] => the cache name, used for redis key prefix
6
+ # ['version'] => the cache version, maybe you change your cache object format and you will change a version of the cache
7
+ # ['expire_time'] => cache expire time for every key in millisecond
8
+ # ['cache_class'] => which type of object you cache, this will help us to encode/decode your object
9
+ # ['key_field_name'] => define which field in the object we used for redis key
10
+ # ['regex'] => a regex string which used for match consistency hash key
11
+ # ['shard_infos'] => define which redis instance we used for caching
12
+ #
13
+ #
14
+ def self.build(file)
15
+ configs = YAML.load_file(file)
16
+ shard_info_configs = configs["shard_infos"]
17
+ cache_configs = configs["caches"]
18
+
19
+ shards = {}
20
+ shard_info_configs.each_value { |shard_info_config|
21
+ options = {}
22
+ shard_info_config.each { |k,v|
23
+ options[k.to_sym] = v
24
+ }
25
+ shards[options[:name]] = ShardInfo.new(options)
26
+ }
27
+ caches = {}
28
+ lazy_caches = {}
29
+ cache_configs.each_value { |cache_config|
30
+ shard_names = cache_config["shards"].split(",")
31
+ shard_infos = []
32
+ shard_names.each { |shard_name|
33
+ shard_infos.push(shards[shard_name])
34
+ }
35
+ case cache_config["type"]
36
+ when CacheType::OBJECT
37
+ cache = Xunch::ObjectCache.new(cache_config,shard_infos)
38
+ caches[cache_config["name"]] = cache
39
+ when CacheType::FIELDOBJECT
40
+ cache = Xunch::FieldObjectCache.new(cache_config,shard_infos)
41
+ caches[cache_config["name"]] = cache
42
+ when CacheType::LISTOBJECT
43
+ lazy_caches[cache_config] = shard_infos
44
+ when CacheType::LISTFIELDOBJECT
45
+ lazy_caches[cache_config] = shard_infos
46
+ else
47
+ raise XunchConfigError.new("Unknown cache type #{cache_config["type"]}.")
48
+ end
49
+ }
50
+ lazy_caches.each { |cache_config,shard_infos|
51
+ delegate = caches[cache_config["delegate"]]
52
+ raise XunchConfigError.new("list_cache init error, delegate does not exist.") unless delegate != nil
53
+ cache = nil
54
+ case cache_config["type"]
55
+ when CacheType::LISTOBJECT
56
+ cache = Xunch::ListObjectCache.new(cache_config,shard_infos,delegate)
57
+ when CacheType::LISTFIELDOBJECT
58
+ cache = Xunch::ListFieldObjectCache.new(cache_config,shard_infos,delegate)
59
+ else
60
+ raise XunchConfigError.new("Unknown cache type #{cache_config["type"]}.")
61
+ end
62
+
63
+ caches[cache_config["name"]] = cache
64
+ }
65
+ caches
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,120 @@
1
+ module Xunch
2
+ #
3
+ #
4
+ #
5
+ #
6
+ class FieldObjectCache < Cache
7
+
8
+ def initialize(options, shard_infos)
9
+ super
10
+ fields = options['fields']
11
+ if fields == nil
12
+ raise XunchConfigError.new("fields can not be nil")
13
+ end
14
+ fields_array = fields.split(",")
15
+ if fields_array.length == 1 and fields_array[0].strip.empty?
16
+ raise XunchConfigError.new("fields config error")
17
+ end
18
+ formatted_fields_array = Utils.format_fields(fields_array)
19
+ @fields_array = formatted_fields_array
20
+ @codec = HashCodec.new(@options[:cache_class],formatted_fields_array)
21
+ end
22
+
23
+ def get(key)
24
+ redis_key = assembleKey(key)
25
+ data = @shard_redis.mapped_hget(redis_key,@fields_array)
26
+ @codec.decode(data)
27
+ end
28
+
29
+ def put(value)
30
+ putex(value,@options[:expire_time])
31
+ end
32
+
33
+ def putex(value, ttl)
34
+ if(value == nil)
35
+ return
36
+ end
37
+ key = getCacheObjectKey(value)
38
+ key = assembleKey(key)
39
+ data = @codec.encode(value)
40
+ @shard_redis.hsetall(key,data,ttl)
41
+ end
42
+
43
+ def multi_get(keys)
44
+ redis_keys = Array.new(keys.length)
45
+ for i in 0 .. keys.length - 1 do
46
+ redis_keys[i] = assembleKey(keys[i])
47
+ end
48
+ datas = @shard_redis.mapped_hmget(redis_keys,@fields_array)
49
+ for i in 0 .. datas.length - 1 do
50
+ datas[i] = @codec.decode(datas[i])
51
+ end
52
+ return datas
53
+ end
54
+
55
+ def multi_put(values)
56
+ multi_putex(values,@options[:expire_time])
57
+ end
58
+
59
+ def multi_putex(values,ttl)
60
+ kvs = Hash.new
61
+ values.each { | value |
62
+ if value == nil
63
+ next
64
+ end
65
+ key = getCacheObjectKey(value)
66
+ redis_key = assembleKey(key);
67
+ kvs[redis_key] = @codec.encode(value);
68
+ }
69
+ @shard_redis.hmsetall(kvs, ttl);
70
+ end
71
+
72
+ def get_with_field(key, fields)
73
+ redis_key = assembleKey(key)
74
+ data = @shard_redis.mapped_hget(redis_key,fields)
75
+ @codec.decode_fields(data,fields)
76
+ end
77
+
78
+ def put_with_field(value, fields)
79
+ put_with_field_ex(value,@options[:expire_time],fields)
80
+ end
81
+
82
+ def put_with_field_ex(value, ttl, fields)
83
+ if(value == nil)
84
+ return
85
+ end
86
+ key = getCacheObjectKey(value)
87
+ key = assembleKey(key)
88
+ data = @codec.encode_fields(value,fields)
89
+ @shard_redis.hsetall(key,data,ttl)
90
+ end
91
+
92
+ def multi_put_with_field_ex(values, ttl, fields)
93
+ kvs = Hash.new
94
+ values.each { | value |
95
+ if value == nil
96
+ next
97
+ end
98
+ key = getCacheObjectKey(value)
99
+ key = assembleKey(key);
100
+ kvs[key] = @codec.encode_fields(value,fields);
101
+ }
102
+ @shard_redis.hmsetall(kvs, ttl);
103
+ end
104
+
105
+ def multi_put_with_field(values, fields)
106
+ multi_put_with_field_ex(values,@options[:expire_time],fields)
107
+ end
108
+
109
+ def multi_get_with_field(keys, fields)
110
+ for i in 0 .. keys.length - 1 do
111
+ keys[i] = assembleKey(keys[i])
112
+ end
113
+ datas = @shard_redis.mapped_hmget(keys,fields)
114
+ for i in 0 .. datas.length - 1 do
115
+ datas[i] = @codec.decode_fields(datas[i],fields)
116
+ end
117
+ return datas
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,63 @@
1
+ module Xunch
2
+ #列表缓存目前不支持并发写入,未来也不打算支持并发
3
+ #主要的使用场景是发现页热门的单线程写入和并发的读取
4
+ #并且提供remove接口,帮助从列表中移除已经不存在的声音,用户,专辑
5
+ class ListFieldObjectCache < Cache
6
+
7
+ def initialize(options, shard_infos, object_cache)
8
+ super(options,shard_infos)
9
+ @delegate = object_cache
10
+ end
11
+
12
+ # 查询接口
13
+ # @key 列表的key
14
+ # @page 页码
15
+ # @size 页大小
16
+ #
17
+ def get(key, page, size)
18
+ raise "key can not be nil." unless key != nil
19
+ raise "page must be a positive number." unless page > 0
20
+ raise "size must be a positive number and less than 100." unless page != nil or size < 100
21
+ start = (page - 1) * size;
22
+ stop = page * size - 1;
23
+ new_key = assembleKey(key)
24
+ object_keys = @shard_redis.lrange(new_key,start,stop)
25
+ hash = {}
26
+ hash["keys"] = object_keys
27
+ hash["values"] = @delegate.multi_get(object_keys)
28
+ hash
29
+ end
30
+
31
+ def put(key, values)
32
+ raise "key can not be nil." unless key != nil
33
+ raise "values can not be nil." unless values != nil
34
+ sub_keys = []
35
+ values.each { | value |
36
+ raise "value in values can not be nil." unless value != nil
37
+ sub_keys.push(@delegate.getCacheObjectKey(value))
38
+ }
39
+ temp_key = assembleTempKey(key)
40
+ new_key = assembleKey(key)
41
+ @delegate.multi_putex(values,@options[:expire_time])
42
+ @shard_redis.lset(temp_key,new_key,sub_keys,@options[:expire_time])
43
+ end
44
+
45
+ def remove(key, sub_key)
46
+ raise "key can not be nil." unless key != nil
47
+ raise "sub_key can not be nil." unless sub_key != nil
48
+ new_key = assembleKey(key)
49
+ @shard_redis.lremove(new_key,sub_key)
50
+ end
51
+
52
+ def size(key)
53
+ raise "key can not be nil." unless key != nil
54
+ new_key = assembleKey(key)
55
+ @shard_redis.llen(new_key)
56
+ end
57
+
58
+ def delegate
59
+ @delegate
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ module Xunch
2
+ #列表缓存目前不支持并发写入,未来也不打算支持并发
3
+ #主要的使用场景是发现页热门的单线程写入和并发的读取
4
+ #并且提供remove接口,帮助从列表中移除已经不存在的声音,用户,专辑
5
+ class ListObjectCache < Cache
6
+
7
+ def initialize(options, shard_infos, object_cache)
8
+ super(options,shard_infos)
9
+ @delegate = object_cache
10
+ end
11
+
12
+ # 查询接口
13
+ # @key 列表的key
14
+ # @page 页码
15
+ # @size 页大小
16
+ #
17
+ def get(key, page, size)
18
+ raise "key can not be nil." unless key != nil
19
+ raise "page must be a positive number." unless page > 0
20
+ raise "size must be a positive number and less than 100." unless page != nil or size < 100
21
+ start = (page - 1) * size;
22
+ stop = page * size - 1;
23
+ new_key = assembleKey(key)
24
+ object_keys = @shard_redis.lrange(new_key,start,stop)
25
+ hash = {}
26
+ hash["keys"] = object_keys
27
+ hash["values"] = @delegate.multi_get(object_keys)
28
+ hash
29
+ end
30
+
31
+ def put(key, values)
32
+ raise "key can not be nil." unless key != nil
33
+ raise "values can not be nil." unless values != nil
34
+ sub_keys = []
35
+ values.each { | value |
36
+ raise "value in values can not be nil." unless value != nil
37
+ sub_keys.push(@delegate.getCacheObjectKey(value))
38
+ }
39
+ temp_key = assembleTempKey(key)
40
+ new_key = assembleKey(key)
41
+ @delegate.multi_putex(values,@options[:expire_time])
42
+ @shard_redis.lset(temp_key,new_key,sub_keys,@options[:expire_time])
43
+ end
44
+
45
+ def remove(key, sub_key)
46
+ raise "key can not be nil." unless key != nil
47
+ raise "sub_key can not be nil." unless sub_key != nil
48
+ new_key = assembleKey(key)
49
+ @shard_redis.lremove(new_key,sub_key)
50
+ end
51
+
52
+ def size(key)
53
+ raise "key can not be nil." unless key != nil
54
+ new_key = assembleKey(key)
55
+ @shard_redis.llen(new_key)
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ module Xunch
2
+ class ObjectCache < Cache
3
+
4
+ def initialize(options, shard_infos)
5
+ super
6
+ @codec = JsonCodec.new(@options[:cache_class])
7
+ end
8
+
9
+ def get(key)
10
+ redis_key = assembleKey(key)
11
+ data = @shard_redis.get(redis_key)
12
+ if data != nil
13
+ @codec.decode(data)
14
+ end
15
+ end
16
+
17
+ def put(value)
18
+ putex(value,@options[:expire_time])
19
+ end
20
+
21
+ def putex(value, ttl)
22
+ if(value == nil)
23
+ return
24
+ end
25
+ key = getCacheObjectKey(value)
26
+ key = assembleKey(key)
27
+ data = @codec.encode(value)
28
+ @shard_redis.set(key,data,ttl.to_s)
29
+ end
30
+
31
+ def multi_get(keys)
32
+ redis_keys = Array.new(keys.length)
33
+ for i in 0 .. keys.length - 1 do
34
+ redis_keys[i] = assembleKey(keys[i])
35
+ end
36
+ datas = @shard_redis.mget(redis_keys)
37
+ for i in 0 .. datas.length - 1 do
38
+ if datas[i] != nil
39
+ datas[i] = @codec.decode(datas[i])
40
+ end
41
+ end
42
+ datas
43
+ end
44
+
45
+ def multi_put(values)
46
+ multi_putex(values,@options[:expire_time])
47
+ end
48
+
49
+ def multi_putex(values,ttl)
50
+ kvs = Hash.new
51
+ values.each { | value |
52
+ if value == nil
53
+ next
54
+ end
55
+ key = getCacheObjectKey(value)
56
+ key = assembleKey(key);
57
+ kvs[key] = @codec.encode(value);
58
+ }
59
+ @shard_redis.mset(kvs, ttl);
60
+ end
61
+
62
+ end
63
+ end