├── 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 | ![流程图](ngx_lua_php_queue.png) 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 |
10 | 11 |
12 |
13 |

点击下方按钮进行排队

14 | 15 |

16 |
17 |
18 |
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 | --------------------------------------------------------------------------------