Redis深度解析:把缓存核心讲透,吊打面试官

Redis深度解析:把缓存核心讲透,吊打面试官

🎯 写在前面:Redis是后端开发的"核武器",面试必问、业务必用。但你真的理解Redis为什么这么快吗?缓存三兄弟(穿透、击穿、雪崩)到底怎么破?集群选型又该怎么选?这篇文章,带你彻底搞懂Redis!

前言

Redis,这个看起来简简单单的KV数据库,却是后端工程师必须掌握的核心技能。面试时,面试官总喜欢问:

"Redis为什么这么快?"

"缓存穿透、击穿、雪崩有什么区别?怎么解决?"

"Redis的数据结构有哪些?分别在什么场景下使用?"

"Redis集群怎么选?哨兵和集群模式有什么区别?"

这些问题,你能答上来几个?

今天这篇文章,我将从原理到实战,把Redis的核心知识点讲透,让你面试再也不慌。


一、Redis为什么这么快?------ 单线程I/O模型的秘密

1.1 面试高频问题

java 复制代码
面试官灵魂拷问:
"Redis是单线程的,为什么还能这么快?"

1.2 答案解析

核心原因:单线程 + I/O多路复用

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                      Redis 单线程模型                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                    ┌─────────────────┐                      │
│                    │   事件循环线程    │                      │
│                    │  (Event Loop)    │                      │
│                    └────────┬────────┘                      │
│                             │                                │
│              ┌──────────────┼──────────────┐                │
│              ▼              ▼              ▼                │
│        ┌─────────┐    ┌─────────┐    ┌─────────┐           │
│        │ Socket1 │    │ Socket2 │    │ Socket3 │           │
│        │  (GET)  │    │  (SET)  │    │  (DEL)  │           │
│        └─────────┘    └─────────┘    └─────────┘           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
              ▲              ▲              ▲
              │              │              │
         客户端请求      客户端请求      客户端请求

1.3 I/O多路复用详解

什么是多路复用?

scss 复制代码
传统阻塞模型(效率低):
┌────────┐     ┌────────┐     ┌────────┐
│Client1 │────▶│Server  │────▶│Client2 │
│Client3 │     │(Block) │     │Client4 │
└────────┘     └────────┘     └────────┘
问题:每个连接一个线程,线程切换开销大

I/O多路复用模型(Redis采用):
┌─────────────────────────────────────┐
│          多路复用器 (Select/Epoll)   │
├─────────────────────────────────────┤
│  Client1 ──▶│──▶  可读事件队列        │
│  Client2 ──▶│──▶  可写事件队列        │
│  Client3 ──▶│──▶  错误事件队列        │
└─────────────┴───────────────────────┘
              │
              ▼
        ┌───────────┐
        │ 单线程处理 │
        │  事件循环  │
        └───────────┘

1.4 Redis 6.0 多线程优化

java 复制代码
// Redis 6.0 引入的 I/O 多线程
// 注意:这里的多线程只用于网络 I/O,不用于命令执行

// redis.conf 配置
io-threads 4        // I/O 线程数(建议 CPU 核数的一半)
io-threads-do-reads yes  // 是否启用 I/O 线程读取

// 工作流程:
// 1. 主线程 accept() 接收连接
// 2. I/O 线程负责读取请求、解析命令
// 3. 主线程执行命令
// 4. I/O 线程负责写回响应

1.5 单线程的优势

优势 说明
无锁竞争 不需要考虑并发修改数据的问题
CPU亲和 单线程绑定的CPU缓存命中率高
简单高效 避免了线程切换、锁等开销
原子操作 所有操作都是原子的

1.6 性能数据

yaml 复制代码
Redis 性能指标:
├── QPS: 10万~100万+(单机)
├── 延迟: 亚毫秒级(< 1ms)
└── 内存: 基于内存操作

对比:
├── MySQL: 3000~5000 QPS
├── MongoDB: 1万~5万 QPS
└── Redis: 10万+ QPS

二、缓存三兄弟:穿透、击穿、雪崩

2.1 三兄弟全景图

vbnet 复制代码
┌────────────────────────────────────────────────────────────────┐
│                      缓存三兄弟对比图                           │
├──────────┬──────────┬──────────┬──────────────────────────────┤
│   类型   │   定义   │  发生场景 │         核心特征              │
├──────────┼──────────┼──────────┼──────────────────────────────┤
│  缓存穿透 │ 恶意攻击 │ 不存在数据│ 缓存和DB都没有,大量请求      │
│  缓存击穿 │ 热点失效 │ 热点key过期│ 缓存过期瞬间,大量请求涌入   │
│  缓存雪崩 │ 批量失效 │ 大量key过期│ 大量key同时过期,系统崩溃    │
└──────────┴──────────┴──────────┴──────────────────────────────┘

2.2 缓存穿透(Cache Penetration)

什么是缓存穿透?

scss 复制代码
请求流程:
┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│  请求   │───▶│  缓存   │───▶│   DB    │    │  返回   │
│         │    │(miss)   │    │ (miss)  │    │  空数据 │
└─────────┘    └─────────┘    └─────────┘    └─────────┘

问题:大量请求查询不存在的数据,每次都打到DB

解决方案1:接口参数校验

java 复制代码
/**
 * 方案一:基础参数校验
 * 适用于:明显非法请求
 */
public User queryById(Long id) {
    // 1. 参数校验
    if (id == null || id <= 0) {
        return null;  // 非法参数直接返回
    }
    
    // 2. 查询缓存
    User user = getFromCache(id);
    if (user != null) {
        return user;
    }
    
    // 3. 查询数据库
    return queryFromDB(id);
}

解决方案2:布隆过滤器(推荐)

java 复制代码
/**
 * 方案二:布隆过滤器
 * 适用于:数据量级大、存在合法/非法边界模糊的情况
 * 
 * 原理:用一个大型bitmap,存储所有合法数据的指纹
 * 特点:空间效率高,但存在误判(可能把不存在的判定为存在)
 */

