Proyectos de Subversion Moodle

Rev

Rev 11 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 11 Rev 1441
Línea 12... Línea 12...
12
// GNU General Public License for more details.
12
// GNU General Public License for more details.
13
//
13
//
14
// You should have received a copy of the GNU General Public License
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
Línea 16... Línea -...
16
 
-
 
17
/**
16
 
18
 * Redis Cache Store - Main library
17
use core_cache\configurable_cache_interface;
19
 *
18
use core_cache\definition;
20
 * @package   cachestore_redis
19
use core_cache\key_aware_cache_interface;
21
 * @copyright 2013 Adam Durana
20
use core_cache\lockable_cache_interface;
-
 
21
use core_cache\searchable_cache_interface;
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
use core_cache\store;
23
 */
-
 
24
 
23
use core\clock;
Línea 25... Línea 24...
25
defined('MOODLE_INTERNAL') || die();
24
use core\di;
26
 
25
 
27
/**
26
/**
28
 * Redis Cache Store
27
 * Redis Cache Store
29
 *
28
 *
30
 * To allow separation of definitions in Moodle and faster purging, each cache
29
 * To allow separation of definitions in Moodle and faster purging, each cache
31
 * is implemented as a Redis hash.  That is a trade-off between having functionality of TTL
30
 * is implemented as a Redis hash.  That is a trade-off between having functionality of TTL
32
 * and being able to manage many caches in a single redis instance.  Given the recommendation
31
 * and being able to manage many caches in a single redis instance.  Given the recommendation
33
 * not to use TTL if at all possible and the benefits of having many stores in Redis using the
32
 * not to use TTL if at all possible and the benefits of having many stores in Redis using the
-
 
33
 * hash configuration, the hash implementation has been used.
34
 * hash configuration, the hash implementation has been used.
34
 *
35
 *
35
 * @package   cachestore_redis
36
 * @copyright   2013 Adam Durana
36
 * @copyright   2013 Adam Durana
37
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-
 
38
 */
38
 */
39
class cachestore_redis extends store implements
-
 
40
    key_aware_cache_interface,
-
 
41
    configurable_cache_interface,
-
 
42
    searchable_cache_interface,
39
class cachestore_redis extends cache_store implements cache_is_key_aware, cache_is_lockable,
43
    lockable_cache_interface
