1 <?php
2
3
4 /**
5 * redis负载均衡
6 *
7 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 *
9 * $array = array(
10 * 'master' => array(
11 * "redis://127.0.0.1:6379?timeout=1",
12 * ),
13 * 'slave' => array(
14 * "redis://127.0.0.1:6479?timeout=1",
15 * "redis://127.0.0.1:6579?timeout=1",
16 * )
17 * );
18 *
19 * $redis = RedisServer::instance($array);
20 * $redis->set("aaaa","aaaa") //将会把数据缓存到.127.0.0.1:6379中
21 * $redis->get("aaaa")//将会用127.0.0.1:6479或者127.0.0.1:6579中读取.从而实现读写分离
22 *
23 *
24 *
25 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26 *
27 * 如果本类报找不到方法.但是redis中存在改方法.请在列成员$methodMap中加入对应的方法.如果是缓存数据.设置为true,读取数据.设置为false
28 *
29 *
30 *
31 * Class RedisServer
32 * @author 136045277#qq.com
33 */
34 class RedisServer
35 {
36 private static $masterServers = array();
37 private static $slaveServers = array();
38
39 private static $masterLength = 0;
40 private static $slaveLength = 0;
41
42
43 private static $serverConfigs = array();
44 private static $dbIndex = null;
45 private static $instance = null;
46
47
48 private static $prohibitMap = array(
49 'slaveof', 'config'
50 );
51
52
53 private static $methodMap = array (
54 'del' => true,
55 'set' => true,
56 'get' => false,
57 'ttl' => false,
58 'hget' => false,
59 'incr' => true,
60 'lpop' => true,
61 'rpop' => true,
62 'spop' => true,
63 'decr' => true,
64 'ping' => true,
65 'info' => false,
66 'type' => false,
67 'hlen' => false,
68 'sadd' => true,
69 'keys' => false,
70 'mset' => true,
71 'exec' => true,
72 'hdel' => true,
73 'auth' => true,
74 'srem' => true,
75 'hset' => true,
76 'zrem' => true,
77 'scan' => false,
78 'dump' => false,
79 'zset' => true,
80 'move' => true,
81 'save' => true,
82 'lrem' => true,
83 'lset' => true,
84 'lget' => false,
85 'sort' => false,
86 'hmget' => false,
87 'ltrim' => true,
88 'hvals' => false,
89 'hkeys' => false,
90 'brpop' => true,
91 'lpush' => true,
92 'rpush' => true,
93 'smove' => true,
94 'setex' => true,
95 'watch' => true,
96 'multi' => true,
97 'bitop' => true,
98 'setnx' => true,
99 'zrank' => false,
100 'sscan' => false,
101 'hscan' => false,
102 'scard' => false,
103 'hmset' => true,
104 'zsize' => false,
105 'ssize' => false,
106 'lsize' => false,
107 'zscan' => false,
108 'blpop' => true,
109 'sdiff' => false,
110 'zcard' => false,
111 'exists' => false,
112 'zrange' => false,
113 'lindex' => false,
114 'getbit' => false,
115 'sunion' => false,
116 'sinter' => false,
117 'strlen' => false,
118 'decrby' => true,
119 'object' => false,
120 'incrby' => true,
121 'zinter' => true,
122 'getset' => true,
123 'lrange' => true,
124 'append' => true,
125 'lpushx' => true,
126 'zscore' => false,
127 'dbsize' => false,
128 'zcount' => false,
129 'zunion' => true,
130 'expire' => true,
131 'config' => true,
132 'rename' => true,
133 'setbit' => true,
134 'delete' => true,
135 'zincrby' => true,
136 'lremove' => true,
137 'sremove' => true,
138 'linsert' => true,
139 'hincrby' => true,
140 'flushdb' => true,
141 'migrate' => true,
142 'hgetall' => false,
143 'unwatch' => true,
144 'hexists' => false,
145 'zdelete' => false,
146 'discard' => true,
147 'getkeys' => false,
148 'persist' => true,
149 'setrange' => true,
150 'renamenx' => true,
151 'getrange' => false,
152 'bitcount' => false,
153 'smembers' => true,
154 'expireat' => true,
155 'lastsave' => true,
156 'listtrim' => true,
157 'flushall' => true,
158 'zrevrank' => false,
159 'sismember' => false,
160 'zrevrange' => false,
161 'randomkey' => false,
162 'rpoplpush' => true,
163 'scontains' => false,
164 'lgetrange' => false,
165 'renamekey' => true,
166 'sdiffstore' => true,
167 'settimeout' => true,
168 'sgetmembers' => true,
169 'sinterstore' => true,
170 'srandmember' => false,
171 'sunionstore' => true,
172 'getmultiple' => false,
173 'bgrewriteaof' => true,
174 'zrangebyscore' => false,
175 'zrevrangebyscore' => false,
176 'zremrangebyscore' => true,
177 'zdeleterangebyscore' => true,
178 );
179
180
181
182 private function __construct()
183 {
184 }
185
186 /**
187 * redis初始化
188 * 配置
189 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
190 *
191 * $array = array(
192 * 'master' => array(
193 * "redis://127.0.0.1:6379?timeout=1",
194 * ),
195 * 'slave' => array(
196 * "redis://127.0.0.1:6479?timeout=1",
197 * "redis://127.0.0.1:6579?timeout=1",
198 * )
199 * );
200 *
201 * $redis = RedisServer::instance($array);
202 *
203 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204 *
205 *
206 *
207 * @param array $configs
208 * @return null|RedisServer
209 */
210 public static function instance(array $configs)
211 {
212 if (self::$instance === null) {
213 self::$instance = new self();
214 foreach ($configs['master'] as $master) {
215 $connect = self::$instance->parseStr($master);
216 self::$instance->addMaster($connect[0], $connect[1], $connect[2]);
217 }
218 foreach ($configs['slave'] as $master) {
219 $connect = self::$instance->parseStr($master);
220 self::$instance->addSlave($connect[0], $connect[1], $connect[2]);
221 }
222 }
223 return self::$instance;
224 }
225
226
227 private function parseStr($string)
228 {
229 $urls = parse_url($string);
230 $array[] = $urls['host'];
231 $array[] = isset($urls['port']) ? $urls['port'] : 6379;
232 $array[] = 1;
233 return $array;
234 }
235
236
237 public function addMaster($host, $port = 6379, $timeout = 0.0)
238 {
239 self::$masterLength++;
240 self::$serverConfigs['master'][] = [$host, $port, $timeout];
241 }
242
243 public function addSlave($host, $port = 6379, $timeout = 0.0)
244 {
245 self::$slaveLength++;
246 self::$serverConfigs['slave'][] = [$host, $port, $timeout];
247 }
248
249
250 /**
251 * @param null $key
252 * @param bool|true $isMaster
253 * @return \Redis
254 */
255 protected function getRedis($key = null, $isMaster = true)
256 {
257 empty($key) && $key = uniqid(true);
258 list($length, $server) = $isMaster ? [self::$masterLength, &self::$masterServers] : [self::$slaveLength, &self::$slaveServers];
259 $index = $this->getHostByHash($key, $length);
260 if (!isset($server[$index])) {
261 $connect = $isMaster ? self::$serverConfigs['master'][$index] : self::$serverConfigs['slave'][$index];
262 $server[$index] = new \Redis();
263 $server[$index]->connect($connect[0], $connect[1], $connect[2]);
264 if (self::$dbIndex !== null) {
265 $server[$index]->select($index);
266 }
267 /*if (!$isMaster) {
268 $server[$index]->slaveof(self::$serverConfigs['master'][0][0], self::$serverConfigs['master'][0][1]);
269 }*/
270 }
271 return $server[$index];
272 }
273
274
275 private function getHostByHash($key, $n)
276 {
277 if ($n < 2) return 0;
278 $id = sprintf("%u", crc32($key));
279 $m = base_convert(intval(fmod($id, $n)), 10, $n);
280 return $m{0};
281 }
282
283
284 public function __call($method, $args)
285 {
286 if (in_array(strtolower($method), self::$prohibitMap)) {
287 $this->prohibit($method);
288 } elseif (isset(self::$methodMap[strtolower($method)])) {
289 return call_user_func_array(array($this->getRedis(null, self::$methodMap[strtolower($method)]), $method), $args);
290 }
291 trigger_error("Call to undefined method " . __CLASS__ . "::{$method}() ", E_USER_ERROR);
292 }
293
294
295 /**
296 * 禁用slaveof方法
297 */
298 private function prohibit($method)
299 {
300 trigger_error("Call to prohibit access method " . __CLASS__ . "::{$method}() ", E_USER_ERROR);
301 }
302
303
304 public function select($dbIndex)
305 {
306 foreach (self::$masterServers as &$master) {
307 $master->select($dbIndex);
308 }
309 unset($master);
310 foreach (self::$slaveServers as &$slave) {
311 $slave->select($dbIndex);
312 }
313 self::$dbIndex = $dbIndex;
314 unset($slave);
315 }
316
317
318 }