// 引入依赖
// <dependency>
//     <groupId>com.google.guava</groupId>
//     <artifactId>guava</artifactId>
//     <version>32.1.3-jre</version>
// </dependency>

import com.google.common.hash.BloomFilter;

@Service
public class BloomFilterService {
    
    // 创建布隆过滤器,expectedInsertions=期望插入数量,fpp=误判率
    private BloomFilter<Long> bloomFilter = BloomFilter.create(
        Funnels.longFunnel(),
        100000000,  // 1亿数据
        0.01        // 0.01% 误判率
    );
    
    /**
     * 初始化布隆过滤器(从DB加载所有合法ID)
     */
    @PostConstruct
    public void init() {
        List<Long> allUserIds = userMapper.selectAllIds();
        allUserIds.forEach(bloomFilter::put);
    }
    
    /**
     * 使用布隆过滤器判断
     */
    public boolean mightContain(Long userId) {
        return bloomFilter.mightContain(userId);
    }
}

// 使用示例
public User queryUserById(Long userId) {
    // 1. 先检查布隆过滤器
    if (!bloomFilterService.mightContain(userId)) {
        // 布隆过滤器说不存在,直接返回(不需要查DB)
        return null;
    }
    
    // 2. 布隆过滤器说可能存在,继续查缓存和DB
    User user = getFromCache(userId);
    if (user != null) {
        return user;
    }
    
    return queryFromDB(userId);
}

布隆过滤器参数选择

ini 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                  布隆过滤器参数计算公式                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  n = 期望插入数量                                               │
│  p = 期望误判率                                                │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  m = - (n * ln(p)) / (ln(2)^2)   // 所需bit位         │   │
│  │  k = (ln(2) * m / n)             // 哈希函数数量        │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  示例:n=1亿,p=0.01%                                            │
│  ├── m ≈ 1.44GB                                                 │
│  └── k = 17个哈希函数                                           │
│                                                                 │
│  常见配置:                                                      │
│  ├── 1000万数据,1%误判 → 约140MB                               │
│  ├── 1亿数据,1%误判   → 约1.4GB                                │
│  └── 1亿数据,0.1%误判 → 约2.9GB                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

解决方案3:缓存空值

java 复制代码
/**
 * 方案三:缓存空值 + 短过期时间
 * 适用于:不存在的数据是合法但暂时无数据的
 */
private static final String CACHE_NULL_PREFIX = "cache:null:";
private static final long CACHE_NULL_TTL = 60;  // 空值缓存60秒

public User queryById(Long id) {
    String cacheKey = "user:" + id;
    
    // 1. 查询缓存
    String cachedValue = redisTemplate.opsForValue().get(cacheKey);
    if (cachedValue != null) {
        // 2. 判断是否是空值
        if ("NULL".equals(cachedValue)) {
            return null;  // 缓存的空值,直接返回
        }
        return JSON.parseObject(cachedValue, User.class);
    }
    
    // 3. 查询数据库
    User user = userMapper.selectById(id);
    
    // 4. 写入缓存(包含空值)
    if (user == null) {
        redisTemplate.opsForValue().set(cacheKey, "NULL", CACHE_NULL_TTL, TimeUnit.SECONDS);
    } else {
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user), CACHE_TTL, TimeUnit.HOURS);
    }
    
    return user;
}

2.3 缓存击穿(Cache Breakdown)

什么是缓存击穿?

scss 复制代码
热点key失效瞬间:
                    ┌─────────────────────────────────┐
                    │        大量请求同时涌入          │
                    └─────────────────────────────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
              ┌─────────┐     ┌─────────┐     ┌─────────┐
              │ 请求1   │     │ 请求2   │     │ 请求3   │
              │(查DB)   │     │(查DB)   │     │(查DB)   │
              └────┬────┘     └────┬────┘     └────┬────┘
                   │               │               │
                   └───────────────┼───────────────┘
                                   ▼
                         ┌─────────────────┐
                         │   数据库被打爆    │
                         │   (连接池耗尽)    │
                         └─────────────────┘

解决方案1:互斥锁(分布式锁)

java 复制代码
/**
 * 方案一:分布式锁 + 单线程回源
 * 核心:只有一个请求去查DB,其他等待
 */
@Service
public class UserService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public User queryUserById(Long id) {
        String cacheKey = "user:" + id;
        
        // 1. 先查缓存
        User user = getFromCache(cacheKey);
        if (user != null) {
            return user;
        }
        
        // 2. 获取分布式锁
        String lockKey = "lock:user:" + id;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试加锁(等待3秒,锁自动过期30秒)
            boolean locked = lock.tryLock(3, 30, TimeUnit.SECONDS);
            
            if (locked) {
                try {
                    // 3. 双检锁定:获取锁后再次检查缓存
                    user = getFromCache(cacheKey);
                    if (user != null) {
                        return user;
                    }
                    
                    // 4. 查数据库
                    user = userMapper.selectById(id);
                    
                    // 5. 写入缓存
                    if (user != null) {
                        setCache(cacheKey, user);
                    }
                    
                    return user;
                } finally {
                    lock.unlock();
                }
            } else {
                // 没获取到锁,短暂等待后重试查缓存
                Thread.sleep(100);
                return queryUserById(id);  // 递归重试
            }
        } catch (InterruptedException e) {
            // 异常情况,直接查DB
            return userMapper.selectById(id);
        }
    }
}

解决方案2:热点数据永不过期

java 复制代码
/**
 * 方案二:热点数据永不过期 + 异步更新
 * 核心:用逻辑过期替代物理过期
 */

// 缓存数据结构
@Data
public class CacheData<T> {
    private T data;           // 真实数据
    private long expireTime;  // 逻辑过期时间
    private long version;      // 数据版本
}