40
        cache_is_configurable, cache_is_searchable {
44
{
41
    /**
45
    /**
42
     * Compressor: none.
46
     * Compressor: none.
Línea 61... Línea 65...
61
    /**
65
    /**
62
     * @var int Number of items to delete from cache in one batch when expiring old TTL data.
66
     * @var int Number of items to delete from cache in one batch when expiring old TTL data.
63
     */
67
     */
64
    const TTL_EXPIRE_BATCH = 10000;
68
    const TTL_EXPIRE_BATCH = 10000;
Línea -... Línea 69...
-
 
69
 
-
 
70
    /** @var int The number of seconds to wait for a connection or response from the Redis server. */
-
 
71
    const CONNECTION_TIMEOUT = 3;
65
 
72
 
66
    /**
73
    /**
67
     * Name of this store.
74
     * Name of this store.
68
     *
75
     *
69
     * @var string
76
     * @var string
Línea 85... Línea 92...
85
    protected $isready = false;
92
    protected $isready = false;
Línea 86... Línea 93...
86
 
93
 
87
    /**
94
    /**
88
     * Cache definition for this store.
95
     * Cache definition for this store.
89
     *
96
     *
90
     * @var cache_definition
97
     * @var definition
91
     */
98
     */
Línea 92... Línea 99...
92
    protected $definition = null;
99
    protected $definition = null;
93
 
100
 
Línea 110... Línea 117...
110
     *
117
     *
111
     * @var int
118
     * @var int
112
     */
119
     */
113
    protected $compressor = self::COMPRESSOR_NONE;
120
    protected $compressor = self::COMPRESSOR_NONE;
Línea -... Línea 121...
-
 
121
 
-
 
122
 
-
 
123
    /**
-
 
124
     * The number of seconds to wait for a connection or response from the Redis server.
-
 
125
     *
-
 
126
     * @var int
-
 
127
     */
-
 
128
    protected $connectiontimeout = self::CONNECTION_TIMEOUT;
114
 
129
 
115
    /**
130
    /**
116
     * Bytes read or written by last call to set()/get() or set_many()/get_many().
131
     * Bytes read or written by last call to set()/get() or set_many()/get_many().
117
     *
132
     *
118
     * @var int
133
     * @var int
Línea 126... Línea 141...
126
    protected $locktimeout = 600;
141
    protected $locktimeout = 600;
Línea 127... Línea 142...
127
 
142
 
128
    /** @var ?array Array of current locks, or null if we haven't registered shutdown function */
143
    /** @var ?array Array of current locks, or null if we haven't registered shutdown function */
Línea -... Línea 144...
-
 
144
    protected $currentlocks = null;
-
 
145
 
-
 
146
    /** @var clock */
129
    protected $currentlocks = null;
147
    private readonly clock $clock;
130
 
148
 
131
    /**
149
    /**
132
     * Determines if the requirements for this type of store are met.
150
     * Determines if the requirements for this type of store are met.
133
     *
151
     *
Línea 190... Línea 208...
190
            $this->serializer = (int)$configuration['serializer'];
208
            $this->serializer = (int)$configuration['serializer'];
191
        }
209
        }
192
        if (array_key_exists('compressor', $configuration)) {
210
        if (array_key_exists('compressor', $configuration)) {
193
            $this->compressor = (int)$configuration['compressor'];
211
            $this->compressor = (int)$configuration['compressor'];
194
        }
212
        }
-
 
213
        if (array_key_exists('connectiontimeout', $configuration)) {
-
 
214
            $this->connectiontimeout = (int)$configuration['connectiontimeout'];
-
 
215
        }
195
        if (array_key_exists('lockwait', $configuration)) {
216
        if (array_key_exists('lockwait', $configuration)) {
196
            $this->lockwait = (int)$configuration['lockwait'];
217
            $this->lockwait = (int)$configuration['lockwait'];
197
        }
218
        }
198
        if (array_key_exists('locktimeout', $configuration)) {
219
        if (array_key_exists('locktimeout', $configuration)) {
199
            $this->locktimeout = (int)$configuration['locktimeout'];
220
            $this->locktimeout = (int)$configuration['locktimeout'];
200
        }
221
        }
201
        $this->redis = $this->new_redis($configuration);
222
        $this->redis = $this->new_redis($configuration);
-
 
223
        $this->clock = di::get(clock::class);
202
    }
224
    }
Línea 203... Línea 225...
203
 
225
 
204
    /**
226
    /**
205
     * Create a new Redis or RedisCluster instance and connect to the server.
227
     * Create a new Redis or RedisCluster instance and connect to the server.
Línea 263... Línea 285...
263
        }
285
        }
264
        // Connect to redis.
286
        // Connect to redis.
265
        $redis = null;
287
        $redis = null;
266
        try {
288
        try {
267
            // Create a $redis object of a RedisCluster or Redis class.
289
            // Create a $redis object of a RedisCluster or Redis class.
-
 
290
            $phpredisversion = phpversion('redis');
268
            if ($clustermode) {
291
            if ($clustermode) {
-
 
292
                if (version_compare($phpredisversion, '6.0.0', '>=')) {
-
 
293
                    // Named parameters are fully supported starting from version 6.0.0.
-
 
294
                    $redis = new RedisCluster(
-
 
295
                        name: null,
-
 
296
                        seeds: $trimmedservers,
-
 
297
                        timeout: $this->connectiontimeout, // Timeout.
-
 
298
                        read_timeout: $this->connectiontimeout, // Read timeout.
-
 
299
                        persistent: true,
-
 
300
                        auth: $password,
269
                $redis = new RedisCluster(null, $trimmedservers, 1, 1, true, $password, !empty($opts) ? $opts : null);
301
                        context: !empty($opts) ? $opts : null,
-
 
302
                    );
-
 
303
                } else {
-
 
304
                    $redis = new RedisCluster(
-
 
305
                        null,
-
 
306
                        $trimmedservers,
-
 
307
                        $this->connectiontimeout,
-
 
308
                        $this->connectiontimeout,
-
 
309
                        true, $password,
-
 
310
                        !empty($opts) ? $opts : null,
-
 
311
                    );
-
 
312
                }
270
            } else {
313
            } else {
271
                $redis = new Redis();
314
                $redis = new Redis();
272
                $redis->connect($server, $port, 1, null, 100, 1, $opts);
315
                if (version_compare($phpredisversion, '6.0.0', '>=')) {
-
 
316
                    // Named parameters are fully supported starting from version 6.0.0.
-
 
317
                    $redis->connect(
-
 
318
                        host: $server,
-
 
319
                        port: $port,
-
 
320
                        timeout: $this->connectiontimeout, // Timeout.
-
 
321
                        retry_interval: 100, // Retry interval.
-
 
322
                        read_timeout: $this->connectiontimeout, // Read timeout.
-
 
323
                        context: $opts,
-
 
324
                    );
-
 
325
                } else {
-
 
326
                    $redis->connect(
-
 
327
                        $server, $port,
-
 
328
                        $this->connectiontimeout,
-
 
329
                        null,
-
 
330
                        100,
-
 
331
                        $this->connectiontimeout,
-
 
332
                        $opts,
-
 
333
                    );
-
 
334
                }
-
 
335
 
273
                if (!empty($password)) {
336
                if (!empty($password)) {
274
                    $redis->auth($password);
337
                    $redis->auth($password);
275
                }
338
                }
276
            }
339
            }
Línea 329... Línea 392...
329
    }
392
    }
Línea 330... Línea 393...
330
 
393
 
331
    /**
394
    /**
332
     * Initialize the store.
395
     * Initialize the store.
333
     *
396
     *
334
     * @param cache_definition $definition
397
     * @param definition $definition
335
     * @return bool
398
     * @return bool
336
     */
399
     */
337
    public function initialise(cache_definition $definition) {
400
    public function initialise(definition $definition) {
338
        $this->definition = $definition;
401
        $this->definition = $definition;
339
        $this->hash       = $definition->generate_definition_hash();
402
        $this->hash       = $definition->generate_definition_hash();
340
        return true;
403
        return true;
Línea 551... Línea 614...
551
    }
614
    }
Línea 552... Línea 615...
552
 
615
 
553
    /**
616
    /**
554
     * Determines if the store has a given key.
617
     * Determines if the store has a given key.
555
     *
618
     *
556
     * @see cache_is_key_aware
619
     * @see key_aware_cache_interface
557
     * @param string $key The key to check for.
620
     * @param string $key The key to check for.
558
     * @return bool True if the key exists, false if it does not.
621
     * @return bool True if the key exists, false if it does not.
559
     */
622
     */
560
    public function has($key) {
623
    public function has($key) {
561
        return !empty($this->redis->hExists($this->hash, $key));
624
        return !empty($this->redis->hExists($this->hash, $key));
Línea 562... Línea 625...
562
    }
625
    }
563
 
626
 
564
    /**
627
    /**
565
     * Determines if the store has any of the keys in a list.
628
     * Determines if the store has any of the keys in a list.
566
     *
629
     *
567
     * @see cache_is_key_aware
630
     * @see key_aware_cache_interface
568
     * @param array $keys The keys to check for.
631
     * @param array $keys The keys to check for.
569
     * @return bool True if any of the keys are found, false none of the keys are found.
632
     * @return bool True if any of the keys are found, false none of the keys are found.
570
     */
633
     */
Línea 578... Línea 641...
578
    }
641
    }
