Redis String 类型的底层实现与性能优化

Redis String 是 Redis 中最基础也是应用最广泛的数据类型,它能存储文本、数字、二进制数据等多种形式的信息。深入理解其底层实现对构建高性能分布式系统至关重要。

Redis 字符串的底层结构:SDS

Redis 没有使用 C 语言原生字符串,而是设计了 Simple Dynamic String (SDS)数据结构,随着版本演进有不同实现。

SDS 的版本演进

Redis 3.2 之前的 SDS 结构:

struct sdshdr {
    int len;    // 已使用的字节数量
    int free;   // 剩余可用字节数量
    char buf[]; // 字符数组,实际数据
};

Redis 3.2 及以后版本根据字符串长度使用不同的结构体优化内存使用:

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 字符串的关键区别

  1. O(1)时间复杂度获取字符串长度:直接访问 len 字段,而 C 字符串需 O(N)遍历
  2. 内存安全:修改前检查空间是否充足,避免缓冲区溢出
  3. 高效内存管理:通过预分配和惰性释放减少内存操作次数
  4. 二进制安全:可存储任意二进制数据,不受'\0'字符限制

"二进制安全"意味着字符串可以存储任何字节序列,包括空字符和非打印字符,而不会被截断或误解。"惰性释放"指字符串缩短时不立即释放多余空间,而是保留用于将来可能的增长。

Redis 对象系统与 String 编码

Redis 使用统一的对象系统(redisObject)管理所有数据:

typedef struct redisObject {
    unsigned type:4;      // 类型(String, List等)
    unsigned encoding:4;  // 编码方式
    unsigned lru:LRU_BITS; // LRU信息
    int refcount;         // 引用计数
    void *ptr;            // 指向实际数据
} robj;

String 类型的三种编码

String 类型根据内容自动选择最适合的编码:

  1. int 编码:存储可用 64 位有符号整数表示的数值
  2. embstr 编码:存储短字符串(≤44 字节),redisObject 和 SDS 在内存中连续存储
  3. raw 编码:存储长字符串(>44 字节),redisObject 和 SDS 在内存中分开存储

embstr 的 44 字节阈值原理

Redis 5.0 及以上版本中 embstr 的 44 字节阈值计算:

44字节 = 64字节(jemalloc分配的最小内存块) - 16字节(redisObject大小) - 3字节(sdshdr8大小) - 1字节('\0')

这样设计确保一个 embstr 字符串恰好占用一个 jemalloc 分配的内存块,优化内存使用效率。Redis 默认使用 jemalloc 而非 libc malloc,因为 jemalloc 能更好地处理内存碎片问题,特别是在长时间运行的服务中。

内存分配与管理策略

SDS 的空间分配采用预分配策略,避免频繁的内存重分配:

从 Redis 4.0 开始,引入了内存碎片整理功能,可以通过activedefrag相关配置启用,有助于减少 String 类型频繁修改造成的内存碎片问题。

编码转换规则与原理

Redis String 类型的编码转换遵循以下规则:

  1. int → embstr/raw:当对整数值执行非数值操作(如 APPEND)时发生
  2. embstr → raw:当修改 embstr 编码字符串时发生,因为 embstr 是只读的
  3. raw → embstr/int:不会发生,编码降级不存在
  4. embstr/raw → int:当使用 SET 等命令将值设为整数时发生

embstr 转换为 raw 的技术原因:embstr 是一整块连续内存,修改可能需要重分配空间,而直接转为 raw 编码(两块分离内存)操作更高效。

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);
        }
    }
}

分布式锁实现

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 模式与断路器模式结合

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 应用示例

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 基准测试示例

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 使用决策

优化建议

  1. 编码选择
  2. 计数器场景优先使用 int 编码
  3. 短文本(≤44 字节)使用 embstr 编码
  4. 避免频繁修改 embstr 编码的字符串
  5. 操作优化
  6. 使用 Pipeline 减少网络往返
  7. 使用原子操作代替 GET+SET 模式
  8. 批量操作使用 MGET/MSET
  9. 使用 LUA 脚本进行复杂的原子操作
  10. 位操作场景使用 GETBIT/SETBIT
  11. 内存管理
  12. 设置合理的 maxmemory 和淘汰策略
  13. 启用 activedefrag 减少内存碎片
  14. 合理使用过期时间
  15. 压缩键名和值减少内存占用
  16. 高可用与可扩展性
  17. 主从复制确保数据可用性
  18. Sentinel 提供自动故障转移
  19. 集群模式分散数据,提高可扩展性
  20. 使用哈希标签处理集群环境中的关联数据
  21. RedLock 算法解决跨节点分布式锁问题
  22. 连接管理
  23. 使用连接池管理连接资源
  24. 设置合理的连接超时和重试策略
  25. 实现断路器模式处理服务不可用情况
  26. 监控并优化连接池配置参数

总结

特性

实现方式

优势

注意事项

数据结构

SDS

O(1)获取长度, 内存安全, 二进制安全

根据长度选择不同结构体

编码方式

int/embstr/raw

自动选择最佳编码,优化性能和内存

embstr 修改会转为 raw

内存管理

预分配+惰性释放

减少内存操作,提高性能

可能导致暂时内存浪费

原子操作

命令集+事务+LUA 脚本

支持复杂的原子操作,减少竞态条件

事务不支持回滚

高可用

主从+哨兵+集群

提高可用性和可扩展性

需注意主从一致性延迟

应用场景

缓存/计数器/分布式锁/位图应用

丰富的应用场景支持

选择合适的数据结构和编码

监控运维

Prometheus/Grafana

全面监控性能和健康状况

关注内存使用和命令统计

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值