// 使用示例
public User queryUserById(Long id) {
    String cacheKey = "user:" + id;
    
    // 1. 先查缓存
    CacheData<User> cacheData = getFromCache(cacheKey);
    
    if (cacheData != null) {
        // 2. 检查是否逻辑过期
        if (cacheData.getExpireTime() > System.currentTimeMillis()) {
            // 没过期,直接返回
            return cacheData.getData();
        }
        
        // 3. 逻辑过期了,开启异步更新
        threadPool.execute(() -> {
            // 只有一个线程能抢到锁执行更新
            refreshCache(id);
        });
        
        // 4. 返回旧数据(短暂不一致可接受)
        return cacheData.getData();
    }
    
    // 5. 缓存不存在,直接查DB并回填
    User user = userMapper.selectById(id);
    setCacheWithLogicExpire(cacheKey, user, 30 * 60 * 1000L);  // 30分钟逻辑过期
    
    return user;
}

2.4 缓存雪崩(Cache Avalanche)

什么是缓存雪崩?

css 复制代码
大量key同时过期:
┌─────────────────────────────────────────────────────┐
│  缓存中的热点key们                                   │
├─────────────────────────────────────────────────────┤
│  key1 ──▶ [过期时间 12:00] ──▶ 🔴 同时过期           │
│  key2 ──▶ [过期时间 12:00] ──▶ 🔴 同时过期           │
│  key3 ──▶ [过期时间 12:00] ──▶ 🔴 同时过期           │
│  ...                                                │
│  key999 ─▶ [过期时间 12:00] ──▶ 🔴 同时过期         │
└─────────────────────────────────────────────────────┘
                    │
                    ▼
        所有请求同时打向数据库
                    │
                    ▼
        ┌─────────────────────┐
        │   数据库扛不住       │
        │   系统雪崩           │
        └─────────────────────┘

解决方案:随机过期时间 + 多级缓存

java 复制代码
/**
 * 解决方案一:随机过期时间
 */
public void setCache(String key, Object value) {
    // 基础过期时间:30分钟
    long baseExpire = 30 * 60;
    // 随机偏移:±5分钟
    int randomOffset = new Random().nextInt(10 * 60) - 5 * 60;
    // 最终过期时间:25~35分钟
    long expire = baseExpire + randomOffset;
    
    redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
}

/**
 * 解决方案二:多级缓存架构
 * L1: Caffeine (本地缓存) → L2: Redis → L3: MySQL
 */
@Configuration
public class MultiLevelCacheConfig {
    
    // L1 本地缓存(Caffeine)
    @Bean
    public Cache<Long, User> localCache() {
        return Caffeine.newBuilder()
            .maximumSize(10000)           // 最多1万条
            .expireAfterWrite(1, TimeUnit.MINUTES)  // 1分钟过期
            .build();
    }
    
    // L2 分布式缓存(Redis)
    // ... 已有的Redis配置
}

@Service
public class MultiLevelUserService {
    
    @Autowired
    private Cache<Long, User> localCache;
    
    public User queryUserById(Long id) {
        // 1. 先查L1本地缓存
        User user = localCache.getIfPresent(id);
        if (user != null) {
            return user;
        }
        
        // 2. 查L2 Redis缓存
        String redisKey = "user:" + id;
        String json = redisTemplate.opsForValue().get(redisKey);
        if (json != null) {
            user = JSON.parseObject(json, User.class);
            // 回填L1
            localCache.put(id, user);
            return user;
        }
        
        // 3. 查MySQL
        user = userMapper.selectById(id);
        if (user != null) {
            // 回填L2和L1
            redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(user), 1, TimeUnit.HOURS);
            localCache.put(id, user);
        }
        
        return user;
    }
}

/**
 * 解决方案三:Redis集群 + 熔断降级
 */
@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        // 开启事务
        template.setEnableTransactionSupport(true);
        return template;
    }
}

/**
 * 降级兜底:Redis挂了返回默认值
 */
public User queryUserById(Long id) {
    try {
        return redisUserService.queryUserById(id);
    } catch (Exception e) {
        // Redis异常,降级到DB
        log.warn("Redis异常,降级到DB查询", e);
        return userMapper.selectById(id);
    }
}

2.5 三兄弟解决方案总结

vbnet 复制代码
┌────────────────────────────────────────────────────────────────┐
│                    缓存问题综合解决方案                          │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  穿透:布隆过滤器 + 接口校验 + 缓存空值                          │
│  ├── 布隆过滤器:挡住不存在的请求                               │
│  ├── 接口校验:拦住明显非法的请求                               │
│  └── 空值缓存:缓存短时间不存在的值                             │
│                                                                │
│  击穿:互斥锁 / 逻辑过期 / 永不过期                             │
│  ├── 互斥锁:保证只有一个人回源                                │
│  ├── 逻辑过期:返回旧数据 + 异步更新                            │
│  └── 热点永不过期:用版本号控制更新                             │
│                                                                │
│  雪崩:随机过期 + 多级缓存 + 熔断降级                           │
│  ├── 随机过期:打散key的过期时间                                │
│  ├── 多级缓存:L1本地 + L2Redis + L3DB                        │
│  └── 熔断降级:Redis挂了有兜底                                 │
│                                                                │
└────────────────────────────────────────────────────────────────┘

三、Redis数据结构避坑地图

3.1 数据结构全景图

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        Redis 数据结构                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                        String (字符串)                           │
│                           │                                     │
│            ┌──────────────┼──────────────┐                      │
│            ▼              ▼              ▼                      │
│      List(列表)      Hash(哈希)      Set(集合)                   │
│            │              │              │                      │
│            ▼              ▼              ▼                      │
│     Sorted Set     Stream(流)     HyperLogLog                   │
│      (有序集合)     (Redis5.0)   (基数统计)                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.2 String(字符串)------ 最常用的数据结构

内部编码

编码 条件 说明
int value是整数,且小于2^63 使用int类型存储
embstr value <= 39字节 embstr编码的SDS
raw value > 39字节 raw编码的SDS
java 复制代码
/**
 * String 类型的典型使用场景
 */

// 1. 缓存对象(最常见)
String userJson = JSON.toJSONString(user);
redisTemplate.opsForValue().set("user:" + id, userJson);

