Skip to main content

Redis

Component Redis cung cấp interface thống nhất để làm việc với Redis, hỗ trợ hai client (phpredis extension và predis package), connection pool cho Swoole/coroutine, và đầy đủ các lệnh Redis.

Cấu hình

Cấu hình Redis nằm trong config/database.php dưới key redis:

'redis' => [
// Client: 'phpredis' (extension) hoặc 'predis' (package)
'client' => env('REDIS_CLIENT', 'phpredis'),

// Options toàn cục
'options' => [
'prefix' => env('REDIS_PREFIX', 'tourdb_'),
],

// Connection mặc định
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', 6379),
'password' => env('REDIS_PASSWORD', null),
'username' => env('REDIS_USERNAME', null), // Redis 6+ ACL
'database' => env('REDIS_DB', 0),
'timeout' => 2.0,
'read_timeout' => 60.0,
'persistent' => false,
'prefix' => '',

// Connection pool (Swoole)
'pool_options' => [
'min_connections' => 2,
'max_connections' => 20,
'wait_timeout' => 3.0,
'idle_timeout' => 60.0,
],
],

// Connection riêng cho cache
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', 6379),
'password' => env('REDIS_PASSWORD', null),
'database' => 1,
],
],

Cài đặt client

# phpredis (khuyến nghị - nhanh hơn)
pecl install redis

# predis (pure PHP - không cần extension)
composer require predis/predis

Sử Dụng

Qua Facade

use Vietiso\Core\Redis\Facade\Redis;

Redis::set('key', 'value');
$value = Redis::get('key');

Nhiều connections

// Connection mặc định
Redis::connection()->get('key');

// Connection cụ thể
Redis::connection('cache')->set('key', 'value');

Qua container

$redis = app()->get('redis');
$redis->set('key', 'value');

// Connection cụ thể
$redis->connection('cache')->get('key');

Lệnh String

// Set / Get
Redis::set('name', 'An');
Redis::get('name'); // 'An'

// Set với TTL (giây)
Redis::setex('token', 3600, 'abc123');

// Set nếu key chưa tồn tại
Redis::setnx('lock', '1'); // true nếu thành công

// Set nhiều key cùng lúc
Redis::mset(['a' => '1', 'b' => '2', 'c' => '3']);

// Get nhiều key
Redis::mget(['a', 'b', 'c']); // ['1', '2', '3']

// Xóa key
Redis::del('name');
Redis::del('a', 'b', 'c'); // xóa nhiều key

// Kiểm tra tồn tại
Redis::exists('name'); // bool

// Append
Redis::append('log', ' new entry'); // trả về length mới
Redis::strlen('log'); // độ dài string

Lệnh Expiration

// Đặt TTL (giây)
Redis::expire('key', 3600);

// Đặt TTL (milliseconds)
Redis::pexpire('key', 3600000);

// Lấy TTL còn lại
Redis::ttl('key'); // giây, -1 nếu không có TTL, -2 nếu key không tồn tại
Redis::pttl('key'); // milliseconds

// Xóa TTL (làm key persistent)
Redis::persist('key');

Lệnh Increment / Decrement

Redis::set('views', 0);

Redis::incr('views'); // 1
Redis::incrby('views', 10); // 11
Redis::incrbyfloat('price', 1.5);

Redis::decr('views'); // 10
Redis::decrby('views', 5); // 5

Lệnh Key

// Tìm key theo pattern (cẩn thận dùng trên production)
Redis::keys('user:*'); // ['user:1', 'user:2', ...]

// Scan (an toàn hơn keys trên production)
$cursor = null;
do {
$keys = Redis::scan($cursor, 'session:*', 100);
foreach ($keys as $key) {
// xử lý từng key
}
} while ($cursor !== 0);

// Lấy kiểu dữ liệu
Redis::type('name'); // 'string', 'hash', 'list', 'set', 'zset', 'none'

// Đổi tên key
Redis::rename('old_key', 'new_key');
Redis::renamenx('old_key', 'new_key'); // chỉ đổi nếu new_key chưa tồn tại

// Chọn database
Redis::select(1);

// Xóa toàn bộ database hiện tại
Redis::flushdb();

Hash

Hash lưu trữ object dạng field => value:

// Set field
Redis::hset('user:1', 'name', 'Nguyễn An');
Redis::hset('user:1', 'email', 'an@example.com');

// Set nhiều field cùng lúc
Redis::hmset('user:1', [
'name' => 'Nguyễn An',
'email' => 'an@example.com',
'age' => 25,
]);

