xunch 0.0.6

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.
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