// 2. 分布式锁
Boolean success = redisTemplate.opsForValue()
    .setIfAbsent("lock:" + key, "1", 30, TimeUnit.SECONDS);

// 3. 计数器
redisTemplate.opsForValue().increment("view:article:" + articleId);

// 4. 分布式session
redisTemplate.opsForValue().set("session:" + sessionId, userInfo, 2, TimeUnit.HOURS);

// 5. 限流
String key = "rate:limit:" + userId + ":" + minute;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
    redisTemplate.expire(key, 60, TimeUnit.SECONDS);
}
return count <= 100;  // 每分钟最多100次

// 6. 验证码 / Token
redisTemplate.opsForValue().set("sms:code:" + phone, code, 5, TimeUnit.MINUTES);

String的"陷阱"

java 复制代码
// ❌ 错误:把大对象存为String
String bigObject = JSON.toJSONString(hugeMap);  // 可能超过512MB限制

// ✅ 正确:使用Hash存储大对象
redisTemplate.opsForHash().putAll("user:" + id, map);

// ❌ 错误:String做自增当ID
redisTemplate.opsForValue().increment("user:id");  // 并发不安全

// ✅ 正确:用UUID或号段服务
String userId = idGenerator.nextId();

3.3 Hash(哈希)------ 存储对象

底层实现

元素数量 encoding 说明
< 512 ziplist 压缩列表,内存紧凑
>= 512 hashtable 哈希表,支持O(1)操作
java 复制代码
/**
 * Hash 类型的使用场景
 */

// 1. 存储对象(比String更节省空间)
Map<String, Object> userMap = new HashMap<>();
userMap.put("name", "张三");
userMap.put("age", "25");
userMap.put("city", "北京");
redisTemplate.opsForHash().putAll("user:h:" + id, userMap);

// 2. 获取单个字段
String name = (String) redisTemplate.opsForHash().get("user:h:" + id, "name");

// 3. 获取所有字段
Map<Object, Object> user = redisTemplate.opsForHash().entries("user:h:" + id);

// 4. 字段自增(适合计数器)
redisTemplate.opsForHash().increment("stats:article:" + id, "views", 1);

// 5. 购物车实现
// 添加商品
redisTemplate.opsForHash().put("cart:" + userId, productId, String.valueOf(count));
// 增加数量
redisTemplate.opsForHash().increment("cart:" + userId, productId, addCount);
// 删除商品
redisTemplate.opsForHash().delete("cart:" + userId, productId);
// 查看购物车
Map<Object, Object> cart = redisTemplate.opsForHash().entries("cart:" + userId);

Hash的"陷阱"

java 复制代码
// ❌ 错误:hgetall获取大Hash的所有字段
Map<Object, Object> bigHash = redisTemplate.opsForHash().entries("big:hash");  // 内存爆炸

// ✅ 正确:分页获取
ScanOptions options = ScanOptions.scanOptions()
    .match("field:*")
    .count(100)
    .build();
Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan("big:hash", options);
while (cursor.hasNext()) {
    // 处理每个字段
}

// ❌ 错误:Hash的field没有过期功能
// Redis的Hash不支持对单个field设置过期时间

// ✅ 正确:想要field过期,用String + key组合
redisTemplate.opsForValue().set("user:" + id + ":name", name, 1, TimeUnit.HOURS);

3.4 List(列表)------ 队列和栈

底层实现

编码 条件 说明
ziplist 元素个数 < 512,且每个元素 < 64字节 压缩列表
linkedlist 其他情况 双向链表
java 复制代码
/**
 * List 类型的使用场景
 */

// 1. 消息队列(简单版,不保证 Exactly-Once)
// 生产者
redisTemplate.opsForList().leftPush("queue:msg", message);
// 消费者
String msg = redisTemplate.opsForList().rightPop("queue:msg");

// 2. 关注列表 / 粉丝列表
// 添加关注
redisTemplate.opsForList().leftPush("user:" + userId + ":follows", followedUserId);
// 获取关注列表(前10个)
List<Object> follows = redisTemplate.opsForList().range("user:" + userId + ":follows", 0, 9);

// 3. 最新商品列表
redisTemplate.opsForList().leftPush("product:new", productId);
redisTemplate.opsForList().trim("product:new", 0, 99);  // 只保留前100个

// 4. 日志收集
redisTemplate.opsForList().rightPush("logs", logMessage);
redisTemplate.opsForList().trim("logs", 0, 9999);  // 限制长度

// 5. 实现分页(性能差,不推荐大数据量)
// 获取第N页
long start = (page - 1) * pageSize;
long end = start + pageSize - 1;
List<Object> pageData = redisTemplate.opsForList().range("list:key", start, end);

List的"陷阱"

java 复制代码
// ❌ 错误:用List做分页(性能差)
// lrange是O(S+N)的,N越大越慢

// ✅ 正确:大数据量分页用Sorted Set
// ZADD score view_count
// ZREVRANGE 0 9 WITHSCORES

// ❌ 错误:没有消费者组概念
// 多个消费者可能消费同一条消息

// ✅ 正确:需要可靠队列用Stream

3.5 Set(集合)------ 无序去重

底层实现

编码 条件 说明
intset 集合所有元素都是整数,且元素个数 < 512 整数集合
hashtable 其他情况 哈希表
java 复制代码
/**
 * Set 类型的使用场景
 */

// 1. 标签系统(去重)
redisTemplate.opsForSet().add("tags:article:" + articleId, "Java", "Redis", "面试");
redisTemplate.opsForSet().add("tags:user:" + userId, "Java", "架构", "微服务");

// 2. 关注列表(判断是否关注)
redisTemplate.opsForSet().add("follows:" + userId, followedUserId);
Boolean isFollowed = redisTemplate.opsForSet().isMember("follows:" + userId, followedUserId);

// 3. 抽奖系统
// 添加参与者
redisTemplate.opsForSet().add("lottery:" + activityId, userId);
// 抽取N个中奖者
List<Object> winners = redisTemplate.opsForSet().distinctRandomMembers("lottery:" + activityId, 3);