// Get field
Redis::hget('user:1', 'name'); // 'Nguyễn An'
Redis::hget('user:1', 'missing'); // null

// Get nhiều field
Redis::hmget('user:1', ['name', 'email']);
// ['name' => 'Nguyễn An', 'email' => 'an@example.com']

// Get tất cả fields
Redis::hgetall('user:1');
// ['name' => 'Nguyễn An', 'email' => 'an@example.com', 'age' => '25']

// Kiểm tra field tồn tại
Redis::hexists('user:1', 'name'); // true

// Xóa field
Redis::hdel('user:1', 'age');
Redis::hdel('user:1', 'field1', 'field2');

// Đếm fields
Redis::hlen('user:1'); // 2

// Lấy danh sách keys / values
Redis::hkeys('user:1'); // ['name', 'email']
Redis::hvals('user:1'); // ['Nguyễn An', 'an@example.com']

// Increment field
Redis::hincrby('user:1', 'login_count', 1);
Redis::hincrbyfloat('user:1', 'balance', 50.5);

// Set field nếu chưa tồn tại
Redis::hsetnx('user:1', 'created_at', time());

List

List là danh sách có thứ tự, hỗ trợ thêm/lấy từ cả hai đầu:

// Push vào đầu (left) hoặc cuối (right)
Redis::lpush('queue', 'job1', 'job2'); // ['job2', 'job1']
Redis::rpush('queue', 'job3', 'job4'); // ['job2', 'job1', 'job3', 'job4']

// Pop từ đầu hoặc cuối
Redis::lpop('queue'); // 'job2'
Redis::rpop('queue'); // 'job4'

// Blocking pop (chờ đến khi có phần tử, timeout giây)
$item = Redis::blpop(['queue', 'fallback'], 5);
// ['queue', 'job1'] hoặc null nếu timeout

$item = Redis::brpop(['queue'], 5);

// Độ dài list
Redis::llen('queue');

// Lấy range (0-based, -1 là cuối)
Redis::lrange('queue', 0, -1); // toàn bộ list
Redis::lrange('queue', 0, 9); // 10 phần tử đầu

// Get/Set theo index
Redis::lindex('queue', 0); // phần tử đầu tiên
Redis::lset('queue', 0, 'new_val'); // thay giá trị tại index

// Xóa phần tử theo giá trị
Redis::lrem('queue', 1, 'job1'); // xóa 1 phần tử 'job1' từ đầu
Redis::lrem('queue', -1, 'job1'); // xóa 1 phần tử 'job1' từ cuối
Redis::lrem('queue', 0, 'job1'); // xóa tất cả 'job1'

Set

Set là tập hợp không có thứ tự, không trùng lặp:

// Thêm thành viên
Redis::sadd('tags', 'php', 'redis', 'swoole');

// Xóa thành viên
Redis::srem('tags', 'swoole');

// Kiểm tra tồn tại
Redis::sismember('tags', 'php'); // true

// Lấy tất cả thành viên
Redis::smembers('tags'); // ['php', 'redis']

// Số lượng thành viên
Redis::scard('tags'); // 2

// Phép toán tập hợp
Redis::sunion('tags', 'more_tags'); // hợp
Redis::sinter('tags', 'more_tags'); // giao
Redis::sdiff('tags', 'more_tags'); // hiệu

// Lấy ngẫu nhiên và xóa
Redis::spop('tags'); // lấy 1 phần tử ngẫu nhiên
Redis::spop('tags', 2); // lấy 2 phần tử ngẫu nhiên

// Lấy ngẫu nhiên không xóa
Redis::srandmember('tags');
Redis::srandmember('tags', 3); // lấy 3 phần tử ngẫu nhiên

Sorted Set (ZSet)

Sorted set giống set nhưng mỗi thành viên có điểm số (score) để sắp xếp:

// Thêm thành viên với score
Redis::zadd('leaderboard', 1500.0, 'user:1');
Redis::zadd('leaderboard', 2000.0, 'user:2');
Redis::zadd('leaderboard', 1800.0, 'user:3');

// Xóa thành viên
Redis::zrem('leaderboard', 'user:1');

// Lấy score
Redis::zscore('leaderboard', 'user:2'); // 2000.0

// Lấy rank (0-based, tăng dần)
Redis::zrank('leaderboard', 'user:2'); // 2 (hạng thấp nhất vì score cao)
Redis::zrevrank('leaderboard', 'user:2'); // 0 (hạng cao nhất)

