1 /*
2  * Hunt - A redis client library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.redis.util.Sharded;
13 
14 import hunt.redis.util.Hashing;
15 import hunt.redis.util.SafeEncoder;
16 import hunt.redis.util.ShardInfo;
17 
18 import hunt.collection;
19 import hunt.Long;
20 
21 import std.conv;
22 import std.regex;
23 
24 alias Pattern = Regex!char;
25 
26 enum int DEFAULT_WEIGHT = 1;
27     // the tag is anything between {}
28 enum DEFAULT_KEY_TAG_PATTERN = ctRegex!("\\{(.+?)\\}");
29 
30 class Sharded(R, S) if(is(S : ShardInfo!(R))) {
31 
32     private TreeMap!(long, S) nodes;
33     private Hashing algo;
34     private Map!(ShardInfo!(R), R) resources;
35 
36     /**
37      * The default pattern used for extracting a key tag. The pattern must have a group (between
38      * parenthesis), which delimits the tag to be hashed. A null pattern avoids applying the regular
39      * expression for each lookup, improving performance a little bit is key tags aren't being used.
40      */
41     private Regex!char tagPattern;
42 
43     this(List!(S) shards) {
44         this(shards, Hashing.MURMUR_HASH); // MD5 is really not good as we works
45         // with 64-bits not 128
46     }
47 
48     this(List!(S) shards, Hashing algo) {
49         this.algo = algo;
50         initialize(shards);
51     }
52 
53     this(List!(S) shards, Pattern tagPattern) {
54         this(shards, Hashing.MURMUR_HASH, tagPattern); // MD5 is really not good
55         // as we works with
56         // 64-bits not 128
57     }
58 
59     this(List!(S) shards, Hashing algo, Pattern tagPattern) {
60         this.algo = algo;
61         this.tagPattern = tagPattern;
62         initialize(shards);
63     }
64 
65     private void initialize(List!(S) shards) {
66         resources = new LinkedHashMap!(ShardInfo!(R), R)();
67         nodes = new TreeMap!(long, S)();
68 
69         for (int i = 0; i != shards.size(); ++i) {
70             S shardInfo = shards.get(i);
71             if (shardInfo.getName() is null) for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
72                 long v = this.algo.hash("SHARD-" ~ i.to!string() ~ "-NODE-" ~ n.to!string());
73                 nodes.put(v, shardInfo);
74             } else {
75                  for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
76                      long v = this.algo.hash(shardInfo.getName() ~ "*" ~ n.to!string());
77                     nodes.put(v, shardInfo);
78                 }
79             }
80             resources.put(shardInfo, shardInfo.createResource());
81         }
82     }
83 
84     R getShard(const(ubyte)[] key) {
85         return resources.get(getShardInfo(key));
86     }
87 
88     R getShard(string key) {
89         return resources.get(getShardInfo(key));
90     }
91 
92     S getShardInfo(const(ubyte)[] key) {
93         SortedMap!(long, S) tail = nodes.tailMap(algo.hash(key));
94         if (tail.isEmpty()) {
95             return nodes.get(nodes.firstKey());
96         }
97         return tail.get(tail.firstKey());
98     }
99 
100     S getShardInfo(string key) {
101         return getShardInfo(SafeEncoder.encode(getKeyTag(key)));
102     }
103 
104     /**
105      * A key tag is a special pattern inside a key that, if preset, is the only part of the key hashed
106      * in order to select the server for this key.
107      * @see <a href="http://redis.io/topics/partitioning">partitioning</a>
108      * @param key
109      * @return The tag if it exists, or the original key
110      */
111     string getKeyTag(string key) {
112         if (!tagPattern.empty()) {
113             auto m = matchFirst(key, tagPattern);
114             if (!m.empty()) return m[1];
115         }
116         return key;
117     }
118 
119     S[] getAllShardInfo() {
120         return nodes.values();
121     }
122 
123     R[] getAllShards() {
124         return resources.values();
125     }
126 }