// 4. UV统计
redisTemplate.opsForSet().add("uv:2024:01:01", visitorId);
// 最终UV数量
Long uvCount = redisTemplate.opsForSet().size("uv:2024:01:01");

// 5. 好友关系(交集、并集、差集)
// 共同好友
Set<Object> mutualFriends = redisTemplate.opsForSet().intersect("friends:user1", "friends:user2");
// 我认识但他不认识的人(差集)
Set<Object> diff = redisTemplate.opsForSet().difference("friends:user1", "friends:user2");

3.6 Sorted Set(有序集合)------ 排行榜

底层实现

编码 条件 说明
ziplist 元素个数 < 128,且每个member < 64字节 压缩列表
skiplist + hashtable 其他情况 跳表 + 哈希表
java 复制代码
/**
 * Sorted Set 类型的使用场景
 */

// 1. 排行榜(最经典场景)
// 增加用户积分
redisTemplate.opsForZSet().add("rank:score", userId, score);
// 更新积分
redisTemplate.opsForZSet().incrementScore("rank:score", userId, addedScore);
// 获取排名前10
Set<Object> top10 = redisTemplate.opsForZSet().reverseRange("rank:score", 0, 9);
// 获取用户排名
Long rank = redisTemplate.opsForZSet().reverseRank("rank:score", userId);
// 获取用户积分
Double score = redisTemplate.opsForZSet().score("rank:score", userId);

// 2. 延迟队列(按时间戳排序)
long delayTime = System.currentTimeMillis() + 5000;  // 5秒后执行
redisTemplate.opsForZSet().add("delay:queue", taskId, delayTime);
// 消费者:获取到期的任务
Set<Object> expiredTasks = redisTemplate.opsForZSet().rangeByScore("delay:queue", 0, System.currentTimeMillis());
// 删除已执行的任务
redisTemplate.opsForZSet().remove("delay:queue", taskId);

// 3. 时间窗口去重
long windowStart = System.currentTimeMillis() - 60000;  // 1分钟窗口
redisTemplate.opsForZSet().removeRangeByScore("unique:1min", 0, windowStart);
redisTemplate.opsForZSet().add("unique:1min", requestId, System.currentTimeMillis());
Long count = redisTemplate.opsForZSet().zCard("unique:1min");

// 4. 滑动窗口限流
long windowKey = System.currentTimeMillis() / 1000;  // 按秒
String key = "rate:" + windowKey;
redisTemplate.opsForZSet().add(key, requestId, System.currentTimeMillis());
redisTemplate.opsForZSet().removeRangeByScore(key, 0, System.currentTimeMillis() - 60000);  // 清理60秒前的
Long count = redisTemplate.opsForZSet().zCard(key);
return count <= 100;

3.7 Stream(流)------ Redis 5.0 消息队列

java 复制代码
/**
 * Stream 类型 - 真正的消息队列
 */

// 1. 创建消费者组
redisTemplate.opsForStream().createGroup("stream:msg", "group1", "0");

// 2. 生产消息
Map<String, Object> msg = new HashMap<>();
msg.put("user", "张三");
msg.put("content", "消息内容");
redisTemplate.opsForStream().add("stream:msg", msg);

// 3. 消费消息(阻塞)
StreamOffset<String> offset = StreamOffset.create("stream:msg", ReadOffset.lastConsumed());
List<MapRecord<String, Object, Object>> records = 
    redisTemplate.opsForStream().read(String.class, StreamReadOptions.empty().count(10).block(Duration.ofSeconds(2)), offset);

// 4. 确认消息
for (MapRecord<String, Object, Object> record : records) {
    // 处理消息
    processMessage(record.getValue());
    // 确认
    redisTemplate.opsForStream().acknowledge("stream:msg", "group1", record.getId());
}

// 5. 死信队列
// 消息处理失败后,放入死信队列
redisTemplate.opsForStream().add("stream:dlq", msg);

3.8 数据结构选择指南

javascript 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Redis 数据结构选择指南                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  场景                    推荐数据结构    备选方案                 │
│  ────────────────────────────────────────────────────────────  │
│  缓存对象                String(JSON)    Hash                   │
│  分布式锁                String          -                      │
│  计数器                  String          Hash                   │
│  验证码/Token            String          -                      │
│  Session管理             String(JSON)    Hash                   │
│  购物车                  Hash            String(JSON)           │
│  消息队列                Stream          List                   │
│  标签系统                Set             Sorted Set             │
│  关注列表                Set             -                      │
│  排行榜                  Sorted Set      -                      │
│  延迟队列                Sorted Set      Stream                 │
│  去重                    Set             Sorted Set             │
│  抽奖                    Set             -                      │
│  UV统计                  HyperLogLog     Set                    │
│  附近的人                GeoHash         -                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

四、Redis集群与高可用

4.1 高可用方案对比

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                   Redis 高可用方案对比                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   单机 ──▶ 主从复制 ──▶ 哨兵模式 ──▶ Redis集群                   │
│                                                                 │
│   适用:开发测试   适用:中小规模     适用:大规模                │
│                                                                 │
│   优点:简单       优点:自动故障转移  优点:数据分片              │
│   缺点:无HA       缺点:需要Sentinel 缺点:架构复杂              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.2 主从复制原理

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    主从复制架构                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                        ┌─────────┐                              │
│                        │  Master │  (主节点,可读写)             │
│                        │  :6379  │                              │
│                        └────┬────┘                              │
│                   ┌─────────┼─────────┐                        │
│                   │         │         │                        │
│                   ▼         ▼         ▼                        │
│              ┌────────┐ ┌────────┐ ┌────────┐                  │
│              │ Slave1 │ │ Slave2 │ │ Slave3 │                  │
│              │ :6380  │ │ :6381  │ │ :6382  │                  │
│              └────────┘ └────────┘ └────────┘                  │
│                                                                 │
│  数据流:Master ──────────────────────▶ Slave                   │
│         (异步复制,有延迟)                                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

主从复制配置

