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 }