redis-rb
GitHub Overview
Topics
Star History
Cache Library
redis-rb
Overview
redis-rb is the official Ruby client library that provides an interface between Redis and Ruby applications, featuring a one-to-one mapping with Redis API while maintaining an idiomatic Ruby interface, with a design focused on performance optimization.
Details
redis-rb is the official Ruby client library that provides an interface between Redis and Ruby applications. It aims to match Redis' API one-to-one while still providing an idiomatic Ruby interface. Key features include thread-safety, client-side sharding, pipelining, and an obsession for performance. It provides comprehensive support for all Redis commands, Pub/Sub functionality, transactions (MULTI/EXEC), automatic failover through Sentinel support, distributed operations, and SSL/TLS connections. Integration with the hiredis driver enables speed optimization for large replies or big pipelines. By default, each Redis instance has one and only one connection to the server, and use of this connection is protected by a mutex. The client does not provide connection pooling; it is heavily recommended to use the connection_pool gem. Redis Sentinel support is available for automatic failover when using Redis 2.8+, and cluster support is provided through the companion gem "redis-clustering". The library supports SSL certificate authentication, configurable timeouts, reconnection attempt settings, and comprehensive error handling for production environments.
Pros and Cons
Pros
- Official Support: Official Ruby client library for Redis
- One-to-One API Mapping: Direct correspondence with Redis API for easy learning
- High Performance: Speed optimization through hiredis driver support
- Thread Safety: Safe concurrent access through mutex protection
- Pipelining: Efficient batch execution of multiple commands
- Sentinel Support: Automatic failover for high availability
- Rich Features: Complete support for Pub/Sub, transactions, and distributed operations
Cons
- No Connection Pooling: Requires separate connection_pool gem
- Separate Cluster Support: Cluster functionality provided through separate gem
- Single Connection: Limited to one connection per instance
- Ruby Dependency: Strongly dependent on Ruby-specific features
- Learning Curve: Advanced features (Sentinel, distributed operations) require expertise
Key Links
- redis-rb GitHub Repository
- redis-rb RubyGems Page
- Redis Ruby Development Guide
- redis-rb Official Documentation
- Redis Official Documentation
Code Examples
Installation and Basic Connection
# Gemfile
gem 'redis'
# Or with hiredis support (for performance)
gem 'redis'
gem 'hiredis-client'
# Installation
# bundle install
# Basic connection
require 'redis'
redis = Redis.new
# Detailed configuration
redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)
# URL connection
redis = Redis.new(url: "redis://:[email protected]:6380/15")
# SSL connection
redis = Redis.new(url: "rediss://10.0.1.1:6380/15")
# Connection test
puts redis.ping # => PONG
Basic Key-Value Operations
require 'redis'
redis = Redis.new
# String set and get
redis.set('best_car_ever', 'Tesla Model S')
car = redis.get('best_car_ever')
puts car # => "Tesla Model S"
# Key with expiration
redis.setex('session:123', 3600, 'session_data') # Expires in 1 hour
# Numeric operations
redis.set('counter', 0)
count = redis.incr('counter')
puts count # => 1
# Bulk operations for multiple keys
redis.mset('key1', 'value1', 'key2', 'value2', 'key3', 'value3')
values = redis.mget('key1', 'key2', 'key3')
puts values # => ["value1", "value2", "value3"]
# Key existence check
if redis.exists('best_car_ever')
puts 'Key exists'
end
# Key deletion
redis.del('best_car_ever')
List, Set, and Hash Operations
# List operations
redis.lpush('tasks', 'task1', 'task2', 'task3')
redis.rpush('tasks', 'task4')
# Get list contents
tasks = redis.lrange('tasks', 0, -1)
puts tasks # => ["task3", "task2", "task1", "task4"]
# Pop elements from list
task = redis.lpop('tasks')
puts task # => "task3"
# Set operations
redis.sadd('tags', 'ruby', 'redis', 'cache')
members = redis.smembers('tags')
puts members # => ["ruby", "redis", "cache"]
# Set operations
redis.sadd('set1', 'a', 'b', 'c')
redis.sadd('set2', 'b', 'c', 'd')
intersection = redis.sinter('set1', 'set2')
puts intersection # => ["b", "c"]
# Hash operations
redis.hset('user:1', 'name', 'John')
redis.hset('user:1', 'email', '[email protected]')
redis.hset('user:1', 'age', 30)
# Bulk hash set
redis.hmset('user:2', 'name', 'Alice', 'email', '[email protected]', 'age', 25)
user_data = redis.hgetall('user:1')
puts user_data # => {"name"=>"John", "email"=>"john@example.com", "age"=>"30"}
name = redis.hget('user:1', 'name')
puts name # => "John"
Pipelining
# Efficient execution of multiple commands with pipeline
redis.pipelined do |pipe|
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.get('key1')
pipe.get('key2')
pipe.incr('counter')
end
# Using Future objects
redis.pipelined do |pipe|
future_value1 = pipe.get('key1')
future_value2 = pipe.get('key2')
# Get values after pipeline execution
puts future_value1.value # => "value1"
puts future_value2.value # => "value2"
end
# Pipeline with transactions
redis.multi do |multi|
multi.set('account:1:balance', 100)
multi.set('account:2:balance', 200)
multi.decrby('account:1:balance', 50)
multi.incrby('account:2:balance', 50)
end
Pub/Sub Functionality
require 'redis'
require 'thread'
# Publisher side
def publisher(redis)
sleep(1) # Wait for subscriber setup
5.times do |i|
redis.publish('notifications', "Message #{i}")
sleep(1)
end
end
# Subscriber side
def subscriber(redis)
redis.subscribe('notifications') do |on|
on.subscribe do |channel, subscriptions|
puts "Subscribed to ##{channel} (#{subscriptions} subscriptions)"
end
on.message do |channel, message|
puts "Received message: #{message}"
end
on.unsubscribe do |channel, subscriptions|
puts "Unsubscribed from ##{channel} (#{subscriptions} subscriptions)"
end
end
end
# Run publisher in separate thread
redis_pub = Redis.new
redis_sub = Redis.new
pub_thread = Thread.new { publisher(redis_pub) }
# Run subscriber
subscriber(redis_sub)
pub_thread.join
Connection Pool Usage
require 'redis'
require 'connection_pool'
# Connection pool configuration
pool = ConnectionPool.new(size: 5, timeout: 5) do
Redis.new(host: 'localhost', port: 6379)
end
# Using connection pool
pool.with do |redis|
redis.set('pooled_key', 'pooled_value')
value = redis.get('pooled_key')
puts value # => "pooled_value"
end
# Multi-threaded usage
threads = []
10.times do |i|
threads << Thread.new do
pool.with do |redis|
redis.incr('thread_counter')
count = redis.get('thread_counter')
puts "Thread #{i}: counter = #{count}"
end
end
end
threads.each(&:join)
Error Handling
require 'redis'
redis = Redis.new
begin
redis.set('test_key', 'test_value')
value = redis.get('test_key')
puts value
rescue Redis::CannotConnectError
puts "Redis connection failed"
rescue Redis::TimeoutError
puts "Redis operation timed out"
rescue Redis::CommandError => e
puts "Redis command error: #{e.message}"
rescue Redis::BaseError => e
puts "Redis error: #{e.message}"
end
# Reconnection configuration
redis = Redis.new(
reconnect_attempts: 3,
reconnect_delay: 0.5,
reconnect_delay_max: 5.0
)
# Disable reconnection for specific block
redis.without_reconnect do
# No reconnection within this block
redis.ping
end
Sentinel Configuration
require 'redis'
# Sentinel configuration
redis = Redis.new(
url: "redis://mymaster",
sentinels: [
{ host: "127.0.0.1", port: 26379 },
{ host: "127.0.0.1", port: 26380 },
{ host: "127.0.0.1", port: 26381 }
],
role: :master
)
# Slave connection
redis_slave = Redis.new(
url: "redis://mymaster",
sentinels: [
{ host: "127.0.0.1", port: 26379 },
{ host: "127.0.0.1", port: 26380 },
{ host: "127.0.0.1", port: 26381 }
],
role: :slave
)
# Operations
redis.set('sentinel_key', 'sentinel_value')
value = redis_slave.get('sentinel_key')
puts value # => "sentinel_value"
Distributed Redis Operations
require 'redis'
require 'redis/distributed'
# Distributed Redis configuration
distributed = Redis::Distributed.new([
"redis://127.0.0.1:6379/0",
"redis://127.0.0.1:6380/0",
"redis://127.0.0.1:6381/0"
])
# Distributed operations (consistent hashing for node selection)
distributed.set('user:1', 'Alice') # Placed on node 1
distributed.set('user:2', 'Bob') # Placed on node 2
distributed.set('user:3', 'Charlie') # Placed on node 3
# Data retrieval
user1 = distributed.get('user:1')
user2 = distributed.get('user:2')
puts "#{user1}, #{user2}" # => "Alice, Bob"
# Get information from all nodes
distributed.nodes.each_with_index do |node, index|
puts "Node #{index}: #{node.info['redis_version']}"
end
SSL/TLS Connection
require 'redis'
# SSL configuration
redis = Redis.new(
host: 'localhost',
port: 6380,
ssl: true,
ssl_params: {
cert_file: '/path/to/client.crt',
key_file: '/path/to/client.key',
ca_file: '/path/to/ca.crt',
verify_mode: OpenSSL::SSL::VERIFY_PEER
}
)
# Or using URL
redis = Redis.new(url: "rediss://localhost:6380")
# Connection test
puts redis.ping # => PONG
Advanced Caching Patterns
require 'redis'
require 'json'
class CacheManager
def initialize(redis_client)
@redis = redis_client
end
def cached_method(key, expiration = 3600)
# Try to get from cache
cached_value = @redis.get(key)
return JSON.parse(cached_value) if cached_value
# Execute block if not in cache
result = yield
# Save result to cache
@redis.setex(key, expiration, JSON.dump(result))
result
end
def invalidate_pattern(pattern)
keys = @redis.keys(pattern)
@redis.del(keys) if keys.any?
end
end
# Usage example
redis = Redis.new
cache = CacheManager.new(redis)
# Cache expensive computation
result = cache.cached_method('expensive_calc', 1800) do
# Heavy computation
sleep(2)
{ result: (1..100000).sum, timestamp: Time.now }
end
puts result # First call takes 2 seconds
result = cache.cached_method('expensive_calc') { nil } # Second call returns immediately
# Pattern-based cache invalidation
cache.invalidate_pattern('user:*')