bash 复制代码
# 从节点配置
replicaof <master-ip> <master-port>
replica-serve-stale-data yes      # 从节点是否可读
replica-read-only yes             # 从节点只读
repl-diskless-sync no             # 是否无盘复制

4.3 哨兵模式(Sentinel)

哨兵的工作原理

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    哨兵模式架构                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                        ┌─────────────┐                          │
│                        │  应用程序    │                          │
│                        │  (客户端)   │                          │
│                        └──────┬──────┘                          │
│                               │                                  │
│              ┌────────────────┼────────────────┐               │
│              │                │                │               │
│              ▼                ▼                ▼               │
│        ┌──────────┐    ┌──────────┐    ┌──────────┐            │
│        │ Sentinel │    │ Sentinel │    │ Sentinel │            │
│        │  (主)    │    │  (从)    │    │  (从)    │            │
│        └────┬─────┘    └────┬─────┘    └────┬─────┘            │
│             │               │               │                   │
│             └───────────────┼───────────────┘                   │
│                             │                                   │
│                             ▼                                   │
│                    ┌─────────────────┐                          │
│                    │  Redis Master   │                          │
│                    │  + Replicas     │                          │
│                    └─────────────────┘                          │
│                                                                 │
│  哨兵职责:                                                     │
│  ├── 监控:监控Master/Slave健康状态                              │
│  ├── 通知:发现问题通知客户端                                     │
│  ├── 自动故障转移:Master挂了,选举新Master                       │
│  └── 提供配置:客户端查询当前Master地址                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Sentinel配置

bash 复制代码
# sentinel.conf
port 26379

# 监控的Master
sentinel monitor mymaster <master-ip> <master-port> 2
# 判定down的票数(至少2个哨兵认为down才down)
sentinel down-after-milliseconds mymaster 30000
# 故障转移超时时间
sentinel failover-timeout mymaster 180000
# 故障转移时,同时同步新Master的从节点数量
sentinel parallel-syncs mymaster 1

Java客户端连接哨兵

java 复制代码
/**
 * Spring Boot 连接哨兵
 */
spring:
  redis:
    sentinel:
      master: mymaster                    # Master名称
      nodes: 192.168.1.1:26379,192.168.1.2:26379,192.168.1.3:26379

// Jedis连接
public JedisSentinelPool getJedisSentinelPool() {
    Set<String> sentinels = new HashSet<>();
    sentinels.add("192.168.1.1:26379");
    sentinels.add("192.168.1.2:26379");
    sentinels.add("192.168.1.3:26379");
    
    return new JedisSentinelPool("mymaster", sentinels, 
        new JedisPoolConfig(), 3000, null);
}

4.4 Gossip协议 ------ 集群内通信

Gossip协议工作原理

css 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Gossip 协议通信机制                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  每隔1秒,每个节点随机选择一个节点交换信息:                       │
│                                                                 │
│  节点A ──────── ping ─────────▶ 节点B                          │
│        ◀──────── pong ────────                                 │
│                                                                 │
│  信息传播:                                                     │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  第1轮:A告诉B                                            │   │
│  │  第2轮:A告诉C,B告诉D                                     │   │
│  │  第3轮:A告诉E,C告诉F,B告诉G...                           │   │
│  │  经过log(N)轮,所有节点都知道这个信息                      │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  配置参数:                                                     │
│  ├── cluster-node-timeout: 15000    # 节点超时时间              │
│  ├── cluster-gossip-interval: 100   # 每次通信间隔(ms)         │
│  └── cluster-ping-timeout: 10000    # ping超时时间             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Gossip协议的问题 ------ Gossip风暴

java 复制代码
/**
 * Gossip风暴问题
 * 
 * 当集群规模大时,Gossip消息会呈指数级增长
 * 
 * 假设集群有N个节点:
 * - 每秒每个节点发送 N-1 个Gossip消息
 * - 每秒全集群产生 N*(N-1) 个消息
 * 
 * 问题场景:
 * - 100节点集群:每秒 9900 条消息
 * - 500节点集群:每秒 249500 条消息
 * 
 * 解决方案:
 */

// 1. 减少Gossip频率(牺牲实时性)
# redis.conf
cluster-gossip-interval 5000  # 从1秒改为5秒

// 2. 限制集群规模(推荐 < 100节点)
// 如果数据量大,用Codis或Twemproxy做代理分片

// 3. 使用更好的网络
// 集群节点尽量在同一个机房、同一个网段

// 4. 合理配置超时时间
cluster-node-timeout 30000  # 适当增大,避免误判

4.5 Redis Cluster(集群模式)

集群架构

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Redis Cluster 架构                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   客户端 ─────────────────────────▶ Redis Cluster               │
│                                                                 │
│   Slot分布(16384个槽位):                                       │
│   ┌────────┬────────┬────────┬────────┐                        │
│   │Slot 0  │Slot 5460│Slot5461│Slot10922│                       │
│   │  ~5460 │        │~10922  │~16383  │                        │
│   └────────┴────────┴────────┴────────┘                        │
│       │           │           │                                 │
│       ▼           ▼           ▼                                 │
│   ┌────────┐ ┌────────┐ ┌────────┐                             │
│   │Node A  │ │Node B  │ │Node C  │                             │
│   │Master  │ │Master  │ │Master  │                             │
│   └───┬────┘ └───┬────┘ └───┬────┘                             │
│       │           │           │                                 │
│       ▼           ▼           ▼                                 │
│   ┌────────┐ ┌────────┐ ┌────────┐                             │
│   │Slave A │ │Slave B │ │Slave C │                             │
│   │(副本)  │ │(副本)  │ │(副本)  │                             │
│   └────────┘ └────────┘ └────────┘                             │
│                                                                 │
│   数据路由:                                                     │
│   key的CRC16(slot) = CRC16(key) % 16384                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

集群槽位计算

java 复制代码
/**
 * 槽位计算公式
 */

// Redis内部计算
slot = CRC16(key) % 16384