Línea 579... Línea 642...
579
 
642
 
580
    /**
643
    /**
581
     * Determines if the store has all of the keys in a list.
644
     * Determines if the store has all of the keys in a list.
582
     *
645
     *
583
     * @see cache_is_key_aware
646
     * @see key_aware_cache_interface
584
     * @param array $keys The keys to check for.
647
     * @param array $keys The keys to check for.
585
     * @return bool True if all of the keys are found, false otherwise.
648
     * @return bool True if all of the keys are found, false otherwise.
586
     */
649
     */
587
    public function has_all(array $keys) {
650
    public function has_all(array $keys) {
Línea 594... Línea 657...
594
    }
657
    }
Línea 595... Línea 658...
595
 
658
 
596
    /**
659
    /**
597
     * Tries to acquire a lock with a given name.
660
     * Tries to acquire a lock with a given name.
598
     *
661
     *
599
     * @see cache_is_lockable
662
     * @see lockable_cache_interface
600
     * @param string $key Name of the lock to acquire.
663
     * @param string $key Name of the lock to acquire.
601
     * @param string $ownerid Information to identify owner of lock if acquired.
664
     * @param string $ownerid Information to identify owner of lock if acquired.
602
     * @return bool True if the lock was acquired, false if it was not.
665
     * @return bool True if the lock was acquired, false if it was not.
603
     */
