├── .gitignore ├── readme.md ├── composer.json └── src ├── SwooleRedisStore.php ├── SwooleRedisStoreBasic.php ├── SwooleRedisServiceProvider.php ├── RedisPoolManager.php ├── SwooleRedisPool.php └── SwooleRedisPoolConnection.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## laravel swoole redis pool 2 | 3 | Laravel package to provide swoole redis pool integration,laravel redis pool cache and session driver. Aims to avoid redis server timeout exception 4 | 5 | ```$xslt 6 | public $config = [ 7 | //min 3 8 | 'poolMin' => 3, 9 | //max 1000 10 | 'poolMax' => 64, 11 | //when lost connection retry 12 | 'retryTimes' => 2, 13 | 14 | //options config 15 | 'connect_timeout' => 1, 16 | 'timeout' => 1, 17 | 'reconnect' => 1 18 | ]; 19 | ``` 20 | 21 | ## install 22 | `composer require falcolee/laravel-swoole-redis` 23 | 24 | ## how to use 25 | * step 1: make true you've got a right swoole environment 26 | * step 2: 27 | add 28 | ``` 29 | 'redis_pool' => [ 30 | 'driver' => 'redis', 31 | 'connection' => 'default', 32 | ], 33 | ``` 34 | in your config/cache.php `stores` section below `redis` array 35 | 36 | * step 3: change your redis drive or session drive to `redis_pool` in your `.env` file , that is it 37 | 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "falcolee/laravel-swoole-redis", 3 | "description": "Laravel package to provide swoole redis pool integration,laravel redis pool cache and session driver. Aims to avoid redis server timeout exception", 4 | "license": "MIT", 5 | "keywords": [ 6 | "laravel swoole redis pool", 7 | "laravel seoole redis cache", 8 | "laravel seoole session driver" 9 | ], 10 | "homepage": "https://github.com/falcolee/laravel-swoole-redis", 11 | "authors": [ 12 | { 13 | "name": "Falco Lee", 14 | "email": "skylzl1007@gmail.com", 15 | "homepage": "https://github.com/falcolee", 16 | "role": "Developer" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=5.6.4", 21 | "illuminate/support": "^5.0|^6.0|^7.0|^8.0" 22 | }, 23 | "require-dev": { 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Falcolee\\SwooleRedis\\": "src" 28 | } 29 | }, 30 | "autoload-dev": { 31 | }, 32 | "extra": { 33 | "laravel": { 34 | "providers": [ 35 | "Falcolee\\SwooleRedis\\SwooleRedisServiceProvider" 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/SwooleRedisStore.php: -------------------------------------------------------------------------------- 1 | redis = $redis; 33 | $this->setPrefix($prefix); 34 | $this->setConnection($connection); 35 | } 36 | 37 | /** 38 | * Store an item in the cache if the key doesn't exist. 39 | * 40 | * @param string $key 41 | * @param mixed $value 42 | * @param float|int $minutes 43 | * @return bool 44 | */ 45 | public function add($key, $value, $minutes) 46 | { 47 | $lua = "return redis.call('exists',KEYS[1])<1 and redis.call('setex',KEYS[1],ARGV[2],ARGV[1])"; 48 | 49 | $result = (bool)$this->connection()->eval( 50 | $lua, [$this->prefix . $key, $this->serialize($value), (int)max(1, $minutes * 60)], 1 51 | ); 52 | return $result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/SwooleRedisStoreBasic.php: -------------------------------------------------------------------------------- 1 | redis = new Redis(); 20 | $this->setPrefix(config('cache.prefix')); 21 | $this->setConnection(config("cache.stores.redis_pool.connection", "default")); 22 | } 23 | 24 | /** 25 | * Get the Redis connection instance. 26 | * 27 | * @return \Predis\ClientInterface 28 | */ 29 | public function connection() 30 | { 31 | // load the config or use the default 32 | $config = config('database.redis.' . $this->connection, [ 33 | 'host' => env('REDIS_HOST', 'localhost'), 34 | 'password' => env('REDIS_PASSWORD', null), 35 | 'port' => env('REDIS_PORT', 6379), 36 | 'database' => 0, 37 | ]); 38 | $this->redis->connect($config['host'], $config['port']); 39 | if ($config['password']) { 40 | $this->redis->auth($config['password']); 41 | } 42 | return $this->redis; 43 | } 44 | 45 | /** 46 | * Store an item in the cache if the key doesn't exist. 47 | * 48 | * @param string $key 49 | * @param mixed $value 50 | * @param float|int $minutes 51 | * @return bool 52 | */ 53 | public function add($key, $value, $minutes) 54 | { 55 | $lua = "return redis.call('exists',KEYS[1])<1 and redis.call('setex',KEYS[1],ARGV[2],ARGV[1])"; 56 | 57 | return (bool)$this->connection()->eval( 58 | $lua, [$this->prefix . $key, $this->serialize($value), (int)max(1, $minutes * 60)], 1 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/SwooleRedisServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerRedisPoolStore(); 31 | $this->registerCache(); 32 | $this->registerSession(); 33 | } 34 | 35 | protected function registerRedisPoolStore(){ 36 | $this->app->singleton(RedisPoolManager::class, function ($app) { 37 | $config = $app->make('config')->get('database.redis'); 38 | return new RedisPoolManager($config,false); 39 | }); 40 | 41 | $this->app->alias(RedisPoolManager::class, 'redis_pool'); 42 | } 43 | 44 | protected function registerSession(){ 45 | $this->app->afterResolving('session', function (SessionManager $manager) { 46 | $manager->extend('redis_pool',function ($app){ 47 | return new CacheBasedSessionHandler($app['cache']->store('redis_pool'), config('session.lifetime')); 48 | }); 49 | }); 50 | } 51 | 52 | protected function registerCache(){ 53 | $this->app->afterResolving('cache', function (CacheManager $manager) { 54 | $manager->extend('redis_pool',function ($app) use ($manager){ 55 | return $manager->repository(new SwooleRedisStore($app->make('redis_pool'),config('cache.prefix'),config("cache.stores.redis_pool.connection", "default"))); 56 | }); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/RedisPoolManager.php: -------------------------------------------------------------------------------- 1 | config = $config; 42 | $this->autoFill = $autoFill; 43 | } 44 | 45 | /** 46 | * Get a Redis pool connection by name. 47 | * 48 | * @param string|null $name 49 | * @return 50 | */ 51 | public function connection($name = null) 52 | { 53 | $name = $name ?: 'default'; 54 | if (isset($this->connections[$name])) { 55 | return $this->connections[$name]; 56 | } 57 | 58 | return $this->connections[$name] = $this->resolve($name); 59 | } 60 | 61 | /** 62 | * Resolve the given connection by name. 63 | * 64 | * @param string|null $name 65 | * @return \Illuminate\Redis\Connections\Connection 66 | * 67 | * @throws \InvalidArgumentException 68 | */ 69 | public function resolve($name = null) 70 | { 71 | $name = $name ?: 'default'; 72 | 73 | $options = $this->config['options'] ?? []; 74 | 75 | if (isset($this->config[$name])) { 76 | return $this->connect($this->config[$name], $options); 77 | } 78 | 79 | throw new InvalidArgumentException( 80 | "Redis connection [{$name}] not configured." 81 | ); 82 | } 83 | 84 | /** 85 | * @param $config 86 | * @param $options 87 | * @return \Illuminate\Redis\Connections\Connection 88 | */ 89 | public function connect($config,$options=[]){ 90 | return new SwooleRedisPoolConnection(new SwooleRedisPool($config,$this->autoFill)); 91 | } 92 | 93 | /** 94 | * Return all of the created connections. 95 | * 96 | * @return array 97 | */ 98 | public function connections() 99 | { 100 | return $this->connections; 101 | } 102 | } -------------------------------------------------------------------------------- /src/SwooleRedisPool.php: -------------------------------------------------------------------------------- 1 | 3, 23 | //max 1000 24 | 'poolMax' => 64, 25 | //when lost connection retry 26 | 'retryTimes' => 2, 27 | 28 | //options config 29 | 'connect_timeout' => 1, 30 | 'timeout' => 1, 31 | 'reconnect' => 1 32 | ]; 33 | 34 | public function __construct($config,$autoFill=false) 35 | { 36 | $this->config = array_merge($this->config, $config); 37 | $this->pool = new Channel($this->config['poolMax']); 38 | if ($autoFill){ 39 | $this->fillup(); 40 | } 41 | } 42 | 43 | public function fillup(){ 44 | while ($this->pool->length() < $this->config['poolMin']){ 45 | $this->pool->push($this->getRedis()); 46 | } 47 | } 48 | 49 | public function length(){ 50 | return $this->pool->length(); 51 | } 52 | 53 | public function pushTime(){ 54 | return $this->pushTime; 55 | } 56 | 57 | public function getRedis(){ 58 | $redis = new Redis([ 59 | 'connect_timeout' => $this->config['connect_timeout'], 60 | 'timeout' => $this->config['timeout'], 61 | 'reconnect' => $this->config['reconnect'] 62 | ]); 63 | 64 | $redis->connect($this->config['host'], $this->config['port']); 65 | 66 | if (!empty($this->config['password'])) { 67 | $redis->auth($this->config['password']); 68 | } 69 | 70 | $redis->select($this->config['database']); 71 | return $redis; 72 | } 73 | 74 | /** 75 | * @出池 76 | */ 77 | public function get() 78 | { 79 | $re_i = -1; 80 | 81 | back: 82 | $re_i++; 83 | 84 | //有空闲连接且连接池处于可用状态 85 | if ($this->pool->length() > 0) { 86 | $redis = $this->pool->pop(); 87 | } else { 88 | //无空闲连接,创建新连接 89 | $redis = $this->getRedis(); 90 | $this->addPoolTime = time(); 91 | } 92 | 93 | if ($redis->connected === true && $redis->errCode === 0) { 94 | return $redis; 95 | } else { 96 | if ($re_i <= $this->config['retryTimes']) { 97 | $this->dumpError("redis-重连次数{$re_i},[errCode:{$redis->errCode},errMsg:{$redis->errMsg}]"); 98 | 99 | $redis->close(); 100 | unset($redis); 101 | goto back; 102 | } 103 | $this->dumpError('Redis重连失败'); 104 | } 105 | } 106 | 107 | 108 | public function put($redis){ 109 | //未超出池最大值时 110 | if ($this->pool->length() < $this->config['poolMax']) { 111 | $this->pool->push($redis); 112 | } 113 | $this->pushTime = time(); 114 | } 115 | 116 | /** 117 | * @打印错误信息 118 | * 119 | * @param $msg 120 | */ 121 | public function dumpError($msg) 122 | { 123 | Log::error(date('Y-m-d H:i:s', time()) . ":{$msg}"); 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /src/SwooleRedisPoolConnection.php: -------------------------------------------------------------------------------- 1 | pool = $pool; 173 | $this->client = null; 174 | } 175 | 176 | /** 177 | * Subscribe to a set of given channels for messages. 178 | * 179 | * @param array|string $channels 180 | * @param \Closure $callback 181 | * @param string $method 182 | * @return void 183 | */ 184 | public function createSubscription($channels, Closure $callback, $method = 'subscribe') 185 | { 186 | $loop = $this->pubSubLoop(); 187 | 188 | call_user_func_array([$loop, $method], (array) $channels); 189 | 190 | foreach ($loop as $message) { 191 | if ($message->kind === 'message' || $message->kind === 'pmessage') { 192 | call_user_func($callback, $message->payload, $message->channel); 193 | } 194 | } 195 | 196 | unset($loop); 197 | } 198 | 199 | /** 200 | * Get the underlying Redis client. 201 | * 202 | * @return mixed 203 | */ 204 | public function client() 205 | { 206 | return $this->pool->get(); 207 | } 208 | 209 | /** 210 | * Pass other method calls down to the underlying client. 211 | * 212 | * @param string $method 213 | * @param array $parameters 214 | * @return mixed 215 | */ 216 | public function __call($method, $parameters) 217 | { 218 | $this->client = $this->pool->get(); 219 | $result = $this->command($method, $parameters); 220 | $this->pool->put($this->client); 221 | return $result; 222 | } 223 | } --------------------------------------------------------------------------------