// Java中模拟
public static int calculateSlot(String key) {
    // CRC16计算
    int crc = CRC16_CCITT(key.getBytes());
    return crc & 0x3FFF;  // & 16383
}

// Hash_tag 确保同一批数据在同一个槽
// user:100:profile 和 user:100:orders 使用 {user:100}
// 这样两个key一定在同一个槽,可以做跨槽操作

Spring Boot连接集群

yaml 复制代码
# application.yml
spring:
  redis:
    cluster:
      nodes: 
        - 192.168.1.1:6379
        - 192.168.1.2:6379
        - 192.168.1.3:6379
        - 192.168.1.4:6379
        - 192.168.1.5:6379
        - 192.168.1.6:6379
      max-redirects: 3  # 最大跳转次数

# Jedis连接池配置
redis:
  pool:
    max-total: 1000
    max-idle: 200
    min-idle: 50
    max-wait: 3000

4.6 哨兵 vs 集群 ------ 选型指南

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    哨兵 vs 集群 选型指南                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌───────────────┬─────────────────┬─────────────────────┐   │
│   │     指标       │     哨兵模式      │     Redis集群        │   │
│   ├───────────────┼─────────────────┼─────────────────────┤   │
│   │   数据分片     │      不支持       │       支持           │   │
│   │               │  (所有数据在一组)  │   (16384槽位分片)    │   │
│   ├───────────────┼─────────────────┼─────────────────────┤   │
│   │   数据量       │      ~20G        │       不限制         │   │
│   ├───────────────┼─────────────────┼─────────────────────┤   │
│   │   写能力       │      瓶颈在Master │     可水平扩展       │   │
│   ├───────────────┼─────────────────┼─────────────────────┤   │
│   │   架构复杂度   │       低          │       高            │   │
│   ├───────────────┼─────────────────┼─────────────────────┤   │
│   │   Gossip风暴  │       无          │       有            │   │
│   ├───────────────┼─────────────────┼─────────────────────┤   │
│   │   故障转移    │      自动          │      自动           │   │
│   ├───────────────┼─────────────────┼─────────────────────┤   │
│   │   跨槽操作     │       支持         │     部分不支持       │   │
│   └───────────────┴─────────────────┴─────────────────────┘   │
│                                                                 │
│   选择建议:                                                     │
│   ├── 数据量 < 20G,QPS < 10万  → 哨兵模式                       │
│   ├── 数据量 > 20G,QPS > 10万  → Redis集群                     │
│   ├── 需要多key操作      → 哨兵(不支持跨槽)                     │
│   └── 追求高可用 + 扩展性  → Redis集群                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.7 集群问题与解决方案

java 复制代码
/**
 * Redis集群常见问题及解决方案
 */

// 问题1:跨槽位操作
// ❌ 错误:mset跨槽会报错
redisTemplate.opsForValue().multiSet(map);  // map中key可能在不同槽

// ✅ 正确:使用Hash_tag确保同槽
// {user:1}:name = 张三
// {user:1}:age = 25
// 两个key都在同一个槽

// 问题2:批量操作效率低
// ❌ 错误:pipeline不支持跨槽
List<Object> results = redisTemplate.executePipelined((RedisCallback) connection -> {
    for (String key : keys) {
        connection.stringCommands().get(key.getBytes());
    }
    return null;
});

// ✅ 正确:按槽分组,分批执行
Map<Integer, List<String>> slotMap = new HashMap<>();
for (String key : keys) {
    int slot = calculateSlot(key);
    slotMap.computeIfAbsent(slot, k -> new ArrayList<>()).add(key);
}
for (List<String> sameSlotKeys : slotMap.values()) {
    // 同一槽的key可以一次pipeline
}

// 问题3:热点key问题
// 热点key会集中在某个节点上
// ✅ 解决:客户端本地缓存 + Redis
public String getHotKey(String key) {
    // 先查本地缓存(Caffeine)
    String localValue = localCache.getIfPresent(key);
    if (localValue != null) {
        return localValue;
    }
    // 查Redis
    String value = redisTemplate.opsForValue().get(key);
    // 写入本地缓存
    localCache.put(key, value);
    return value;
}

五、实战经验总结

5.1 Redis开发避坑清单

vbnet 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Redis 开发避坑清单                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  【容量规划】                                                    │
│  ├── 单实例内存建议 < 10GB                                     │
│  ├── 大key拆分:单个key < 1MB                                  │
│  └── 热点key分散:用Hash替代大String                            │
│                                                                 │
│  【连接管理】                                                    │
│  ├── 使用连接池,不要频繁创建连接                                │
│  ├── Jedis: 建议 maxTotal = QPS / 1000                        │
│  └── 记得关闭连接(try-with-resources)                         │
│                                                                 │
│  【数据安全】                                                    │
│  ├── 敏感数据加密存储                                           │
│  ├── 定期RDB/AOF备份                                            │
│  └── 开启密码认证(requirepass)                                │
│                                                                 │
│  【性能优化】                                                    │
│  ├── 避免使用keys * / flushall                                  │
│  ├── 使用scan代替keys                                           │
│  ├── Pipeline批量操作减少RTT                                    │
│  └── Lua脚本保证原子性                                          │
│                                                                 │
│  【内存管理】                                                    │
│  ├── 设置maxmemory + 淘汰策略                                   │
│  ├── volatile-lru适合有过期时间的场景                           │
│  └── 定期监控内存使用率                                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

5.2 Redis监控指标

java 复制代码
/**
 * 关键监控指标
 */

// INFO命令查看
redis-cli info

// 关键指标
// memory
// ├── used_memory: 当前内存使用
// ├── used_memory_peak: 峰值内存
// └── maxmemory: 最大内存配置

// stats
// ├── total_connections_received: 总连接数
// ├── instantaneous_ops_per_sec: QPS
// └── keyspace_hits_misses: 命中率

// replication
// ├── role: master/slave
// ├── master_link_status: 主从连接状态
// └── slave_repl_offset: 从节点偏移量

// Spring Boot Actuator 监控
@Bean
public MeterRegistry meterRegistry() {
    return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}