666
     */
604
    public function acquire_lock($key, $ownerid) {
667
    public function acquire_lock($key, $ownerid) {
-
 
668
        $timelimit = $this->clock->time() + $this->lockwait;
-
 
669
        $startlocktime = $this->clock->time();
605
        $timelimit = time() + $this->lockwait;
670
 
606
        do {
671
        do {
-
 
672
            // Lock already exists, wait 1 second then retry.
-
 
673
            $haslock = $this->redis->set($key, $ownerid, ['nx', 'ex' => $this->locktimeout]);
607
            // If the key doesn't already exist, grab it and return true.
674
            if (!$haslock) {
608
            if ($this->redis->setnx($key, $ownerid)) {
675
                if ($this->clock->time() < $startlocktime + 5) {
609
                // Ensure Redis deletes the key after a bit in case something goes wrong.
676
                    // We want a random delay to stagger the polling load. Ideally, this delay should be a fraction
610
                $this->redis->expire($key, $this->locktimeout);
677
                    // of the average response time. If it is too small we will poll too much and if it is too
611
                // If we haven't got it already, better register a shutdown function.
678
                    // large we will waste time waiting for no reason. 100ms is the default starting point.
-
 
679
                    $delay = rand(100, 110);
-
 
680
                } else {
612
                if ($this->currentlocks === null) {
681
                    // If we don't get a lock within 5 seconds then there must be a very long-lived process holding the lock
613
                    core_shutdown_manager::register_function([$this, 'shutdown_release_locks']);
682
                    // so throttle back to just polling roughly once a second.
614
                    $this->currentlocks = [];
683
                    $delay = rand(1000, 1100);
-
 
684
                }
615
                }
685
 
616
                $this->currentlocks[$key] = $ownerid;
686
                usleep($delay * 1000);
617
                return true;
687
                continue;
-
 
688
            }
-
 
689
 
-
 
690
            // If we haven't got it already, better register a shutdown function.
-
 
691
            if ($this->currentlocks === null) {
618
            }
692
                core_shutdown_manager::register_function([$this, 'shutdown_release_locks']);
-
 
693
                $this->currentlocks = [];
-
 
694
            }
-
 
695
 
-
 
696
            $this->currentlocks[$key] = $ownerid;
619
            // Wait 1 second then retry.
697
 
620
            sleep(1);
698
            return true;
-
 
699
        } while ($this->clock->time() < $timelimit);
621
        } while (time() < $timelimit);
700
 
622
        return false;
701
        return false;
Línea 623... Línea 702...
623
    }
702
    }
624
 
703
 
Línea 637... Línea 716...
637
    }
716
    }
Línea 638... Línea 717...
638
 
717
 
639
    /**
718
    /**
640
     * Checks a lock with a given name and owner information.
719
     * Checks a lock with a given name and owner information.
641
     *
720
     *
642
     * @see cache_is_lockable
721
     * @see lockable_cache_interface
643
     * @param string $key Name of the lock to check.
722
     * @param string $key Name of the lock to check.
644
     * @param string $ownerid Owner information to check existing lock against.
723
     * @param string $ownerid Owner information to check existing lock against.
645
     * @return mixed True if the lock exists and the owner information matches, null if the lock does not
724
     * @return mixed True if the lock exists and the owner information matches, null if the lock does not
646
     *      exist, and false otherwise.
725
     *      exist, and false otherwise.
Línea 683... Línea 762...
683
    }
762
    }
Línea 684... Línea 763...
684
 
763
 
685
    /**
764
    /**
686
     * Releases a given lock if the owner information matches.
765
     * Releases a given lock if the owner information matches.
687
     *
766
     *
688
     * @see cache_is_lockable
767
     * @see lockable_cache_interface
689
     * @param string $key Name of the lock to release.
768
     * @param string $key Name of the lock to release.
690
     * @param string $ownerid Owner information to use.
769
     * @param string $ownerid Owner information to use.
691
     * @return bool True if the lock is released, false if it is not.
770
     * @return bool True if the lock is released, false if it is not.
692
     */
