├── LICENSE
├── README.md
├── lua_scripts
└── content.lua
├── nginx
└── conf
│ ├── fastcgi_params
│ ├── nginx.conf
│ └── vhosts
│ └── lua_queue.com.conf
├── ngx_lua_php_queue.png
└── php_scripts
├── config.php
├── cron_queue.php
├── index.html
├── queue.php
├── redisserver
├── APCObject.php
├── IKeyLocker.php
├── IMemcacheDecorator.php
├── IMemoryStorage.php
├── IRedisServer.php
├── KeyAutoUnlocker.php
├── MemcacheObject.php
├── MemcachedDecorator.php
├── MemcachedObject.php
├── MemoryObject.php
├── PhpRedisObject.php
├── README.md
├── RedisObject.php
├── RedisServer.php
├── Shm
│ ├── DummyMutex.php
│ ├── IMutex.php
│ ├── ISingleMemory.php
│ ├── MultiAccess.php
│ ├── ReadOnlyAccess.php
│ ├── SHMObject.php
│ ├── ShmMem.php
│ └── SingleMemory.php
├── Tests
│ ├── TestMemoryObject.php
│ └── TestRedisServer.php
└── demo.php
└── static
├── css
└── bootstrap.css
└── js
├── bootstrap.js
└── jquery.js
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 John Wang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ngx_lua_php_queue
2 | =================
3 |
4 | nginx+lua+php+redis实现单业务排队系统架构
5 |
6 | 在做并发量很大的秒杀活动时,php会因为并发量过大而php进程不足导致服务器负载过大,无法同时处理过多请求,返回不友好的502错误。该项目旨在将php进程的压力转移到nginx层,使用lua作为中间语言,redis读取/储存队列信息,大大提高了并发量,降低了php进程的负载压力。
7 |
8 | LICENSE:
9 | https://github.com/takatost/ngx_lua_php_queue/blob/master/LICENSE
10 |
11 | 安装
12 | ------------
13 |
14 | 1. 首先安装[openresty](http://openresty.org/cn/index.html)和[php](http://www.php.net)、[redis](http://www.redis.io)
15 | 2. 安装完成后,将`lua_script`和`php_scripts`放入web文档目录,默认为`/opt/htdocs/lua_queue`
16 | 3. 接着讲nginx/conf中的配置文件放入openresty的nginx配置文件目录,如`/opt/ngx_openresty_1.7.2.1/nginx/conf`
17 | 4. 打开配置文件`conf/vhosts/lua_queue.com.conf`。
18 |
第2行:redis服务器的配置,`server 地址:端口;`
19 | 第8行:web域名配置,`server_name 域名;`
20 | 第9行:web和php文档目录配置,`root 文档目录;`,默认为`root /opt/htdocs/lua_queue/php_scripts;`
21 | 第9行:web和php文档目录配置,`root 文档目录;`
22 | 第27行:lua文档目录配置,`root lua文档目录;`,默认为`access_by_lua_file /opt/htdocs/lua_queue/lua_scripts/content.lua;`
23 | 5. 配置完成后,重启nginx,载入配置文件
24 | 6. 将域名的hosts写入/etc/hosts
25 | 7. 配置php的redis设置
26 |
27 | 打开`php_scripts`文档目录下的config.php,将redis地址和端口填入其中
28 |
29 | 8. 建立cron任务,`*/1 * * * * php /opt/htdocs/lua_queue/php_scripts/cron_queue.php`,一分钟执行一次,更新redis计数器
30 | 9. 到此,安装步骤全部完成,打开配置的域名首页来看看吧
31 |
32 | 原理
33 | ------------
34 | 
35 |
36 | 相关文档
37 | ------------
38 | 1. http://openresty.org/cn/index.html
39 | 2. http://wiki.nginx.org/HttpRedis2Module
40 |
--------------------------------------------------------------------------------
/lua_scripts/content.lua:
--------------------------------------------------------------------------------
1 | ngx.header.content_type = "text/plain";
2 | local parser = require "redis.parser"
3 |
4 | local res = ngx.location.capture("/redis?",
5 | {
6 | args = {
7 | query = 'decr lua_queue_count\n'
8 | }
9 | })
10 |
11 | if res.status ~= 200 or not res.body then
12 | ngx.log(ngx.ERR, "failed to query redis")
13 | ngx.say('{"code": 0, "message": "failed to query redis server", "environment": "Lua"}')
14 | ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE);
15 | end
16 |
17 | local res,typ = parser.parse_reply(res.body)
18 | if typ == parser.INTEGER_REPLY then
19 | if res < 0 then
20 | ngx.say('{"code": -1, "message": "sorry, haven\'t you turn.", "environment": "Lua"}')
21 | ngx.exit(200);
22 | end
23 | else
24 | ngx.say('{"code": 0, "message": "redis server error", "environment": "Lua"}')
25 | ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE);
26 | end
27 |
--------------------------------------------------------------------------------
/nginx/conf/fastcgi_params:
--------------------------------------------------------------------------------
1 |
2 | fastcgi_param QUERY_STRING $query_string;
3 | fastcgi_param REQUEST_METHOD $request_method;
4 | fastcgi_param CONTENT_TYPE $content_type;
5 | fastcgi_param CONTENT_LENGTH $content_length;
6 |
7 | fastcgi_param SCRIPT_NAME $fastcgi_script_name;
8 | fastcgi_param REQUEST_URI $request_uri;
9 | fastcgi_param DOCUMENT_URI $document_uri;
10 | fastcgi_param DOCUMENT_ROOT $document_root;
11 | fastcgi_param SERVER_PROTOCOL $server_protocol;
12 | fastcgi_param HTTPS $https if_not_empty;
13 |
14 | fastcgi_param GATEWAY_INTERFACE CGI/1.1;
15 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
16 |
17 | fastcgi_param REMOTE_ADDR $remote_addr;
18 | fastcgi_param REMOTE_PORT $remote_port;
19 | fastcgi_param SERVER_ADDR $server_addr;
20 | fastcgi_param SERVER_PORT $server_port;
21 | fastcgi_param SERVER_NAME $server_name;
22 |
23 | # PHP only, required if PHP was built with --enable-force-cgi-redirect
24 | fastcgi_param REDIRECT_STATUS 200;
25 |
--------------------------------------------------------------------------------
/nginx/conf/nginx.conf:
--------------------------------------------------------------------------------
1 | user www www;
2 | worker_processes 4;
3 | worker_cpu_affinity 0001 0010 0100 1000;
4 |
5 | #error_log logs/error.log;
6 | #error_log logs/error.log notice;
7 | #error_log logs/error.log info;
8 |
9 | #pid logs/nginx.pid;
10 |
11 |
12 | events {
13 | worker_connections 1024;
14 | }
15 |
16 |
17 | http {
18 | include mime.types;
19 | default_type application/octet-stream;
20 |
21 | lua_shared_dict config 1m;
22 |
23 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
24 | # '$status $body_bytes_sent "$http_referer" '
25 | # '"$http_user_agent" "$http_x_forwarded_for"';
26 |
27 | #access_log logs/access.log main;
28 |
29 | sendfile on;
30 | #tcp_nopush on;
31 |
32 | #keepalive_timeout 0;
33 | keepalive_timeout 65;
34 |
35 | #gzip on;
36 |
37 | server {
38 | listen 80;
39 | server_name localhost;
40 |
41 | #charset koi8-r;
42 |
43 | #access_log logs/host.access.log main;
44 |
45 | location / {
46 | root html;
47 | index index.html index.htm;
48 | }
49 |
50 | #error_page 404 /404.html;
51 |
52 | # redirect server error pages to the static page /50x.html
53 | #
54 | error_page 500 502 503 504 /50x.html;
55 | location = /50x.html {
56 | root html;
57 | }
58 |
59 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80
60 | #
61 | #location ~ \.php$ {
62 | # proxy_pass http://127.0.0.1;
63 | #}
64 |
65 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
66 | #
67 | #location ~ \.php$ {
68 | # root html;
69 | # fastcgi_pass 127.0.0.1:9000;
70 | # fastcgi_index index.php;
71 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
72 | # include fastcgi_params;
73 | #}
74 |
75 | # deny access to .htaccess files, if Apache's document root
76 | # concurs with nginx's one
77 | #
78 | #location ~ /\.ht {
79 | # deny all;
80 | #}
81 | }
82 |
83 |
84 | # another virtual host using mix of IP-, name-, and port-based configuration
85 | #
86 | #server {
87 | # listen 8000;
88 | # listen somename:8080;
89 | # server_name somename alias another.alias;
90 |
91 | # location / {
92 | # root html;
93 | # index index.html index.htm;
94 | # }
95 | #}
96 |
97 |
98 | # HTTPS server
99 | #
100 | #server {
101 | # listen 443 ssl;
102 | # server_name localhost;
103 |
104 | # ssl_certificate cert.pem;
105 | # ssl_certificate_key cert.key;
106 |
107 | # ssl_session_cache shared:SSL:1m;
108 | # ssl_session_timeout 5m;
109 |
110 | # ssl_ciphers HIGH:!aNULL:!MD5;
111 | # ssl_prefer_server_ciphers on;
112 |
113 | # location / {
114 | # root html;
115 | # index index.html index.htm;
116 | # }
117 | #}
118 |
119 | include vhosts/*.conf;
120 | }
121 |
--------------------------------------------------------------------------------
/nginx/conf/vhosts/lua_queue.com.conf:
--------------------------------------------------------------------------------
1 | upstream redis-server {
2 | server 127.0.0.1:6379;
3 | keepalive 1024;
4 | }
5 |
6 | server {
7 | listen 80;
8 | server_name www.luaqueue.com;
9 | root /opt/htdocs/lua_queue/php_scripts;
10 |
11 | # lua_code_cache off; # 关闭LUA文件缓存
12 |
13 | location /redis { # 内部redis请求转发
14 | internal;
15 |
16 | set_unescape_uri $query $arg_query;
17 | redis2_raw_query $query;
18 | redis2_pass redis-server;
19 | }
20 |
21 | location / {
22 | index index.html index.htm index.php;
23 | }
24 |
25 | location ~ \.php$ {
26 | if ($request_filename ~ ^(.*)queue\.php$){ # queue.php文件单独走lua脚本,若通过,继续执行php文件
27 | access_by_lua_file /opt/htdocs/lua_queue/lua_scripts/content.lua;
28 | }
29 |
30 | include fastcgi_params;
31 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
32 | fastcgi_pass 127.0.0.1:9000;
33 | fastcgi_index index.php;
34 | }
35 |
36 | access_log logs/lua_queue.access.log;
37 | error_log logs/lua_queue.error.log;
38 | }
39 |
--------------------------------------------------------------------------------
/ngx_lua_php_queue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takatost/ngx_lua_php_queue/8fb1dd076df89a26b628671de47bc3b1ce7ce6ce/ngx_lua_php_queue.png
--------------------------------------------------------------------------------
/php_scripts/config.php:
--------------------------------------------------------------------------------
1 | "localhost",
4 | "port" => 6379
5 | );
--------------------------------------------------------------------------------
/php_scripts/cron_queue.php:
--------------------------------------------------------------------------------
1 | connect($redisConfig['host'], $redisConfig['port']);
7 | $result = $redis->send_command('set', 'lua_queue_count', '10');
8 | echo 'success';
9 | ?>
10 |
--------------------------------------------------------------------------------
/php_scripts/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Lua Queue
7 |
8 |
9 |
19 |
20 |
42 |
43 |
49 |
50 |
51 |
52 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/php_scripts/queue.php:
--------------------------------------------------------------------------------
1 | connect($redisConfig['host'], $redisConfig['port']);
7 | $stock = $redis->send_command('get', 'lua_queue_stock');
8 | if($stock && $stock > 0)
9 | {
10 | $redis->send_command('decr', 'lua_queue_stock');
11 | $result = $redis->send_command('get', 'lua_queue_count');
12 | echo json_encode(['code' => 1, 'message' => 'success', 'stock' => intval($stock) - 1, 'queue' => intval($result), 'environment' => 'Php']);
13 | }
14 | else
15 | echo json_encode(['code' => -2, 'message' => 'failed. out of stock.', 'stock' => intval($stock), 'environment' => 'Php']);
16 | ?>
17 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/APCObject.php:
--------------------------------------------------------------------------------
1 | set_ID($ID);
26 | }
27 |
28 | /**
29 | * Add value to memory storage, only if this key does not exists (or false will be returned).
30 | *
31 | * @param string $k
32 | * @param mixed $v
33 | * @param int $ttl
34 | * @param array|string $tags
35 | * @return boolean
36 | */
37 | public function add($k, $v, $ttl = 259200, $tags = NULL)
38 | {
39 | if (empty($k))
40 | {
41 | $this->ReportError('empty keys are not allowed', __LINE__);
42 | return false;
43 | }
44 |
45 | $add = apc_add($this->prefix.$k, $v, intval($ttl));
46 | if (!$add)
47 | {
48 | if (!apc_exists($this->prefix.$k))
49 | {
50 | $this->ReportError('Can not add non existing key', __LINE__);
51 | }
52 | return false;
53 | }
54 | if (!empty($tags)) $this->set_tags($k, $tags, $ttl);
55 | return true;
56 | }
57 |
58 | /**
59 | * Associate tags with keys
60 | * @param string $key
61 | * @param string|array $tags
62 | * @param int $ttl
63 | * @return bool
64 | */
65 | public function set_tags($key, $tags, $ttl = self::max_ttl)
66 | {
67 | if (!is_array($tags))
68 | {
69 | if (is_scalar($tags)) $tags = array($tags);
70 | else $tags = array();
71 | }
72 | if (!empty($tags))
73 | {
74 | return apc_store($this->tags_prefix.$key, $tags, intval($ttl));
75 | }
76 | return false;
77 | }
78 |
79 | /**
80 | * Save variable in memory storage
81 | *
82 | * @param string $k
83 | * @param mixed $v
84 | * @param int $ttl - time to live (store) in seconds
85 | * @param array|string $tags - array of tags for this key
86 | * @return bool
87 | */
88 | public function save($k, $v, $ttl = 259200, $tags = NULL)
89 | {
90 | if (empty($k))
91 | {
92 | $this->ReportError('empty keys are not allowed', __LINE__);
93 | return false;
94 | }
95 |
96 | static $cleaned = false;
97 | if (!$cleaned)
98 | {
99 | $this->del_old_cached();
100 | $cleaned = true;
101 | }
102 |
103 | if (!apc_store($this->prefix.$k, $v, intval($ttl)))
104 | {
105 | $this->ReportError('apc can not store key', __LINE__);
106 | return false;
107 | }
108 |
109 | if (!empty($tags)) $this->set_tags($k, $tags, $ttl);
110 | return true;
111 | }
112 |
113 | /**
114 | * Returns, how many seconds left till key expiring.
115 | * @param string $key
116 | * @return int
117 | */
118 | public function getKeyTTL($key)
119 | {
120 | $i = new \APCIterator('user', '/^'.preg_quote($this->prefix.$key).'$/', APC_ITER_TTL+APC_ITER_CTIME, 1);
121 | $item = $i->current();
122 | if (empty($item)) return NULL;
123 | if ($item[self::apc_arr_ttl]!=0) return (($item[self::apc_arr_ctime]+$item[self::apc_arr_ttl])-time());
124 | else return self::max_ttl;
125 | }
126 |
127 | /**
128 | * Read data from memory storage
129 | *
130 | * @param string|array $k (string or array of string keys)
131 | * @param mixed $ttl_left = (ttl - time()) of key. Use to exclude dog-pile effect, with lock/unlock_key methods.
132 | * @return mixed
133 | */
134 | public function read($k, &$ttl_left = -1)
135 | {
136 | if (empty($k))
137 | {
138 | $this->ReportError('empty keys are not allowed', __LINE__);
139 | return NULL;
140 | }
141 | if (is_array($k))
142 | {
143 | $data = array();
144 | $return_ttl = ($ttl_left!==-1 ? true : false);
145 | $ttl_left = array();
146 | foreach ($k as $key)
147 | {
148 | $key = (string)$key;
149 | $data[$key] = apc_fetch($this->prefix.$key, $success);
150 | if (!$success)
151 | {
152 | unset($data[$key]);
153 | continue;
154 | }
155 | if ($return_ttl) $ttl_left[$key] = $this->getKeyTTL($key);
156 | }
157 | }
158 | else
159 | {
160 | $data = apc_fetch($this->prefix.$k, $success);
161 | if (!$success)
162 | {
163 | if (apc_exists($this->prefix.$k))
164 | {
165 | $this->ReportError('apc can not fetch key '.$k, __LINE__);
166 | }
167 | return false;
168 | }
169 | if ($ttl_left!==-1)
170 | {
171 | $ttl_left = $this->getKeyTTL($k);
172 | if ($ttl_left < 0) $data = false; //key expired
173 | }
174 | }
175 | return $data;
176 | }
177 |
178 | /** Return array of all stored keys */
179 | public function get_keys()
180 | {
181 | $map = array();
182 | $l = strlen($this->prefix);
183 | $i = new \APCIterator('user', '/^'.preg_quote($this->prefix).'/', APC_ITER_KEY);
184 | foreach ($i as $item)
185 | {
186 | $map[] = substr($item[self::apc_arr_key], $l);
187 | }
188 | return $map;
189 | }
190 |
191 | /**
192 | * Delete key or array of keys from storage
193 | * @param string|array $k
194 | * @return boolean
195 | */
196 | public function del($k)
197 | {
198 | if (empty($k))
199 | {
200 | $this->ReportError('empty keys are not allowed', __LINE__);
201 | return false;
202 | }
203 |
204 | if (is_array($k))
205 | {
206 | $todel = array();
207 | foreach ($k as $key)
208 | {
209 | $todel[] = $this->prefix.$key;
210 | if (\apc_exists($this->tags_prefix.$key)) $todel[] = $this->tags_prefix.$key;
211 | if (\apc_exists($this->lock_key_prefix.$key)) $todel[] = $this->lock_key_prefix.$key;
212 | }
213 | $r = apc_delete($todel);
214 | if (empty($r)) return true;
215 | else return $r;
216 | }
217 | else
218 | {
219 | if (\apc_exists($this->tags_prefix.$k)) apc_delete($this->tags_prefix.$k);
220 | if (\apc_exists($this->lock_key_prefix.$k)) apc_delete($this->lock_key_prefix.$k);
221 | return apc_delete($this->prefix.$k);
222 | }
223 | }
224 |
225 | /**
226 | * Delete old (by ttl) variables from storage
227 | * It's very important function to prevent APC's cache fragmentation.
228 | * @return boolean
229 | */
230 | public function del_old()
231 | {
232 | $t = time();
233 | $todel = array();
234 | $apc_user_info = apc_cache_info('user', true);
235 | $apc_ttl = 0;
236 | if (!empty($apc_user_info['ttl']))
237 | {
238 | $apc_ttl = $apc_user_info['ttl']/2;
239 | }
240 |
241 | $i = new \APCIterator('user', null, APC_ITER_TTL+APC_ITER_KEY+APC_ITER_CTIME+APC_ITER_ATIME);
242 | foreach ($i as $key)
243 | {
244 | if ($key[self::apc_arr_ttl] > 0 && ($t-$key[self::apc_arr_ctime]) > $key[self::apc_arr_ttl]) $todel[] = $key[self::apc_arr_key];
245 | else
246 | {
247 | //this code is necessary to prevent deletion variables from cache by apc.ttl (they deletes not by their ttl+ctime, but apc.ttl+atime)
248 | if ($apc_ttl > 0 && (($t-$key[self::apc_arr_atime]) > $apc_ttl)) apc_fetch($key[self::apc_arr_key]);
249 | }
250 | }
251 | if (!empty($todel))
252 | {
253 | $r = apc_delete($todel);
254 | if (!empty($r)) return $r;
255 | else return true;
256 | }
257 | return true;
258 | }
259 |
260 | protected function del_old_cached()
261 | {
262 | $t = time();
263 | $apc_user_info = apc_cache_info('user', true);
264 | if (!empty($apc_user_info['ttl']))
265 | {
266 | $apc_ttl = $apc_user_info['ttl']/2;
267 | $check_period = $apc_ttl;
268 | }
269 | if (empty($check_period) || $check_period > 1800) $check_period = 1800;
270 |
271 | $ittl = new \APCIterator('user', '/^'.preg_quote($this->defragmentation_prefix).'$/', APC_ITER_ATIME, 1);
272 | $cttl = $ittl->current();
273 | $previous_cleaning = $cttl[self::apc_arr_atime];
274 | if (empty($previous_cleaning) || ($t-$previous_cleaning) > $check_period)
275 | {
276 | apc_store($this->defragmentation_prefix, $t, $check_period);
277 | $this->del_old();
278 | }
279 | return true;
280 | }
281 |
282 | /**
283 | * Delete keys by tags
284 | *
285 | * @param array|string $tags - tag or array of tags
286 | * @return boolean
287 | */
288 | public function del_by_tags($tags)
289 | {
290 | if (!is_array($tags)) $tags = array($tags);
291 |
292 | $todel = array();
293 | $l = strlen($this->tags_prefix);
294 | $i = new \APCIterator('user', '/^'.preg_quote($this->tags_prefix).'/', APC_ITER_KEY+APC_ITER_VALUE);
295 | foreach ($i as $key_tags)
296 | {
297 | if (is_array($key_tags[self::apc_arr_value]))
298 | {
299 | $intersect = array_intersect($tags, $key_tags[self::apc_arr_value]);
300 | if (!empty($intersect)) $todel[] = substr($key_tags[self::apc_arr_key], $l);
301 | }
302 | }
303 |
304 | if (!empty($todel)) return $this->del($todel);
305 | return true;
306 | }
307 |
308 | /**
309 | * Select from storage via callback function
310 | *
311 | * @param callback $fx ($value_array,$key)
312 | * @param bool $get_array
313 | * @return mixed
314 | */
315 | public function select_fx($fx, $get_array = false)
316 | {
317 | $arr = array();
318 | $l = strlen($this->prefix);
319 | $i = new \APCIterator('user', '/^'.preg_quote($this->prefix).'/', APC_ITER_KEY+APC_ITER_VALUE);
320 | foreach ($i as $item)
321 | {
322 | if (!is_array($item[self::apc_arr_value])) continue;
323 | $s = $item[self::apc_arr_value];
324 | $index = substr($item[self::apc_arr_key], $l);
325 |
326 | if ($fx($s, $index)===true)
327 | {
328 | if (!$get_array) return $s;
329 | else $arr[$index] = $s;
330 | }
331 | }
332 | if (!$get_array || empty($arr)) return false;
333 | else return $arr;
334 | }
335 |
336 | /**
337 | * Increment value of the key
338 | * @param string $key
339 | * @param mixed $by_value
340 | * if stored value is an array:
341 | * if $by_value is a value in array, new element will be pushed to the end of array,
342 | * if $by_value is a key=>value array, new key=>value pair will be added (or updated)
343 | * @param int $limit_keys_count - maximum count of elements (used only if stored value is array)
344 | * @param int $ttl - set time to live for key
345 | * @return int|string|array new value of key
346 | */
347 | public function increment($key, $by_value = 1, $limit_keys_count = 0, $ttl = 259200)
348 | {
349 | if (empty($key))
350 | {
351 | $this->ReportError('empty keys are not allowed', __LINE__);
352 | return false;
353 | }
354 |
355 | if (!$this->acquire_key($key, $auto_unlocker)) return false;
356 |
357 | $value = apc_fetch($this->prefix.$key, $success);
358 | if (!$success)
359 | {
360 | if ($this->save($key, $by_value, $ttl)) return $by_value;
361 | else return false;
362 | }
363 | if (is_array($value))
364 | {
365 | $value = $this->incrementArray($limit_keys_count, $value, $by_value);
366 | }
367 | elseif (is_numeric($value) && is_numeric($by_value))
368 | {
369 | $value = $value+$by_value;
370 | }
371 | else
372 | {
373 | $value .= $by_value;
374 | }
375 | if ($this->save($key, $value, $ttl)) return $value;
376 | else return false;
377 | }
378 |
379 | /**
380 | * Get exclusive mutex for key. Key will be still accessible to read and write, but
381 | * another process can exclude dog-pile effect, if before updating the key he will try to get this mutex.
382 | * Example:
383 | * Process 1 reads key simultaneously with Process 2.
384 | * Value of this key are too old, so Process 1 going to refresh it. Simultaneously with Process 2.
385 | * But both of them trying to lock_key, and Process 1 only will refresh value of key (taking it from database, e.g.),
386 | * and Process 2 can decide, what he want to do - use old value and not spent time to database, or something else.
387 | * @param mixed $key
388 | * @param mixed $auto_unlocker_variable - pass empty, just declared variable
389 | * @return bool
390 | */
391 | public function lock_key($key, &$auto_unlocker_variable)
392 | {
393 | $r = apc_add($this->lock_key_prefix.$key, 1, $this->key_lock_time);
394 | if (!$r) return false;
395 | $auto_unlocker_variable = new KeyAutoUnlocker(array($this, 'unlock_key'));
396 | $auto_unlocker_variable->setKey($key);
397 | return true;
398 | }
399 |
400 | /**
401 | * Unlock key, locked by method 'lock_key'
402 | * @param KeyAutoUnlocker $auto_unlocker
403 | * @return bool
404 | */
405 | public function unlock_key(KeyAutoUnlocker $auto_unlocker)
406 | {
407 | $key = $auto_unlocker->getKey();
408 | if (empty($key))
409 | {
410 | $this->ReportError('Empty name of key in the AutoUnlocker', __LINE__);
411 | return false;
412 | }
413 | $auto_unlocker->revoke();
414 | return apc_delete($this->lock_key_prefix.$key);
415 | }
416 |
417 | /**
418 | * @return array
419 | */
420 | public function get_stat()
421 | {
422 | return array(
423 | 'system' => apc_cache_info('', true),
424 | 'user' => apc_cache_info('user', true)
425 | );
426 | }
427 |
428 | public function set_ID($ID)
429 | {
430 | if (!empty($ID))
431 | {
432 | $this->prefix = str_replace('.', '_', $ID).'.';
433 | }
434 | $this->lock_key_prefix = self::lock_key_prefix.$this->prefix;
435 | $this->defragmentation_prefix = self::defragmentation_prefix;
436 | $this->tags_prefix = self::tags_prefix.$this->prefix;
437 | }
438 |
439 | public function get_ID()
440 | {
441 | return str_replace('_', '.', trim($this->prefix, '.'));
442 | }
443 | }
444 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/IKeyLocker.php:
--------------------------------------------------------------------------------
1 | value array, new key=>value pair will be added (or updated)
74 | * @param int $limit_keys_count - maximum count of elements (used only if stored value is array)
75 | * @param int $ttl - set time to live for key
76 | * @return int|string|array new value of key
77 | */
78 | public function increment($key, $by_value = 1, $limit_keys_count = 0, $ttl = 259200);
79 |
80 | /**
81 | * Get exclusive mutex for key. Key will be still accessible to read and write, but
82 | * another process can exclude dog-pile effect, if before updating the key he will try to get this mutex.
83 | * @param mixed $key
84 | * @param mixed $auto_unlocker_variable - pass empty, just declared variable
85 | */
86 | public function lock_key($key, &$auto_unlocker_variable);
87 |
88 | /**
89 | * Try to lock key, and if key is already locked - wait, until key will be unlocked.
90 | * Time of waiting is defined in max_wait_unlock constant of MemoryObject class.
91 | * @param string $key
92 | * @param $auto_unlocker
93 | * @return boolean
94 | */
95 | public function acquire_key($key, &$auto_unlocker);
96 |
97 | /**
98 | * Unlock key, locked by method 'lock_key'
99 | * @param KeyAutoUnlocker $auto_unlocker
100 | * @return bool
101 | */
102 | public function unlock_key(KeyAutoUnlocker $auto_unlocker);
103 |
104 | /**
105 | * @return array of all stored keys
106 | */
107 | public function get_keys();
108 |
109 | /**
110 | * @return string
111 | */
112 | public function getLastErr();
113 |
114 | /**
115 | * @return array
116 | */
117 | public function get_stat();
118 |
119 | public function getErrLog();
120 |
121 | public function set_errors_triggering($errors_triggering = true);
122 |
123 | public function set_ID($ID);
124 |
125 | public function get_ID();
126 | }
127 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/IRedisServer.php:
--------------------------------------------------------------------------------
1 | value)
266 | */
267 | public function hMSet($key, $fields);
268 |
269 | /**
270 | * Set the string value of a hash field
271 | * @param string $key hash
272 | * @param string $field
273 | * @param string $value
274 | * @return int
275 | */
276 | public function hSet($key, $field, $value);
277 |
278 | /**
279 | * Set the value of a hash field, only if the field does not exist
280 | * @param string $key
281 | * @param string $field
282 | * @param string $value
283 | * @return int
284 | */
285 | public function hSetNX($key, $field, $value);
286 |
287 | /**
288 | * Get all the values in a hash
289 | * @param string $key
290 | * @return array
291 | */
292 | public function hVals($key);
293 |
294 | /**
295 | * Increment the integer value of a key by one
296 | * @param string $key
297 | * @return int
298 | */
299 | public function Incr($key);
300 |
301 | /**
302 | * Returns the element at index $index in the list stored at $key.
303 | * The index is zero-based, so 0 means the first element, 1 the second element and so on.
304 | * Negative indices can be used to designate elements starting at the tail of the list.
305 | * Here, -1 means the last element, -2 means the penultimate and so forth.
306 | * When the value at key is not a list, an error is returned.
307 | * @param string $key
308 | * @param int $index
309 | * @return string|boolean
310 | */
311 | public function LIndex($key, $index);
312 |
313 | /**
314 | * Insert an element before or after another element in a list
315 | * @param string $key
316 | * @param bool $after
317 | * @param string $pivot
318 | * @param string $value
319 | * @return int
320 | */
321 | public function LInsert($key, $after = true, $pivot, $value);
322 |
323 | /**
324 | * Get the length of a list
325 | * @param string $key
326 | * @return int
327 | */
328 | public function LLen($key);
329 |
330 | /**
331 | * Remove and get the first element in a list
332 | * @param string $key
333 | * @return string|boolean
334 | */
335 | public function LPop($key);
336 |
337 | /**
338 | * Inserts value at the head of the list stored at key.
339 | * If key does not exist, it is created as empty list before performing the push operation.
340 | * When key holds a value that is not a list, an error is returned.
341 | * @param string $key
342 | * @param string|array $value
343 | * @usage
344 | * LPush(key, value)
345 | * LPush(key, value1, value2)
346 | * LPush(key, array(value1, value2))
347 | * @return int
348 | */
349 | public function LPush($key, $value);
350 |
351 | /**
352 | * Inserts value at the head of the list stored at key, only if key already exists and holds a list.
353 | * In contrary to LPush, no operation will be performed when key does not yet exist.
354 | * @param string $key
355 | * @param string $value
356 | * @return int
357 | */
358 | public function LPushX($key, $value);
359 |
360 | /**
361 | * Returns the specified elements of the list stored at key.
362 | * The offsets $start and $stop are zero-based indexes, with 0 being the first element of the list (the head of the list),
363 | * 1 being the next element and so on.
364 | * These offsets can also be negative numbers indicating offsets starting at the end of the list.
365 | * For example, -1 is the last element of the list, -2 the penultimate, and so on.
366 | * @param string $key
367 | * @param int $start
368 | * @param int $stop
369 | * @return array
370 | */
371 | public function LRange($key, $start, $stop);
372 |
373 | /**
374 | * Removes the first count occurrences of elements equal to value from the list stored at key.
375 | * The count argument influences the operation in the following ways:
376 | * count > 0: Remove elements equal to value moving from head to tail.
377 | * count < 0: Remove elements equal to value moving from tail to head.
378 | * count = 0: Remove all elements equal to value.
379 | * For example, LREM list -2 "hello" will remove the last two occurrences of "hello" in the list stored at list.
380 | * @param string $key
381 | * @param int $count
382 | * @param string $value
383 | * @return int
384 | */
385 | public function LRem($key, $count, $value);
386 |
387 | /**
388 | * Sets the list element at index to value.
389 | * For more information on the index argument, see LINDEX.
390 | * An error is returned for out of range indexes.
391 | * @param $key
392 | * @param $index
393 | * @param $value
394 | * @return boolean
395 | */
396 | public function LSet($key, $index, $value);
397 |
398 | /**
399 | * Trim a list to the specified range
400 | * @link http://redis.io/commands/ltrim
401 | * @param string $key
402 | * @param int $start
403 | * @param int $stop
404 | * @return boolean
405 | */
406 | public function LTrim($key, $start, $stop);
407 |
408 | /**
409 | * Returns the values of all specified keys.
410 | * For every key that does not hold a string value or does not exist, the special value nil is returned.
411 | * Parameters: $key, [key ...]
412 | * or: array($key1, $key2...)
413 | * @param string $key
414 | * @return array
415 | */
416 | public function MGet($key);
417 |
418 | /**
419 | * Move key from the currently selected database (see SELECT) to the specified destination database.
420 | * When key already exists in the destination database, or it does not exist in the source database, it does nothing.
421 | * It is possible to use MOVE as a locking primitive because of this.
422 | * @param string $key
423 | * @param int $db
424 | * @return int
425 | */
426 | public function Move($key, $db);
427 |
428 | /**
429 | * Set multiple keys to multiple values
430 | * @param array $keys (key => value)
431 | * @return string
432 | */
433 | public function MSet(array $keys);
434 |
435 | /**
436 | * Set multiple keys to multiple values, only if none of the keys exist
437 | * @param array $keys (key => value)
438 | * Returns:
439 | * 1 if the all the keys were set.
440 | * 0 if no key was set (at least one key already existed).
441 | * @return int
442 | */
443 | public function MSetNX(array $keys);
444 |
445 | /**
446 | * Remove the expiration from a key
447 | * @param string $key
448 | * @return int
449 | */
450 | public function Persist($key);
451 |
452 | /**
453 | * Subscribes the client to the given patterns.
454 | * @param string $pattern
455 | */
456 | public function PSubscribe($pattern);
457 |
458 | /**
459 | * Post a message to a channel
460 | * Returns the number of clients that received the message.
461 | * @param string $channel
462 | * @param string $message
463 | * @return int
464 | */
465 | public function Publish($channel, $message);
466 |
467 | /**
468 | * Stop listening for messages posted to channels matching the given patterns
469 | * @param array|string|null $patterns
470 | * @return int
471 | */
472 | public function PUnsubscribe($patterns = null);
473 |
474 | /** Close the connection */
475 | public function Quit();
476 |
477 | /**
478 | * Renames key to newkey.
479 | * It returns an error when the source and destination names are the same, or when key does not exist.
480 | * If newkey already exists it is overwritten.
481 | * @param string $key
482 | * @param string $newkey
483 | * @return boolean
484 | */
485 | public function Rename($key, $newkey);
486 |
487 | /**
488 | * Rename a key, only if the new key does not exist
489 | * @param string $key
490 | * @param string $newkey
491 | * @return int
492 | */
493 | public function RenameNX($key, $newkey);
494 |
495 | /**
496 | * Removes and returns the last element of the list stored at key.
497 | * @param string $key
498 | * @return string|boolean
499 | */
500 | public function RPop($key);
501 |
502 | /**
503 | * Atomically returns and removes the last element (tail) of the list stored at source,
504 | * and pushes the element at the first element (head) of the list stored at destination.
505 | * If source does not exist, the value nil is returned and no operation is performed.
506 | * @param string $source
507 | * @param string $destination
508 | * @return string
509 | */
510 | public function RPopLPush($source, $destination);
511 |
512 | /**
513 | * Inserts value at the tail of the list stored at key.
514 | * If key does not exist, it is created as empty list before performing the push operation.
515 | * When key holds a value that is not a list, an error is returned.
516 | * Parameters: key value [value ...]
517 | * or: key, array(value,value,...)
518 | * @param string $key
519 | * @param string|array $value
520 | * @return int|boolean
521 | */
522 | public function RPush($key, $value);
523 |
524 | /**
525 | * Append a value to a list, only if the list exists
526 | * @param string $key
527 | * @param string $value
528 | * @return int
529 | */
530 | public function RPushX($key, $value);
531 |
532 | /**
533 | * Get the number of members in a set
534 | * @param string $key
535 | * @return int
536 | */
537 | public function sCard($key);
538 |
539 | /**
540 | * Returns the members of the set resulting from the difference between the first set and all the successive sets.
541 | * For example:
542 | * key1 = {a,b,c,d}
543 | * key2 = {c}
544 | * key3 = {a,c,e}
545 | * SDIFF key1 key2 key3 = {b,d}
546 | * Keys that do not exist are considered to be empty sets.
547 | *
548 | * Parameters: key1, key2, key3...
549 | * @param string|array $key
550 | * @return array
551 | */
552 | public function sDiff($key);
553 |
554 | /**
555 | * This command is equal to SDIFF, but instead of returning the resulting set, it is stored in destination.
556 | * If destination already exists, it is overwritten.
557 | * Returns the number of elements in the resulting set.
558 | * Parameters: destination, key [key, ...]
559 | * or: destination, array(key,key, ...)
560 | * @param string $destination
561 | * @param string|array $key
562 | * @return int
563 | */
564 | public function sDiffStore($destination, $key);
565 |
566 | /**
567 | * Select the DB with having the specified zero-based numeric index. New connections always use DB 0.
568 | * @param int $index
569 | */
570 | public function Select($index);
571 |
572 | /**
573 | * Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type.
574 | * @param string $key
575 | * @param string $value
576 | * @return string
577 | */
578 | public function Set($key, $value);
579 |
580 | /**
581 | * Sets or clears the bit at offset in the string value stored at key
582 | * @link http://redis.io/commands/setbit
583 | * @param string $key
584 | * @param int $offset
585 | * @param int $value
586 | * Returns the original bit value stored at offset.
587 | * @return int
588 | */
589 | public function SetBit($key, $offset, $value);
590 |
591 | /**
592 | * Set the value of a key, only if the key does not exist
593 | * @param string $key
594 | * @param string $value
595 | * @return void
596 | */
597 | public function SetNX($key, $value);
598 |
599 | /**
600 | * Set the value and expiration of a key
601 | * @param string $key
602 | * @param int $seconds
603 | * @param string $value
604 | * @return boolean
605 | */
606 | public function SetEX($key, $seconds, $value);
607 |
608 | /**
609 | * Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value.
610 | * If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit.
611 | * Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough
612 | * to be able to set value at offset.
613 | *
614 | * Thanks to SETRANGE and the analogous GETRANGE commands, you can use Redis strings as a linear array with O(1) random access.
615 | * This is a very fast and efficient storage in many real world use cases.
616 | * @link http://redis.io/commands/setrange
617 | * @param string $key
618 | * @param int $offset
619 | * @param string $value
620 | * Returns the length of the string after it was modified by the command.
621 | * @return int
622 | */
623 | public function SetRange($key, $offset, $value);
624 |
625 | /**
626 | * Returns the members of the set resulting from the intersection of all the given sets.
627 | * For example:
628 | * key1 = {a,b,c,d}
629 | * key2 = {c}
630 | * key3 = {a,c,e}
631 | * SINTER key1 key2 key3 = {c}
632 | * Parameters: key [key ...]
633 | * or: array(key, key, ...)
634 | * @param string|array $key
635 | * @return array
636 | */
637 | public function sInter($key);
638 |
639 | /**
640 | * Intersect multiple sets and store the resulting set in a key
641 | * This command is equal to SINTER, but instead of returning the resulting set, it is stored in destination.
642 | * If destination already exists, it is overwritten.
643 | * Parameters: $destination,$key [key ...]
644 | * or: $destination, array($key, $key...)
645 | * @param string $destination
646 | * @param string|array $key
647 | * Returns the number of elements in the resulting set.
648 | * @return int
649 | */
650 | public function sInterStore($destination, $key);
651 |
652 | /**
653 | * Make the server a slave of another instance, or promote it as master
654 | * @link http://redis.io/commands/slaveof
655 | * @param string $host
656 | * @param int $port
657 | * @return string
658 | */
659 | public function SlaveOf($host, $port);
660 |
661 | /**
662 | * Move member from the set at source to the set at destination.
663 | * This operation is atomic.
664 | * In every given moment the element will appear to be a member of source or destination for other clients.
665 | * If the source set does not exist or does not contain the specified element, no operation is performed and 0 is returned.
666 | * Otherwise, the element is removed from the source set and added to the destination set.
667 | * When the specified element already exists in the destination set, it is only removed from the source set.
668 | * @param string $source
669 | * @param string $destination
670 | * @param string $member
671 | * @return int
672 | */
673 | public function sMove($source, $destination, $member);
674 |
675 | /**
676 | * Sort the elements in a list, set or sorted set
677 | * @link http://redis.io/commands/sort
678 | * @param string $key
679 | * @param string $sort_rule [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]
680 | * Returns list of sorted elements.
681 | * @return array
682 | */
683 | public function Sort($key, $sort_rule);
684 |
685 | /**
686 | * Get the length of the value stored in a key
687 | * @param string $key
688 | * @return int
689 | */
690 | public function StrLen($key);
691 |
692 | /** Subscribes the client to the specified channels.
693 | * Once the client enters the subscribed state it is not supposed to issue any other commands,
694 | * except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE and PUNSUBSCRIBE commands.
695 | * @param $channel
696 | * Parameters: $channel [channel ...]
697 | */
698 | public function Subscribe($channel);
699 |
700 | /**
701 | * Returns the members of the set resulting from the union of all the given sets.
702 | * For example:
703 | * key1 = {a,b,c,d}
704 | * key2 = {c}
705 | * key3 = {a,c,e}
706 | * SUNION key1 key2 key3 = {a,b,c,d,e}
707 | * Parameters: key [key...]
708 | * @param string|array $key
709 | * @return array
710 | */
711 | public function sUnion($key);
712 |
713 | /**
714 | * Add multiple sets and store the resulting set in a key
715 | * Parameters: $destination, $key [key ...]
716 | * @param $destination
717 | * @param string|array $key
718 | * Returns the number of elements in the resulting set.
719 | * @return int
720 | */
721 | public function sUnionStore($destination, $key);
722 |
723 | /**
724 | * Set a key's time to live in seconds
725 | * @param string $key
726 | * @param int $seconds
727 | * @return boolean
728 | */
729 | public function Expire($key, $seconds);
730 |
731 | /**
732 | * Get the time to live for a key
733 | * @param string $key
734 | * @return int
735 | */
736 | public function TTL($key);
737 |
738 | /**
739 | * Returns the string representation of the type of the value stored at key.
740 | * The different types that can be returned are: string, list, set, zset and hash.
741 | * @param string $key
742 | * Returns type of key, or 'none' when key does not exist.
743 | * @return string
744 | */
745 | public function Type($key);
746 |
747 | /**
748 | * Unsubscribes the client from the given channels, or from all of them if none is given.
749 | * Parameters: [channel [channel ...]]
750 | * @param string $channel
751 | */
752 | public function Unsubscribe($channel = '');
753 |
754 | /** Forget about all watched keys */
755 | public function Unwatch();
756 |
757 | /**
758 | * Add a member to a sorted set, or update its score if it already exists
759 | * @param string $key
760 | * @param int|array $score
761 | * @param string $member
762 | * Can be used as:
763 | * zadd(key, score, member)
764 | * zadd(key, score1, member1, score2, member2)
765 | * zadd(key, array(score1 => member1, score2 => member2))
766 | * @return int
767 | */
768 | public function zAdd($key, $score, $member = NULL);
769 |
770 | /**
771 | * Get the number of members in a sorted set
772 | * @param string $key
773 | * @return int
774 | */
775 | public function zCard($key);
776 |
777 | /**
778 | * Returns the number of elements in the sorted set at key with a score between min and max.
779 | * The min and max arguments have the same semantic as described for ZRANGEBYSCORE.
780 | * @param string $key
781 | * @param string|int $min
782 | * @param string|int $max
783 | * @return int
784 | */
785 | public function zCount($key, $min, $max);
786 |
787 | /**
788 | * Increment the score of a member in a sorted set
789 | * @param string $key
790 | * @param number $increment
791 | * @param string $member
792 | * @return number
793 | */
794 | public function zIncrBy($key, $increment, $member);
795 |
796 | /**
797 | * Intersect multiple sorted sets and store the resulting sorted set in a new key
798 | * @link http://redis.io/commands/zinterstore
799 | * @param string $destination
800 | * @param array $keys
801 | * @param array|null $weights
802 | * @param string|null $aggregate see Aggregate* constants
803 | * Returns the number of elements in the resulting sorted set at destination.
804 | * @return int
805 | */
806 | public function zInterStore($destination, array $keys, array $weights = null, $aggregate = null);
807 |
808 | /**
809 | * @abstract
810 | * @param string $key
811 | * @param int $start
812 | * @param int $stop
813 | * @param bool $withscores
814 | * @return array
815 | */
816 | public function zRange($key, $start, $stop, $withscores = false);
817 |
818 | /**
819 | * Return a range of members in a sorted set, by score
820 | * @link http://redis.io/commands/zrangebyscore
821 | * @param string $key
822 | * @param string|number $min
823 | * @param string|number $max
824 | * @param bool $withscores
825 | * @param array|null $limit
826 | * @return array
827 | */
828 | public function zRangeByScore($key, $min, $max, $withscores = false, array $limit = null);
829 |
830 | /**
831 | * Returns the rank of member in the sorted set stored at key, with the scores ordered from low to high.
832 | * The rank (or index) is 0-based, which means that the member with the lowest score has rank 0.
833 | * Use ZREVRANK to get the rank of an element with the scores ordered from high to low.
834 | * @param string $key
835 | * @param string $member
836 | * @return int|boolean
837 | */
838 | public function zRank($key, $member);
839 |
840 | /**
841 | * Remove a member from a sorted set
842 | * @param string $key
843 | * @param string|array $member
844 | * @usage
845 | * zrem(key, member)
846 | * zrem(key, member1, member2)
847 | * zrem(key, array(member1, member2))
848 | * @return int
849 | */
850 | public function zRem($key, $member);
851 |
852 | /**
853 | * Removes all elements in the sorted set stored at key with rank between start and stop.
854 | * Both start and stop are 0-based indexes with 0 being the element with the lowest score.
855 | * These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score.
856 | * For example: -1 is the element with the highest score, -2 the element with the second highest score and so forth.
857 | * @param string $key
858 | * @param int $start
859 | * @param int $stop
860 | * Returns the number of elements removed.
861 | * @return int
862 | */
863 | public function zRemRangeByRank($key, $start, $stop);
864 |
865 | /**
866 | * Remove all members in a sorted set within the given scores
867 | * @param string $key
868 | * @param string|number $min
869 | * @param string|number $max
870 | * @return int
871 | */
872 | public function zRemRangeByScore($key, $min, $max);
873 |
874 | /**
875 | * Returns the specified range of elements in the sorted set stored at key.
876 | * The elements are considered to be ordered from the highest to the lowest score.
877 | * Descending lexicographical order is used for elements with equal score.
878 | * @param string $key
879 | * @param int $start
880 | * @param int $stop
881 | * @param bool $withscores
882 | * @return array
883 | */
884 | public function zRevRange($key, $start, $stop, $withscores = false);
885 |
886 | /**
887 | * Returns all the elements in the sorted set at key with a score between max and min
888 | * (including elements with score equal to max or min).
889 | * In contrary to the default ordering of sorted sets, for this command
890 | * the elements are considered to be ordered from high to low scores.
891 | * The elements having the same score are returned in reverse lexicographical order.
892 | * @param string $key
893 | * @param number $max
894 | * @param number $min
895 | * @param bool $withscores
896 | * @param array|null $limit (offset, count)
897 | * @return array
898 | */
899 | public function zRevRangeByScore($key, $max, $min, $withscores = false, array $limit = null);
900 |
901 | /**
902 | * Returns the rank of member in the sorted set stored at key, with the scores ordered from high to low.
903 | * The rank (or index) is 0-based, which means that the member with the highest score has rank 0.
904 | * Use ZRANK to get the rank of an element with the scores ordered from low to high.
905 | * @param string $key
906 | * @param string $member
907 | * @return int|boolean
908 | */
909 | public function zRevRank($key, $member);
910 |
911 | /**
912 | * Get the score associated with the given member in a sorted set
913 | * @param string $key
914 | * @param string $member
915 | * @return string
916 | */
917 | public function zScore($key, $member);
918 |
919 | /**
920 | * Add multiple sorted sets and store the resulting sorted set in a new key
921 | * @link http://redis.io/commands/zunionstore
922 | * @param string $destination
923 | * @param array $keys
924 | * @param array|null $weights
925 | * @param string|null $aggregate see Aggregate* constants
926 | * @return int
927 | */
928 | public function zUnionStore($destination, array $keys, array $weights = null, $aggregate = null);
929 |
930 | /**
931 | * Increment the integer value of a key by the given number
932 | * @param string $key
933 | * @param int $increment
934 | * @return int
935 | */
936 | public function IncrBy($key, $increment);
937 |
938 | /**
939 | * Returns all keys matching pattern.
940 | * @param string $pattern
941 | * Supported glob-style patterns:
942 | * h?llo matches hello, hallo and hxllo
943 | * h*llo matches hllo and heeeello
944 | * h[ae]llo matches hello and hallo, but not hillo
945 | * Use \ to escape special characters if you want to match them verbatim.
946 | * @return array
947 | */
948 | public function Keys($pattern);
949 |
950 | /** Mark the start of a transaction block */
951 | public function Multi();
952 |
953 | /**
954 | * Marks the given keys to be watched for conditional execution of a transaction
955 | * each argument is a key:
956 | * watch('key1', 'key2', 'key3', ...)
957 | */
958 | public function Watch();
959 |
960 | /**
961 | * Executes all previously queued commands in a transaction and restores the connection state to normal.
962 | * When using WATCH, EXEC will execute commands only if the watched keys were not modified, allowing for a check-and-set mechanism.
963 | */
964 | public function Exec();
965 |
966 | /**
967 | * Flushes all previously queued commands in a transaction and restores the connection state to normal.
968 | * If WATCH was used, DISCARD unwatches all keys.
969 | */
970 | public function Discard();
971 |
972 | /** Add a member to a set
973 | * @param string $set
974 | * @param string|array $value or multiple arguments
975 | * @return boolean
976 | */
977 | public function sAdd($set, $value);
978 |
979 | /**
980 | * Returns if value is a member of the set.
981 | * @param string $set
982 | * @param string $value
983 | * @return boolean
984 | */
985 | public function sIsMember($set, $value);
986 |
987 | /**
988 | * Returns all the members of the set.
989 | * @param string $set
990 | * @return array
991 | */
992 | public function sMembers($set);
993 |
994 | /**
995 | * Remove member from the set. If 'value' is not a member of this set, no operation is performed.
996 | * An error is returned when the value stored at key is not a set.
997 | * @param string $set
998 | * @param string $value
999 | * @return boolean
1000 | */
1001 | public function sRem($set, $value);
1002 |
1003 | /** Get information and statistics about the server */
1004 | public function Info();
1005 | }
1006 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/KeyAutoUnlocker.php:
--------------------------------------------------------------------------------
1 | Unlock = $Unlock;
16 | }
17 |
18 | public function revoke()
19 | {
20 | $this->Unlock = NULL;
21 | }
22 |
23 | public function __destruct()
24 | {
25 | if (isset($this->Unlock)) call_user_func($this->Unlock, $this);
26 | }
27 |
28 | public function getKey()
29 | {
30 | return $this->key;
31 | }
32 |
33 | public function setKey($key)
34 | {
35 | $this->key = $key;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/MemcacheObject.php:
--------------------------------------------------------------------------------
1 | ttl) */
10 | protected $ttl_table_name;
11 | /** @var string $tags_table_name array (key=>array(tags)) */
12 | protected $tags_table_name;
13 | /** @var IMemcacheDecorator */
14 | protected $memcache;
15 |
16 | const lock_key_prefix = '.lock_key.';
17 | const ttl_table_prefix = '.ttl.';
18 | const tags_table_prefix = '.tags.';
19 |
20 | /**
21 | * @param string $ID Symbol "." will be replaced to "_"
22 | * @param string $host
23 | * @param int $port
24 | */
25 | public function __construct($ID = '', $host = 'localhost', $port = 11211)
26 | {
27 | $this->setMemcacheObject($host, $port);
28 | $this->set_ID($ID);
29 | }
30 |
31 | protected function setMemcacheObject($host = 'localhost', $port = 11211)
32 | {
33 | $this->memcache = new \Memcache();
34 | if (!$this->memcache->connect($host, $port))
35 | {
36 | $this->ReportError('memcache connection error', __LINE__);
37 | }
38 | }
39 |
40 | /**
41 | * Add value to memory storage, only if this key does not exists (or false will be returned).
42 | *
43 | * @param string $k
44 | * @param mixed $v
45 | * @param int $ttl
46 | * @param array|string $tags
47 | * @return boolean
48 | */
49 | public function add($k, $v, $ttl = 259200, $tags = NULL)
50 | {
51 | if (empty($k))
52 | {
53 | $this->ReportError('empty keys are not allowed', __LINE__);
54 | return false;
55 | }
56 | $k = (string)$k;
57 |
58 | $ttl = $this->ttl_to_expiration($ttl);
59 |
60 | if (!$this->memcache->add($this->prefix.$k, $v, null, $ttl))
61 | {
62 | $reason = 'key already exists';
63 | if (strlen($this->prefix.$k) > 250) $reason = 'key length should be <250';
64 | elseif (strlen(serialize($v)) > 1048576) $reason = 'size of value should be <1Mb';
65 | $this->ReportError('can not add key: '.$reason, __LINE__);
66 | return false;
67 | }
68 |
69 | $ttl_table = $this->read_TTL_table();
70 | $ttl_table[$k] = $ttl;
71 | $this->save_TTL_table($ttl_table);
72 |
73 | if (!empty($tags)) $this->set_tags($k, $tags);
74 |
75 | return true;
76 | }
77 |
78 | protected function ttl_to_expiration($ttl)
79 | {
80 | $ttl = intval($ttl);
81 | if ($ttl <= 0 || $ttl > self::max_ttl) $ttl = self::max_ttl;
82 | return $ttl+time();
83 | }
84 |
85 | protected function &read_TTL_table()
86 | {
87 | $ttl_table = $this->memcache->get($this->ttl_table_name);
88 | if (empty($ttl_table)) $ttl_table = array();
89 | return $ttl_table;
90 | }
91 |
92 | /**
93 | * Delete key or array of keys from storage
94 | * @param string|array $key - keys
95 | * @return boolean|array - if array of keys was passed, on error will be returned array of not deleted keys, or 'true' on success.
96 | */
97 | public function del($key)
98 | {
99 | if (empty($key))
100 | {
101 | $this->ReportError('empty keys are not allowed', __LINE__);
102 | return false;
103 | }
104 |
105 | $tags_table = $this->memcache->get($this->tags_table_name);
106 | if (empty($tags_table)) $tags_table = array();
107 | $tags_table_changed = false;
108 |
109 | $ttl_table = $this->read_TTL_table();
110 | $ttl_table_changed = false;
111 |
112 | $r = true;
113 |
114 | if (is_array($key))
115 | {
116 | foreach ($key as $k)
117 | {
118 | $k = (string)$k;
119 | $this->memcache->delete($this->prefix.$k);
120 | $this->memcache->delete($this->lock_key_prefix.$k);
121 | if ($this->delTagsOfKey($k, $tags_table)) $tags_table_changed = true;
122 | if ($this->delTTLOfKey($k, $ttl_table)) $ttl_table_changed = true;
123 | }
124 | }
125 | else
126 | {
127 | $r = $this->memcache->delete($this->prefix.$key);
128 | if ($this->delTagsOfKey($key, $tags_table)) $tags_table_changed = true;
129 | if ($this->delTTLOfKey($key, $ttl_table)) $ttl_table_changed = true;
130 | $this->memcache->delete($this->lock_key_prefix.$key);
131 | }
132 |
133 | if ($tags_table_changed) $this->memcache->set($this->tags_table_name, $tags_table, null, time()+self::max_ttl);
134 | if ($ttl_table_changed) $this->save_TTL_table($ttl_table);
135 | return $r;
136 | }
137 |
138 | protected function delTTLOfKey($key, &$ttl_table)
139 | {
140 | if (array_key_exists($key, $ttl_table))
141 | {
142 | unset($ttl_table[$key]);
143 | return true;
144 | }
145 | return false;
146 | }
147 |
148 | protected function delTagsOfKey($key, &$tags_table)
149 | {
150 | if (array_key_exists($key, $tags_table))
151 | {
152 | unset($tags_table[$key]);
153 | return true;
154 | }
155 | return false;
156 | }
157 |
158 | /**
159 | * Delete keys by tags
160 | *
161 | * @param array|string $tags - tag or array of tags
162 | * @return boolean
163 | */
164 | public function del_by_tags($tags)
165 | {
166 | if (!is_array($tags)) $tags = array($tags);
167 |
168 | $tags_table = $this->memcache->get($this->tags_table_name);
169 | if (empty($tags_table)) return false;
170 |
171 | $tags_table_changed = false;
172 | foreach ($tags_table as $key => $key_tags)
173 | {
174 | $intersect = array_intersect($tags, $key_tags);
175 | if (!empty($intersect))
176 | {
177 | $this->memcache->delete($this->prefix.$key);
178 | unset($tags_table[$key]);
179 | $tags_table_changed = true;
180 | }
181 | }
182 |
183 | if ($tags_table_changed) $this->memcache->set($this->tags_table_name, $tags_table, null, time()+self::max_ttl);
184 |
185 | return true;
186 | }
187 |
188 | /**
189 | * Delete old (by ttl) variables from storage
190 | * @return boolean
191 | */
192 | public function del_old()
193 | {
194 | $ttl_table = $this->read_TTL_table();
195 | if (empty($ttl_table)) return true;
196 | $t = time();
197 | $changed = false;
198 | foreach ($ttl_table as $key => $ttl)
199 | {
200 | if ($ttl < $t)
201 | {
202 | $this->memcache->delete($this->prefix.$key);
203 | unset($ttl_table[$key]);
204 | $changed = true;
205 | }
206 | }
207 | if ($changed) $this->memcache->set($this->ttl_table_name, $ttl_table, null, time()+self::max_ttl);
208 | return true;
209 | }
210 |
211 | /** Return array of all stored keys */
212 | public function get_keys()
213 | {
214 | return array_keys($this->read_TTL_table());
215 | }
216 |
217 | /**
218 | * Increment value of key
219 | * @param string $key
220 | * @param mixed $by_value
221 | * if stored value is array:
222 | * if $by_value is value in array, new element will be pushed to the end of array,
223 | * if $by_value is key=>value array, key=>value pair will be added (or updated)
224 | * @param int $limit_keys_count - maximum count of elements (used only if stored value is array)
225 | * @param int $ttl
226 | * @return int|string|array new value of key
227 | */
228 | public function increment($key, $by_value = 1, $limit_keys_count = 0, $ttl = 259200)
229 | {
230 | if (empty($key))
231 | {
232 | $this->ReportError('empty keys are not allowed', __LINE__);
233 | return false;
234 | }
235 |
236 | if (!$this->acquire_key($key, $auto_unlocker)) return false;
237 |
238 | $value = $this->memcache->get($this->prefix.$key);
239 | if (empty($value))
240 | {
241 | if ($this->save($key, $by_value, $ttl)) return $by_value;
242 | else return false;
243 | }
244 |
245 | if (is_array($value))
246 | {
247 | $value = $this->incrementArray($limit_keys_count, $value, $by_value);
248 | }
249 | elseif (is_numeric($value) && is_numeric($by_value))
250 | {
251 | if ($by_value > 0) return $this->memcache->increment($this->prefix.$key, $by_value);
252 | else
253 | {
254 | $value += $by_value;
255 | }
256 | }
257 | else
258 | {
259 | $value .= $by_value;
260 | }
261 | if ($this->save($key, $value, $ttl)) return $value;
262 | else return false;
263 | }
264 |
265 | /**
266 | * Get exclusive mutex for key. Key will be still accessible to read and write, but
267 | * another process can exclude dog-pile effect, if before updating the key he will try to get this mutex.
268 | * @param mixed $key
269 | * @param mixed $auto_unlocker_variable - pass empty, just declared variable
270 | */
271 | public function lock_key($key, &$auto_unlocker_variable)
272 | {
273 | $r = $this->memcache->add($this->lock_key_prefix.$key, 1, null, $this->key_lock_time);
274 | if (!$r) return false;
275 | $auto_unlocker_variable = new KeyAutoUnlocker(array($this, 'unlock_key'));
276 | $auto_unlocker_variable->setKey($key);
277 | return true;
278 | }
279 |
280 | /**
281 | * Read data from memory storage
282 | *
283 | * @param string|array $k (string or array of string keys)
284 | * @param mixed $ttl_left = (ttl - time()) of key. Use to exclude dog-pile effect, with lock/unlock_key methods.
285 | * @return mixed
286 | */
287 | public function read($k, &$ttl_left = -1)
288 | {
289 | if (empty($k))
290 | {
291 | $this->ReportError('empty keys are not allowed', __LINE__);
292 | return NULL;
293 | }
294 | if (is_array($k))
295 | {
296 | $data = array();
297 | $return_ttl = ($ttl_left!==-1 ? true : false);
298 | $ttl_left = array();
299 | foreach ($k as $key)
300 | {
301 | $key = (string)$key;
302 | $data[$key] = $this->memcache->get($this->prefix.$key);
303 | if ($data[$key]===false || $data[$key]===null)
304 | {
305 | unset($data[$key]);
306 | continue;
307 | }
308 | if ($return_ttl)
309 | {
310 | $ttl_left[$key] = $this->getKeyTTL($key);
311 | if ($ttl_left <= 0)
312 | {
313 | unset($data[$key]);
314 | $this->del($key);
315 | }
316 | }
317 | }
318 | }
319 | else
320 | {
321 | $data = $this->memcache->get($this->prefix.$k);
322 | if ($data===false || $data===null)
323 | {
324 | if (strlen($this->prefix.$k) > 250) $this->ReportError('Length of key should be <250', __LINE__);
325 | return false;
326 | }
327 | if ($ttl_left!==-1)
328 | {
329 | $ttl_left = $this->getKeyTTL($k);
330 | if ($ttl_left <= 0) //key expired
331 | {
332 | $data = false;
333 | $this->del($k);
334 | }
335 | }
336 | }
337 | return $data;
338 | }
339 |
340 | /**
341 | * Save variable in memory storage
342 | *
343 | * @param string $k - key
344 | * @param mixed $v - value
345 | * @param int $ttl - time to live (store) in seconds
346 | * @param array|string $tags - array of tags for this key
347 | * @return bool
348 | */
349 | public function save($k, $v, $ttl = 259200, $tags = NULL)
350 | {
351 | if (empty($k))
352 | {
353 | $this->ReportError('empty keys are not allowed', __LINE__);
354 | return false;
355 | }
356 |
357 | $k = (string)$k;
358 | $ttl = $this->ttl_to_expiration($ttl);
359 |
360 | if (false===$this->memcache->set($this->prefix.$k, $v, 0, $ttl))
361 | {
362 | $reason = $this->prefix.$k;
363 | if (strlen($this->prefix.$k) > 250) $reason = 'key length should be <250';
364 | elseif (strlen(serialize($v)) > 1048576) $reason = 'size of value should be <1Mb';
365 | $this->ReportError('memcache can not store key: '.$reason, __LINE__);
366 | return false;
367 | }
368 |
369 | $ttl_table = $this->read_TTL_table();
370 | if (!isset($ttl_table[$k]) || $ttl_table[$k]!=$ttl)
371 | {
372 | $ttl_table[$k] = $ttl;
373 | $this->save_TTL_table($ttl_table);
374 | }
375 |
376 | if (!empty($tags)) $this->set_tags($k, $tags);
377 | return true;
378 | }
379 |
380 | /**
381 | * Select from storage via callback function
382 | * Only values of 'array' type will be selected
383 | * @param callback $fx ($value_array,$key)
384 | * @param bool $get_array
385 | * @return mixed
386 | */
387 | public function select_fx($fx, $get_array = false)
388 | {
389 | $arr = array();
390 | $keys = $this->get_keys();
391 | if (empty($keys)) return false;
392 |
393 | foreach ($keys as $index)
394 | {
395 | $s = $this->read($index);
396 | if (!is_array($s)) continue;
397 |
398 | if ($fx($s, $index)===true)
399 | {
400 | if (!$get_array) return $s;
401 | else $arr[$index] = $s;
402 | }
403 | }
404 | if (!$get_array || empty($arr)) return false;
405 | else return $arr;
406 | }
407 |
408 | /**
409 | * Unlock key, locked by method 'lock_key'
410 | * @param KeyAutoUnlocker $auto_unlocker
411 | * @return bool
412 | */
413 | public function unlock_key(KeyAutoUnlocker $auto_unlocker)
414 | {
415 | $key = $auto_unlocker->getKey();
416 | if (empty($key))
417 | {
418 | $this->ReportError('Empty key in the AutoUnlocker', __LINE__);
419 | return false;
420 | }
421 | $auto_unlocker->revoke();
422 | return $this->memcache->delete($this->lock_key_prefix.$key);
423 | }
424 |
425 | /**
426 | * @param string $key
427 | * @param array|string $tags
428 | * @return bool
429 | */
430 | public function set_tags($key, $tags)
431 | {
432 | if (!is_array($tags))
433 | {
434 | if (is_scalar($tags)) $tags = array($tags);
435 | else $tags = array();
436 | }
437 |
438 | if (!empty($tags))
439 | {
440 | $key = (string)$key;
441 | $tags_table = $this->memcache->get($this->tags_table_name);
442 | if (empty($tags_table)) $tags_table = array();
443 | if (!array_key_exists($key, $tags_table) || $tags!=$tags_table[$key])
444 | {
445 | $tags_table[$key] = $tags;
446 | return $this->memcache->set($this->tags_table_name, $tags_table, null, time()+self::max_ttl);
447 | }
448 | else return true;
449 | }
450 | return false;
451 | }
452 |
453 | public function getKeyTTL($key)
454 | {
455 | $ttl_table = $this->read_TTL_table();
456 | $key = (string)$key;
457 | if (!array_key_exists($key, $ttl_table)) return false;
458 | else return ($ttl_table[$key]-time());
459 | }
460 |
461 | /**
462 | * @param array $table
463 | * @return void
464 | */
465 | protected function save_TTL_table($table)
466 | {
467 | if (!empty($table))
468 | {
469 | $t = time();
470 | foreach ($table as $key => $ttl)
471 | {
472 | if ($ttl < $t) unset($table[$key]);
473 | }
474 | }
475 | if (false===$this->memcache->set($this->ttl_table_name, $table, null, time()+self::max_ttl))
476 | {
477 | $this->ReportError('memcache can not save ttl table', __LINE__);
478 | }
479 | }
480 |
481 | /**
482 | * @return array
483 | */
484 | public function get_stat()
485 | {
486 | return $this->memcache->getStats();
487 | }
488 |
489 | public function set_ID($ID)
490 | {
491 | if (!empty($ID))
492 | {
493 | $this->prefix = str_replace('.', '_', $ID).'.';
494 | }
495 | $this->lock_key_prefix = self::lock_key_prefix.$this->prefix;
496 | $this->ttl_table_name = self::ttl_table_prefix.$this->prefix;
497 | $this->tags_table_name = self::tags_table_prefix.$this->prefix;
498 | }
499 |
500 | public function get_ID()
501 | {
502 | return str_replace('_', '.', trim($this->prefix, '.'));
503 | }
504 | }
505 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/MemcachedDecorator.php:
--------------------------------------------------------------------------------
1 | memcached = new \Memcached();
12 | $this->memcached->addServer($host, $port);
13 | $this->memcached->setOption(\Memcached::OPT_COMPRESSION, true);
14 | }
15 |
16 | public function add($key, $var, $flag = null, $ttl = 0)
17 | {
18 | return $this->memcached->add($key, $var, $ttl);
19 | }
20 |
21 | public function delete($key)
22 | {
23 | return $this->memcached->delete($key);
24 | }
25 |
26 | public function get($key)
27 | {
28 | return $this->memcached->get($key);
29 | }
30 |
31 | public function increment($key, $by_value)
32 | {
33 | return $this->memcached->increment($key, $by_value);
34 | }
35 |
36 | public function set($key, $value, $flag = null, $ttl = 0)
37 | {
38 | return $this->memcached->set($key, $value, $ttl);
39 | }
40 |
41 | public function connect($host = 'localhost', $port = 11211)
42 | { }
43 |
44 | public function getStats()
45 | {
46 | return $this->memcached->getStats();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/MemcachedObject.php:
--------------------------------------------------------------------------------
1 | memcache = new MemcachedDecorator($host, $port);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/MemoryObject.php:
--------------------------------------------------------------------------------
1 | last_err;
17 | $this->last_err = '';
18 | return $last_err;
19 | }
20 |
21 | protected function ReportError($msg, $line)
22 | {
23 | $error_message = $line.': '.$msg;
24 | $this->last_err = $error_message;
25 | $this->err_log[] = $error_message;
26 | if (count($this->err_log) > 100)
27 | {
28 | array_shift($this->err_log);
29 | }
30 | if ($this->errors_triggering) trigger_error($error_message, E_USER_WARNING);
31 | return false;
32 | }
33 |
34 | public function getErrLog()
35 | { return $this->err_log; }
36 |
37 | protected function addErrLog($err_log)
38 | {
39 | $this->err_log = array_merge($this->err_log, $err_log);
40 | }
41 |
42 | public function acquire_key($key, &$auto_unlocker)
43 | {
44 | $t = microtime(true);
45 | while (!$this->lock_key($key, $auto_unlocker))
46 | {
47 | if ((microtime(true)-$t) > $this->max_wait_unlock) return false;
48 | }
49 | return true;
50 | }
51 |
52 | public function set_errors_triggering($errors_triggering = true)
53 | {
54 | $this->errors_triggering = $errors_triggering;
55 | }
56 |
57 | protected function incrementArray($limit_keys_count, $value, $by_value)
58 | {
59 | if ($limit_keys_count > 0 && (count($value) > $limit_keys_count)) $value = array_slice($value, $limit_keys_count*(-1)+1);
60 |
61 | if (is_array($by_value))
62 | {
63 | $set_key = key($by_value);
64 | if (!empty($set_key)) $value[$set_key] = $by_value[$set_key];
65 | else $value[] = $by_value[0];
66 | }
67 | else $value[] = $by_value;
68 | return $value;
69 | }
70 |
71 | public function set_max_wait_unlock_time($max_wait_unlock = 0.05)
72 | {
73 | $this->max_wait_unlock = $max_wait_unlock;
74 | }
75 |
76 | public function set_key_lock_time($key_lock_time = 30)
77 | {
78 | $this->key_lock_time = $key_lock_time;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/PhpRedisObject.php:
--------------------------------------------------------------------------------
1 | redis = new \Redis();
11 | $this->redis->connect('127.0.0.1', '6379');
12 | }
13 | catch (\RedisException $e)
14 | {
15 | return $this->ReportError('connection error:'.$e->getMessage(), __LINE__);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/README.md:
--------------------------------------------------------------------------------
1 | PHP Memory Cacher
2 | =================
3 | ##Key-value storage in memory
4 | As a storage may be used:
5 |
6 | * [APC](http://pecl.php.net/package/APC)
7 | * [Redis](http://redis.io)
8 | * [Memcache](http://pecl.php.net/package/memcache)
9 | * [Shared memory](http://php.net/manual/en/book.shmop.php)
10 |
11 | All storage objects have one interface, so you can switch them without changing the working code.
12 |
13 | ##Features:
14 | + Tags for keys
15 | + "Dog-pile" ("cache miss storm") and "race condition" effects are excluded
16 | + Lock, unlock or acquire key just by one command
17 | + Auto Unlocker - any locked key will be automatically unlocked (on exit from function or script)
18 | + You can select keys via callback-function
19 | + One interface for all storages - you can change storage without changing your code
20 | + Increment() method can work with arrays, strings and numeric values
21 | + MultiAccess class can be used for any resource, to create an access model *one write multiple read*
22 |
23 | ##Usage:
24 | See [demo.php](https://github.com/jamm/Memory/blob/master/demo.php) to get examples of code.
25 |
26 | You can use MemoryObjects (RedisObject, APCObject, MemcacheObject, SHMObject) as usual key-value storage: get/set/delete.
27 | What for this library was designed is to provide additional features, such as Tags or "dog-pile" effect avoidance.
28 |
29 | In all storages race conditions are excluded, but you can also lock keys to avoid race conditions in your algorithm:
30 | for example, see this code:
31 |
32 | $value = $mem->read('key');
33 | if (some_condition()) $mem->save('key', $value . 'append');
34 |
35 | If this code will be executed by 2 scripts simultaneously, 'append' of one script will be lost.
36 | To avoid it, you can lock key:
37 |
38 | if ($mem->lock_key('key', $au))
39 | {
40 | if (some_condition()) $mem->save('key', $value . 'append');
41 | }
42 |
43 | or acquire:
44 |
45 | if ($mem->acquire_key('key', $au))
46 | {
47 | if (some_condition()) $mem->save('key', $value . 'append');
48 | }
49 |
50 | Difference between these methods is what they will do when key is locked by another process: lock_key() will just return 'false',
51 | acquire_key() will wait until key will not be unlocked (maximum time of waiting declared in code).
52 |
53 | All 'locks' here are *soft*. It means keys aren't locked for write or read, but you can check, if key is 'locked' or not, and what to do with this - is decision of your script.
54 | It was designed to avoid dead-locks and unnecessary queues of clients which waits for access the key.
55 |
56 | Example in code:
57 |
58 | if ($mem->lock_key('key', $au))
59 | {
60 | if (some_condition()) $mem->save('key', $value . 'append');
61 | }
62 | else
63 | {
64 | // key is not hard-locked actually
65 | $mem->del('key'); // we can do this
66 | // but we can use 'locks' to manage multi-process interactions properly and easy (see previous code examples)
67 | }
68 |
69 | To avoid the "Dog-pile" effect ("cache miss storm", "cache stampede"), we can use second argument of method read() - when time of expiration is near, we can try to lock key, and if key was locked - update value.
70 | See example in [demo.php](https://github.com/jamm/Memory/blob/master/demo.php).
71 |
72 | ##Requirements:
73 | You can use each storage separately, requirements are individually for storages
74 |
75 | ###PHP version: 5.3+ (maybe 5.2+, not checked)
76 |
77 | ###If you want to use APC:
78 | [APC](http://pecl.php.net/package/APC) should be installed, and this setting should be added in php.ini (or apc.ini if you use it)
79 |
80 | + apc.slam_defense = Off
81 | + __recommended:__ apc.user_ttl = 0
82 |
83 | ###If you want to use Memcached:
84 | [Memcache](http://pecl.php.net/package/memcache) or [Memcached](http://pecl.php.net/package/memcached) PHP extension should be installed.
85 | Memcache is not the fastest and not secure enough storage, so use it only when it's necessary. [Read more](http://code.google.com/p/memcached/wiki/WhyNotMemcached)
86 |
87 | ###If you want to use Redis:
88 | [Redis](http://redis.io) server should be installed (in debian/ubuntu: "apt-get install redis-server").
89 | Supported version is 2.4 and below.
90 | Also, [phpredis](https://github.com/nicolasff/phpredis) (if installed) can be used as client library - just use `PhpRedisObject` instead of default `RedisObject`.
91 |
92 | ###If you want to use the Unix shared memory or MultiAccess:
93 | PHP should support shm-functions and msg-functions (--enable-shmop --enable-sysvsem --enable-sysvshm --enable-sysvmsg)
94 | Should be used only in specific cases (e.g. mutexes), or when other extensions can not be installed.
95 |
96 | #Storages comparison:
97 | **APC** is a very fast and easy to use storage, and it's usually already installed on the host with PHP.
98 | If APC can not be used for caching data, or each your php-process uses separate APC instance - use **Redis**.
99 | Redis and Memcache can be used for cross-process communication. Also, data in Redis storage will be restored even after server reboot.
100 | Don't want to install Redis (it's just 1 line in console :))? Use **Memcache**.
101 | If you can't install any third-party packages, you can use **Shared Memory** - but your PHP should be compiled with support of shmop-functions.
102 |
103 | ##Performance comparison
104 | + **APC** - best performance, let speed result of APC in benchmark is 1.
105 | + **PhpRedis** - speed 1.23
106 | + **Redis** - speed 1.6
107 | + **Shared memory** - speed 130
108 | + **Memcache** - speed 192 (slowest)
109 |
110 |
111 | Tests:
112 | =====
113 |
114 | RunTests();
121 | $testRedisServer = new TestRedisServer();
122 | $testRedisServer->RunTests();
123 |
124 | $printer = new \Jamm\Tester\ResultsPrinter();
125 | $printer->addTests($testRedisObject->getTests());
126 | $printer->addTests($testRedisServer->getTests());
127 | $printer->printResultsLine();
128 | $printer->printFailedTests();
129 |
130 | ***
131 | _Look at the comments in demo.php for additional info. Ask, what you want to see commented._
132 |
133 | License: [MIT](http://en.wikipedia.org/wiki/MIT_License)
134 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/RedisObject.php:
--------------------------------------------------------------------------------
1 | redis = $RedisServer;
17 | else $this->setDefaultRedisServer();
18 | $this->set_ID($ID);
19 | }
20 |
21 | protected function setDefaultRedisServer()
22 | {
23 | $this->redis = new RedisServer();
24 | }
25 |
26 | /**
27 | * Add value to the memory storage, only if this key does not exists (or false will be returned).
28 | *
29 | * @param string $key
30 | * @param mixed $value
31 | * @param int $ttl
32 | * @param array|string $tags
33 | * @return boolean
34 | */
35 | public function add($key, $value, $ttl = 259200, $tags = NULL)
36 | {
37 | $redis_key = $this->prefix.$key;
38 | $set = $this->redis->SetNX($redis_key, serialize($value));
39 | if (!$set) return false;
40 | $ttl = intval($ttl);
41 | if ($ttl < 1) $ttl = self::max_ttl;
42 | $this->redis->Expire($redis_key, $ttl);
43 | if (!empty($tags)) $this->setTags($key, $tags);
44 | return true;
45 | }
46 |
47 | /**
48 | * Set tags, associated with the key
49 | *
50 | * @param string $key
51 | * @param string|array $tags
52 | * @return bool
53 | */
54 | public function setTags($key, $tags)
55 | {
56 | if (!is_array($tags)) $tags = array($tags);
57 | foreach ($tags as $tag)
58 | {
59 | if (!$this->redis->sAdd($this->tag_prefix.$tag, $key)) return false;
60 | }
61 | return true;
62 | }
63 |
64 | /**
65 | * Save variable in memory storage
66 | *
67 | * @param string $key - key
68 | * @param mixed $value - value
69 | * @param int $ttl - time to live (store) in seconds
70 | * @param array|string $tags - array of tags for this key
71 | * @return bool
72 | */
73 | public function save($key, $value, $ttl = 259200, $tags = NULL)
74 | {
75 | $ttl = intval($ttl);
76 | if ($ttl < 1) $ttl = self::max_ttl;
77 | $set = $this->redis->SetEX($this->prefix.$key, $ttl, serialize($value));
78 | if (!$set) return false;
79 | if (!empty($tags)) $this->setTags($key, $tags);
80 | return true;
81 | }
82 |
83 | /**
84 | * Read data from memory storage
85 | *
86 | * @param string|array $key (string or array of string keys)
87 | * @param mixed $ttl_left = (ttl - time()) of key. Use to exclude dog-pile effect, with lock/unlock_key methods.
88 | * @return mixed
89 | */
90 | public function read($key, &$ttl_left = -1)
91 | {
92 | $r = unserialize($this->redis->get($this->prefix.$key));
93 | if ($r===false) return false;
94 | if ($ttl_left!==-1)
95 | {
96 | $ttl_left = $this->redis->TTL($this->prefix.$key);
97 | if ($ttl_left < 1) $ttl_left = self::max_ttl;
98 | }
99 | return $r;
100 | }
101 |
102 | /**
103 | * Delete key or array of keys from storage
104 | * @param string|array $keys - keys
105 | * @return boolean|array - if array of keys was passed, on error will be returned array of not deleted keys, or 'true' on success.
106 | */
107 | public function del($keys)
108 | {
109 | if (empty($keys)) return false;
110 | if (!is_array($keys)) $keys = array($keys);
111 | $todel = array();
112 | $tags = $this->redis->Keys($this->tag_prefix.'*');
113 | foreach ($keys as $key)
114 | {
115 | $todel[] = $this->prefix.$key;
116 | if (!empty($tags))
117 | {
118 | foreach ($tags as $tag) $this->redis->sRem($tag, $key);
119 | }
120 | }
121 | return $this->redis->Del($todel);
122 | }
123 |
124 | public function del_old()
125 | {
126 | //should be automatically done by Redis
127 | return true;
128 | }
129 |
130 | /**
131 | * Delete keys by tags
132 | *
133 | * @param array|string $tags - tag or array of tags
134 | * @return boolean
135 | */
136 | public function del_by_tags($tags)
137 | {
138 | if (!is_array($tags)) $tags = array($tags);
139 | $d = 0;
140 | foreach ($tags as $tag)
141 | {
142 | $keys = $this->redis->sMembers($this->tag_prefix.$tag);
143 | if (!empty($keys)) $d += $this->del($keys);
144 | $this->redis->Del($this->tag_prefix.$tag);
145 | }
146 | return $d;
147 | }
148 |
149 | /**
150 | * Select from storage via callback function
151 | * @param callback $fx ($value, $key) - should return true to select key(s)
152 | * @param bool $get_array
153 | * @return mixed
154 | */
155 | public function select_fx($fx, $get_array = false)
156 | {
157 | $arr = array();
158 | $prefix_length = strlen($this->prefix);
159 | $keys = $this->redis->Keys($this->prefix.'*');
160 | foreach ($keys as $key)
161 | {
162 | $content = $this->redis->Get($key);
163 | if (empty($content)) continue;
164 | $value = unserialize($content);
165 | $index = substr($key, $prefix_length);
166 | if ($fx($value, $index)===true)
167 | {
168 | if (!$get_array) return $value;
169 | else $arr[$index] = $value;
170 | }
171 | }
172 | if (!$get_array || empty($arr)) return false;
173 | else return $arr;
174 | }
175 |
176 | /**
177 | * Increment value of the key
178 | * @param string $key
179 | * @param mixed $by_value
180 | * if stored value is an array:
181 | * if $by_value is a value in array, new element will be pushed to the end of array,
182 | * if $by_value is a key=>value array, new key=>value pair will be added (or updated)
183 | * @param int $limit_keys_count - maximum count of elements (used only if stored value is array)
184 | * @param int $ttl - set time to live for key
185 | * @return int|string|array new value of key
186 | */
187 | public function increment($key, $by_value = 1, $limit_keys_count = 0, $ttl = 259200)
188 | {
189 | if (empty($key))
190 | {
191 | $this->ReportError('empty keys are not allowed', __LINE__);
192 | return false;
193 | }
194 |
195 | if (!$this->acquire_key($key, $auto_unlocker)) return false;
196 |
197 | $value = $this->read($key);
198 | if ($value===null || $value===false) return $this->save($key, $by_value, $ttl);
199 |
200 | if (is_array($value))
201 | {
202 | $value = $this->incrementArray($limit_keys_count, $value, $by_value);
203 | }
204 | elseif (is_numeric($value) && is_numeric($by_value))
205 | {
206 | $value += $by_value;
207 | }
208 | else
209 | {
210 | $value .= $by_value;
211 | }
212 |
213 | if ($this->save($key, $value, $ttl)) return $value;
214 | else return false;
215 | }
216 |
217 | /**
218 | * Get exclusive mutex for key. Key will be still accessible to read and write, but
219 | * another process can exclude dog-pile effect, if before updating the key he will try to get this mutex.
220 | * @param mixed $key
221 | * @param mixed $auto_unlocker_variable - pass empty, just declared variable
222 | */
223 | public function lock_key($key, &$auto_unlocker_variable)
224 | {
225 | $r = $this->redis->SetNX($this->lock_key_prefix.$key, 1);
226 | if (!$r) return false;
227 | $this->redis->Expire($this->lock_key_prefix.$key, $this->key_lock_time);
228 | $auto_unlocker_variable = new KeyAutoUnlocker(array($this, 'unlock_key'));
229 | $auto_unlocker_variable->setKey($key);
230 | return true;
231 | }
232 |
233 | /**
234 | * Unlock key, locked by method 'lock_key'
235 | * @param KeyAutoUnlocker $auto_unlocker
236 | * @return bool
237 | */
238 | public function unlock_key(KeyAutoUnlocker $auto_unlocker)
239 | {
240 | $key = $auto_unlocker->getKey();
241 | if (empty($key))
242 | {
243 | $this->ReportError('Empty key in the AutoUnlocker', __LINE__);
244 | return false;
245 | }
246 | $auto_unlocker->revoke();
247 | return $this->redis->Del($this->lock_key_prefix.$key);
248 | }
249 |
250 | /**
251 | * @return array
252 | */
253 | public function get_stat()
254 | {
255 | return $this->redis->info();
256 | }
257 |
258 | /** Return array of all stored keys */
259 | public function get_keys()
260 | {
261 | $l = strlen($this->prefix);
262 | $keys = $this->redis->Keys($this->prefix.'*');
263 | if (!empty($keys))
264 | {
265 | foreach ($keys as &$key) $key = substr($key, $l);
266 | return $keys;
267 | }
268 | else return array();
269 | }
270 |
271 | public function set_ID($ID)
272 | {
273 | if (!empty($ID))
274 | {
275 | $this->prefix = str_replace('.', '_', $ID).'.';
276 | }
277 | $this->tag_prefix = self::tag_prefix.$this->prefix;
278 | $this->lock_key_prefix = self::lock_key_prefix.$this->prefix;
279 | }
280 |
281 | public function get_ID()
282 | {
283 | return str_replace('_', '.', trim($this->prefix, '.'));
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/RedisServer.php:
--------------------------------------------------------------------------------
1 | host = $host;
37 | $this->port = $port;
38 | }
39 |
40 | public function connect($host, $port)
41 | {
42 | if (!empty($this->connection))
43 | {
44 | fclose($this->connection);
45 | $this->connection = NULL;
46 | }
47 | $socket = fsockopen($host, $port, $errno, $errstr);
48 | if (!$socket)
49 | {
50 | $this->reportError('Connection error: '.$errno.':'.$errstr);
51 | return false;
52 | }
53 | $this->connection = $socket;
54 | return $socket;
55 | }
56 |
57 | protected function reportError($msg)
58 | {
59 | trigger_error($msg, E_USER_WARNING);
60 | }
61 |
62 | /**
63 | * Execute send_command and return the result
64 | * Each entity of the send_command should be passed as argument
65 | * Example:
66 | * send_command('set','key','example value');
67 | * or:
68 | * send_command('multi');
69 | * send_command('set','a', serialize($arr));
70 | * send_command('set','b', 1);
71 | * send_command('execute');
72 | * @return array|bool|int|null|string
73 | */
74 | public function send_command()
75 | {
76 | return $this->_send(func_get_args());
77 | }
78 |
79 | protected function _send($args)
80 | {
81 | if (empty($this->connection))
82 | {
83 | if (!$this->connect($this->host, $this->port))
84 | {
85 | return false;
86 | }
87 | }
88 | $command = '*'.count($args)."\r\n";
89 | foreach ($args as $arg) $command .= "$".strlen($arg)."\r\n".$arg."\r\n";
90 |
91 | $w = fwrite($this->connection, $command);
92 | if (!$w)
93 | {
94 | //if connection was lost
95 | $this->connect($this->host, $this->port);
96 | if (!fwrite($this->connection, $command))
97 | {
98 | $this->reportError('command was not sent');
99 | return false;
100 | }
101 | }
102 | $answer = $this->_read_reply();
103 | if ($answer===false && $this->repeat_reconnected)
104 | {
105 | if (fwrite($this->connection, $command))
106 | {
107 | $answer = $this->_read_reply();
108 | }
109 | $this->repeat_reconnected = false;
110 | }
111 | return $answer;
112 | }
113 |
114 | /* If some command is not wrapped... */
115 | public function __call($name, $args)
116 | {
117 | array_unshift($args, str_replace('_', ' ', $name));
118 | return $this->_send($args);
119 | }
120 |
121 | protected function _read_reply()
122 | {
123 | $server_reply = fgets($this->connection);
124 | if ($server_reply===false)
125 | {
126 | if (!$this->connect($this->host, $this->port))
127 | {
128 | return false;
129 | }
130 | else
131 | {
132 | $server_reply = fgets($this->connection);
133 | if (empty($server_reply))
134 | {
135 | $this->repeat_reconnected = true;
136 | return false;
137 | }
138 | }
139 | }
140 | $reply = trim($server_reply);
141 | $response = null;
142 |
143 | /**
144 | * Thanks to Justin Poliey for original code of parsing the answer
145 | * https://github.com/jdp
146 | * Error was fixed there: https://github.com/jamm/redisent
147 | */
148 | switch ($reply[0])
149 | {
150 | /* Error reply */
151 | case '-':
152 | $this->reportError('error: '.$reply);
153 | return false;
154 | /* Inline reply */
155 | case '+':
156 | return substr($reply, 1);
157 | /* Bulk reply */
158 | case '$':
159 | if ($reply=='$-1') return null;
160 | $response = null;
161 | $size = intval(substr($reply, 1));
162 | if ($size > 0)
163 | {
164 | $response = stream_get_contents($this->connection, $size);
165 | }
166 | fread($this->connection, 2); /* discard crlf */
167 | break;
168 | /* Multi-bulk reply */
169 | case '*':
170 | $count = substr($reply, 1);
171 | if ($count=='-1') return null;
172 | $response = array();
173 | for ($i = 0; $i < $count; $i++)
174 | {
175 | $response[] = $this->_read_reply();
176 | }
177 | break;
178 | /* Integer reply */
179 | case ':':
180 | return intval(substr($reply, 1));
181 | break;
182 | default:
183 | $this->reportError('Non-protocol answer: '.print_r($server_reply, 1));
184 | return false;
185 | }
186 |
187 | return $response;
188 | }
189 |
190 | public function Get($key)
191 | {
192 | return $this->_send(array('get', $key));
193 | }
194 |
195 | public function Set($key, $value)
196 | {
197 | return $this->_send(array('set', $key, $value));
198 | }
199 |
200 | public function SetEx($key, $seconds, $value)
201 | {
202 | return $this->_send(array('setex', $key, $seconds, $value));
203 | }
204 |
205 | public function Keys($pattern)
206 | {
207 | return $this->_send(array('keys', $pattern));
208 | }
209 |
210 | public function Multi()
211 | {
212 | return $this->_send(array('multi'));
213 | }
214 |
215 | public function sAdd($set, $value)
216 | {
217 | if (!is_array($value)) $value = func_get_args();
218 | else array_unshift($value, $set);
219 | return $this->__call('sadd', $value);
220 | }
221 |
222 | public function sMembers($set)
223 | {
224 | return $this->_send(array('smembers', $set));
225 | }
226 |
227 | public function hSet($key, $field, $value)
228 | {
229 | return $this->_send(array('hset', $key, $field, $value));
230 | }
231 |
232 | public function hGetAll($key)
233 | {
234 | $arr = $this->_send(array('hgetall', $key));
235 | $c = count($arr);
236 | $r = array();
237 | for ($i = 0; $i < $c; $i += 2)
238 | {
239 | $r[$arr[$i]] = $arr[$i+1];
240 | }
241 | return $r;
242 | }
243 |
244 | public function FlushDB()
245 | {
246 | return $this->_send(array('flushdb'));
247 | }
248 |
249 | public function Info()
250 | {
251 | return $this->_send(array('info'));
252 | }
253 |
254 | /** Close connection */
255 | public function __destruct()
256 | {
257 | if (!empty($this->connection)) fclose($this->connection);
258 | }
259 |
260 | public function SetNX($key, $value)
261 | {
262 | return $this->_send(array('setnx', $key, $value));
263 | }
264 |
265 | public function Watch()
266 | {
267 | $args = func_get_args();
268 | array_unshift($args, 'watch');
269 | return $this->_send($args);
270 | }
271 |
272 | public function Exec()
273 | {
274 | return $this->_send(array('exec'));
275 | }
276 |
277 | public function Discard()
278 | {
279 | return $this->_send(array('discard'));
280 | }
281 |
282 | public function sIsMember($set, $value)
283 | {
284 | return $this->_send(array('sismember', $set, $value));
285 | }
286 |
287 | public function sRem($set, $value)
288 | {
289 | if (!is_array($value)) $value = func_get_args();
290 | else array_unshift($value, $set);
291 | return $this->__call('srem', $value);
292 | }
293 |
294 | public function Expire($key, $seconds)
295 | {
296 | return $this->_send(array('expire', $key, $seconds));
297 | }
298 |
299 | public function TTL($key)
300 | {
301 | return $this->_send(array('ttl', $key));
302 | }
303 |
304 | public function Del($key)
305 | {
306 | if (!is_array($key)) $key = func_get_args();
307 | return $this->__call('del', $key);
308 | }
309 |
310 | public function IncrBy($key, $increment)
311 | {
312 | return $this->_send(array('incrby', $key, $increment));
313 | }
314 |
315 | public function Append($key, $value)
316 | {
317 | return $this->_send(array('append', $key, $value));
318 | }
319 |
320 | public function Auth($pasword)
321 | {
322 | return $this->_send(array('Auth', $pasword));
323 | }
324 |
325 | public function bgRewriteAOF()
326 | {
327 | return $this->_send(array('bgRewriteAOF'));
328 | }
329 |
330 | public function bgSave()
331 | {
332 | return $this->_send(array('bgSave'));
333 | }
334 |
335 | public function BLPop($keys, $timeout)
336 | {
337 | if (!is_array($keys)) $keys = func_get_args();
338 | else array_push($keys, $timeout);
339 | return $this->__call('BLPop', $keys);
340 | }
341 |
342 | public function BRPop($keys, $timeout)
343 | {
344 | if (!is_array($keys)) $keys = func_get_args();
345 | else array_push($keys, $timeout);
346 | return $this->__call('BRPop', $keys);
347 | }
348 |
349 | public function BRPopLPush($source, $destination, $timeout)
350 | {
351 | return $this->_send(array('BRPopLPush', $source, $destination, $timeout));
352 | }
353 |
354 | public function Config_Get($pattern)
355 | {
356 | return $this->_send(array('CONFIG', 'GET', $pattern));
357 | }
358 |
359 | public function Config_Set($parameter, $value)
360 | {
361 | return $this->_send(array('CONFIG', 'SET', $parameter, $value));
362 | }
363 |
364 | public function Config_ResetStat()
365 | {
366 | return $this->_send(array('CONFIG RESETSTAT'));
367 | }
368 |
369 | public function DBsize()
370 | {
371 | return $this->_send(array('dbsize'));
372 | }
373 |
374 | public function Decr($key)
375 | {
376 | return $this->_send(array('decr', $key));
377 | }
378 |
379 | public function DecrBy($key, $decrement)
380 | {
381 | return $this->_send(array('DecrBy', $key, $decrement));
382 | }
383 |
384 | public function Exists($key)
385 | {
386 | return $this->_send(array('Exists', $key));
387 | }
388 |
389 | public function Expireat($key, $timestamp)
390 | {
391 | return $this->_send(array('Expireat', $key, $timestamp));
392 | }
393 |
394 | public function FlushAll()
395 | {
396 | return $this->_send(array('flushall'));
397 | }
398 |
399 | public function GetBit($key, $offset)
400 | {
401 | return $this->_send(array('GetBit', $key, $offset));
402 | }
403 |
404 | public function GetRange($key, $start, $end)
405 | {
406 | return $this->_send(array('getrange', $key, $start, $end));
407 | }
408 |
409 | public function GetSet($key, $value)
410 | {
411 | return $this->_send(array('GetSet', $key, $value));
412 | }
413 |
414 | public function hDel($key, $field)
415 | {
416 | if (!is_array($field)) $field = func_get_args();
417 | else array_unshift($field, $key);
418 | return $this->__call('hdel', $field);
419 | }
420 |
421 | public function hExists($key, $field)
422 | {
423 | return $this->_send(array('hExists', $key, $field));
424 | }
425 |
426 | public function hGet($key, $field)
427 | {
428 | return $this->_send(array('hGet', $key, $field));
429 | }
430 |
431 | public function hIncrBy($key, $field, $increment)
432 | {
433 | return $this->_send(array('hIncrBy', $key, $field, $increment));
434 | }
435 |
436 | public function hKeys($key)
437 | {
438 | return $this->_send(array('hKeys', $key));
439 | }
440 |
441 | public function hLen($key)
442 | {
443 | return $this->_send(array('hLen', $key));
444 | }
445 |
446 | public function hMGet($key, array $fields)
447 | {
448 | array_unshift($fields, $key);
449 | return $this->__call('hMGet', $fields);
450 | }
451 |
452 | public function hMSet($key, $fields)
453 | {
454 | $args[] = $key;
455 | foreach ($fields as $field => $value)
456 | {
457 | $args[] = $field;
458 | $args[] = $value;
459 | }
460 | return $this->__call('hMSet', $args);
461 | }
462 |
463 | public function hSetNX($key, $field, $value)
464 | {
465 | return $this->_send(array('hSetNX', $key, $field, $value));
466 | }
467 |
468 | public function hVals($key)
469 | {
470 | return $this->_send(array('hVals', $key));
471 | }
472 |
473 | public function Incr($key)
474 | {
475 | return $this->_send(array('Incr', $key));
476 | }
477 |
478 | public function LIndex($key, $index)
479 | {
480 | return $this->_send(array('LIndex', $key, $index));
481 | }
482 |
483 | public function LInsert($key, $after = true, $pivot, $value)
484 | {
485 | if ($after) $position = self::Position_AFTER;
486 | else $position = self::Position_BEFORE;
487 | return $this->_send(array('LInsert', $key, $position, $pivot, $value));
488 | }
489 |
490 | public function LLen($key)
491 | {
492 | return $this->_send(array('LLen', $key));
493 | }
494 |
495 | public function LPop($key)
496 | {
497 | return $this->_send(array('LPop', $key));
498 | }
499 |
500 | public function LPush($key, $value)
501 | {
502 | if (!is_array($value)) $value = func_get_args();
503 | else array_unshift($value, $key);
504 | return $this->__call('lpush', $value);
505 | }
506 |
507 | public function LPushX($key, $value)
508 | {
509 | return $this->_send(array('LPushX', $key, $value));
510 | }
511 |
512 | public function LRange($key, $start, $stop)
513 | {
514 | return $this->_send(array('LRange', $key, $start, $stop));
515 | }
516 |
517 | public function LRem($key, $count, $value)
518 | {
519 | return $this->_send(array('LRem', $key, $count, $value));
520 | }
521 |
522 | public function LSet($key, $index, $value)
523 | {
524 | return $this->_send(array('LSet', $key, $index, $value));
525 | }
526 |
527 | public function LTrim($key, $start, $stop)
528 | {
529 | return $this->_send(array('LTrim', $key, $start, $stop));
530 | }
531 |
532 | public function MGet($key)
533 | {
534 | if (!is_array($key)) $key = func_get_args();
535 | return $this->__call('MGet', $key);
536 | }
537 |
538 | public function Move($key, $db)
539 | {
540 | return $this->_send(array('Move', $key, $db));
541 | }
542 |
543 | public function MSet(array $keys)
544 | {
545 | $q = array();
546 | foreach ($keys as $k => $v)
547 | {
548 | $q[] = $k;
549 | $q[] = $v;
550 | }
551 | return $this->__call('MSet', $q);
552 | }
553 |
554 | public function MSetNX(array $keys)
555 | {
556 | $q = array();
557 | foreach ($keys as $k => $v)
558 | {
559 | $q[] = $k;
560 | $q[] = $v;
561 | }
562 | return $this->__call('MSetNX', $q);
563 | }
564 |
565 | public function Persist($key)
566 | {
567 | return $this->_send(array('Persist', $key));
568 | }
569 |
570 | public function PSubscribe($pattern)
571 | {
572 | return $this->_send(array('PSubscribe', $pattern));
573 | }
574 |
575 | public function Publish($channel, $message)
576 | {
577 | return $this->_send(array('Publish', $channel, $message));
578 | }
579 |
580 | public function PUnsubscribe($patterns = null)
581 | {
582 | if (!empty($patterns))
583 | {
584 | if (!is_array($patterns)) $patterns = array($patterns);
585 | return $this->__call('PUnsubscribe', $patterns);
586 | }
587 | else return $this->_send(array('PUnsubscribe'));
588 | }
589 |
590 | public function Quit()
591 | {
592 | return $this->_send(array('Quit'));
593 | }
594 |
595 | public function Rename($key, $newkey)
596 | {
597 | return $this->_send(array('Rename', $key, $newkey));
598 | }
599 |
600 | public function RenameNX($key, $newkey)
601 | {
602 | return $this->_send(array('RenameNX', $key, $newkey));
603 | }
604 |
605 | public function RPop($key)
606 | {
607 | return $this->_send(array('RPop', $key));
608 | }
609 |
610 | public function RPopLPush($source, $destination)
611 | {
612 | return $this->_send(array('RPopLPush', $source, $destination));
613 | }
614 |
615 | public function RPush($key, $value)
616 | {
617 | if (!is_array($value)) $value = func_get_args();
618 | else array_unshift($value, $key);
619 | return $this->__call('rpush', $value);
620 | }
621 |
622 | public function RPushX($key, $value)
623 | {
624 | return $this->_send(array('RPushX', $key, $value));
625 | }
626 |
627 | public function sCard($key)
628 | {
629 | return $this->_send(array('sCard', $key));
630 | }
631 |
632 | public function sDiff($key)
633 | {
634 | if (!is_array($key)) $key = func_get_args();
635 | return $this->__call('sDiff', $key);
636 | }
637 |
638 | public function sDiffStore($destination, $key)
639 | {
640 | if (!is_array($key)) $key = func_get_args();
641 | else array_unshift($key, $destination);
642 | return $this->__call('sDiffStore', $key);
643 | }
644 |
645 | public function Select($index)
646 | {
647 | return $this->_send(array('Select', $index));
648 | }
649 |
650 | public function SetBit($key, $offset, $value)
651 | {
652 | return $this->_send(array('SetBit', $key, $offset, $value));
653 | }
654 |
655 | public function SetRange($key, $offset, $value)
656 | {
657 | return $this->_send(array('SetRange', $key, $offset, $value));
658 | }
659 |
660 | public function sInter($key)
661 | {
662 | if (!is_array($key)) $key = func_get_args();
663 | return $this->__call('sInter', $key);
664 | }
665 |
666 | public function sInterStore($destination, $key)
667 | {
668 | if (is_array($key)) array_unshift($key, $destination);
669 | else $key = func_get_args();
670 | return $this->__call('sInterStore', $key);
671 | }
672 |
673 | public function SlaveOf($host, $port)
674 | {
675 | return $this->_send(array('SlaveOf', $host, $port));
676 | }
677 |
678 | public function sMove($source, $destination, $member)
679 | {
680 | return $this->_send(array('sMove', $source, $destination, $member));
681 | }
682 |
683 | public function Sort($key, $sort_rule)
684 | {
685 | return $this->_send(array('Sort', $key, $sort_rule));
686 | }
687 |
688 | public function StrLen($key)
689 | {
690 | return $this->_send(array('StrLen', $key));
691 | }
692 |
693 | public function Subscribe($channel)
694 | {
695 | if (!is_array($channel)) $channel = func_get_args();
696 | return $this->__call('Subscribe', $channel);
697 | }
698 |
699 | public function sUnion($key)
700 | {
701 | if (!is_array($key)) $key = func_get_args();
702 | return $this->__call('sUnion', $key);
703 | }
704 |
705 | public function sUnionStore($destination, $key)
706 | {
707 | if (!is_array($key)) $key = func_get_args();
708 | else array_unshift($key, $destination);
709 | return $this->__call('sUnionStore', $key);
710 | }
711 |
712 | public function Type($key)
713 | {
714 | return $this->_send(array('Type', $key));
715 | }
716 |
717 | public function Unsubscribe($channel = '')
718 | {
719 | $args = func_get_args();
720 | if (empty($args)) return $this->_send(array('Unsubscribe'));
721 | else
722 | {
723 | if (is_array($channel)) return $this->__call('Unsubscribe', $channel);
724 | else return $this->__call('Unsubscribe', $args);
725 | }
726 | }
727 |
728 | public function Unwatch()
729 | {
730 | return $this->_send(array('Unwatch'));
731 | }
732 |
733 | public function zAdd($key, $score, $member = NULL)
734 | {
735 | if (!is_array($score)) $values = func_get_args();
736 | else
737 | {
738 | foreach ($score as $score_value => $member)
739 | {
740 | $values[] = $score_value;
741 | $values[] = $member;
742 | }
743 | array_unshift($values, $key);
744 | }
745 | return $this->__call('zadd', $values);
746 | }
747 |
748 | public function zCard($key)
749 | {
750 | return $this->_send(array('zCard', $key));
751 | }
752 |
753 | public function zCount($key, $min, $max)
754 | {
755 | return $this->_send(array('zCount', $key, $min, $max));
756 | }
757 |
758 | public function zIncrBy($key, $increment, $member)
759 | {
760 | return $this->_send(array('zIncrBy', $key, $increment, $member));
761 | }
762 |
763 | public function zInterStore($destination, array $keys, array $weights = null, $aggregate = null)
764 | {
765 | $destination = array($destination, count($keys));
766 | $destination = array_merge($destination, $keys);
767 | if (!empty($weights))
768 | {
769 | $destination[] = 'WEIGHTS';
770 | $destination = array_merge($destination, $weights);
771 | }
772 | if (!empty($aggregate))
773 | {
774 | $destination[] = 'AGGREGATE';
775 | $destination[] = $aggregate;
776 | }
777 | return $this->__call('zInterStore', $destination);
778 | }
779 |
780 | public function zRange($key, $start, $stop, $withscores = false)
781 | {
782 | if ($withscores) return $this->_send(array('zRange', $key, $start, $stop, self::WITHSCORES));
783 | else return $this->_send(array('zRange', $key, $start, $stop));
784 | }
785 |
786 | public function zRangeByScore($key, $min, $max, $withscores = false, array $limit = null)
787 | {
788 | $args = array($key, $min, $max);
789 | if ($withscores) $args[] = self::WITHSCORES;
790 | if (!empty($limit))
791 | {
792 | $args[] = 'LIMIT';
793 | $args[] = $limit[0];
794 | $args[] = $limit[1];
795 | }
796 | return $this->__call('zRangeByScore', $args);
797 | }
798 |
799 | public function zRank($key, $member)
800 | {
801 | return $this->_send(array('zRank', $key, $member));
802 | }
803 |
804 | public function zRem($key, $member)
805 | {
806 | if (!is_array($member)) $member = func_get_args();
807 | else array_unshift($member, $key);
808 | return $this->__call('zrem', $member);
809 | }
810 |
811 | public function zRemRangeByRank($key, $start, $stop)
812 | {
813 | return $this->_send(array('zRemRangeByRank', $key, $start, $stop));
814 | }
815 |
816 | public function zRemRangeByScore($key, $min, $max)
817 | {
818 | return $this->_send(array('zRemRangeByScore', $key, $min, $max));
819 | }
820 |
821 | public function zRevRange($key, $start, $stop, $withscores = false)
822 | {
823 | if ($withscores) return $this->_send(array('zRevRange', $key, $start, $stop, self::WITHSCORES));
824 | else return $this->_send(array('zRevRange', $key, $start, $stop));
825 | }
826 |
827 | public function zRevRangeByScore($key, $max, $min, $withscores = false, array $limit = null)
828 | {
829 | $args = array($key, $max, $min);
830 | if ($withscores) $args[] = self::WITHSCORES;
831 | if (!empty($limit))
832 | {
833 | $args[] = 'LIMIT';
834 | $args[] = $limit[0];
835 | $args[] = $limit[1];
836 | }
837 | return $this->__call('zRevRangeByScore', $args);
838 | }
839 |
840 | public function zRevRank($key, $member)
841 | {
842 | return $this->_send(array('zRevRank', $key, $member));
843 | }
844 |
845 | public function zScore($key, $member)
846 | {
847 | return $this->_send(array('zScore', $key, $member));
848 | }
849 |
850 | public function zUnionStore($destination, array $keys, array $weights = null, $aggregate = null)
851 | {
852 | $destination = array($destination, count($keys));
853 | $destination = array_merge($destination, $keys);
854 | if (!empty($weights))
855 | {
856 | $destination[] = 'WEIGHTS';
857 | $destination = array_merge($destination, $weights);
858 | }
859 | if (!empty($aggregate))
860 | {
861 | $destination[] = 'AGGREGATE';
862 | $destination[] = $aggregate;
863 | }
864 | return $this->__call('zUnionStore', $destination);
865 | }
866 | }
867 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/Shm/DummyMutex.php:
--------------------------------------------------------------------------------
1 | writers_count <= 0) && ($this->readers_count <= 0)) //if it is nested call - access should be given only once
25 | {
26 | if (!$this->wait(self::writers)) return false; //if somebody are writing here now - we will wait
27 |
28 | $sent = $this->increment(self::readers); //increment count of readers - writers will be waiting for us while we read.
29 | if (!$sent) return false;
30 | }
31 |
32 | $auto_unlocker_reference = new \Jamm\Memory\KeyAutoUnlocker(array($this, 'release_access_read'));
33 | $this->readers_count++;
34 | return true;
35 | }
36 |
37 | public function release_access_read(IKeyLocker $autoUnlocker = NULL)
38 | {
39 | if (!empty($autoUnlocker)) $autoUnlocker->revoke();
40 | if ($this->readers_count > 0) $this->readers_count--;
41 | if ($this->readers_count <= 0) $this->decrement(self::readers); //tell to writers, that we have read and they can write now
42 | return true;
43 | }
44 |
45 | public function get_access_write(&$auto_unlocker_reference)
46 | {
47 | if ($this->writers_count <= 0) //if we are writers already - don't need to lock semaphore again
48 | {
49 | if ($this->readers_count > 0) //if we got reader's access and want to write - release our reader's access and got new access - writer's (else process will wait itself for a while)
50 | {
51 | $this->readers_count = 0;
52 | $this->release_access_read($auto_unlocker_reference);
53 | }
54 |
55 | //acquire mutex for writing
56 | if (!$this->acquire_writers_mutex()) return false;
57 |
58 | //only 1 writer can send message to writers queue, so if in queue more than 1 message - it's somebody's error, and we will fix it now:
59 | $this->clean_queue(self::writers);
60 |
61 | //tell to readers, that they should wait while we will write
62 | //this action should be made before writer will wait for readers
63 | $sent = $this->increment(self::writers); //after this command, readers will wait, until we will leave the queue
64 | if (!$sent)
65 | {
66 | $this->release_writers_mutex();
67 | return false;
68 | }
69 |
70 | //but if readers has come before us - wait, until they finish
71 | if (!$this->wait(self::readers))
72 | {
73 | $this->decrement(self::writers);
74 | $this->release_writers_mutex();
75 | return false;
76 | }
77 | }
78 |
79 | $auto_unlocker_reference = new \Jamm\Memory\KeyAutoUnlocker(array($this, 'release_access_write'));
80 | $this->writers_count++;
81 | //and now we can write :)
82 | return true;
83 | }
84 |
85 | public function release_access_write(IKeyLocker $autoUnlocker = NULL)
86 | {
87 | if (!empty($autoUnlocker)) $autoUnlocker->revoke();
88 | if ($this->writers_count > 0) $this->writers_count--;
89 | if ($this->writers_count <= 0)
90 | {
91 | $this->decrement(self::writers); //tell to readers, that they can read now
92 | $this->release_writers_mutex();
93 | }
94 | return true;
95 | }
96 |
97 | /**
98 | * Returned value of this function should not be ignored.
99 | * 'False' means that access should not be granted.
100 | * @return bool
101 | */
102 | protected function acquire_writers_mutex()
103 | {
104 | if (!$this->mutex_acquired)
105 | {
106 | if (empty($this->writers_mutex)) $this->writers_mutex = sem_get($this->writers_mutex_key, 1, 0777, 1);
107 | if (empty($this->writers_mutex)) return false;
108 | ignore_user_abort(true); //don't hang with semaphore, please :)
109 | set_time_limit(30);
110 | if (!sem_acquire($this->writers_mutex))
111 | {
112 | $this->err_log[] = 'Can not acquire writers mutex';
113 | return false;
114 | }
115 | $this->mutex_acquired = true;
116 | }
117 | return true;
118 | }
119 |
120 | protected function release_writers_mutex()
121 | {
122 | if (!empty($this->writers_mutex))
123 | {
124 | if (sem_release($this->writers_mutex))
125 | {
126 | unset($this->writers_mutex);
127 | $this->mutex_acquired = false;
128 | return true;
129 | }
130 | else return false;
131 | }
132 | return true;
133 | }
134 |
135 | protected function clean_queue($type = self::writers)
136 | {
137 | $q = $this->select_q($type);
138 | $stat = msg_stat_queue($q);
139 | if ($stat['msg_qnum'] > 0)
140 | {
141 | for ($i = $stat['msg_qnum']; $i > 0; $i--)
142 | {
143 | msg_receive($q, $type, $t, 100, $msg, false, MSG_IPC_NOWAIT+MSG_NOERROR, $err);
144 | }
145 | }
146 | }
147 |
148 | protected function increment($type = self::readers)
149 | {
150 | $q = $this->select_q($type);
151 | if (empty($q)) return false;
152 | $sent = msg_send($q, $type, $type, false, false, $err);
153 | if ($sent==false)
154 | {
155 | $counter = $this->get_counter($type);
156 | $this->err_log[] = 'Message was not sent to queue '.($type==self::readers ? 'readers '.$this->read_q_key
157 | : 'writers '.$this->write_q_key)
158 | .' counter: '.$counter.', error: '.$err;
159 | return false;
160 | }
161 | return true;
162 | }
163 |
164 | protected function decrement($type = self::readers)
165 | {
166 | $q = $this->select_q($type);
167 | if (empty($q)) return false;
168 | $recieve = msg_receive($q, $type, $t, 100, $msg, false, MSG_IPC_NOWAIT+MSG_NOERROR, $err);
169 | if ($recieve===false)
170 | {
171 | $counter = $this->get_counter($type);
172 | if ($counter > 0)
173 | {
174 | $this->err_log[] = 'Message was not recieved from queue '.($type==self::readers
175 | ? 'readers '.$this->read_q_key : 'writers '.$this->write_q_key)
176 | .' counter: '.$counter.', error: '.$err;
177 | return false;
178 | }
179 | }
180 | return true;
181 | }
182 |
183 | public function get_counter($type = self::writers)
184 | {
185 | $q = $this->select_q($type);
186 | $stat = msg_stat_queue($q);
187 | return $stat['msg_qnum'];
188 | }
189 |
190 | /**
191 | * Wait for queue. If this function has returned 'false', access should not be granted.
192 | * @param int $type
193 | * @return bool
194 | */
195 | protected function wait($type = self::writers)
196 | {
197 | $q = $this->select_q($type);
198 | if (empty($q)) return false;
199 |
200 | $stat = msg_stat_queue($q);
201 | if ($stat['msg_qnum'] > 0)
202 | {
203 | $starttime = microtime(true);
204 | do
205 | {
206 | $stat = msg_stat_queue($q);
207 | if (empty($stat)) break;
208 | if ($stat['msg_qnum'] <= 0) break;
209 |
210 | if ((microtime(true)-$starttime) > $this->max_wait_time) return false;
211 | }
212 | while ($stat['msg_qnum'] > 0);
213 | }
214 | return true;
215 | }
216 |
217 | protected function select_q($type)
218 | {
219 | if ($type==self::readers)
220 | {
221 | if (empty($this->read_q)) $this->read_q = msg_get_queue($this->read_q_key, 0777);
222 | $q = $this->read_q;
223 | }
224 | else
225 | {
226 | if (empty($this->write_q)) $this->write_q = msg_get_queue($this->write_q_key, 0777);
227 | $q = $this->write_q;
228 | }
229 | return $q;
230 | }
231 |
232 | public function __construct($id = '')
233 | {
234 | if (!empty($id))
235 | {
236 | if (is_string($id))
237 | {
238 | if (is_file($id) || is_dir($id)) $id = ftok($id, 'I');
239 | else $id = intval($id);
240 | }
241 | $this->read_q_key += $id*10;
242 | $this->write_q_key += $id*10;
243 | $this->writers_mutex_key += $id*10;
244 | }
245 | }
246 |
247 | public function __destruct()
248 | {
249 | if ($this->writers_count > 0)
250 | {
251 | $this->err_log[] = 'writers count = '.$this->writers_count;
252 | $this->writers_count = 0;
253 | $this->release_access_write();
254 | }
255 | $this->release_writers_mutex();
256 |
257 | if ($this->readers_count > 0)
258 | {
259 | $this->err_log[] = 'readers count = '.$this->readers_count;
260 | $this->readers_count = 0;
261 | $this->release_access_read();
262 | }
263 |
264 | if (!empty($this->err_log))
265 | {
266 | trigger_error('MultiAccess errors '.implode("\n", $this->err_log), E_USER_NOTICE);
267 | }
268 | }
269 |
270 | public function getReadQKey()
271 | { return $this->read_q_key; }
272 |
273 | public function getWriteQKey()
274 | { return $this->write_q_key; }
275 |
276 | public function getErrLog()
277 | { return $this->err_log; }
278 |
279 | public function setMaxWaitTime($max_wait_time)
280 | { $this->max_wait_time = floatval($max_wait_time); }
281 |
282 | }
283 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/Shm/ReadOnlyAccess.php:
--------------------------------------------------------------------------------
1 | shmsize = $size;
32 | if (!empty($maxsize)) $this->max_size = $maxsize;
33 | $this->set_ID($ID);
34 | }
35 |
36 | /**
37 | * Add value to memory storage, only if this key does not exists (or false will be returned).
38 | *
39 | * @param string $k key
40 | * @param mixed $v value
41 | * @param integer $ttl Time To Live in seconds (value will be added to the current time)
42 | * @param array|string $tags tag array of tags for this key
43 | * @return bool
44 | */
45 | public function add($k, $v, $ttl = 259200, $tags = NULL)
46 | {
47 | if (empty($k) || $v==NULL)
48 | {
49 | $this->ReportError('empty keys and values are not allowed', __LINE__);
50 | return false;
51 | }
52 | $k = (string)$k;
53 | $auto_unlocker = NULL;
54 | if (!$this->mutex->get_access_write($auto_unlocker))
55 | {
56 | return false;
57 | }
58 | $map = $this->mem_object->read('map');
59 | if (isset($map[$k]))
60 | {
61 | return false;
62 | }
63 | return $this->save($k, $v, $ttl, $tags);
64 | }
65 |
66 | /**
67 | * Write data to storage directly
68 | *
69 | * @param string|array $data
70 | * @param int|array $start
71 | * @return bool
72 | */
73 | protected function write_data($data, $start)
74 | {
75 | $r = 0;
76 | if (is_array($start) && is_array($data))
77 | {
78 | $i = 0;
79 | $c = sizeof($start);
80 | for (; $i < $c; $i++)
81 | {
82 | if (isset($data[$i]) && isset($start[$i])) $r += shmop_write($this->shm_data_id, $data[$i], $start[$i]);
83 | }
84 | }
85 | else $r = shmop_write($this->shm_data_id, $data, $start);
86 | return $r;
87 | }
88 |
89 | /**
90 | * Save variable in memory storage
91 | *
92 | * @param string $k key
93 | * @param mixed $v value
94 | * @param integer $ttl Time To Live in seconds (value will be added to the current time)
95 | * @param string|array $tags tag array of tags for this key
96 | * @return bool
97 | */
98 | public function save($k, $v, $ttl = 259200, $tags = NULL)
99 | {
100 | if (empty($k) || $v===NULL)
101 | {
102 | $this->ReportError('empty key and null value are not allowed', __LINE__);
103 | return false;
104 | }
105 |
106 | $k = (string)$k;
107 | $auto_unlocker = NULL;
108 | if (!$this->mutex->get_access_write($auto_unlocker))
109 | {
110 | return false;
111 | }
112 | $map = $this->mem_object->read('map');
113 | $data_serialized = 0;
114 | if (!is_scalar($v))
115 | {
116 | $v = serialize($v);
117 | $data_serialized = 1;
118 | }
119 | $size = strlen($v);
120 | if (empty($map)) $start = 0;
121 | else
122 | {
123 | if (!array_key_exists($k, $map))
124 | {
125 | $start = $this->find_free_space($map, $size);
126 | if ($start===false)
127 | {
128 | $this->ReportError('Can not find enough space in memory', __LINE__);
129 | return false;
130 | }
131 | }
132 | else
133 | {
134 | if ($size <= ($map[$k][self::map_key_fin]-$map[$k][self::map_key_start])) $start = $map[$k][self::map_key_start];
135 | else
136 | {
137 | $this->del($k);
138 | $this->del_old();
139 | $map = $this->mem_object->read('map');
140 | $start = $this->find_free_space($map, $size);
141 | if ($start===false)
142 | {
143 | $this->ReportError('Can not find enough space in memory', __LINE__);
144 | return false;
145 | }
146 | }
147 | }
148 | }
149 | $r = $this->write_data($v, $start);
150 | if ($r===false) return false;
151 | $set_ttl = 0;
152 | $ttl = intval($ttl);
153 | if ($ttl > self::max_ttl) $ttl = self::max_ttl;
154 | if ($ttl > 0) $set_ttl = time()+$ttl;
155 | $map[$k] = array(self::map_key_start => $start, self::map_key_fin => ($start+$size));
156 | if ($set_ttl > 0) $map[$k][self::map_key_ttl] = $set_ttl;
157 | if ($data_serialized) $map[$k][self::map_key_serialized] = $data_serialized;
158 | $r = $this->mem_object->save('map', $map);
159 | if ($r===false)
160 | {
161 | $this->ReportError('map was not saved', __LINE__);
162 | return false;
163 | }
164 | if (!empty($tags))
165 | {
166 | if (!is_array($tags)) $tags = array($tags);
167 | $tags_was_changed = false;
168 | $mem_tags = $this->mem_object->read('tags');
169 | foreach ($tags as $tag)
170 | {
171 | if (empty($mem_tags[$tag]) || !in_array($k, $mem_tags[$tag]))
172 | {
173 | $mem_tags[$tag][] = $k;
174 | $tags_was_changed = true;
175 | }
176 | }
177 | if ($tags_was_changed) $this->mem_object->save('tags', $mem_tags);
178 | }
179 | return true;
180 | }
181 |
182 | /**
183 | * Find free space in map to store data
184 | * @param array $map
185 | * @param int $size
186 | * @return int
187 | */
188 | protected function find_free_space(array $map, $size)
189 | {
190 | $c = count($map);
191 | if ($c < 1) return 0;
192 | $_end = $this->max_size;
193 | usort($map, array($this, 'sort_map'));
194 | $imap = array_values($map);
195 | $i = 0;
196 | $eoa = $c-1; //end of array
197 | if ($imap[0][0] > $size) return 0;
198 | for (; $i < $c; $i++)
199 | {
200 | $free_from = $imap[$i][self::map_key_fin]+1;
201 | if ($i==$eoa) $free_to = $_end;
202 | else $free_to = $imap[($i+1)][self::map_key_start]-1;
203 | if (($free_to-$free_from) >= $size) return $free_from;
204 | }
205 | $this->ReportError('Can not find enough space in memory', __LINE__);
206 | return false;
207 | }
208 |
209 | /**
210 | * Sort map by start value at map
211 | * This function are public only for the function "usort", she should not be used in interface.
212 | * @access private
213 | * @param array $a
214 | * @param array $b
215 | * @return int
216 | */
217 | public function sort_map($a, $b)
218 | {
219 | if ($a[self::map_key_start]==$b[self::map_key_start]) return 0;
220 | if ($a[self::map_key_start] < $b[self::map_key_start]) return -1;
221 | else return 1;
222 | }
223 |
224 | /**
225 | * Read data from memory storage
226 | *
227 | * @param string|array $k key or array of keys
228 | * @param int $ttl_left = (ttl - time()) of key. Use to exclude dog-pile effect, with lock/unlock_key methods.
229 | * @return mixed
230 | */
231 | public function read($k, &$ttl_left = -1)
232 | {
233 | if (empty($k))
234 | {
235 | $this->ReportError('empty key are not allowed', __LINE__);
236 | return NULL;
237 | }
238 | $auto_unlocker = NULL;
239 | if (!$this->mutex->get_access_read($auto_unlocker))
240 | {
241 | return NULL;
242 | }
243 | $map = $this->mem_object->read('map');
244 | if (empty($map))
245 | {
246 | $this->ReportError('map are empty', __LINE__);
247 | return NULL;
248 | }
249 |
250 | if (is_array($k))
251 | {
252 | $todelete = array();
253 | $from_points = array();
254 | $to_points = array();
255 | foreach ($k as $key)
256 | {
257 | $key = (string)$key;
258 | if (!array_key_exists($key, $map)) continue;
259 | if (!empty($map[$key][self::map_key_ttl]) && $map[$key][self::map_key_ttl] < time())
260 | {
261 | $todelete[] = $key;
262 | continue;
263 | }
264 | $from_points[] = $map[$key][self::map_key_start];
265 | $to_points[] = $map[$key][self::map_key_fin];
266 | }
267 | if (!empty($todelete)) $this->del($todelete);
268 | $data = $this->read_data($from_points, $to_points, $k);
269 | if (!empty($data))
270 | {
271 | foreach ($data as $key => &$value)
272 | {
273 | if ($map[$key][self::map_key_serialized]==1) $value = unserialize($value);
274 | if (is_numeric($value))
275 | {
276 | if (intval($value)==$value) $value = intval($value);
277 | else
278 | {
279 | if (floatval($value)==$value) $value = floatval($value);
280 | }
281 | }
282 | }
283 | }
284 | }
285 | else
286 | {
287 | $k = (string)$k;
288 | if (!array_key_exists($k, $map))
289 | {
290 | return NULL;
291 | }
292 | $ttl_left = self::max_ttl;
293 | if (!empty($map[$k][self::map_key_ttl]))
294 | {
295 | $ttl_left = $map[$k][self::map_key_ttl]-time();
296 | if ($ttl_left <= 0)
297 | {
298 | $this->del($k);
299 | return NULL;
300 | }
301 | }
302 |
303 | $from = $map[$k][self::map_key_start];
304 | $to = $map[$k][self::map_key_fin];
305 | $data = $this->read_data($from, $to);
306 | if ($map[$k][self::map_key_serialized]==1) $data = unserialize($data);
307 | else
308 | {
309 | if (is_numeric($data))
310 | {
311 | if (intval($data)==$data) $data = intval($data);
312 | else
313 | {
314 | if (floatval($data)==$data) $data = floatval($data);
315 | }
316 | }
317 | }
318 | }
319 | return $data;
320 | }
321 |
322 | /** Return array of all stored keys */
323 | public function get_keys()
324 | {
325 | $map = array_keys($this->mem_object->read('map'));
326 | if (!is_array($map))
327 | {
328 | $this->ReportError('can not read map', __LINE__);
329 | return false;
330 | }
331 | $rebase = false;
332 | foreach ($map as $i => $key)
333 | {
334 | if (strpos($key, self::lock_key_prefix)===0 || strpos($key, '_ttl')===0 || strpos($key, '_info')===0)
335 | {
336 | $rebase = true;
337 | unset($map[$i]);
338 | }
339 | }
340 | if ($rebase) $map = array_unique($map);
341 | return $map;
342 | }
343 |
344 | /**
345 | * Read data from storage directly
346 | *
347 | * @param int|int[] $from (integer or array of integers)
348 | * @param int|int[] $to (integer or array of integers)
349 | * @param array $keys
350 | * @return string
351 | */
352 | protected function read_data($from, $to, Array $keys = NULL)
353 | {
354 | if (is_array($from) && is_array($to) && !empty($keys))
355 | {
356 | $i = 0;
357 | $c = count($from);
358 | $data = array();
359 | for (; $i < $c; $i++)
360 | {
361 | if (isset($from[$i]) && isset($to[$i]) && isset($keys[$i])) $data[$keys[$i]] = shmop_read($this->shm_data_id, $from[$i], ($to[$i]-$from[$i]));
362 | }
363 | }
364 | else $data = shmop_read($this->shm_data_id, $from, ($to-$from));
365 | return $data;
366 | }
367 |
368 | /**
369 | * Delete key or array of keys from storage (from map)
370 | *
371 | * @param string|array $k key or array of keys
372 | * @return boolean
373 | */
374 | public function del($k)
375 | {
376 | if ($k==NULL || $k=='')
377 | {
378 | $this->ReportError('Can not delete empty key', __LINE__);
379 | return false;
380 | }
381 | $auto_unlocker = NULL;
382 | if (!$this->mutex->get_access_write($auto_unlocker))
383 | {
384 | return false;
385 | }
386 | $map = $this->mem_object->read('map');
387 | if (is_array($k))
388 | {
389 | foreach ($k as $key)
390 | {
391 | $key = (string)$key;
392 | unset($map[$key]);
393 | }
394 | }
395 | else
396 | {
397 | $k = (string)$k;
398 | unset($map[$k]);
399 | }
400 | $r = $this->mem_object->save('map', $map);
401 | if ($r===false)
402 | {
403 | $this->ReportError('map was not saved', __LINE__);
404 | return false;
405 | }
406 | return true;
407 | }
408 |
409 | /**
410 | * Delete old (by ttl) variables from storage (map)
411 | * @return boolean
412 | */
413 | public function del_old()
414 | {
415 | $auto_unlocker = NULL;
416 | if (!$this->mutex->get_access_write($auto_unlocker))
417 | {
418 | return false;
419 | }
420 | $r = 0;
421 | $map = $this->mem_object->read('map');
422 | $todel = array();
423 | foreach ($map as $k => &$v)
424 | {
425 | if (!empty($v[self::map_key_ttl]) && $v[self::map_key_ttl] < time()) $todel[] = $k;
426 | }
427 | if (!empty($todel)) $r = $this->del($todel);
428 | return $r;
429 | }
430 |
431 | /**
432 | * Delete keys by tags
433 | *
434 | * @param array|string $tag - tag or array of tags
435 | * @return boolean
436 | */
437 | public function del_by_tags($tag)
438 | {
439 | if (empty($tag))
440 | {
441 | $this->ReportError('empty value instead of tags given', __LINE__);
442 | return false;
443 | }
444 | $auto_unlocker = NULL;
445 | if (!$this->mutex->get_access_write($auto_unlocker))
446 | {
447 | return false;
448 | }
449 | $mem_tags = $this->mem_object->read('tags');
450 | if (!is_array($tag)) $tag = array($tag);
451 | $todel = array();
452 | foreach ($tag as $t)
453 | {
454 | if (!empty($mem_tags[$t])) $todel = array_merge($todel, $mem_tags[$t]);
455 | }
456 | $r = $this->del($todel);
457 | return $r;
458 | }
459 |
460 | /**
461 | * Select from storage via callback function
462 | *
463 | * @param callback $fx ($value, $index)
464 | * @param bool $get_array
465 | * @return mixed
466 | */
467 | public function select_fx($fx, $get_array = false)
468 | {
469 | $auto_unlocker = NULL;
470 | if (!$this->mutex->get_access_read($auto_unlocker))
471 | {
472 | return false;
473 | }
474 | $map = $this->mem_object->read('map');
475 | $arr = array();
476 | foreach ($map as $index => &$zs)
477 | {
478 | if (!$zs[self::map_key_serialized]) continue;
479 | $s = $this->read($index);
480 | if (empty($s)) continue;
481 | if ($fx($s, $index)===true)
482 | {
483 | if (!$get_array) return $s;
484 | else $arr[$index] = $s;
485 | }
486 | }
487 | if (!$get_array || empty($arr)) return false;
488 | else return $arr;
489 | }
490 |
491 | /**
492 | * Increment value of key
493 | * @param string $key
494 | * @param mixed $by_value
495 | * if stored value is array:
496 | * if $by_value is value in array, new element will be pushed to the end of array,
497 | * if $by_value is key=>value array, key=>value pair will be added (or updated)
498 | * @param int $limit_keys_count - maximum count of elements (used only if stored value is array)
499 | * @param int $ttl
500 | * @return int|string|array new value of key
501 | */
502 | public function increment($key, $by_value = 1, $limit_keys_count = 0, $ttl = 259200)
503 | {
504 | if (empty($key))
505 | {
506 | $this->ReportError('empty key can not be incremented', __LINE__);
507 | return false;
508 | }
509 | $auto_unlocker = NULL;
510 | if (!$this->mutex->get_access_write($auto_unlocker))
511 | {
512 | return false;
513 | }
514 | $map = $this->mem_object->read('map');
515 | if (!array_key_exists($key, $map))
516 | {
517 | if ($this->save($key, $by_value, $ttl)) return $by_value;
518 | else return false;
519 | }
520 | $value = $this->read($key);
521 | if (is_array($value))
522 | {
523 | $value = $this->incrementArray($limit_keys_count, $value, $by_value);
524 | }
525 | elseif (is_numeric($value) && is_numeric($by_value))
526 | {
527 | $value += $by_value;
528 | }
529 | else
530 | {
531 | $value .= $by_value;
532 | }
533 | $this->save($key, $value, $ttl);
534 | return $value;
535 | }
536 |
537 | /**
538 | * Returns statistic information
539 | * @return array
540 | */
541 | public function get_stat()
542 | {
543 | $stat = array();
544 | $map = $this->mem_object->read('map');
545 | $size = 0;
546 | if (!empty($map)) foreach ($map as $v) $size += ($v[self::map_key_fin]-$v[self::map_key_start]);
547 | $stat['size'] = $size;
548 | $q_read = msg_get_queue($this->mutex->getReadQKey());
549 | if (!empty($q_read))
550 | {
551 | $q_stat = msg_stat_queue($q_read);
552 | $stat['readers'] = $q_stat['msg_qnum'];
553 | $stat['readers_qid'] = $this->mutex->getReadQKey();
554 | }
555 | $q_writers = msg_get_queue($this->mutex->getWriteQKey());
556 | if (!empty($q_writers))
557 | {
558 | $q_stat = msg_stat_queue($q_writers);
559 | $stat['writers'] = $q_stat['msg_qnum'];
560 | $stat['writers_qid'] = $this->mutex->getWriteQKey();
561 | }
562 | $stat['shm_key'] = $this->shm_data_key;
563 | $stat['shm_id'] = $this->shm_data_id;
564 | $stat['max_size'] = $this->max_size;
565 | $stat['head'] = $this->mem_object->get_stat();
566 | $stat['err_log'] = $this->mutex->getErrLog();
567 | return $stat;
568 | }
569 |
570 | /**
571 | * Get exclusive mutex for key. Key will be still accessible to read and write, but
572 | * another process can exclude dog-pile effect, if before updating the key he will try to get this mutex.
573 | * Example:
574 | * Process 1 reads key simultaneously with Process 2.
575 | * Value of this key are too old, so Process 1 going to refresh it. Simultaneously with Process 2.
576 | * But both of them trying to lock_key, and Process 1 only will refresh value of key (taking it from database, e.g.),
577 | * and Process 2 can decide, what he want to do - use old value and not spent time to database, or something else.
578 | * @param mixed $key
579 | * @param mixed $auto_unlocker_variable - pass empty, just declared variable
580 | */
581 | public function lock_key($key, &$auto_unlocker_variable)
582 | {
583 | $r = $this->mem_object->add(self::lock_key_prefix.$key, 1, $this->key_lock_time);
584 | if (!$r) return false;
585 | $auto_unlocker_variable = new \Jamm\Memory\KeyAutoUnlocker(array($this, 'unlock_key'));
586 | $auto_unlocker_variable->setKey($key);
587 | return true;
588 | }
589 |
590 | /**
591 | * Unlock key, locked by method 'lock_key'
592 | * @param \Jamm\Memory\KeyAutoUnlocker $auto_unlocker
593 | * @return bool
594 | */
595 | public function unlock_key(\Jamm\Memory\KeyAutoUnlocker $auto_unlocker)
596 | {
597 | $key = $auto_unlocker->getKey();
598 | if (empty($key))
599 | {
600 | $this->ReportError('Empty key in the AutoUnlocker', __LINE__);
601 | return false;
602 | }
603 | $auto_unlocker->revoke();
604 | return $this->mem_object->del(self::lock_key_prefix.$key);
605 | }
606 |
607 | public function set_ID($ID)
608 | {
609 | if (!empty($ID)) $this->id = $ID;
610 |
611 | //Create Mutex ("multiple read, one write")
612 | $this->mutex = new MultiAccess($this->id);
613 |
614 | //Create "shmop" to store data
615 | $this->shm_data_key = ftok($this->id, 'D'); //D - Data. But I still love my son Nikita ;)
616 | $this->shm_data_id = @shmop_open($this->shm_data_key, "w", 0, 0);
617 | if (!$this->shm_data_id)
618 | {
619 | $this->shm_data_id = @shmop_open($this->shm_data_key, "a", 0, 0);
620 | if ($this->shm_data_id!==false) $this->readonly = true;
621 | }
622 |
623 | //if memory not yet exists - lets create
624 | if (!$this->shm_data_id) $this->shm_data_id = shmop_open($this->shm_data_key, "n", 0777, $this->max_size);
625 | if (!$this->shm_data_id)
626 | {
627 | $this->ReportError('Can not create data segment in shared memory', __LINE__);
628 | return false;
629 | }
630 |
631 | //Create an mem-object to store the Map
632 | $map_id_key = ftok($this->id, 'h')+12;
633 | $this->mem_object = new ShmMem($map_id_key, $this->shmsize, $this->max_size);
634 | if (!is_object($this->mem_object))
635 | {
636 | $this->ReportError('Can not create map', __LINE__);
637 | return false;
638 | }
639 |
640 | return true;
641 | }
642 |
643 | public function get_ID()
644 | {
645 | return $this->id;
646 | }
647 | }
648 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/Shm/ShmMem.php:
--------------------------------------------------------------------------------
1 | shmsize = $size;
20 | if (!empty($maxsize) && $maxsize > $this->shmsize) $this->max_size = $maxsize;
21 | $this->set_ID($ID);
22 | }
23 |
24 | public function __destruct()
25 | {
26 | shmop_close($this->shm);
27 | }
28 |
29 | public function del_mem_block()
30 | {
31 | shmop_delete($this->shm);
32 | shmop_close($this->shm);
33 | }
34 |
35 | /**
36 | * Resize memory block
37 | * @param int $size
38 | * @return bool
39 | */
40 | protected function resize($size)
41 | {
42 | if ($size > $this->max_size) return false;
43 | //should be called AFTER reading memory (to not loose changing of variables)
44 | if (empty($this->mem)) return false;
45 | ignore_user_abort(true);
46 | set_time_limit(180);
47 | if (is_array($this->mem))
48 | {
49 | $this->mem[self::map_info][self::map_info_resized] = $this->mem[self::map_info][self::map_info_resized]+1;
50 | $this->mem[self::map_info][self::map_info_resizetime] = time();
51 | }
52 | shmop_delete($this->shm);
53 | shmop_close($this->shm);
54 | $t = serialize($this->mem);
55 | $memsize = strlen($t);
56 | if ($memsize > $size) $size = $memsize+1000;
57 | $this->shm = shmop_open($this->shmkey, "n", 0777, $size);
58 | if (!$this->shm) return false; //mmm... oops.
59 | unset($this->mem);
60 | $w = shmop_write($this->shm, str_pad($t, shmop_size($this->shm), ' ', STR_PAD_RIGHT), 0);
61 | if (!$w) return false;
62 | return true;
63 | }
64 |
65 | /**
66 | * Synchronize data with memory storage
67 | * @return bool|int
68 | */
69 | protected function refresh()
70 | {
71 | ignore_user_abort(true);
72 | set_time_limit(180);
73 | //don't call readmemory() here
74 | if (!empty($this->mem[self::map_key_ttl]) && intval(date('s'))==0)
75 | {
76 | $_time = time();
77 | foreach ($this->mem[self::map_key_ttl] as $ttl_key => $ttl_value)
78 | {
79 | if ($ttl_value < $_time) unset($this->mem[self::map_keys][$ttl_key]);
80 | }
81 | }
82 | $t = serialize($this->mem);
83 | $size = strlen($t);
84 | $current_size = shmop_size($this->shm);
85 | if ($size > $current_size) $r = $this->resize($size+ceil($current_size/5)+1000);
86 | else $r = shmop_write($this->shm, str_pad($t, shmop_size($this->shm), ' ', STR_PAD_RIGHT), 0);
87 | unset($this->mem);
88 | return $r;
89 | }
90 |
91 | /**
92 | * Read data from memory storage
93 | * @return mixed
94 | */
95 | protected function readmemory()
96 | {
97 | if (empty($this->mem))
98 | {
99 | $auto_unlocker = NULL;
100 | if (!$this->sem->get_access_read($auto_unlocker))
101 | {
102 | $this->ReportError('can not acquire readers access', __LINE__);
103 | return false;
104 | }
105 | $this->mem = unserialize(trim(shmop_read($this->shm, 0, shmop_size($this->shm))));
106 | $this->sem->release_access_read($auto_unlocker);
107 | }
108 | return true;
109 | }
110 |
111 | public function get_stat()
112 | {
113 | $stat['shm_id'] = $this->shm;
114 | $stat['shm_key'] = $this->shmkey;
115 | $sem = $this->sem;
116 | if (is_a($this->sem, 'MultiAccess'))
117 | {
118 | /** @var MultiAccess $sem */
119 | $q_read = msg_get_queue($sem->getReadQKey());
120 | if (!empty($q_read))
121 | {
122 | $q_stat = msg_stat_queue($q_read);
123 | $stat['readers'] = $q_stat['msg_qnum'];
124 | $stat['readers_qid'] = $sem->getReadQKey();
125 | }
126 | $q_writers = msg_get_queue($sem->getWriteQKey());
127 | if (!empty($q_writers))
128 | {
129 | $q_stat = msg_stat_queue($q_writers);
130 | $stat['writers'] = $q_stat['msg_qnum'];
131 | $stat['writers_qid'] = $sem->getWriteQKey();
132 | }
133 | $this->addErrLog($sem->getErrLog());
134 | }
135 |
136 | $this->readmemory();
137 | $stat['info'] = $this->mem[self::map_info];
138 | $stat['size'] = strlen(serialize($this->mem));
139 | $stat['max_size'] = shmop_size($this->shm);
140 | $stat['err_log'] = $this->getErrLog();
141 |
142 | return $stat;
143 | }
144 |
145 | public function set_ID($ID)
146 | {
147 | if (!empty($ID)) $this->id = $ID;
148 | if (is_string($this->id)) $this->shmkey = ftok($this->id, 'N'); //"N" because i love my son Nikita :)
149 | else $this->shmkey = $this->id;
150 | $this->shm = @shmop_open($this->shmkey, "w", 0, 0);
151 | if (!$this->shm)
152 | {
153 | $this->shm = @shmop_open($this->shmkey, "a", 0, 0);
154 | if ($this->shm!==false) $this->sem = new ReadOnlyAccess($this->id);
155 | }
156 |
157 | //if memory not yet exists - lets create
158 | if (!$this->shm) $this->shm = shmop_open($this->shmkey, "n", 0777, $this->shmsize);
159 | if (!$this->shm) return false;
160 |
161 | if (empty($this->sem)) $this->sem = new MultiAccess($this->id);
162 | return true;
163 | }
164 |
165 | public function get_ID()
166 | {
167 | return $this->id;
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/Shm/SingleMemory.php:
--------------------------------------------------------------------------------
1 | ReportError('empty keys and null values are not allowed', __LINE__);
31 | return false;
32 | }
33 | $k = (string)$k;
34 | $auto_unlocker = NULL;
35 | if (!$this->sem->get_access_write($auto_unlocker))
36 | {
37 | return false;
38 | }
39 |
40 | $this->del_old();
41 |
42 | $this->readmemory();
43 | $this->mem[self::map_keys][$k] = $v;
44 | $ttl = intval($ttl);
45 | if ($ttl > 0) $this->mem[self::map_key_ttl][$k] = time()+$ttl;
46 | if (!empty($tags))
47 | {
48 | if (!is_array($tags)) $tags = array($tags);
49 | foreach ($tags as $tag)
50 | {
51 | if (empty($this->mem[self::map_key_tags][$tag])
52 | || !in_array($k, $this->mem[self::map_key_tags][$tag])
53 | )
54 | $this->mem[self::map_key_tags][$tag][] = $k;
55 | }
56 | }
57 |
58 | return $this->refresh();
59 | }
60 |
61 | /**
62 | * Read key value from memory
63 | * @param string|array $k
64 | * @param $ttl_left
65 | * @return mixed
66 | */
67 | public function read($k, &$ttl_left = -1)
68 | {
69 | if (empty($k))
70 | {
71 | $this->ReportError('empty keys are not allowed', __LINE__);
72 | return false;
73 | }
74 |
75 | $this->readmemory();
76 | if (empty($this->mem)) return false;
77 |
78 | if (is_array($k))
79 | {
80 | $keys = array();
81 | $ttl_left = array();
82 | foreach ($k as $ki)
83 | {
84 | $ki = (string)$ki;
85 | if (!isset($this->mem[self::map_keys][$ki])) continue;
86 | $ttl_left[$ki] = $this->get_key_ttl($ki, $this->mem);
87 | if ($ttl_left[$ki] <= 0) continue;
88 | $keys[$ki] = $this->mem[self::map_keys][$ki];
89 | if (is_numeric($keys[$ki]))
90 | {
91 | if (intval($keys[$ki])==$keys[$ki]) $keys[$ki] = intval($keys[$ki]);
92 | else
93 | {
94 | if (floatval($keys[$ki])==$keys[$ki]) $keys[$ki] = floatval($keys[$ki]);
95 | }
96 | }
97 | }
98 | return $keys;
99 | }
100 | else
101 | {
102 | $k = (string)$k;
103 | $r = $this->mem[self::map_keys][$k];
104 | $ttl_left = $this->get_key_ttl($k, $this->mem);
105 | if ($ttl_left <= 0) $r = NULL;
106 | else
107 | {
108 | if (is_numeric($r))
109 | {
110 | if (intval($r)==$r) $r = intval($r);
111 | else
112 | {
113 | if (floatval($r)==$r) $r = floatval($r);
114 | }
115 | }
116 | }
117 | return $r;
118 | }
119 | }
120 |
121 | public function getSingleMemory()
122 | {
123 | $this->readmemory();
124 | if (!empty($this->mem[self::map_keys])) return $this->mem[self::map_keys];
125 | else return array();
126 | }
127 |
128 | /**
129 | * Delete key from memory
130 | * @param string $k
131 | * @return bool
132 | */
133 | public function del($k)
134 | {
135 | $auto_unlocker = NULL;
136 | if (!$this->sem->get_access_write($auto_unlocker))
137 | {
138 | return false;
139 | }
140 | $this->readmemory();
141 | if (empty($this->mem))
142 | {
143 | return false;
144 | }
145 |
146 | if (!is_array($k)) $k = array($k);
147 | foreach ($k as $key)
148 | {
149 | $key = (string)$key;
150 | unset($this->mem[self::map_keys][$key]);
151 | unset($this->mem[self::map_key_ttl][$key]);
152 | if (!empty($this->mem[self::map_key_tags]))
153 | {
154 | foreach ($this->mem[self::map_key_tags] as $tag_index => &$tag)
155 | {
156 | $indexes = array_keys($tag, $key);
157 | if (!empty($indexes))
158 | {
159 | foreach ($indexes as $index) unset($tag[$index]);
160 | if (empty($tag)) unset($this->mem[self::map_key_tags][$tag_index]);
161 | }
162 | }
163 | }
164 | unset($this->mem[self::map_key_locks][$key]);
165 | }
166 |
167 | return $this->refresh();
168 | }
169 |
170 | /** Add key to memory. If this key already exists - false will returned.
171 | * Excludes simultaneously adding keys to exclude race condition.
172 | * @param string $key
173 | * @param mixed $value
174 | * @param int $ttl
175 | * @param string|array $tags
176 | * @return bool|int
177 | */
178 | public function add($key, $value, $ttl = 2592000, $tags = NULL)
179 | {
180 | if (empty($key)) return false;
181 |
182 | $auto_unlocker = NULL;
183 | if (!$this->sem->get_access_write($auto_unlocker))
184 | {
185 | return false;
186 | }
187 |
188 | $key = (string)$key;
189 | $this->readmemory();
190 | if (isset($this->mem[self::map_keys][$key]))
191 | {
192 | return false;
193 | }
194 |
195 | return $this->save($key, $value, $ttl, $tags);
196 | }
197 |
198 | /**
199 | * Select from memory elements by function $fx
200 | * @param callback $fx
201 | * @param bool $get_array
202 | * @return mixed
203 | */
204 | public function select_fx($fx, $get_array = false)
205 | {
206 | $this->readmemory();
207 | if (empty($this->mem[self::map_keys])) return false;
208 | $arr = array();
209 | foreach ($this->mem[self::map_keys] as $index => $s)
210 | {
211 | if (!is_array($s)) continue;
212 | if ($fx($s, $index)===true)
213 | {
214 | if (!$get_array) return $s;
215 | else $arr[$index] = $s;
216 | }
217 | }
218 | if (!$get_array || empty($arr)) return false;
219 | else return $arr;
220 | }
221 |
222 | /**
223 | * Delete keys by tags
224 | *
225 | * @param array|string $tags - tag or array of tags
226 | * @return boolean
227 | */
228 | public function del_by_tags($tags)
229 | {
230 | if (empty($tags)) return false;
231 | if (!is_array($tags)) $tags = array($tags);
232 |
233 | $auto_unlocker = NULL;
234 | if (!$this->sem->get_access_write($auto_unlocker))
235 | {
236 | return false;
237 | }
238 |
239 | $this->readmemory();
240 | if (empty($this->mem[self::map_key_tags]))
241 | {
242 | return false;
243 | }
244 |
245 | $todel = array();
246 | foreach ($tags as $tag)
247 | {
248 | if (!empty($this->mem[self::map_key_tags][$tag])) $todel = array_merge($todel, $this->mem[self::map_key_tags][$tag]);
249 | }
250 | return $this->del($todel);
251 | }
252 |
253 | /**
254 | * Delete old (by ttl) variables from storage
255 | * @return boolean
256 | */
257 | public function del_old()
258 | {
259 | $auto_unlocker = NULL;
260 | if (!$this->sem->get_access_write($auto_unlocker))
261 | {
262 | return false;
263 | }
264 |
265 | $this->readmemory();
266 | if (empty($this->mem) || empty($this->mem[self::map_key_ttl])) return false;
267 |
268 | $t = time();
269 | if (empty($this->mem[self::map_key_cleantime]) || ($t-$this->mem[self::map_key_cleantime]) > 1800)
270 | {
271 | foreach ($this->mem[self::map_key_ttl] as $key => $ttl)
272 | {
273 | if ($ttl < $t) unset($this->mem[self::map_keys][$key]);
274 | }
275 | $this->mem[self::map_key_cleantime] = $t;
276 | $this->refresh();
277 | }
278 |
279 | return true;
280 | }
281 |
282 | /** Return array of all stored keys */
283 | public function get_keys()
284 | {
285 | $this->readmemory();
286 | if (!empty($this->mem[self::map_keys])) return array_keys($this->mem[self::map_keys]);
287 | else return array();
288 | }
289 |
290 | /**
291 | * Increment value of key
292 | * @param string $key
293 | * @param mixed $by_value
294 | * if stored value is array:
295 | * if $by_value is value in array, new element will be pushed to the end of array,
296 | * if $by_value is key=>value array, key=>value pair will be added (or updated)
297 | * @param int $limit_keys_count - maximum count of elements (used only if stored value is array)
298 | * @param int $ttl
299 | * @return int|string|array new value of key
300 | */
301 | public function increment($key, $by_value = 1, $limit_keys_count = 0, $ttl = 259200)
302 | {
303 | if (empty($key))
304 | {
305 | $this->ReportError('empty keys are not allowed', __LINE__);
306 | return false;
307 | }
308 | $key = (string)$key;
309 | $auto_unlocker = NULL;
310 | if (!$this->sem->get_access_write($auto_unlocker))
311 | {
312 | return false;
313 | }
314 |
315 | $this->readmemory();
316 | if (!isset($this->mem[self::map_keys][$key])) return $this->save($key, $by_value);
317 |
318 | $value = $this->mem[self::map_keys][$key];
319 | if (is_array($value))
320 | {
321 | $value = $this->incrementArray($limit_keys_count, $value, $by_value);
322 | }
323 | elseif (is_numeric($value) && is_numeric($by_value))
324 | {
325 | $value += $by_value;
326 | }
327 | else
328 | {
329 | $value .= $by_value;
330 | }
331 |
332 | $ttl = intval($ttl);
333 | if ($ttl > 0) $this->mem[self::map_key_ttl][$key] = time()+$ttl;
334 | $this->mem[self::map_keys][$key] = $value;
335 | $this->refresh();
336 | return $value;
337 | }
338 |
339 | /**
340 | * Get exclusive mutex for key. Key will be still accessible to read and write, but
341 | * another process can exclude dog-pile effect, if before updating the key he will try to get this mutex.
342 | * @param mixed $key
343 | * @param mixed $auto_unlocker_variable - pass empty, just declared variable
344 | */
345 | public function lock_key($key, &$auto_unlocker_variable)
346 | {
347 | $auto_unlocker = NULL;
348 | if (!$this->sem->get_access_write($auto_unlocker))
349 | {
350 | return false;
351 | }
352 | $this->readmemory();
353 | $key = (string)$key;
354 |
355 | if (isset($this->mem[self::map_key_locks][$key]))
356 | {
357 | return false;
358 | }
359 |
360 | $this->mem[self::map_key_locks][$key] = 1;
361 | if ($this->refresh())
362 | {
363 | $auto_unlocker_variable = new \Jamm\Memory\KeyAutoUnlocker(array($this, 'unlock_key'));
364 | $auto_unlocker_variable->setKey($key);
365 | return true;
366 | }
367 | else return false;
368 | }
369 |
370 | /**
371 | * Unlock key, locked by method 'lock_key'
372 | * @param \Jamm\Memory\KeyAutoUnlocker $key_auto_unlocker
373 | * @return bool
374 | */
375 | public function unlock_key(\Jamm\Memory\KeyAutoUnlocker $key_auto_unlocker)
376 | {
377 | $key = $key_auto_unlocker->getKey();
378 | if (empty($key))
379 | {
380 | $this->ReportError('Empty key in the AutoUnlocker', __LINE__);
381 | return false;
382 | }
383 | $key_auto_unlocker->revoke();
384 |
385 | if (!$this->sem->get_access_write($auto_unlocker))
386 | {
387 | return false;
388 | }
389 | $this->readmemory();
390 | if (!isset($this->mem[self::map_keys][$key]))
391 | {
392 | $this->ReportError('key ['.$key.'] does not exists', __LINE__);
393 | return false;
394 | }
395 |
396 | if (isset($this->mem[self::map_key_locks][$key]))
397 | {
398 | unset($this->mem[self::map_key_locks][$key]);
399 | return $this->refresh();
400 | }
401 | return true;
402 | }
403 |
404 | public function setMutex(IMutex $mutex)
405 | { $this->sem = $mutex; }
406 |
407 | abstract protected function readmemory();
408 |
409 | abstract protected function refresh();
410 |
411 | protected function get_key_ttl($key, &$mem)
412 | {
413 | $ttl_left = self::max_ttl;
414 | if (!empty($mem[self::map_key_ttl][$key]))
415 | {
416 | $ttl_left = $mem[self::map_key_ttl][$key]-time();
417 | }
418 | return $ttl_left;
419 | }
420 | }
421 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/Tests/TestMemoryObject.php:
--------------------------------------------------------------------------------
1 | mem = $mem;
12 | $this->mem->set_errors_triggering(true);
13 | }
14 |
15 | public function getErrLog()
16 | {
17 | return $this->mem->getErrLog();
18 | }
19 |
20 | public function test_add()
21 | {
22 | $this->mem->del('t1');
23 | $call1 = $this->mem->add('t1', 1);
24 | $this->assertTrue($call1);
25 |
26 | $call2 = $this->mem->add('t1', 2);
27 | $this->assertTrue(!$call2);
28 |
29 | $this->mem->del('t3');
30 | $call3 = $this->mem->add('t3', 3, 10);
31 | $this->assertTrue($call3);
32 |
33 | $this->mem->del('t4');
34 | $call = $this->mem->add('t4', 1, 10, 'tag');
35 | $this->assertTrue($call);
36 |
37 | $this->mem->del('t5');
38 | $call = $this->mem->add('t5', 1, 10, array('tag1', 'tag2'));
39 | $this->assertTrue($call);
40 | }
41 |
42 | public function test_del()
43 | {
44 | $this->mem->add(__METHOD__.'d1', 1);
45 | $call = $this->mem->del(__METHOD__.'d1');
46 | $this->assertTrue($call);
47 | $check = $this->mem->read(__METHOD__.'d1');
48 | $this->assertTrue(empty($check))->addCommentary('variables still in cache');
49 |
50 | $this->mem->add(__METHOD__.'d1', 1);
51 | $this->mem->add(__METHOD__.'d2', 1);
52 | $call = $this->mem->del(array(__METHOD__.'d1', __METHOD__.'d2'));
53 | $this->assertTrue($call);
54 | $check = $this->mem->read(array(__METHOD__.'d1', __METHOD__.'d2'));
55 | $this->assertTrue(empty($check))->addCommentary('variables still in cache');
56 | }
57 |
58 | public function test_del_by_tags()
59 | {
60 | $this->mem->add(__METHOD__.'d1', 1, 10, 'tag');
61 | $call = $this->mem->del_by_tags('tag');
62 | $this->assertTrue($call);
63 | $check = $this->mem->read(__METHOD__.'d1');
64 | $this->assertTrue(empty($check))->addCommentary('variables still in cache');
65 |
66 | $this->mem->add(__METHOD__.'d1', 1, 10, 'tag1');
67 | $this->mem->add(__METHOD__.'d2', 1, 10, 'tag2');
68 | $call = $this->mem->del_by_tags(array('tag1', 'tag2'));
69 | $this->assertTrue($call);
70 | $check = $this->mem->read(array(__METHOD__.'d1', __METHOD__.'d2'));
71 | $this->assertTrue(empty($check))->addCommentary('variables still in cache');
72 | }
73 |
74 | public function test_del_old()
75 | {
76 | $this->mem->save(__METHOD__, 11, 1);
77 | sleep(2);
78 | $call = $this->mem->del_old();
79 | $this->assertTrue($call);
80 | $check = $this->mem->read(__METHOD__);
81 | $this->assertTrue(empty($check))->addCommentary('variable still exists');
82 | }
83 |
84 | public function test_increment()
85 | {
86 | $this->mem->save(__METHOD__, 100);
87 | $call = $this->mem->increment(__METHOD__, 10);
88 | $this->assertEquals($call, 110);
89 | $check = $this->mem->read(__METHOD__);
90 | $this->assertEquals($check, 110);
91 |
92 | $call = $this->mem->increment(__METHOD__, -10);
93 | $this->assertEquals($call, 100);
94 | $check = $this->mem->read(__METHOD__);
95 | $this->assertEquals($check, 100);
96 |
97 | $this->mem->save(__METHOD__, 'string');
98 | $call = $this->mem->increment(__METHOD__, 10);
99 | $this->assertEquals($call, 'string10');
100 | $check = $this->mem->read(__METHOD__);
101 | $this->assertEquals($check, 'string10');
102 |
103 | $this->mem->save(__METHOD__, array(1, 2));
104 | $this->assertEquals($this->mem->increment(__METHOD__, 3), array(1, 2, 3));
105 | $this->assertEquals($this->mem->read(__METHOD__), array(1, 2, 3));
106 |
107 | $this->mem->increment(__METHOD__.'inc', array('a'));
108 | $this->mem->increment(__METHOD__.'inc', array('b'));
109 | $this->assertEquals($this->mem->read(__METHOD__.'inc'), array('a', 'b'));
110 |
111 | $this->mem->increment(__METHOD__.'inc', array('k1' => 'a'));
112 | $this->mem->increment(__METHOD__.'inc', array('k2' => 'b'));
113 | $this->mem->increment(__METHOD__.'inc', array('k2' => 'c'));
114 | $this->assertEquals($this->mem->read(__METHOD__.'inc'),
115 | array('a', 'b', 'k1' => 'a', 'k2' => 'c'));
116 |
117 | $this->mem->save(__METHOD__, array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
118 | $call = $this->mem->increment(__METHOD__, 100, 10);
119 | $this->assertEquals($call, array(3, 4, 5, 6, 7, 8, 9, 10, 11, 100));
120 | $check = $this->mem->read(__METHOD__);
121 | $this->assertEquals($check, array(3, 4, 5, 6, 7, 8, 9, 10, 11, 100));
122 |
123 | $this->mem->save(__METHOD__, 1, 10);
124 | $this->mem->increment(__METHOD__, 2, 0, 25);
125 | $check = $this->mem->read(__METHOD__, $ttl_left);
126 | $this->assertEquals($check, 3);
127 | $this->assertEquals($ttl_left, 25)->addCommentary('ttl_left');
128 | }
129 |
130 | public function test_lock_key()
131 | {
132 | $this->mem->save(__METHOD__, 1);
133 | $call = $this->mem->lock_key(__METHOD__, $l);
134 | $this->assertTrue($call);
135 | if ($call)
136 | {
137 | $check = $this->mem->lock_key(__METHOD__, $l1);
138 | $this->assertTrue(!$check)->addCommentary('key was not locked');
139 | $this->mem->unlock_key($l);
140 | }
141 | }
142 |
143 | public function test_read()
144 | {
145 | //Read 10
146 | $this->mem->save(__METHOD__.'t1', 10);
147 | $this->assertEquals($this->mem->read(__METHOD__.'t1'), 10);
148 |
149 | //Read float key
150 | $key = microtime(true)*100;
151 | $this->mem->save($key, 10);
152 | $this->assertEquals($this->mem->read($key), 10);
153 |
154 | //Read negative float
155 | $key = -10.987;
156 | $this->mem->save($key, 10);
157 | $this->assertEquals($this->mem->read($key), 10);
158 |
159 | //Read string
160 | $this->mem->save(__METHOD__.'t1', 'string', 10);
161 | $this->assertEquals($this->mem->read(__METHOD__.'t1'), 'string');
162 |
163 | //Read and check ttl
164 | $call = $this->mem->read(__METHOD__.'t1', $ttl_left);
165 | $this->assertEquals($call, 'string');
166 | $this->assertEquals($ttl_left, 10)->addCommentary('ttl');
167 |
168 | //Read array and check ttl
169 | $this->mem->save(__METHOD__.'t11', array(10, 'string'), 100);
170 | $call = $this->mem->read(__METHOD__.'t11', $ttl_left);
171 | $this->assertEquals($call, array(10, 'string'));
172 | $this->assertEquals($ttl_left, 100)->addCommentary('ttl');
173 | }
174 |
175 | public function test_save()
176 | {
177 | //Save 100
178 | $call = $this->mem->save(__METHOD__.'s1', 100);
179 | $this->assertTrue($call);
180 | $this->assertEquals($this->mem->read(__METHOD__.'s1'), 100);
181 |
182 | //Save 100 with ttl 10
183 | $this->assertTrue($this->mem->save(__METHOD__.'s2', 100, 10));
184 | $this->assertEquals($this->mem->read(__METHOD__.'s2', $ttl_left), 100);
185 | $this->assertEquals($ttl_left, 10);
186 |
187 | //Save float key
188 | $key = microtime(true)*100;
189 | $this->assertTrue($this->mem->save($key, 100, 12));
190 | $check = $this->mem->read($key, $ttl_left);
191 | $this->assertEquals($check, 100);
192 | $this->assertEquals($ttl_left, 12);
193 |
194 | //Save negative float key
195 | $key = -10.12;
196 | $this->assertTrue($this->mem->save($key, 100, 10));
197 | $check = $this->mem->read($key, $ttl_left);
198 | $this->assertEquals($check, 100);
199 | $this->assertEquals($ttl_left, 10);
200 |
201 | //Save with float ttl
202 | $call = $this->mem->save(__METHOD__.'s21', 100, 0.000001);
203 | $this->assertTrue($call);
204 | $check = $this->mem->read(__METHOD__.'s21', $ttl_left);
205 | $this->assertEquals($check, 100);
206 | $this->assertTrue($ttl_left > 10)->addCommentary('ttl mismatch: '.$ttl_left);
207 |
208 | //Save with string ttl
209 | $call = $this->mem->save(__METHOD__.'s22', 100, 'stringttl');
210 | $this->assertTrue($call);
211 | $check = $this->mem->read(__METHOD__.'s22', $ttl_left);
212 | $this->assertEquals($check, 100);
213 |
214 | //Save with tag
215 | $call = $this->mem->save(__METHOD__.'s3', 100, 10, 'tag');
216 | $this->assertTrue($call);
217 | $check = $this->mem->read(__METHOD__.'s3', $ttl_left);
218 | $this->assertEquals($check, 100);
219 | $this->assertEquals($ttl_left, 10);
220 |
221 | //Save with array of tags
222 | $call = $this->mem->save(__METHOD__.'s4', array('z' => 1), 10, array('tag', 'tag1'));
223 | $this->assertTrue($call);
224 | $check = $this->mem->read(__METHOD__.'s4', $ttl_left);
225 | $this->assertEquals($check, array('z' => 1));
226 | $this->assertEquals($ttl_left, 10);
227 |
228 | }
229 |
230 | public function test_select_fx()
231 | {
232 | $this->mem->del($this->mem->get_keys());
233 | $this->mem->save('key1', array('kk1' => 5, 'kk2' => 7));
234 | $this->mem->save('key2', array('kk1' => 4, 'kk2' => 6));
235 | $this->mem->save('key3', array('kk1' => 5, 'kk2' => 5));
236 | $this->mem->save('key4', array('kk1' => 2, 'kk2' => 4));
237 | $this->mem->save('key5', array('id' => 0, 'kk1' => 6, 'kk2' => 5));
238 | $this->mem->save('key6', array('id' => 1, 'kk1' => 9, 'kk2' => 5));
239 | $this->mem->save('key7', array('id' => 0, 'kk1' => 7, 'kk2' => 4));
240 |
241 | $call = $this->mem->select_fx(function($s, $index)
242 | {
243 | if ($index=='key1' || $s['kk2']==7) return true;
244 | else return false;
245 | });
246 | $this->assertEquals($call, array('kk1' => 5, 'kk2' => 7));
247 |
248 | $call = $this->mem->select_fx(function($s, $index)
249 | {
250 | if ($s['kk1']==$s['kk2']) return true;
251 | else return false;
252 | });
253 | $this->assertEquals($call, array('kk1' => 5, 'kk2' => 5));
254 |
255 | $call = $this->mem->select_fx(function($s, $index)
256 | {
257 | if ($s['kk1']==$s['kk2'] || $index=='key4') return true;
258 | else return false;
259 | }, true);
260 | $this->assertEquals($call, array('key3' => array('kk1' => 5, 'kk2' => 5), 'key4' => array('kk1' => 2, 'kk2' => 4)));
261 |
262 | $call = $this->mem->select_fx(function($s, $index)
263 | {
264 | if ($s['kk1'] > 7 || ($s['id']==0 && $s['kk2'] < 5)) return true;
265 | else return false;
266 | }, true);
267 | $this->assertEquals($call, array('key4' => array('kk1' => 2, 'kk2' => 4), 'key6' => array('id' => 1, 'kk1' => 9, 'kk2' => 5), 'key7' => array('id' => 0, 'kk1' => 7, 'kk2' => 4)));
268 | }
269 |
270 | public function test_unlock_key()
271 | {
272 | $this->mem->save(__METHOD__, 1);
273 | $lock = $this->mem->lock_key(__METHOD__, $l);
274 | $this->assertTrue($lock);
275 | if ($lock)
276 | {
277 | $call = $this->mem->unlock_key($l);
278 | $this->assertTrue($call);
279 | $check = $this->mem->lock_key(__METHOD__, $l1);
280 | $this->assertTrue($check)->addCommentary('can not lock key again');
281 | $this->mem->unlock_key($l1);
282 | }
283 | }
284 |
285 | public function test_get_keys()
286 | {
287 | $this->mem->del($this->mem->get_keys());
288 | $this->mem->save(__METHOD__.':1', 1);
289 | $this->mem->save(__METHOD__.':2', 1);
290 | $this->mem->save(__METHOD__.':3', 1);
291 | $arr = array(__METHOD__.':1', __METHOD__.':2', __METHOD__.':3');
292 | $call = $this->mem->get_keys();
293 | if (is_array($call)) $c = count($call);
294 | else $c = 0;
295 | $this->assertEquals($call, $arr);
296 | $this->mem->del($call);
297 | $check = $this->mem->get_keys();
298 | $this->assertTrue(empty($check))->addCommentary('not all keys was deleted, Left: '.count($check).' from '.$c);
299 | }
300 |
301 | public function test_get_stat()
302 | {
303 | $call = $this->mem->get_stat();
304 | $this->assertTrue(!empty($call));
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/Tests/TestRedisServer.php:
--------------------------------------------------------------------------------
1 | redis = new MockRedisServer();
12 | }
13 |
14 | public function test_Append()
15 | {
16 | $this->assertEquals('append key value', $this->redis->Append('key', 'value'));
17 | }
18 |
19 | public function test_Auth()
20 | {
21 | $this->assertEquals('auth pass', $this->redis->Auth('pass'));
22 | }
23 |
24 | public function test_bgRewriteAOF()
25 | {
26 | $this->assertEquals('bgrewriteaof', $this->redis->bgRewriteAOF());
27 | }
28 |
29 | public function test_bgSave()
30 | {
31 | $this->assertEquals('bgsave', $this->redis->bgSave());
32 | }
33 |
34 | public function test_BLPop()
35 | {
36 | $this->assertEquals('blpop key1 50', $this->redis->BLPop('key1', 50));
37 | $this->assertEquals('blpop key1 key2 50', $this->redis->BLPop('key1', 'key2', 50));
38 | $this->assertEquals('blpop key1 key2 key3 50', $this->redis->BLPop(array('key1', 'key2', 'key3'), 50));
39 | }
40 |
41 | public function test_BRPop()
42 | {
43 | $this->assertEquals('brpop key1 50', $this->redis->brpop('key1', 50));
44 | $this->assertEquals('brpop key1 key2 50', $this->redis->brpop('key1', 'key2', 50));
45 | $this->assertEquals('brpop key1 key2 key3 50', $this->redis->brpop(array('key1', 'key2', 'key3'), 50));
46 | }
47 |
48 | public function test_BRPopLPush()
49 | {
50 | $this->assertEquals('brpoplpush source destination 50', $this->redis->BRPopLPush('source', 'destination', 50));
51 | }
52 |
53 | public function test_Config_Get()
54 | {
55 | $this->assertEquals('config get pattern*', $this->redis->Config_Get('pattern*'));
56 | }
57 |
58 | public function test_Config_Set()
59 | {
60 | $this->assertEquals('config set param val', $this->redis->Config_Set('param', 'val'));
61 | }
62 |
63 | public function test_Config_ResetStat()
64 | {
65 | $this->assertEquals('config resetstat', $this->redis->Config_ResetStat());
66 | }
67 |
68 | public function test_DBsize()
69 | {
70 | $this->assertEquals('dbsize', $this->redis->DBsize());
71 | }
72 |
73 | public function test_Decr()
74 | {
75 | $this->assertEquals('decr key', $this->redis->Decr('key'));
76 | }
77 |
78 | public function test_DecrBy()
79 | {
80 | $this->assertEquals('decrby key 5', $this->redis->DecrBy('key', 5));
81 | }
82 |
83 | public function test_Del()
84 | {
85 | $this->assertEquals('del key', $this->redis->del('key'));
86 | }
87 |
88 | public function test_Exists()
89 | {
90 | $this->assertEquals('exists key', $this->redis->Exists('key'));
91 | }
92 |
93 | public function test_Expireat()
94 | {
95 | $this->assertEquals('expireat key 50', $this->redis->Expireat('key', 50));
96 | }
97 |
98 | public function test_FlushAll()
99 | {
100 | $this->assertEquals('flushall', $this->redis->FlushAll());
101 | }
102 |
103 | public function test_FlushDB()
104 | {
105 | $this->assertEquals('flushdb', $this->redis->FlushDB());
106 | }
107 |
108 | public function test_Get()
109 | {
110 | $this->assertEquals('get key', $this->redis->get('key'));
111 | }
112 |
113 | public function test_GetBit()
114 | {
115 | $this->assertEquals('getbit key 5', $this->redis->GetBit('key', 5));
116 | }
117 |
118 | public function test_GetRange()
119 | {
120 | $this->assertEquals('getrange key 1 2', $this->redis->GetRange('key', 1, 2));
121 | }
122 |
123 | public function test_GetSet()
124 | {
125 | $this->assertEquals('getset k v', $this->redis->GetSet('k', 'v'));
126 | }
127 |
128 | public function test_hDel()
129 | {
130 | $this->assertEquals('hdel key field', $this->redis->hDel('key', 'field'));
131 | $this->assertEquals('hdel key field field1', $this->redis->hDel('key', 'field', 'field1'));
132 | $this->assertEquals('hdel key field field1', $this->redis->hDel('key', array('field', 'field1')));
133 | }
134 |
135 | public function test_hExists()
136 | {
137 | $this->assertEquals('hexists key field', $this->redis->hExists('key', 'field'));
138 | }
139 |
140 | public function test_hGet()
141 | {
142 | $this->assertEquals('hget key field', $this->redis->hget('key', 'field'));
143 | }
144 |
145 | public function test_hGetAll()
146 | {
147 | $this->assertEquals(array('h' => 'g'), $this->redis->hGetAll('key'));
148 | }
149 |
150 | public function test_hIncrBy()
151 | {
152 | $this->assertEquals('hincrby key field 50', $this->redis->hIncrBy('key', 'field', 50));
153 | }
154 |
155 | public function test_hKeys()
156 | {
157 | $this->assertEquals('hkeys key', $this->redis->hKeys('key'));
158 | }
159 |
160 | public function test_hLen()
161 | {
162 | $this->assertEquals('hlen k', $this->redis->hLen('k'));
163 | }
164 |
165 | public function test_hMGet()
166 | {
167 | $this->assertEquals('hmget key field1 field2', $this->redis->hMGet('key', array('field1', 'field2')));
168 | }
169 |
170 | public function test_hMSet()
171 | {
172 | $this->assertEquals('hmset key f1 v1 f2 v2', $this->redis->hMSet('key', array('f1' => 'v1', 'f2' => 'v2')));
173 | }
174 |
175 | public function test_hSet()
176 | {
177 | $this->assertEquals('hset key field value', $this->redis->hSet('key', 'field', 'value'));
178 | }
179 |
180 | public function test_hSetNX()
181 | {
182 | $this->assertEquals('hsetnx key field value', $this->redis->hSetNX('key', 'field', 'value'));
183 | }
184 |
185 | public function test_hVals()
186 | {
187 | $this->assertEquals('hvals key', $this->redis->hVals('key'));
188 | }
189 |
190 | public function test_Incr()
191 | {
192 | $this->assertEquals('incr key', $this->redis->Incr('key'));
193 | }
194 |
195 | public function test_LIndex()
196 | {
197 | $this->assertEquals('lindex key index', $this->redis->LIndex('key', 'index'));
198 | }
199 |
200 | public function test_LInsert()
201 | {
202 | $this->assertEquals('linsert key after pivot value', $this->redis->LInsert('key', true, 'pivot', 'value'));
203 | $this->assertEquals('linsert key before pivot value', $this->redis->LInsert('key', false, 'pivot', 'value'));
204 | }
205 |
206 | public function test_LLen()
207 | {
208 | $this->assertEquals('llen key', $this->redis->LLen('key'));
209 | }
210 |
211 | public function test_LPop()
212 | {
213 | $this->assertEquals('lpop key', $this->redis->LPop('key'));
214 | }
215 |
216 | public function test_LPush()
217 | {
218 | $this->assertEquals('lpush key value', $this->redis->LPush('key', 'value'));
219 | $this->assertEquals('lpush key value v1 v2', $this->redis->LPush('key', 'value', 'v1', 'v2'));
220 | $this->assertEquals('lpush key value v1 v2', $this->redis->LPush('key', array('value', 'v1', 'v2')));
221 | }
222 |
223 | public function test_LPushX()
224 | {
225 | $this->assertEquals('lpushx key value', $this->redis->LPushX('key', 'value'));
226 | }
227 |
228 | public function test_LRange()
229 | {
230 | $this->assertEquals('lrange k 3 5', $this->redis->LRange('k', 3, 5));
231 | }
232 |
233 | public function test_LRem()
234 | {
235 | $this->assertEquals('lrem key 5 value', $this->redis->LRem('key', 5, 'value'));
236 | }
237 |
238 | public function test_LSet()
239 | {
240 | $this->assertEquals('lset key index value', $this->redis->LSet('key', 'index', 'value'));
241 | }
242 |
243 | public function test_LTrim()
244 | {
245 | $this->assertEquals('ltrim key 5 7', $this->redis->LTrim('key', 5, 7));
246 | }
247 |
248 | public function test_MGet()
249 | {
250 | $this->assertEquals('mget k', $this->redis->MGet('k'));
251 | $this->assertEquals('mget k1 k2', $this->redis->MGet(array('k1', 'k2')));
252 | }
253 |
254 | public function test_Move()
255 | {
256 | $this->assertEquals('move key db', $this->redis->Move('key', 'db'));
257 | }
258 |
259 | public function test_MSet()
260 | {
261 | $this->assertEquals('mset k v a b', $this->redis->MSet(array('k' => 'v', 'a' => 'b')));
262 | }
263 |
264 | public function test_MSetNX()
265 | {
266 | $this->assertEquals('msetnx k v a b', $this->redis->MSetNX(array('k' => 'v', 'a' => 'b')));
267 | }
268 |
269 | public function test_Persist()
270 | {
271 | $this->assertEquals('persist key', $this->redis->Persist('key'));
272 | }
273 |
274 | public function test_PSubscribe()
275 | {
276 | $this->assertEquals('psubscribe p*', $this->redis->PSubscribe('p*'));
277 | }
278 |
279 | public function test_Publish()
280 | {
281 | $this->assertEquals('publish c m', $this->redis->Publish('c', 'm'));
282 | }
283 |
284 | public function test_PUnsubscribe()
285 | {
286 | $this->assertEquals('punsubscribe', $this->redis->PUnsubscribe());
287 | $this->assertEquals('punsubscribe p', $this->redis->PUnsubscribe(array('p')));
288 | $this->assertEquals('punsubscribe p1 p2', $this->redis->PUnsubscribe(array('p1', 'p2')));
289 | }
290 |
291 | public function test_Quit()
292 | {
293 | $this->assertEquals('quit', $this->redis->Quit());
294 | }
295 |
296 | public function test_Rename()
297 | {
298 | $this->assertEquals('rename key new', $this->redis->Rename('key', 'new'));
299 | }
300 |
301 | public function test_RenameNX()
302 | {
303 | $this->assertEquals('renamenx key new', $this->redis->RenameNX('key', 'new'));
304 | }
305 |
306 | public function test_RPop()
307 | {
308 | $this->assertEquals('rpop k', $this->redis->RPop('k'));
309 | }
310 |
311 | public function test_RPopLPush()
312 | {
313 | $this->assertEquals('rpoplpush s d', $this->redis->RPopLPush('s', 'd'));
314 | }
315 |
316 | public function test_RPush()
317 | {
318 | $this->assertEquals('rpush k v', $this->redis->RPush('k', 'v'));
319 | }
320 |
321 | public function test_RPushX()
322 | {
323 | $this->assertEquals('rpushx k v', $this->redis->RPushx('k', 'v'));
324 | }
325 |
326 | public function test_sCard()
327 | {
328 | $this->assertEquals('scard key', $this->redis->sCard('key'));
329 | }
330 |
331 | public function test_sDiff()
332 | {
333 | $this->assertEquals('sdiff key', $this->redis->sdiff('key'));
334 | }
335 |
336 | public function test_sDiffStore()
337 | {
338 | $this->assertEquals('sdiffstore d k', $this->redis->sDiffStore('d', 'k'));
339 | }
340 |
341 | public function test_Select()
342 | {
343 | $this->assertEquals('select i', $this->redis->Select('i'));
344 | }
345 |
346 | public function test_Set()
347 | {
348 | $this->assertEquals('set k v', $this->redis->set('k', 'v'));
349 | }
350 |
351 | public function test_SetBit()
352 | {
353 | $this->assertEquals('setbit k 5 v', $this->redis->SetBit('k', 5, 'v'));
354 | }
355 |
356 | public function test_SetNX()
357 | {
358 | $this->assertEquals('setnx k v', $this->redis->setnx('k', 'v'));
359 | }
360 |
361 | public function test_SetEX()
362 | {
363 | $this->assertEquals('setex k 5 v', $this->redis->setex('k', 5, 'v'));
364 | }
365 |
366 | public function test_SetRange()
367 | {
368 | $this->assertEquals('setrange k 5 v', $this->redis->setrange('k', 5, 'v'));
369 | }
370 |
371 | public function test_sInter()
372 | {
373 | $this->assertEquals('sinter k', $this->redis->sInter('k'));
374 | $this->assertEquals('sinter k1 k2', $this->redis->sInter('k1', 'k2'));
375 | $this->assertEquals('sinter k1 k2', $this->redis->sInter(array('k1', 'k2')));
376 | }
377 |
378 | public function test_sInterStore()
379 | {
380 | $this->assertEquals('sinterstore d k', $this->redis->sInterStore('d', 'k'));
381 | $this->assertEquals('sinterstore d k1 k2', $this->redis->sInterStore('d', array('k1', 'k2')));
382 | $this->assertEquals('sinterstore d k1 k2', $this->redis->sInterStore('d', 'k1', 'k2'));
383 | }
384 |
385 | public function test_SlaveOf()
386 | {
387 | $this->assertEquals('slaveof host port', $this->redis->SlaveOf('host', 'port'));
388 | }
389 |
390 | public function test_sMove()
391 | {
392 | $this->assertEquals('smove s d m', $this->redis->sMove('s', 'd', 'm'));
393 | }
394 |
395 | public function test_Sort()
396 | {
397 | $this->assertEquals('sort key r', $this->redis->Sort('key', 'r'));
398 | }
399 |
400 | public function test_StrLen()
401 | {
402 | $this->assertEquals('strlen k', $this->redis->StrLen('k'));
403 | }
404 |
405 | public function test_Subscribe()
406 | {
407 | $this->assertEquals('subscribe c', $this->redis->Subscribe('c'));
408 | }
409 |
410 | public function test_sUnion()
411 | {
412 | $this->assertEquals('sunion key', $this->redis->sUnion('key'));
413 | }
414 |
415 | public function test_sUnionStore()
416 | {
417 | $this->assertEquals('sunionstore d k', $this->redis->sUnionStore('d', 'k'));
418 | $this->assertEquals('sunionstore d k1 k2', $this->redis->sUnionStore('d', 'k1', 'k2'));
419 | $this->assertEquals('sunionstore d k1 k2', $this->redis->sUnionStore('d', array('k1', 'k2')));
420 |
421 | }
422 |
423 | public function test_Expire()
424 | {
425 | $this->assertEquals('expire k 5', $this->redis->Expire('k', 5));
426 | }
427 |
428 | public function test_TTL()
429 | {
430 | $this->assertEquals('ttl key', $this->redis->TTL('key'));
431 | }
432 |
433 | public function test_Type()
434 | {
435 | $this->assertEquals('type key', $this->redis->Type('key'));
436 | }
437 |
438 | public function test_Unsubscribe()
439 | {
440 | $this->assertEquals('unsubscribe', $this->redis->Unsubscribe());
441 | $this->assertEquals('unsubscribe c1', $this->redis->Unsubscribe('c1'));
442 | $this->assertEquals('unsubscribe c1 c2', $this->redis->Unsubscribe('c1', 'c2'));
443 | $this->assertEquals('unsubscribe c1 c2', $this->redis->Unsubscribe(array('c1', 'c2')));
444 |
445 | }
446 |
447 | public function test_Unwatch()
448 | {
449 | $this->assertEquals('unwatch', $this->redis->Unwatch());
450 | }
451 |
452 | public function test_zAdd()
453 | {
454 | $this->assertEquals('zadd k 101 m', $this->redis->zAdd('k', 101, 'm'));
455 | $this->assertEquals('zadd k 101 m 102 m2', $this->redis->zAdd('k', 101, 'm', 102, 'm2'));
456 | $this->assertEquals('zadd k 101 m 102 m2', $this->redis->zAdd('k', array(101 => 'm', 102 => 'm2')));
457 | }
458 |
459 | public function test_zCard()
460 | {
461 | $this->assertEquals('zcard k', $this->redis->zCard('k'));
462 | }
463 |
464 | public function test_zCount()
465 | {
466 | $this->assertEquals('zcount k 5 10', $this->redis->zCount('k', 5, 10));
467 | }
468 |
469 | public function test_zIncrBy()
470 | {
471 | $this->assertEquals('zincrby key 10 m', $this->redis->zIncrBy('key', 10, 'm'));
472 | }
473 |
474 | public function test_zinterstore()
475 | {
476 | $this->assertEquals('zinterstore d 2 a b', $this->redis->zInterStore('d', array('a', 'b')));
477 | $this->assertEquals('zinterstore d 2 a b weights 5 7', $this->redis->zinterstore('d', array('a', 'b'), array(5, 7)));
478 | $this->assertEquals('zinterstore d 2 a b aggregate max', $this->redis->zinterstore('d', array('a', 'b'), null, \Jamm\Memory\RedisServer::Aggregate_MAX));
479 | $this->assertEquals('zinterstore d 2 a b weights 5 7 aggregate sum', $this->redis->zinterstore('d', array('a', 'b'), array(5, 7), \Jamm\Memory\RedisServer::Aggregate_SUM));
480 |
481 | }
482 |
483 | public function test_zRange()
484 | {
485 | $this->assertEquals('zrange key 5 10', $this->redis->zRange('key', 5, 10));
486 | $this->assertEquals('zrange key 5 10 withscores', $this->redis->zRange('key', 5, 10, true));
487 | }
488 |
489 | public function test_zRangeByScore()
490 | {
491 | $this->assertEquals('zrangebyscore k 5 7', $this->redis->zRangeByScore('k', 5, 7));
492 | $this->assertEquals('zrangebyscore k 5 7 withscores', $this->redis->zRangeByScore('k', 5, 7, true));
493 | $this->assertEquals('zrangebyscore k 5 7 limit 1 10', $this->redis->zRangeByScore('k', 5, 7, false, array(1, 10)));
494 | }
495 |
496 | public function test_zRank()
497 | {
498 | $this->assertEquals('zrank k m', $this->redis->zRank('k', 'm'));
499 | }
500 |
501 | public function test_zRem()
502 | {
503 | $this->assertEquals('zrem k m', $this->redis->zRem('k', 'm'));
504 | $this->assertEquals('zrem k m m1', $this->redis->zRem('k', 'm', 'm1'));
505 | $this->assertEquals('zrem k m m1', $this->redis->zRem('k', array('m', 'm1')));
506 | }
507 |
508 | public function test_zRemRangeByRank()
509 | {
510 | $this->assertEquals('zremrangebyrank k 5 10', $this->redis->zRemRangeByRank('k', 5, 10));
511 | }
512 |
513 | public function test_zRemRangeByScore()
514 | {
515 | $this->assertEquals('zremrangebyscore k 5 10', $this->redis->zRemRangeByScore('k', 5, 10));
516 | }
517 |
518 | public function test_zRevRange()
519 | {
520 | $this->assertEquals('zrevrange k 5 10', $this->redis->zRevRange('k', 5, 10));
521 | $this->assertEquals('zrevrange k 5 10 withscores', $this->redis->zRevRange('k', 5, 10, true));
522 | }
523 |
524 | public function test_zRevRangeByScore()
525 | {
526 | $this->assertEquals('zrevrangebyscore k 5 7', $this->redis->zRevRangeByScore('k', 5, 7));
527 | $this->assertEquals('zrevrangebyscore k 5 7 withscores', $this->redis->zRevRangeByScore('k', 5, 7, true));
528 | $this->assertEquals('zrevrangebyscore k 5 7 limit 1 10', $this->redis->zRevRangeByScore('k', 5, 7, false, array(1, 10)));
529 | }
530 |
531 | public function test_zRevRank()
532 | {
533 | $this->assertEquals('zrevrank k m', $this->redis->zRevRank('k', 'm'));
534 | }
535 |
536 | public function test_zScore()
537 | {
538 | $this->assertEquals('zscore k m', $this->redis->zScore('k', 'm'));
539 | }
540 |
541 | public function test_zUnionStore()
542 | {
543 | $this->assertEquals('zunionstore d 2 a b', $this->redis->zunionStore('d', array('a', 'b')));
544 | $this->assertEquals('zunionstore d 2 a b weights 5 7', $this->redis->zunionStore('d', array('a', 'b'), array(5, 7)));
545 | $this->assertEquals('zunionstore d 2 a b aggregate max', $this->redis->zunionStore('d', array('a', 'b'), null, \Jamm\Memory\RedisServer::Aggregate_MAX));
546 | $this->assertEquals('zunionstore d 2 a b weights 5 7 aggregate sum', $this->redis->zunionStore('d', array('a', 'b'), array(5, 7), \Jamm\Memory\RedisServer::Aggregate_SUM));
547 | }
548 |
549 | public function test_IncrBy()
550 | {
551 | $this->assertEquals('incrby key 5', $this->redis->IncrBy('key', 5));
552 | }
553 |
554 | public function test_Keys()
555 | {
556 | $this->assertEquals('keys p*', $this->redis->Keys('p*'));
557 | }
558 |
559 | public function test_Multi()
560 | {
561 | $this->assertEquals('multi', $this->redis->multi());
562 | }
563 |
564 | public function test_Watch()
565 | {
566 | $this->assertEquals('watch', $this->redis->watch());
567 | }
568 |
569 | public function test_Exec()
570 | {
571 | $this->assertEquals('exec', $this->redis->exec());
572 | }
573 |
574 | public function test_Discard()
575 | {
576 | $this->assertEquals('discard', $this->redis->discard());
577 | }
578 |
579 | public function test_sAdd()
580 | {
581 | $this->assertEquals('sadd set v', $this->redis->sAdd('set', 'v'));
582 | $this->assertEquals('sadd set v v1', $this->redis->sAdd('set', 'v', 'v1'));
583 | $this->assertEquals('sadd set v v1', $this->redis->sAdd('set', array('v', 'v1')));
584 | }
585 |
586 | public function test_sIsMember()
587 | {
588 | $this->assertEquals('sismember s v', $this->redis->sIsMember('s', 'v'));
589 | }
590 |
591 | public function test_sMembers()
592 | {
593 | $this->assertEquals('smembers set', $this->redis->sMembers('set'));
594 | }
595 |
596 | public function test_sRem()
597 | {
598 | $this->assertEquals('srem s v', $this->redis->sRem('s', 'v'));
599 | $this->assertEquals('srem s v v1', $this->redis->sRem('s', 'v', 'v1'));
600 | $this->assertEquals('srem s v v1', $this->redis->sRem('s', array('v', 'v1')));
601 | }
602 |
603 | public function test_info()
604 | {
605 | $this->assertEquals('info', $this->redis->info());
606 | }
607 | }
608 |
609 | class MockRedisServer extends \Jamm\Memory\RedisServer
610 | {
611 | protected function _send($args)
612 | {
613 | return strtolower(trim(implode(' ', $args)));
614 | }
615 | }
616 |
--------------------------------------------------------------------------------
/php_scripts/redisserver/demo.php:
--------------------------------------------------------------------------------
1 | save('key', 'value');
10 | //variables of any type can be stored - non-scalar will be serialized (and unserialized then)
11 |
12 | /** Add variable: */
13 | $mem->add('key', 'value');
14 | //if key already exists, false will be returned - it's only difference with the 'save' method
15 |
16 | /** Read variable: */
17 | $var = $mem->read('key');
18 |
19 | /** Delete variable: */
20 | $mem->del('key');
21 |
22 | /** Increment: */
23 | //Increment numeric value
24 | $mem->increment('digit', 1);
25 | //Decrement numeric value
26 | $mem->increment('digit', -1);
27 | //Increment string:
28 | $mem->save('key_s', 'abc', 10);
29 | $mem->increment('key_s', 'defg'); //now key_s = 'abcdefg'
30 | //Increment array:
31 | $mem->save('log', array('start'));
32 | $mem->increment('log', 'message'); //now 'log' = array('start','message')
33 | $mem->increment('log', 'new message', 2); //now 'log' = array('message', 'new message')
34 | //or without initial array:
35 | $mem->increment('users', array('user1')); //now 'users' = array('user1')
36 | $mem->increment('users', array('users2')); //now 'users' = array('user1', 'user2')
37 | //and set pair key-value:
38 | $mem->increment('users', array('admin' => 'user3')); //now 'users' = array('user1', 'user2', 'admin' => 'user3')
39 |
40 | /** Tags */
41 | $mem->save('user_login', 'Adam', 86400, array('users', 'logins'));
42 | $mem->save('guest_login', 'Adam', 86400, 'logins');
43 | $mem->del_by_tags('logins');
44 |
45 | /** Dog-pile protection */
46 | $value = $mem->read('mykey', $ttl_left);
47 | if (!empty($value) && $ttl_left < 10) //if key will expire in less than 10 seconds...
48 | {
49 | //then let's try to update it. But to exclude dog-pile, we should make it exclusively
50 | //so, if I've got exclusive right to refresh this key...
51 | //then I will refresh this key, and nobody else with me simultaneously :)
52 | if ($mem->lock_key('mykey', $auto_unlocker))
53 | {
54 | $value = null;
55 | }
56 | }
57 |
58 | if (empty($value))
59 | {
60 | $value = 'New generated value';
61 | $mem->save('mykey', $value, 43200);
62 | //...
63 | //key will be safely unlocked automatically anyway, don't worry :)
64 | //...
65 | //but you can unlock key in any moment, if you need it:
66 | if (!empty($auto_unlocker)) $mem->unlock_key($auto_unlocker);
67 | }
68 |
--------------------------------------------------------------------------------