5.3 推荐配置

bash 复制代码
# redis.conf 推荐配置

# 内存配置
maxmemory 10gb
maxmemory-policy allkeys-lru

# 持久化配置
appendonly yes
appendfsync everysec
rdbcompression yes
rdbchecksum yes

# 网络配置
timeout 300
tcp-keepalive 60

# 慢查询日志
slowlog-log-slower-than 10000
slowlog-max-len 128

# 客户端
maxclients 10000

六、面试高频问题汇总

6.1 Redis基础

vbnet 复制代码
Q1: Redis为什么这么快?
A: 单线程+IO多路复用,基于内存操作,C语言实现,数据结构高效

Q2: Redis和Memcached的区别?
A: 
├── 数据类型: Redis支持多种数据结构,Memcached只有String
├── 持久化: Redis支持RDB/AOF,Memcached不支持
├── 集群: Redis支持主从/哨兵/集群,Memcached不支持
└── 线程模型: Redis单线程,Memcached多线程+Libevent

6.2 缓存问题

vbnet 复制代码
Q3: 缓存穿透、击穿、雪崩的区别?
A: 
├── 穿透: 查询不存在的数据 → 布隆过滤器
├── 击穿: 热点key过期瞬间 → 互斥锁/逻辑过期
└── 雪崩: 大量key同时过期 → 随机TTL/多级缓存

Q4: 布隆过滤器的原理和缺点?
A: 
├── 原理: K个哈希函数,K个bit位,存在则一定存在
├── 缺点: 不存在可能被误判为存在(假阳性)
└── 适用: 数据量大的存在性判断

6.3 数据结构

yaml 复制代码
Q5: Redis有哪些数据结构?底层实现?
A:
├── String: int/embstr/raw (SDS)
├── List: ziplist/linkedlist
├── Hash: ziplist/hashtable
├── Set: intset/hashtable
├── ZSet: ziplist/skiplist+hashtable
└── Stream: radix tree

Q6: ZSet为什么用跳表而不是红黑树?
A:
├── 跳表范围查询O(logN),红黑树也是O(logN)
├── 跳表实现简单,代码量少
├── 跳表区间遍历更简单
└── 跳表更容易做并发控制(锁粒度小)

6.4 集群

makefile 复制代码
Q7: 哨兵和集群的区别?
A:
├── 哨兵: 无数据分片,数据量受限于单机
├── 集群: 16384槽分片,支持水平扩展
├── Gossip风暴: 集群节点多时消息量指数增长
└── 选型: 数据量<20G选哨兵,否则选集群

Q8: 主从复制原理?
A:
├── 全量同步: master RDB + buffer,slave保存并加载
├── 增量同步: master命令传播,slave回放
└── 断线重连: repl_backlog缓冲区

七、总结

7.1 知识图谱

arduino 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Redis 知识全景图                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                        ┌───────────────┐                        │
│                        │     Redis     │                        │
│                        └───────┬───────┘                        │
│                     ┌──────────┼──────────┐                    │
│                     ▼                     ▼                      │
│              ┌─────────────┐     ┌─────────────┐              │
│              │  核心原理    │     │  数据结构    │              │
│              │  单线程IO    │     │  String     │              │
│              │  多路复用    │     │  Hash        │              │
│              │  内存模型    │     │  List       │              │
│              │              │     │  Set/ZSet   │              │
│              └─────────────┘     │  Stream     │              │
│                     │            └─────────────┘              │
│                     ▼                                          │
│              ┌─────────────┐     ┌─────────────┐              │
│              │  缓存问题    │     │  集群高可用  │              │
│              │  穿透        │     │  主从复制    │              │
│              │  击穿        │     │  哨兵模式    │              │
│              │  雪崩        │     │  Redis集群   │              │
│              │  布隆过滤器  │     │  Gossip协议  │              │
│              └─────────────┘     └─────────────┘              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

7.2 学习建议

markdown 复制代码
1. 原理先行:理解单线程I/O模型和数据结构底层实现
2. 工具掌握:熟练使用redis-cli和客户端API
3. 实战积累:在项目中实践缓存、分布式锁等场景
4. 运维了解:掌握监控、备份、故障排查技能
5. 持续关注:Redis新版本特性(如Redis 7.0)

推荐学习路径:
├── 《Redis设计与实现》- 入门必读
├── Redis官网文档 - 权威参考
├── 源码阅读 - 深入理解
└── 源码天堂 - 博客实战

八、写在最后

Redis是后端工程师的必备技能,从简单的缓存到复杂的分布式系统,到处都有Redis的身影。

记住这些话:

"Redis不是银弹,不要把所有数据都往里塞"

"缓存只保证速度,不保证一致性"

"最好的优化是不需要优化------先想清楚要不要用Redis"

希望这篇文章能帮你深入理解Redis,在面试和工作中都能游刃有余!

📢 讨论话题:你在使用Redis时遇到过哪些"坑"?是怎么解决的?

👇 延伸阅读

如果觉得有帮助,欢迎点赞、收藏、转发!

相关推荐
MgArcher2 小时前
Python高级特性:高阶函数完全指南
后端·面试
wb1892 小时前
NoSQL数据库Redis集群重习
数据库·redis·笔记·云计算·nosql
深蓝轨迹2 小时前
面试常见的jdk---LTS版本新特性梳理
java·面试·jdk
sbjdhjd3 小时前
Docker | 核心概念科普 + 保姆级部署
linux·运维·服务器·docker·云原生·面试·eureka
小兜全糖(xdqt)4 小时前
Ubuntu22.04安装最新版本redis
数据库·redis·缓存
weixin_704266055 小时前
redis 的集群
java·数据库·redis
不爱吃大饼5 小时前
redis主从节点
数据库·redis·bootstrap
蓝色的杯子5 小时前
Python面试30分钟突击掌握-LeetCode1-Array
开发语言·python·面试
晴天sir5 小时前
Redis 在业务中的几种典型用法
java·数据库·redis