MediaWiki REL1_33
RedisBagOStuff.php
Go to the documentation of this file.
1<?php
34 protected $redisPool;
36 protected $servers;
38 protected $serverTagMap;
41
70 function __construct( $params ) {
71 parent::__construct( $params );
72 $redisConf = [ 'serializer' => 'none' ]; // manage that in this class
73 foreach ( [ 'connectTimeout', 'persistent', 'password' ] as $opt ) {
74 if ( isset( $params[$opt] ) ) {
75 $redisConf[$opt] = $params[$opt];
76 }
77 }
78 $this->redisPool = RedisConnectionPool::singleton( $redisConf );
79
80 $this->servers = $params['servers'];
81 foreach ( $this->servers as $key => $server ) {
82 $this->serverTagMap[is_int( $key ) ? $server : $key] = $server;
83 }
84
85 $this->automaticFailover = $params['automaticFailover'] ?? true;
86
88 }
89
90 protected function doGet( $key, $flags = 0, &$casToken = null ) {
91 $casToken = null;
92
93 list( $server, $conn ) = $this->getConnection( $key );
94 if ( !$conn ) {
95 return false;
96 }
97 try {
98 $value = $conn->get( $key );
99 $casToken = $value;
100 $result = $this->unserialize( $value );
101 } catch ( RedisException $e ) {
102 $result = false;
103 $this->handleException( $conn, $e );
104 }
105
106 $this->logRequest( 'get', $key, $server, $result );
107 return $result;
108 }
109
110 public function set( $key, $value, $expiry = 0, $flags = 0 ) {
111 list( $server, $conn ) = $this->getConnection( $key );
112 if ( !$conn ) {
113 return false;
114 }
115 $expiry = $this->convertToRelative( $expiry );
116 try {
117 if ( $expiry ) {
118 $result = $conn->setex( $key, $expiry, $this->serialize( $value ) );
119 } else {
120 // No expiry, that is very different from zero expiry in Redis
121 $result = $conn->set( $key, $this->serialize( $value ) );
122 }
123 } catch ( RedisException $e ) {
124 $result = false;
125 $this->handleException( $conn, $e );
126 }
127
128 $this->logRequest( 'set', $key, $server, $result );
129 return $result;
130 }
131
132 public function delete( $key, $flags = 0 ) {
133 list( $server, $conn ) = $this->getConnection( $key );
134 if ( !$conn ) {
135 return false;
136 }
137 try {
138 $conn->del( $key );
139 // Return true even if the key didn't exist
140 $result = true;
141 } catch ( RedisException $e ) {
142 $result = false;
143 $this->handleException( $conn, $e );
144 }
145
146 $this->logRequest( 'delete', $key, $server, $result );
147 return $result;
148 }
149
150 public function getMulti( array $keys, $flags = 0 ) {
151 $batches = [];
152 $conns = [];
153 foreach ( $keys as $key ) {
154 list( $server, $conn ) = $this->getConnection( $key );
155 if ( !$conn ) {
156 continue;
157 }
158 $conns[$server] = $conn;
159 $batches[$server][] = $key;
160 }
161 $result = [];
162 foreach ( $batches as $server => $batchKeys ) {
163 $conn = $conns[$server];
164 try {
165 $conn->multi( Redis::PIPELINE );
166 foreach ( $batchKeys as $key ) {
167 $conn->get( $key );
168 }
169 $batchResult = $conn->exec();
170 if ( $batchResult === false ) {
171 $this->debug( "multi request to $server failed" );
172 continue;
173 }
174 foreach ( $batchResult as $i => $value ) {
175 if ( $value !== false ) {
176 $result[$batchKeys[$i]] = $this->unserialize( $value );
177 }
178 }
179 } catch ( RedisException $e ) {
180 $this->handleException( $conn, $e );
181 }
182 }
183
184 $this->debug( "getMulti for " . count( $keys ) . " keys " .
185 "returned " . count( $result ) . " results" );
186 return $result;
187 }
188
189 public function setMulti( array $data, $expiry = 0, $flags = 0 ) {
190 $batches = [];
191 $conns = [];
192 foreach ( $data as $key => $value ) {
193 list( $server, $conn ) = $this->getConnection( $key );
194 if ( !$conn ) {
195 continue;
196 }
197 $conns[$server] = $conn;
198 $batches[$server][] = $key;
199 }
200
201 $expiry = $this->convertToRelative( $expiry );
202 $result = true;
203 foreach ( $batches as $server => $batchKeys ) {
204 $conn = $conns[$server];
205 try {
206 $conn->multi( Redis::PIPELINE );
207 foreach ( $batchKeys as $key ) {
208 if ( $expiry ) {
209 $conn->setex( $key, $expiry, $this->serialize( $data[$key] ) );
210 } else {
211 $conn->set( $key, $this->serialize( $data[$key] ) );
212 }
213 }
214 $batchResult = $conn->exec();
215 if ( $batchResult === false ) {
216 $this->debug( "setMulti request to $server failed" );
217 continue;
218 }
219 foreach ( $batchResult as $value ) {
220 if ( $value === false ) {
221 $result = false;
222 }
223 }
224 } catch ( RedisException $e ) {
225 $this->handleException( $conn, $e );
226 $result = false;
227 }
228 }
229
230 return $result;
231 }
232
233 public function deleteMulti( array $keys, $flags = 0 ) {
234 $batches = [];
235 $conns = [];
236 foreach ( $keys as $key ) {
237 list( $server, $conn ) = $this->getConnection( $key );
238 if ( !$conn ) {
239 continue;
240 }
241 $conns[$server] = $conn;
242 $batches[$server][] = $key;
243 }
244
245 $result = true;
246 foreach ( $batches as $server => $batchKeys ) {
247 $conn = $conns[$server];
248 try {
249 $conn->multi( Redis::PIPELINE );
250 foreach ( $batchKeys as $key ) {
251 $conn->del( $key );
252 }
253 $batchResult = $conn->exec();
254 if ( $batchResult === false ) {
255 $this->debug( "deleteMulti request to $server failed" );
256 continue;
257 }
258 foreach ( $batchResult as $value ) {
259 if ( $value === false ) {
260 $result = false;
261 }
262 }
263 } catch ( RedisException $e ) {
264 $this->handleException( $conn, $e );
265 $result = false;
266 }
267 }
268
269 return $result;
270 }
271
272 public function add( $key, $value, $expiry = 0, $flags = 0 ) {
273 list( $server, $conn ) = $this->getConnection( $key );
274 if ( !$conn ) {
275 return false;
276 }
277 $expiry = $this->convertToRelative( $expiry );
278 try {
279 if ( $expiry ) {
280 $result = $conn->set(
281 $key,
282 $this->serialize( $value ),
283 [ 'nx', 'ex' => $expiry ]
284 );
285 } else {
286 $result = $conn->setnx( $key, $this->serialize( $value ) );
287 }
288 } catch ( RedisException $e ) {
289 $result = false;
290 $this->handleException( $conn, $e );
291 }
292
293 $this->logRequest( 'add', $key, $server, $result );
294 return $result;
295 }
296
309 public function incr( $key, $value = 1 ) {
310 list( $server, $conn ) = $this->getConnection( $key );
311 if ( !$conn ) {
312 return false;
313 }
314 try {
315 if ( !$conn->exists( $key ) ) {
316 return false;
317 }
318 // @FIXME: on races, the key may have a 0 TTL
319 $result = $conn->incrBy( $key, $value );
320 } catch ( RedisException $e ) {
321 $result = false;
322 $this->handleException( $conn, $e );
323 }
324
325 $this->logRequest( 'incr', $key, $server, $result );
326 return $result;
327 }
328
329 public function changeTTL( $key, $exptime = 0, $flags = 0 ) {
330 list( $server, $conn ) = $this->getConnection( $key );
331 if ( !$conn ) {
332 return false;
333 }
334
335 $relative = $this->expiryIsRelative( $exptime );
336 try {
337 if ( $exptime == 0 ) {
338 $result = $conn->persist( $key );
339 $this->logRequest( 'persist', $key, $server, $result );
340 } elseif ( $relative ) {
341 $result = $conn->expire( $key, $this->convertToRelative( $exptime ) );
342 $this->logRequest( 'expire', $key, $server, $result );
343 } else {
344 $result = $conn->expireAt( $key, $this->convertToExpiry( $exptime ) );
345 $this->logRequest( 'expireAt', $key, $server, $result );
346 }
347 } catch ( RedisException $e ) {
348 $result = false;
349 $this->handleException( $conn, $e );
350 }
351
352 return $result;
353 }
354
359 protected function serialize( $data ) {
360 // Serialize anything but integers so INCR/DECR work
361 // Do not store integer-like strings as integers to avoid type confusion (T62563)
362 return is_int( $data ) ? $data : serialize( $data );
363 }
364
369 protected function unserialize( $data ) {
370 $int = intval( $data );
371 return $data === (string)$int ? $int : unserialize( $data );
372 }
373
379 protected function getConnection( $key ) {
380 $candidates = array_keys( $this->serverTagMap );
381
382 if ( count( $this->servers ) > 1 ) {
383 ArrayUtils::consistentHashSort( $candidates, $key, '/' );
384 if ( !$this->automaticFailover ) {
385 $candidates = array_slice( $candidates, 0, 1 );
386 }
387 }
388
389 while ( ( $tag = array_shift( $candidates ) ) !== null ) {
390 $server = $this->serverTagMap[$tag];
391 $conn = $this->redisPool->getConnection( $server, $this->logger );
392 if ( !$conn ) {
393 continue;
394 }
395
396 // If automatic failover is enabled, check that the server's link
397 // to its master (if any) is up -- but only if there are other
398 // viable candidates left to consider. Also, getMasterLinkStatus()
399 // does not work with twemproxy, though $candidates will be empty
400 // by now in such cases.
401 if ( $this->automaticFailover && $candidates ) {
402 try {
403 if ( $this->getMasterLinkStatus( $conn ) === 'down' ) {
404 // If the master cannot be reached, fail-over to the next server.
405 // If masters are in data-center A, and replica DBs in data-center B,
406 // this helps avoid the case were fail-over happens in A but not
407 // to the corresponding server in B (e.g. read/write mismatch).
408 continue;
409 }
410 } catch ( RedisException $e ) {
411 // Server is not accepting commands
412 $this->handleException( $conn, $e );
413 continue;
414 }
415 }
416
417 return [ $server, $conn ];
418 }
419
421
422 return [ false, false ];
423 }
424
431 protected function getMasterLinkStatus( RedisConnRef $conn ) {
432 $info = $conn->info();
433 return $info['master_link_status'] ?? null;
434 }
435
440 protected function logError( $msg ) {
441 $this->logger->error( "Redis error: $msg" );
442 }
443
452 protected function handleException( RedisConnRef $conn, $e ) {
454 $this->redisPool->handleError( $conn, $e );
455 }
456
464 public function logRequest( $method, $key, $server, $result ) {
465 $this->debug( "$method $key on $server: " .
466 ( $result === false ? "failure" : "success" ) );
467 }
468}
serialize()
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:58
convertToExpiry( $exptime)
Convert an optionally relative time to an absolute time.
expiryIsRelative( $exptime)
debug( $text)
convertToRelative( $exptime)
Convert an optionally absolute expiry time to a relative time.
setLastError( $err)
Set the "last error" registry.
Redis-based caching module for redis server >= 2.6.12 and phpredis >= 2.2.4.
RedisConnectionPool $redisPool
incr( $key, $value=1)
Non-atomic implementation of incr().
getMasterLinkStatus(RedisConnRef $conn)
Check the master link status of a Redis server that is configured as a replica DB.
array $servers
List of server names.
setMulti(array $data, $expiry=0, $flags=0)
Batch insertion/replace.
changeTTL( $key, $exptime=0, $flags=0)
Change the expiration on a key if it exists.
add( $key, $value, $expiry=0, $flags=0)
Insert an item if it does not already exist.
logError( $msg)
Log a fatal error.
getConnection( $key)
Get a Redis object with a connection suitable for fetching the specified key.
deleteMulti(array $keys, $flags=0)
Batch deletion.
__construct( $params)
Construct a RedisBagOStuff object.
doGet( $key, $flags=0, &$casToken=null)
array $serverTagMap
Map of (tag => server name)
handleException(RedisConnRef $conn, $e)
The redis extension throws an exception in response to various read, write and protocol errors.
getMulti(array $keys, $flags=0)
Get an associative array containing the item for each of the keys that have items.
logRequest( $method, $key, $server, $result)
Send information about a single request to the debug log.
Helper class to handle automatically marking connectons as reusable (via RAII pattern)
Helper class to manage Redis connections.
static singleton(array $options)
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
namespace being checked & $result
Definition hooks.txt:2340
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:181
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
returning false will NOT prevent logging $e
Definition hooks.txt:2175
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
storage can be distributed across multiple servers
Definition memcached.txt:33
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$params