Redis String 是 Redis 中最基础也是应用最广泛的数据类型,它能存储文本、数字、二进制数据等多种形式的信息。深入理解其底层实现对构建高性能分布式系统至关重要。
Redis 字符串的底层结构:SDS
Redis 没有使用 C 语言原生字符串,而是设计了 Simple Dynamic String (SDS)数据结构,随着版本演进有不同实现。
SDS 的版本演进
Redis 3.2 之前的 SDS 结构:
c
struct sdshdr {
int len; // 已使用的字节数量
int free; // 剩余可用字节数量
char buf[]; // 字符数组,实际数据
};
Redis 3.2 及以后版本根据字符串长度使用不同的结构体优化内存使用:
c
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; // 已使用长度(最大255字节)
uint8_t alloc; // 分配的总长度
unsigned char flags; // 类型标志(低3位表示类型)
char buf[]; // 字符数组
};
// 类似地有sdshdr16/32/64,分别用于不同长度范围的字符串
Redis 7.0 维持了这种结构,但对内存管理进行了优化,并保持了字符串最大长度限制为 512MB。
SDS 与 C 字符串的关键区别
- O(1)时间复杂度获取字符串长度:直接访问 len 字段,而 C 字符串需 O(N)遍历
- 内存安全:修改前检查空间是否充足,避免缓冲区溢出
- 高效内存管理:通过预分配和惰性释放减少内存操作次数
- 二进制安全:可存储任意二进制数据,不受'\0'字符限制
"二进制安全"意味着字符串可以存储任何字节序列,包括空字符和非打印字符,而不会被截断或误解。"惰性释放"指字符串缩短时不立即释放多余空间,而是保留用于将来可能的增长。
Redis 对象系统与 String 编码
Redis 使用统一的对象系统(redisObject)管理所有数据:
c
typedef struct redisObject {
unsigned type:4; // 类型(String, List等)
unsigned encoding:4; // 编码方式
unsigned lru:LRU_BITS; // LRU信息
int refcount; // 引用计数
void *ptr; // 指向实际数据
} robj;
String 类型的三种编码
String 类型根据内容自动选择最适合的编码:
- int 编码:存储可用 64 位有符号整数表示的数值
- embstr 编码:存储短字符串(≤44 字节),redisObject 和 SDS 在内存中连续存储
- raw 编码:存储长字符串(>44 字节),redisObject 和 SDS 在内存中分开存储

embstr 的 44 字节阈值原理
Redis 5.0 及以上版本中 embstr 的 44 字节阈值计算:
scss
44字节 = 64字节(jemalloc分配的最小内存块) - 16字节(redisObject大小) - 3字节(sdshdr8大小) - 1字节('\0')
这样设计确保一个 embstr 字符串恰好占用一个 jemalloc 分配的内存块,优化内存使用效率。Redis 默认使用 jemalloc 而非 libc malloc,因为 jemalloc 能更好地处理内存碎片问题,特别是在长时间运行的服务中。
内存分配与管理策略
SDS 的空间分配采用预分配策略,避免频繁的内存重分配:

从 Redis 4.0 开始,引入了内存碎片整理功能,可以通过activedefrag
相关配置启用,有助于减少 String 类型频繁修改造成的内存碎片问题。
编码转换规则与原理
Redis String 类型的编码转换遵循以下规则:
- int → embstr/raw:当对整数值执行非数值操作(如 APPEND)时发生
- embstr → raw:当修改 embstr 编码字符串时发生,因为 embstr 是只读的
- raw → embstr/int:不会发生,编码降级不存在
- embstr/raw → int:当使用 SET 等命令将值设为整数时发生
embstr 转换为 raw 的技术原因:embstr 是一整块连续内存,修改可能需要重分配空间,而直接转为 raw 编码(两块分离内存)操作更高效。
Java 实现
连接池配置
java
import redis.clients.jedis.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class RedisConnectionManager implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(RedisConnectionManager.class);
private final JedisPool jedisPool;
public RedisConnectionManager(String host, int port, String password) {
JedisPoolConfig poolConfig = createPoolConfig();
if (password != null && !password.isEmpty()) {
this.jedisPool = new JedisPool(poolConfig, host, port, 2000, password);
} else {
this.jedisPool = new JedisPool(poolConfig, host, port, 2000);
}
logger.info("Redis connection pool initialized with host: {}, port: {}", host, port);
}
private JedisPoolConfig createPoolConfig() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 核心连接池配置
poolConfig.setMaxTotal(100); // 最大连接数
poolConfig.setMaxIdle(20); // 最大空闲连接
poolConfig.setMinIdle(10); // 最小空闲连接
poolConfig.setMaxWaitMillis(3000); // 获取连接最大等待时间
// 健康检查配置
poolConfig.setTestOnBorrow(true); // 获取连接前测试
poolConfig.setTestOnReturn(false); // 归还连接时不测试
poolConfig.setTestWhileIdle(true); // 空闲时测试连接
poolConfig.setTimeBetweenEvictionRunsMillis(60000); // 驱逐线程运行间隔
// 资源管理配置
poolConfig.setBlockWhenExhausted(true); // 连接耗尽时阻塞
poolConfig.setJmxEnabled(true); // 启用JMX监控
return poolConfig;
}
public Jedis getConnection() {
return jedisPool.getResource();
}
@Override
public void close() {
if (jedisPool != null && !jedisPool.isClosed()) {
jedisPool.close();
logger.info("Redis connection pool closed");
}
}
// 使用示例
public static void main(String[] args) {
try (RedisConnectionManager connectionManager = new RedisConnectionManager("localhost", 6379, null);
Jedis jedis = connectionManager.getConnection()) {
// 验证不同编码
jedis.set("number", "10086");
jedis.set("name", "Zhang San");
// 查看内部编码
logger.info("number encoding: {}", jedis.objectEncoding("number"));
logger.info("name encoding: {}", jedis.objectEncoding("name"));
// 修改验证编码转换
jedis.append("name", " is a developer");
logger.info("name encoding after append: {}", jedis.objectEncoding("name"));
} catch (Exception e) {
logger.error("Redis operation failed", e);
}
}
}
分布式锁实现
java
import redis.clients.jedis.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Supplier;
public class RedisDistributedLock implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
private final JedisPool jedisPool;
private final String lockKey;
private final String lockValue;
private final int expireTimeSeconds;
private final LockConfig config;
private volatile ScheduledExecutorService watchdogExecutor;
private volatile ScheduledFuture<?> watchdogTask;
private volatile boolean locked = false;
public static class LockConfig {
private final int retryTimes;
private final long retryIntervalMillis;
private final int watchdogIntervalSeconds;
public LockConfig(int retryTimes, long retryIntervalMillis, int watchdogIntervalSeconds) {
this.retryTimes = retryTimes;
this.retryIntervalMillis = retryIntervalMillis;
this.watchdogIntervalSeconds = watchdogIntervalSeconds;
}
public static LockConfig DEFAULT = new LockConfig(3, 1000, 10);
}
public RedisDistributedLock(JedisPool jedisPool, String lockKey, int expireTimeSeconds) {
this(jedisPool, lockKey, expireTimeSeconds, LockConfig.DEFAULT);
}
public RedisDistributedLock(JedisPool jedisPool, String lockKey,
int expireTimeSeconds, LockConfig config) {
this.jedisPool = jedisPool;
this.lockKey = "lock:" + lockKey;
this.lockValue = UUID.randomUUID().toString();
this.expireTimeSeconds = expireTimeSeconds;
this.config = config;
// 创建看门狗线程池
this.watchdogExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r, "lock-watchdog-" + lockKey);
thread.setDaemon(true);
return thread;
});
}
public boolean acquire() {
int attempted = 0;
do {
try (Jedis jedis = jedisPool.getResource()) {
String result = jedis.set(lockKey, lockValue,
SetParams.setParams().nx().ex(expireTimeSeconds));
if ("OK".equals(result)) {
locked = true;
logger.debug("Lock {} acquired by thread {}",
lockKey, Thread.currentThread().getName());
startWatchdog();
return true;
}
attempted++;
if (attempted < config.retryTimes) {
logger.debug("Failed to acquire lock: {}, retry {}/{}",
lockKey, attempted, config.retryTimes);
Thread.sleep(config.retryIntervalMillis);
}
} catch (JedisConnectionException e) {
logger.warn("Redis connection failed when acquiring lock: {}", lockKey, e);
attempted++;
if (attempted >= config.retryTimes) {
break;
}
try {
Thread.sleep(config.retryIntervalMillis);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
} while (attempted < config.retryTimes);
logger.warn("Failed to acquire lock after {} retries: {}",
config.retryTimes, lockKey);
return false;
}
public boolean release() {
if (!locked) {
return false;
}
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
try (Jedis jedis = jedisPool.getResource()) {
Object result = jedis.eval(script,
Collections.singletonList(lockKey),
Collections.singletonList(lockValue));
boolean success = Long.valueOf(1L).equals(result);
if (success) {
logger.debug("Lock {} released by thread {}",
lockKey, Thread.currentThread().getName());
locked = false;
stopWatchdog();
} else {
logger.warn("Failed to release lock: {}, it might have expired", lockKey);
}
return success;
} catch (JedisException e) {
logger.error("Error when releasing lock: {}", lockKey, e);
return false;
}
}
private void startWatchdog() {
stopWatchdog(); // 确保不会重复启动
// 计算续期间隔,取过期时间的1/3,最小1秒
int renewInterval = Math.max(1, expireTimeSeconds / 3);
watchdogTask = watchdogExecutor.scheduleAtFixedRate(() -> {
try (Jedis jedis = jedisPool.getResource()) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
Object result = jedis.eval(script,
Collections.singletonList(lockKey),
Arrays.asList(lockValue, String.valueOf(expireTimeSeconds)));
if (Long.valueOf(1L).equals(result)) {
logger.debug("Lock {} renewed for {} seconds", lockKey, expireTimeSeconds);
} else {
logger.warn("Failed to renew lock: {}, it might have been acquired by another process", lockKey);
stopWatchdog();
}
} catch (Exception e) {
logger.error("Error when renewing lock: {}", lockKey, e);
}
}, renewInterval, renewInterval, TimeUnit.SECONDS);
}
private void stopWatchdog() {
if (watchdogTask != null && !watchdogTask.isCancelled()) {
watchdogTask.cancel(false);
watchdogTask = null;
}
}
@Override
public void close() {
release();
if (watchdogExecutor != null) {
watchdogExecutor.shutdownNow();
watchdogExecutor = null;
}
}
// 扩展功能:执行受锁保护的代码块
public <T> T executeWithLock(Supplier<T> task) {
if (!acquire()) {
throw new RuntimeException("Failed to acquire lock: " + lockKey);
}
try {
return task.get();
} finally {
release();
}
}
// 使用示例
public static void main(String[] args) {
JedisPool jedisPool = new JedisPool("localhost", 6379);
String resourceKey = "inventory:item:10001";
try (RedisDistributedLock lock = new RedisDistributedLock(jedisPool, resourceKey, 30)) {
// 函数式方式使用锁
Integer updatedCount = lock.executeWithLock(() -> {
// 这里是需要锁保护的原子操作
try (Jedis jedis = jedisPool.getResource()) {
String countStr = jedis.get(resourceKey);
int count = countStr == null ? 0 : Integer.parseInt(countStr);
count -= 1; // 减库存
jedis.set(resourceKey, String.valueOf(count));
return count;
}
});
System.out.println("Updated inventory count: " + updatedCount);
} catch (Exception e) {
System.err.println("Operation failed: " + e.getMessage());
} finally {
jedisPool.close();
}
}
}
Repository 模式与断路器模式结合
java
import redis.clients.jedis.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.function.Supplier;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
public class RedisRepository<T> {
private static final Logger logger = LoggerFactory.getLogger(RedisRepository.class);
private final JedisPool jedisPool;
private final ObjectMapper objectMapper;
private final Class<T> entityClass;
private final String keyPrefix;
private final int defaultExpireSeconds;
private final CircuitBreaker circuitBreaker;
public RedisRepository(JedisPool jedisPool, Class<T> entityClass, String keyPrefix, int defaultExpireSeconds) {
this.jedisPool = jedisPool;
this.objectMapper = new ObjectMapper();
this.entityClass = entityClass;
this.keyPrefix = keyPrefix;
this.defaultExpireSeconds = defaultExpireSeconds;
// 配置断路器
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 50%的失败率触发断路
.waitDurationInOpenState(Duration.ofSeconds(10)) // 断路器打开10秒后尝试半开状态
.ringBufferSizeInHalfOpenState(5) // 半开状态下尝试5次请求
.ringBufferSizeInClosedState(10) // 关闭状态下记录10次请求结果
.build();
CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
this.circuitBreaker = registry.circuitBreaker(keyPrefix + "-circuit-breaker");
logger.info("Redis repository initialized for entity: {}, prefix: {}",
entityClass.getSimpleName(), keyPrefix);
}
// 保存实体
public void save(String id, T entity) {
save(id, entity, defaultExpireSeconds);
}
public void save(String id, T entity, int expireSeconds) {
String key = buildKey(id);
Supplier<Void> saveOperation = () -> {
try (Jedis jedis = jedisPool.getResource()) {
String json = objectMapper.writeValueAsString(entity);
Pipeline pipeline = jedis.pipelined();
pipeline.set(key, json);
pipeline.expire(key, expireSeconds);
pipeline.sync();
logger.debug("Entity {} with id {} saved with expiration {} seconds",
entityClass.getSimpleName(), id, expireSeconds);
return null;
} catch (Exception e) {
logger.error("Failed to save entity {} with id {}",
entityClass.getSimpleName(), id, e);
throw new RedisOperationException("Save operation failed", e);
}
};
try {
circuitBreaker.executeSupplier(saveOperation);
} catch (Exception e) {
logger.error("Circuit breaker prevented save operation for {} with id {}",
entityClass.getSimpleName(), id, e);
throw new CircuitBreakerException("Save operation prevented by circuit breaker", e);
}
}
// 获取实体
public T findById(String id) {
String key = buildKey(id);
Supplier<T> findOperation = () -> {
try (Jedis jedis = jedisPool.getResource()) {
String json = jedis.get(key);
if (json == null) {
logger.debug("Entity {} with id {} not found",
entityClass.getSimpleName(), id);
return null;
}
T entity = objectMapper.readValue(json, entityClass);
logger.debug("Entity {} with id {} retrieved",
entityClass.getSimpleName(), id);
return entity;
} catch (Exception e) {
logger.error("Failed to retrieve entity {} with id {}",
entityClass.getSimpleName(), id, e);
throw new RedisOperationException("Find operation failed", e);
}
};
try {
return circuitBreaker.executeSupplier(findOperation);
} catch (Exception e) {
logger.error("Circuit breaker prevented find operation for {} with id {}",
entityClass.getSimpleName(), id, e);
throw new CircuitBreakerException("Find operation prevented by circuit breaker", e);
}
}
// 删除实体
public boolean delete(String id) {
String key = buildKey(id);
Supplier<Boolean> deleteOperation = () -> {
try (Jedis jedis = jedisPool.getResource()) {
Long result = jedis.del(key);
boolean deleted = result != null && result > 0;
if (deleted) {
logger.debug("Entity {} with id {} deleted",
entityClass.getSimpleName(), id);
} else {
logger.debug("Entity {} with id {} not found for deletion",
entityClass.getSimpleName(), id);
}
return deleted;
} catch (Exception e) {
logger.error("Failed to delete entity {} with id {}",
entityClass.getSimpleName(), id, e);
throw new RedisOperationException("Delete operation failed", e);
}
};
try {
return circuitBreaker.executeSupplier(deleteOperation);
} catch (Exception e) {
logger.error("Circuit breaker prevented delete operation for {} with id {}",
entityClass.getSimpleName(), id, e);
throw new CircuitBreakerException("Delete operation prevented by circuit breaker", e);
}
}
// 增量计数器
public long increment(String id, String field, long amount) {
String key = buildKey(id + ":" + field);
Supplier<Long> incrementOperation = () -> {
try (Jedis jedis = jedisPool.getResource()) {
long result;
if (amount == 1) {
result = jedis.incr(key);
} else {
result = jedis.incrBy(key, amount);
}
// 如果是新创建的计数器,设置过期时间
if (result == amount) {
jedis.expire(key, defaultExpireSeconds);
}
logger.debug("Counter {} for entity {} with id {} incremented by {}, new value: {}",
field, entityClass.getSimpleName(), id, amount, result);
return result;
} catch (Exception e) {
logger.error("Failed to increment counter {} for entity {} with id {}",
field, entityClass.getSimpleName(), id, e);
throw new RedisOperationException("Increment operation failed", e);
}
};
try {
return circuitBreaker.executeSupplier(incrementOperation);
} catch (Exception e) {
logger.error("Circuit breaker prevented increment operation for {} with id {}",
entityClass.getSimpleName(), id, e);
throw new CircuitBreakerException("Increment operation prevented by circuit breaker", e);
}
}
private String buildKey(String id) {
return keyPrefix + ":" + id;
}
// 自定义异常
public static class RedisOperationException extends RuntimeException {
public RedisOperationException(String message, Throwable cause) {
super(message, cause);
}
}
public static class CircuitBreakerException extends RuntimeException {
public CircuitBreakerException(String message, Throwable cause) {
super(message, cause);
}
}
}
// 使用示例
class Product {
private String id;
private String name;
private double price;
// getters and setters
}
class ProductService {
private final RedisRepository<Product> productRepository;
public ProductService(JedisPool jedisPool) {
this.productRepository = new RedisRepository<>(jedisPool, Product.class, "product", 3600);
}
public void saveProduct(Product product) {
productRepository.save(product.getId(), product);
}
public Product getProduct(String id) {
return productRepository.findById(id);
}
public long incrementViews(String productId) {
return productRepository.increment(productId, "views", 1);
}
}
Bitmap 应用示例
java
import redis.clients.jedis.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
public class UserActivityTracker {
private static final Logger logger = LoggerFactory.getLogger(UserActivityTracker.class);
private final JedisPool jedisPool;
private final String keyPrefix;
public UserActivityTracker(JedisPool jedisPool, String keyPrefix) {
this.jedisPool = jedisPool;
this.keyPrefix = keyPrefix;
}
// 记录用户当日签到
public void recordSignIn(long userId) {
String key = getSignInKey(userId);
int dayOfMonth = LocalDate.now().getDayOfMonth();
try (Jedis jedis = jedisPool.getResource()) {
Boolean wasAlreadySet = jedis.getbit(key, dayOfMonth - 1);
jedis.setbit(key, dayOfMonth - 1, true);
// 如果是新创建的键,设置过期时间(当月结束后再保留7天)
if (Boolean.FALSE.equals(wasAlreadySet)) {
LocalDate firstDayOfNextMonth = LocalDate.now().plusMonths(1).withDayOfMonth(1);
LocalDate expiryDate = firstDayOfNextMonth.plusDays(7);
int ttlSeconds = (int) (expiryDate.toEpochDay() - LocalDate.now().toEpochDay()) * 86400;
jedis.expire(key, ttlSeconds);
logger.info("User {} sign-in recorded for day {}, first time this month",
userId, dayOfMonth);
} else {
logger.info("User {} already signed in for day {}", userId, dayOfMonth);
}
} catch (JedisException e) {
logger.error("Failed to record sign-in for user {}", userId, e);
throw new RuntimeException("Failed to record sign-in", e);
}
}
// 检查用户是否已签到
public boolean hasSignedIn(long userId) {
String key = getSignInKey(userId);
int dayOfMonth = LocalDate.now().getDayOfMonth();
try (Jedis jedis = jedisPool.getResource()) {
Boolean result = jedis.getbit(key, dayOfMonth - 1);
return Boolean.TRUE.equals(result);
} catch (JedisException e) {
logger.error("Failed to check sign-in status for user {}", userId, e);
throw new RuntimeException("Failed to check sign-in status", e);
}
}
// 获取用户本月签到次数
public long getMonthlySignInCount(long userId) {
String key = getSignInKey(userId);
try (Jedis jedis = jedisPool.getResource()) {
return jedis.bitcount(key);
} catch (JedisException e) {
logger.error("Failed to get monthly sign-in count for user {}", userId, e);
throw new RuntimeException("Failed to get monthly sign-in count", e);
}
}
// 获取用户当月签到情况
public List<Integer> getMonthlySignInDays(long userId) {
String key = getSignInKey(userId);
int daysInMonth = LocalDate.now().lengthOfMonth();
List<Integer> signInDays = new ArrayList<>();
try (Jedis jedis = jedisPool.getResource()) {
// 获取整个位图并在Java中处理,减少Redis往返
byte[] bytes = jedis.get(key.getBytes());
if (bytes != null && bytes.length > 0) {
BitSet bitSet = BitSet.valueOf(bytes);
for (int i = 0; i < daysInMonth; i++) {
if (bitSet.get(i)) {
signInDays.add(i + 1); // 添加签到的日期(从1开始)
}
}
}
return signInDays;
} catch (JedisException e) {
logger.error("Failed to get monthly sign-in days for user {}", userId, e);
throw new RuntimeException("Failed to get monthly sign-in days", e);
}
}
// 检查是否连续签到
public int getConsecutiveSignInDays(long userId) {
List<Integer> days = getMonthlySignInDays(userId);
if (days.isEmpty()) {
return 0;
}
// 检查今天是否签到
if (!days.contains(LocalDate.now().getDayOfMonth())) {
return 0;
}
int consecutive = 1;
int today = LocalDate.now().getDayOfMonth();
// 从今天向前检查连续签到
for (int i = 1; i < today; i++) {
if (!days.contains(today - i)) {
break;
}
consecutive++;
}
return consecutive;
}
// 跨多个用户的批量操作 - 获取当日活跃用户数
public long getDailyActiveUserCount(List<Long> userIds) {
if (userIds == null || userIds.isEmpty()) {
return 0;
}
String tempKey = "temp:activeusers:" + System.currentTimeMillis();
int dayOfMonth = LocalDate.now().getDayOfMonth();
try (Jedis jedis = jedisPool.getResource()) {
// 创建临时位图
for (Long userId : userIds) {
String key = getSignInKey(userId);
Boolean isActive = jedis.getbit(key, dayOfMonth - 1);
if (Boolean.TRUE.equals(isActive)) {
jedis.setbit(tempKey, userId, true);
}
}
// 计算临时位图中设置的位数
long count = jedis.bitcount(tempKey);
// 删除临时键
jedis.del(tempKey);
return count;
} catch (JedisException e) {
logger.error("Failed to get daily active user count", e);
throw new RuntimeException("Failed to get daily active user count", e);
}
}
private String getSignInKey(long userId) {
// 分片策略 - 对大规模用户进行分片,避免单个位图过大
int shardIndex = (int)(userId % 10); // 10个分片
String yearMonth = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"));
return keyPrefix + ":signin:" + shardIndex + ":" + yearMonth + ":" + userId;
}
// 使用示例
public static void main(String[] args) {
JedisPool jedisPool = new JedisPool("localhost", 6379);
UserActivityTracker tracker = new UserActivityTracker(jedisPool, "app");
long userId = 1001;
// 记录签到
tracker.recordSignIn(userId);
// 检查签到状态
boolean hasSignedIn = tracker.hasSignedIn(userId);
System.out.println("User has signed in today: " + hasSignedIn);
// 获取本月签到次数
long monthlyCount = tracker.getMonthlySignInCount(userId);
System.out.println("Monthly sign-in count: " + monthlyCount);
// 获取本月签到日期
List<Integer> signInDays = tracker.getMonthlySignInDays(userId);
System.out.println("Sign-in days this month: " + signInDays);
// 获取连续签到天数
int consecutiveDays = tracker.getConsecutiveSignInDays(userId);
System.out.println("Consecutive sign-in days: " + consecutiveDays);
jedisPool.close();
}
}
JMH 基准测试示例
java
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import redis.clients.jedis.*;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
@Fork(value = 1, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 3)
@Measurement(iterations = 5)
public class RedisStringBenchmark {
private JedisPool jedisPool;
private static final String INT_KEY = "benchmark:int";
private static final String EMBSTR_KEY = "benchmark:embstr";
private static final String RAW_KEY = "benchmark:raw";
private static final String PIPELINED_KEY_PREFIX = "benchmark:pipeline:";
@Setup
public void setup() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(32);
poolConfig.setMaxIdle(16);
jedisPool = new JedisPool(poolConfig, "localhost", 6379);
// 准备基准测试数据
try (Jedis jedis = jedisPool.getResource()) {
// 用于int编码的数据
jedis.set(INT_KEY, "12345");
// 用于embstr编码的短字符串
jedis.set(EMBSTR_KEY, "This is a short string for embstr encoding test");
// 用于raw编码的长字符串
StringBuilder longString = new StringBuilder(5000);
for (int i = 0; i < 100; i++) {
longString.append("This is a long string for raw encoding benchmark test. ");
}
jedis.set(RAW_KEY, longString.toString());
}
}
@TearDown
public void tearDown() {
try (Jedis jedis = jedisPool.getResource()) {
jedis.del(INT_KEY, EMBSTR_KEY, RAW_KEY);
// 清理pipeline测试键
for (int i = 0; i < 100; i++) {
jedis.del(PIPELINED_KEY_PREFIX + i);
}
}
if (jedisPool != null) {
jedisPool.close();
}
}
@Benchmark
public String getIntEncoded() {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.get(INT_KEY);
}
}
@Benchmark
public String getEmbstrEncoded() {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.get(EMBSTR_KEY);
}
}
@Benchmark
public String getRawEncoded() {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.get(RAW_KEY);
}
}
@Benchmark
public String setIntValue() {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.set(INT_KEY, "54321");
}
}
@Benchmark
public String setEmbstrValue() {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.set(EMBSTR_KEY, "Updated short string for embstr test");
}
}
@Benchmark
public String setRawValue() {
try (Jedis jedis = jedisPool.getResource()) {
StringBuilder updatedString = new StringBuilder(5000);
for (int i = 0; i < 50; i++) {
updatedString.append("Updated long string for raw benchmark test. ");
}
return jedis.set(RAW_KEY, updatedString.toString());
}
}
@Benchmark
public Long incrIntValue() {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.incr(INT_KEY);
}
}
@Benchmark
public String appendToEmbstr() {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.append(EMBSTR_KEY, " append test");
}
}
@Benchmark
public void pipelinedSetOperations() {
try (Jedis jedis = jedisPool.getResource()) {
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 100; i++) {
pipeline.set(PIPELINED_KEY_PREFIX + i, "value-" + i);
}
pipeline.sync();
}
}
@Benchmark
public List<Object> pipelinedGetOperations() {
try (Jedis jedis = jedisPool.getResource()) {
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 100; i++) {
pipeline.get(PIPELINED_KEY_PREFIX + i);
}
return pipeline.syncAndReturnAll();
}
}
// 比较LUA脚本与直接操作
@Benchmark
public Long incrWithExpireIndividual() {
try (Jedis jedis = jedisPool.getResource()) {
Long value = jedis.incr(INT_KEY);
jedis.expire(INT_KEY, 3600);
return value;
}
}
@Benchmark
public Object incrWithExpireLua() {
String script = "local value = redis.call('incr', KEYS[1]) " +
"redis.call('expire', KEYS[1], ARGV[1]) " +
"return value";
try (Jedis jedis = jedisPool.getResource()) {
return jedis.eval(script,
Collections.singletonList(INT_KEY),
Collections.singletonList("3600"));
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(RedisStringBenchmark.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
Redis String 使用决策

优化建议
-
编码选择:
- 计数器场景优先使用 int 编码
- 短文本(≤44 字节)使用 embstr 编码
- 避免频繁修改 embstr 编码的字符串
-
操作优化:
- 使用 Pipeline 减少网络往返
- 使用原子操作代替 GET+SET 模式
- 批量操作使用 MGET/MSET
- 使用 LUA 脚本进行复杂的原子操作
- 位操作场景使用 GETBIT/SETBIT
-
内存管理:
- 设置合理的 maxmemory 和淘汰策略
- 启用 activedefrag 减少内存碎片
- 合理使用过期时间
- 压缩键名和值减少内存占用
-
高可用与可扩展性:
- 主从复制确保数据可用性
- Sentinel 提供自动故障转移
- 集群模式分散数据,提高可扩展性
- 使用哈希标签处理集群环境中的关联数据
- RedLock 算法解决跨节点分布式锁问题
-
连接管理:
- 使用连接池管理连接资源
- 设置合理的连接超时和重试策略
- 实现断路器模式处理服务不可用情况
- 监控并优化连接池配置参数
总结
特性 | 实现方式 | 优势 | 注意事项 |
---|---|---|---|
数据结构 | SDS | O(1)获取长度, 内存安全, 二进制安全 | 根据长度选择不同结构体 |
编码方式 | int/embstr/raw | 自动选择最佳编码,优化性能和内存 | embstr 修改会转为 raw |
内存管理 | 预分配+惰性释放 | 减少内存操作,提高性能 | 可能导致暂时内存浪费 |
原子操作 | 命令集+事务+LUA 脚本 | 支持复杂的原子操作,减少竞态条件 | 事务不支持回滚 |
高可用 | 主从+哨兵+集群 | 提高可用性和可扩展性 | 需注意主从一致性延迟 |
应用场景 | 缓存/计数器/分布式锁/位图应用 | 丰富的应用场景支持 | 选择合适的数据结构和编码 |
监控运维 | Prometheus/Grafana | 全面监控性能和健康状况 | 关注内存使用和命令统计 |