// Lấy range theo rank (tăng dần)
Redis::zrange('leaderboard', 0, -1);
Redis::zrange('leaderboard', 0, -1, withScores: true);
// ['user:3' => 1800.0, 'user:2' => 2000.0]

// Lấy range theo rank (giảm dần)
Redis::zrevrange('leaderboard', 0, 2);

// Lấy range theo score
Redis::zrangebyscore('leaderboard', '1000', '2000');
Redis::zrangebyscore('leaderboard', '-inf', '+inf');
Redis::zrangebyscore('leaderboard', '1500', '+inf', ['LIMIT' => [0, 10]]);

Redis::zrevrangebyscore('leaderboard', '+inf', '1500');

// Đếm
Redis::zcard('leaderboard'); // tổng số thành viên
Redis::zcount('leaderboard', '1000', '2000'); // đếm theo score range

// Tăng score
Redis::zincrby('leaderboard', 100.0, 'user:3');

// Xóa theo rank/score range
Redis::zremrangebyrank('leaderboard', 0, 2); // xóa 3 hạng thấp nhất
Redis::zremrangebyscore('leaderboard', '-inf', '1000'); // xóa score < 1000

Transaction

// Dùng callback (khuyến nghị)
$results = Redis::transaction(function ($redis) {
$redis->set('key1', 'value1');
$redis->incr('counter');
$redis->lpush('list', 'item');
});
// $results = ['OK', 1, 1]

// Dùng thủ công
Redis::multi();
Redis::set('key1', 'value1');
Redis::incr('counter');
$results = Redis::exec(); // array|false

// Hủy transaction
Redis::multi();
Redis::set('key1', 'value1');
Redis::discard();

Lua Scripting

// Thực thi Lua script
$result = Redis::eval(
script: 'return redis.call("GET", KEYS[1])',
keys: ['mykey'],
args: []
);

// Atomic check-and-set
$result = Redis::eval(
'if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("SET", KEYS[1], ARGV[2])
else
return 0
end',
keys: ['lock_key'],
args: ['expected_value', 'new_value']
);

// Dùng SHA (script đã được cache trên server)
$sha = '...'; // SHA1 của script
Redis::evalsha($sha, keys: ['key'], args: ['arg1']);

Pub/Sub

Lưu ý: subscribepsubscribe sẽ block connection cho đến khi unsubscribe. Dùng subscriber() để lấy dedicated connection không từ pool.

Publisher

// Gửi message đến channel
$subscribers = Redis::publish('notifications', json_encode([
'type' => 'booking_confirmed',
'tour_id' => 42,
]));
// $subscribers = số lượng subscriber nhận được

Subscriber

// Lấy dedicated connection cho subscribe (không block pool)
$sub = Redis::subscriber();

// Subscribe channel cụ thể
$sub->subscribe(['notifications', 'alerts'], function (string $channel, string $message) {
$data = json_decode($message, true);
echo "[$channel] " . $data['type'];
});

// Subscribe theo pattern
$sub->psubscribe(['tour.*', 'booking.*'], function (string $channel, string $message, string $pattern) {
echo "Pattern: $pattern, Channel: $channel";
});

Connection Pool (Swoole)

Với môi trường Swoole/Coroutine, bật pool để tái sử dụng connection:

'default' => [
'host' => '127.0.0.1',
'port' => 6379,

'pool_options' => [
'min_connections' => 2, // số connection tối thiểu
'max_connections' => 20, // số connection tối đa
'wait_timeout' => 3.0, // giây chờ lấy connection
'idle_timeout' => 60.0,// giây idle trước khi đóng
],
],

Connection được tự động trả về pool khi coroutine kết thúc (Coroutine::defer). Có thể trả về thủ công:

// Trả connection 'default' về pool
Redis::release('default');

// Trả tất cả connections về pool
Redis::release();

Custom Connector

Tạo connector tùy chỉnh (ví dụ: Redis Cluster, Sentinel):

use Vietiso\Core\Redis\Connectors\ConnectorInterface;
use Vietiso\Core\Redis\Connections\Connection;

class ClusterConnector implements ConnectorInterface
{
public function connect(array $config): Connection
{
// tạo kết nối cluster
$client = new RedisCluster(null, $config['seeds']);
return new PhpRedisConnection($client);
}
}

// Đăng ký
app()->get('redis')->extend('cluster', ClusterConnector::class);

Sau đó dùng driver mới:

// config/database.php
'cluster_connection' => [
'driver' => 'cluster',
'seeds' => ['127.0.0.1:7001', '127.0.0.1:7002'],
],