771
     */
Línea 802... Línea 881...
802
    }
881
    }
Línea 803... Línea 882...
803
 
882
 
804
    /**
883
    /**
805
     * Creates a configuration array from given 'add instance' form data.
884
     * Creates a configuration array from given 'add instance' form data.
806
     *
885
     *
-
 
886
     * @see configurable_cache_interface
807
     * @see cache_is_configurable
887
     *
808
     * @param stdClass $data
888
     * @param stdClass $data
809
     * @return array
889
     * @return array
810
     */
890
     */
811
    public static function config_get_configuration_array($data) {
891
    public static function config_get_configuration_array($data) {
812
        return array(
892
        return array(
813
            'server' => $data->server,
893
            'server' => $data->server,
814
            'prefix' => $data->prefix,
894
            'prefix' => $data->prefix,
815
            'password' => $data->password,
895
            'password' => $data->password,
816
            'serializer' => $data->serializer,
896
            'serializer' => $data->serializer,
-
 
897
            'compressor' => $data->compressor,
817
            'compressor' => $data->compressor,
898
            'connectiontimeout' => $data->connectiontimeout,
818
            'encryption' => $data->encryption,
899
            'encryption' => $data->encryption,
819
            'cafile' => $data->cafile,
900
            'cafile' => $data->cafile,
820
            'clustermode' => $data->clustermode,
901
            'clustermode' => $data->clustermode,
821
        );
902
        );
Línea 822... Línea 903...
822
    }
903
    }
823
 
904
 
824
    /**
905
    /**
825
     * Sets form data from a configuration array.
906
     * Sets form data from a configuration array.
826
     *
907
     *
827
     * @see cache_is_configurable
908
     * @see configurable_cache_interface
828
     * @param moodleform $editform
909
     * @param moodleform $editform
829
     * @param array $config
910
     * @param array $config
830
     */
911
     */
Línea 837... Línea 918...
837
            $data['serializer'] = $config['serializer'];
918
            $data['serializer'] = $config['serializer'];
838
        }
919
        }
839
        if (!empty($config['compressor'])) {
920
        if (!empty($config['compressor'])) {
840
            $data['compressor'] = $config['compressor'];
921
            $data['compressor'] = $config['compressor'];
841
        }
922
        }
-
 
923
        if (!empty($config['connectiontimeout'])) {
-
 
924
            $data['connectiontimeout'] = $config['connectiontimeout'];
-
 
925
        }
842
        if (!empty($config['encryption'])) {
926
        if (!empty($config['encryption'])) {
843
            $data['encryption'] = $config['encryption'];
927
            $data['encryption'] = $config['encryption'];
844
        }
928
        }
845
        if (!empty($config['cafile'])) {
929
        if (!empty($config['cafile'])) {
846
            $data['cafile'] = $config['cafile'];
930
            $data['cafile'] = $config['cafile'];
Línea 853... Línea 937...
853
 
937
 
854
 
938
 
855
    /**
939
    /**
856
     * Creates an instance of the store for testing.
940
     * Creates an instance of the store for testing.
857
     *
941
     *
858
     * @param cache_definition $definition
942
     * @param definition $definition
859
     * @return mixed An instance of the store, or false if an instance cannot be created.
943
     * @return mixed An instance of the store, or false if an instance cannot be created.
860
     */
944
     */
861
    public static function initialise_test_instance(cache_definition $definition) {
945
    public static function initialise_test_instance(definition $definition) {
862
        if (!self::are_requirements_met()) {
946
        if (!self::are_requirements_met()) {
863
            return false;
947
            return false;
864
        }
948
        }