Connecting to Redis with Jedis
Jedis is a Java client library for connecting to and interacting with a Redis server. Alternatives include Spring Data Redis and Lettuce.
Maven Dependency
Add the following dependency to your pom.xml:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
Basic Connection and Operations
import redis.clients.jedis.Jedis;
public class RedisExample {
public static void main(String[] args) {
// Connect to Redis
Jedis jedis = new Jedis("localhost", 6379);
// Set and get a string value
jedis.set("greeting", "Hello, Redis!");
String value = jedis.get("greeting");
System.out.println(value); // Output: Hello, Redis!
// Close the connection
jedis.close();
}
}
Working with Data Structures
Lists
import redis.clients.jedis.Jedis;
import java.util.List;
public class ListExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.lpush("mylist", "item3", "item2", "item1");
jedis.rpush("mylist", "item4");
List<String> items = jedis.lrange("mylist", 0, -1);
for (String item : items) {
System.out.print(item + " ");
}
System.out.println();
System.out.println("List length: " + jedis.llen("mylist"));
jedis.close();
}
}
Hashes
import redis.clients.jedis.Jedis;
import java.util.Map;
public class HashExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.hset("user:1000", "name", "Alice");
jedis.hset("user:1000", "age", "30");
Map<String, String> user = jedis.hgetAll("user:1000");
System.out.println(user); // Output: {name=Alice, age=30}
System.out.println("Hash size: " + jedis.hlen("user:1000"));
jedis.close();
}
}
Implementing Rate Limiting with Jedis
A common use case is limiting the number of operations a user can perform within a time window. This example shows how to restrict User A to 3 calls per 10 seconds and User B to 6 calls per 10 seconds.
import redis.clients.jedis.Jedis;
public class RateLimiter {
private String userId;
private int maxRequests;
private int windowSeconds;
public RateLimiter(String userId, int maxRequests, int windowSeconds) {
this.userId = userId;
this.maxRequests = maxRequests;
this.windowSeconds = windowSeconds;
}
public boolean allowRequest() {
try (Jedis jedis = new Jedis("localhost", 6379)) {
String key = "ratelimit:" + userId;
Long current = jedis.incr(key);
if (current == 1) {
jedis.expire(key, windowSeconds);
}
if (current <= maxRequests) {
System.out.println("User " + userId + " request " + current + "/" + maxRequests + " allowed.");
return true;
} else {
System.out.println("User " + userId + " rate limit exceeded. Request blocked.");
return false;
}
}
}
public static void main(String[] args) throws InterruptedException {
RateLimiter userA = new RateLimiter("A", 3, 10);
RateLimiter userB = new RateLimiter("B", 6, 10);
for (int i = 0; i < 10; i++) {
userA.allowRequest();
userB.allowRequest();
Thread.sleep(1000);
}
}
}
Using a Jedis Connection Pool
A connection pool improves performance by reusing connections instead of creating new ones for every operation. This singleton-based utility reads configuration from a properties file.
jedis.properties
jedis.host=localhost
jedis.port=6379
jedis.maxTotal=30
jedis.maxIdle=10
RedisPool.java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ResourceBundle;
public class RedisPool {
private static final JedisPool pool;
static {
ResourceBundle bundle = ResourceBundle.getBundle("jedis");
String host = bundle.getString("jedis.host");
int port = Integer.parseInt(bundle.getString("jedis.port"));
int maxTotal = Integer.parseInt(bundle.getString("jedis.maxTotal"));
int maxIdle = Integer.parseInt(bundle.getString("jedis.maxIdle"));
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
pool = new JedisPool(config, host, port);
}
public static Jedis getConnection() {
return pool.getResource();
}
}
Redis Persistence
Persistence in Redis refers to saving the in-memory data to permanent storage (disk) so that it can be recovered after a restart.
Persistence Methods
- RDB (Redis Database): Creates point-in-time snapshots of the dataset at specified intervals. Stores the data state.
- AOF (Append Only File): Logs every write operation received by the server. These logs are replayed at startup to reconstruct the dataset. Stores the operation history.
- No Persistence: Data exists only while the server is running.
- RDB + AOF: Both methods can be enabled simultaneously. On restart, Redis uses the AOF file to reconstruct data because it is typically more complete.
RDB (Snapshot) Persistence
RDB creates binary snapshot files (.rdb).
savecommand: Synchronously blocks the server until the snapshot is complete. Not recommended for production.bgsavecommand: Asynchronously forks a child process to create the snapshot. The parent process continues serving requests.- Configuration: Automatic snapshots can be configured.
Configurtaion Example (redis.conf)
# RDB file name
dbfilename dump.rdb
# Directory to store RDB files
dir /var/lib/redis
# Compress RDB files (yes/no)
rdbcompression yes
# Perform checksum on RDB files
rdbchecksum yes
# Snapshot triggers: <seconds> <changes>
save 900 1 # At least 1 change in 900 seconds (15 min)
save 300 10 # At least 10 changes in 300 seconds (5 min)
save 60 10000 # At least 10000 changes in 60 seconds (1 min)
# Stop writing if bgsave fails
stop-writes-on-bgsave-error yes
Comparison of RDB Triggers
| Method | Blocking | Child Process | Memory Overhead |
|---|---|---|---|
save |
Yes | No | No |
bgsave |
No | Yes | Yes |
Configuration save |
No | Yes | Yes |
Pros and Cons of RDB
Pros
- Compact binary file, efficient storage.
- Excellent for backups and disaster recovery due to point-in-time snapshots.
- Very fast recovery from RDB compared to AOF.
Cons
- Not real-time; possible data loss between snapshots.
bgsaveusesfork(), which can be resource-intensive.- RDB format may not be compatible across different Redis versions.
AOF (Append Only File) Persistence
AOF logs every write command in a human-protocol-readable format.
Write Strategies
| Strategy | Description | Data Safety | Performance |
|---|---|---|---|
always |
Every write is flushed to disk. | Zero data loss on crash | Lowest |
everysec |
Buffered writes are flushed every second. | May lose up to 1 second of data on crash | Good |
no |
Operating system decides when to flush. Unpredictable. | Less predictable | High |
Default is everysec.
Configuration Example (redis.conf)
# Enable AOF
appendonly yes
# AOF file name
appendfilename "appendonly.aof"
# Write strategy: always, everysec, no
appendfsync everysec
# No fsync during AOF rewrite (improves performance)
no-appendfsync-on-rewrite no
AOF Rewrite
Over time, the AOF file can grow large and contain redundant commands (e.g., multiple updates to the same key). AOF rewrite compacts the file by generating the minimal set of commands needed to reconstruct the current dataset.
Configuration
# Trigger rewrite when AOF file size exceeds 100% of last rewrite size
auto-aof-rewrite-percentage 100
# Minimum size to trigger rewrite (avoids frequent rewrites on small files)
auto-aof-rewrite-min-size 64mb
Manual Trigger
BGREWRITEAOF
AOF Rewrite Rules
- Expired keys are excluded.
- Multiple commands affecting the same key are merged into a single command.
- For lists, sets, hashes, etc., at most 64 elements are written per command to prevent client buffer overflow.
Rewrite Mechanism
Redis forks a child process that receives all incoming write commands in a special `` buffer. Once the child finishes creating the new AOF, the parent signals it to replace the old AOF with the new one.
RDB vs AOF: Summary
| Aspect | RDB | AOF |
|---|---|---|
| Data Type | Snapshot (data state) | Log (operations) |
| File Size | Smaller (compressed binary) | Larger (text protocol) |
| Recovery Speed | Very fast | Slower (replays all operations) |
| Real-time Safety | Lower (point-in-time) | Higher (up to 1 second with everysec) |
| Resource Usage | Lower for writing, fork() |
Higher for writing, less for fork() |
When to Use
- AOF: If data loss of more than a few seconds is unacceptable. AOF is often the primary choice for data safety.
- RDB: If fast recovery is more important than minimal data loss, or for backups and disaster recovery scenarios.
- Combined: Enable both. Redis uses AOF on restart for reconstruction (since it is more complete) while RDB serves as a backup mechanism.
Practical Persistence Decision Matrix
| Use Case | Persist? | Reason |
|---|---|---|
| Database primary key generation | No | Persisting a counter won't prevent duplicates after a crash; you still need to scan the DB. |
| Hot data cache (speed up DB reads) | No | Data already exists in the underlying database. |
| Shopping cart | No | Data is typically temporary or stored in the database. |
| Flash sales, limited coupon codes, activation keys | Yes | These are high-speed, ephemeral data that the master DB may not track in real-time. |
| Ordered task queues | Yes | Need to preserve order and not lose tasks. |
| Latest news feed | Yes | To avoid fetching all new items from the DB after restart. |
| Complex graph relations (e.g., friend suggestions) | No | Better to recompute from the primary DB. |
| Blacklists/whitelists | Yes | Long-term rules are typically stored, short-term ones might be persisted if important. |
| Leaderboards & rankings | Yes | Persisting ensures no loss of scores between restarts. |
| Instant messaging / task queues | No | Better handled by dedicated message brokers (e.g., RabbitMQ, Kafka). |
| Per-call billing or service control | Yes | Depends on criticality; may not require persistence for less vital services. |