Redis 从入门到实战(一):基础篇

掌握 Redis 核心概念与实战操作,为黑马点评项目打好基础

📌 前言

这篇学习记录涵盖了 Redis 的基础概念数据类型与操作Java 集成(Jedis + SpringDataRedis)主从复制与哨兵机制以及缓存三大经典问题(穿透/击穿/雪崩)

学完这些内容,就可以无缝衔接到黑马点评项目了。

一、Redis 概述

1.1 什么是 Redis?

Redis 是一个开源(BSD 许可)、内存中的数据结构存储系统,可以用作数据库缓存消息中间件

核心特点:

特性说明
基于内存读写速度极快(读 11w/s,写 8.1w/s)
持久化支持 RDB 和 AOF 两种持久化方式
数据结构丰富String、Hash、List、Set、SortedSet 等
高可用支持主从复制、哨兵监控、集群模式
支持多语言Java、Python、Go、C++、PHP 等

1.2 Redis 历史

  • 2009 年由 Salvatore Sanfilippo 开发完成
  • 2010 年起由 VMware 主持开发
  • 2013 年起由 Pivotal 赞助

1.3 关系型 vs 非关系型

对比项关系型数据库非关系型数据库(Redis)
存储位置硬盘内存
速度
格式固定表结构K-V、文档、图片等
扩展性困难(多表关联)容易(数据间无耦合)
事务强一致性最终一致性
代表MySQL、OracleRedis、MongoDB

二、Redis 安装与配置

2.1 安装步骤

# 1. 下载安装包
wget http://download.redis.io/releases/redis-5.0.3.tar.gz

# 2. 解压
tar -xzvf redis-5.0.3.tar.gz
cd redis-5.0.3

# 3. 创建安装目录
mkdir -p /usr/local/redis

# 4. 编译安装(指定安装路径)
make PREFIX=/usr/local/redis/ install

# 5. 安装完成后目录结构
# /usr/local/redis/bin/
#   ├── redis-server   # 服务端
#   ├── redis-cli      # 客户端
#   ├── redis-benchmark
#   └── redis-sentinel

2.2 启动 Redis

# 前台启动(默认)
/usr/local/redis/bin/redis-server

# 后台启动(修改配置文件)
cp redis.conf /usr/local/redis/bin/
vim /usr/local/redis/bin/redis.conf
# 将 daemonize 修改为 yes

# 指定配置文件启动
/usr/local/redis/bin/redis-server /usr/local/redis/bin/redis.conf

2.3 配置文件关键项

# 允许所有 IP 访问(注释掉 bind)
# bind 127.0.0.1

# 关闭保护模式
protected-mode no

# 设置密码
requirepass root

# 后台启动
daemonize yes

# 修改数据库数量(默认 16)
databases 32

2.4 使用 Redis Desktop Manager 连接

# 配置文件修改三项
bind 0.0.0.0          # 或注释掉 bind
protected-mode no     # 关闭保护模式
requirepass root      # 设置认证密码

# 重启 Redis
kill -9 <pid>
/usr/local/redis/bin/redis-server /usr/local/redis/bin/redis.conf

三、Redis 命令行操作(redis-cli)

3.1 连接 Redis

# 基本连接
redis-cli -h 192.168.10.100 -p 6379 -a root

# 指定数据库(默认 0)
redis-cli -h 192.168.10.100 -p 6379 -a root -n 1

3.2 String(字符串)

# 添加单条
set username zhangsan

# 获取
get username

# 添加多条
mset address bj sex 1

# 获取多条
mget username address sex

# 设置过期时间(秒)
set code test ex 180

3.3 Hash(哈希)

# 添加单条
hset userInfo name lisi

# 添加多条
hmset userInfo age 20 sex 1

# 获取单条
hget userInfo name

# 获取多条
hmget userInfo age sex

# 获取所有
hgetall userInfo

# 删除
hdel userInfo name age

3.4 List(列表)

# 左添加(头)
lpush students wangwu lisi

# 右添加(尾)
rpush students zhaoliu

# 获取(0 到 2)
lrange students 0 2

# 获取总条数
llen students

# 删除(删除 1 个 lisi)
lrem students 1 lisi

3.5 Set(集合,无序)

# 添加
sadd letters aaa bbb ccc ddd eee

# 获取所有
smembers letters

# 获取总条数
scard letters

# 删除
srem letters aaa bbb

3.6 Sorted Set(有序集合)

# 添加(分数在前,值在后)
zadd score 7 zhangsan 3 lisi 5 wangwu 6 zhaoliu 2 tianqi

# 获取(按分数从小到大)
zrange score 0 4

# 获取总条数
zcard score

# 删除
zrem score lisi

3.7 层级目录存储

# 使用 : 分隔层级
set user:01 zhangsan
set user:02 lisi
set order:2024:001 xxx

3.8 通用命令

# 设置过期时间(秒)
expire key 180

# 查看剩余时间(-1 永久,-2 已过期)
ttl key

# 删除
del key

# 获取所有 key
keys *

四、Java 操作 Redis(Jedis)

4.1 引入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version>
</dependency>

4.2 基本连接

// 创建连接
Jedis jedis = new Jedis("192.168.10.100", 6379);
jedis.auth("root");
jedis.select(1);  // 选择数据库

// 测试连接
String result = jedis.ping();  // 返回 PONG

// 操作数据
jedis.set("username", "zhangsan");
String username = jedis.get("username");

// 释放资源
jedis.close();

4.3 连接池方式

// 创建连接池
JedisPool jedisPool = new JedisPool(
    new JedisPoolConfig(),
    "192.168.10.100",
    6379,
    10000,
    "root"
);

// 获取连接
Jedis jedis = jedisPool.getResource();
// ... 操作
jedis.close();  // 归还连接池

4.4 JedisUtil 封装

@Configuration
public class RedisConfig {
    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public JedisPool redisPoolFactory() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(1024);
        config.setMaxIdle(200);
        config.setMinIdle(5);
        config.setMaxWaitMillis(10000);
        return new JedisPool(config, host, port, 10000, password);
    }
}

4.5 Jedis 操作五种数据类型

@Autowired
private JedisPool jedisPool;

private Jedis jedis;

@Before
public void init() {
    jedis = jedisPool.getResource();
}

@After
public void close() {
    if (jedis != null) jedis.close();
}

String:

jedis.set("username", "zhangsan");
jedis.mset("address", "bj", "sex", "1");
String name = jedis.get("username");
List<String> list = jedis.mget("username", "age");

Hash:

jedis.hset("userInfo", "name", "lisi");
Map<String, String> map = new HashMap<>();
map.put("age", "20");
jedis.hmset("userInfo", map);
String name = jedis.hget("userInfo", "name");
Map<String, String> all = jedis.hgetAll("userInfo");

List:

jedis.lpush("students", "wangwu", "lisi");
jedis.rpush("students", "zhaoliu");
List<String> list = jedis.lrange("students", 0, 2);
Long total = jedis.llen("students");

Set:

jedis.sadd("letters", "aaa", "bbb", "ccc");
Set<String> letters = jedis.smembers("letters");
Long total = jedis.scard("letters");

Sorted Set:

Map<String, Double> scoreMembers = new HashMap<>();
scoreMembers.put("zhangsan", 7D);
scoreMembers.put("lisi", 3D);
jedis.zadd("score", scoreMembers);
Set<String> scores = jedis.zrange("score", 0, 4);

4.6 其他操作

// 层级目录
jedis.set("user:01", "zhangsan");

// 设置过期时间(秒)
jedis.expire("code", 180);
jedis.setex("code", 180, "test");

// 事务
Transaction tx = jedis.multi();
tx.set("tel", "10010");
tx.exec();  // 提交
// tx.discard(); // 回滚

// 获取所有 key
Set<String> keys = jedis.keys("*");

五、Spring Data Redis

5.1 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

5.2 配置文件(application.yml)

spring:
  redis:
    host: 192.168.10.100
    port: 6379
    password: root
    database: 0
    timeout: 10000ms
    jedis:
      pool:
        max-active: 1024
        max-wait: 10000ms
        max-idle: 200
        min-idle: 5

5.3 Jedis vs Lettuce

对比项JedisLettuce
线程安全❌ 非线程安全(需连接池)✅ 线程安全
连接方式直连基于 Netty NIO
性能一般更好(异步/响应式)
适用场景简单同步操作高并发、异步场景

5.4 自定义序列化模板

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            LettuceConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // Key 使用 String 序列化
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        // Value 使用 JSON 序列化
        template.setValueSerializer(
            new GenericJackson2JsonRedisSerializer()
        );
        template.setHashValueSerializer(
            new GenericJackson2JsonRedisSerializer()
        );
        template.setConnectionFactory(factory);
        return template;
    }
}

5.5 Spring Data Redis 操作五种数据类型

String:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

public void testString() {
    ValueOperations<String, Object> ops = redisTemplate.opsForValue();
    ops.set("username", "zhangsan");
    ops.set("user:01", "lisi");
    Object name = ops.get("username");
    // 设置过期时间
    ops.set("code", "abcd", 180, TimeUnit.SECONDS);
}

Hash:

HashOperations<String, String, String> ops = redisTemplate.opsForHash();
ops.put("userInfo", "name", "lisi");
Map<String, String> map = new HashMap<>();
map.put("age", "20");
ops.putAll("userInfo", map);
String name = ops.get("userInfo", "name");

List:

ListOperations<String, Object> ops = redisTemplate.opsForList();
ops.leftPush("students", "lisi");
ops.rightPush("students", "zhaoliu");
List<Object> list = ops.range("students", 0, 2);

Set:

SetOperations<String, Object> ops = redisTemplate.opsForSet();
ops.add("letters", "aaa", "bbb", "ccc");
Set<Object> members = ops.members("letters");

Sorted Set:

ZSetOperations<String, Object> ops = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(new DefaultTypedTuple<>("zhangsan", 7D));
ops.add("score", tuples);
Set<Object> scores = ops.range("score", 0, 4);

获取所有 key 和删除:

Set<String> keys = redisTemplate.keys("*");
redisTemplate.delete("score");
redisTemplate.expire("code", 180, TimeUnit.SECONDS);
Long ttl = redisTemplate.getExpire("code");

六、Redis 主从复制与哨兵

6.1 读写分离架构

客户端

主节点 Master
可读可写

从节点 Slave1
只读

从节点 Slave2
只读

6.2 搭建步骤

1. 创建目录结构

/opt/redis/
├── conf/
│   ├── redis-common.conf
│   ├── redis-6379.conf
│   ├── redis-6380.conf
│   └── redis-6381.conf
├── data/
│   ├── dump-6379.rdb
│   ├── dump-6380.rdb
│   └── dump-6381.rdb
└── log/
    ├── redis-6379.log
    ├── redis-6380.log
    └── redis-6381.log

2. 公共配置 redis-common.conf

# 注释 bind
# bind 127.0.0.1

# 关闭保护模式
protected-mode no

# 后台启动
daemonize yes

# 从服务器只读(默认)
slave-read-only yes

# 设置密码(主从都要设置)
requirepass root
masterauth root

3. 各节点私有配置

# redis-6379.conf(主)
include /opt/redis/conf/redis-common.conf
port 6379
pidfile /var/run/redis-6379.pid
logfile "/opt/redis/log/redis-6379.log"
dbfilename dump-6379.rdb

# redis-6380.conf(从)
include /opt/redis/conf/redis-common.conf
port 6380
pidfile /var/run/redis-6380.pid
logfile "/opt/redis/log/redis-6380.log"
dbfilename dump-6380.rdb
slaveof 127.0.0.1 6379

4. 启动主从

/usr/local/redis/bin/redis-server /opt/redis/conf/redis-6379.conf
/usr/local/redis/bin/redis-server /opt/redis/conf/redis-6380.conf
/usr/local/redis/bin/redis-server /opt/redis/conf/redis-6381.conf

5. 查看主从状态

redis-cli -p 6379 -a root info replication
# role:master
# connected_slaves:2

redis-cli -p 6380 -a root info replication
# role:slave
# master_link_status:up

6.3 哨兵机制(主备切换)

1. 哨兵公共配置

# sentinel-common.conf
protected-mode no
daemonize yes

# 监听端口
# port 26379

# 监控主节点(至少 1 个哨兵同意才切换)
sentinel monitor mymaster 127.0.0.1 6379 1

# 设置密码
sentinel auth-pass mymaster root

# 心跳超时时间(毫秒)
sentinel down-after-milliseconds mymaster 30000

# 故障转移超时
sentinel failover-timeout mymaster 180000

2. 各哨兵私有配置

# sentinel-26379.conf
include /opt/redis/conf/sentinel-common.conf
port 26379
pidfile /var/run/sentinel-26379.pid
logfile "/opt/redis/log/sentinel-26379.log"

3. 启动哨兵

/usr/local/redis/bin/redis-sentinel /opt/redis/conf/sentinel-26379.conf
/usr/local/redis/bin/redis-sentinel /opt/redis/conf/sentinel-26380.conf
/usr/local/redis/bin/redis-sentinel /opt/redis/conf/sentinel-26381.conf

4. 测试主备切换

# 杀掉主节点
kill -9 <6379-pid>

# 查看哨兵是否选举新主
redis-cli -p 6380 -a root info replication
# role:master ✅

# 重启原主节点,会自动变为从节点
/usr/local/redis/bin/redis-server /opt/redis/conf/redis-6379.conf

6.4 Spring Data Redis 整合哨兵

spring:
  redis:
    password: root
    database: 0
    timeout: 10000ms
    lettuce:
      pool:
        max-active: 1024
        max-wait: 10000ms
        max-idle: 200
        min-idle: 5
    sentinel:
      master: mymaster
      nodes: 192.168.10.100:26379,192.168.10.100:26380,192.168.10.100:26381
@Bean
public RedisSentinelConfiguration redisSentinelConfiguration() {
    RedisSentinelConfiguration config = new RedisSentinelConfiguration();
    config.master("mymaster");
    config.sentinel("192.168.10.100", 26379);
    config.sentinel("192.168.10.100", 26380);
    config.sentinel("192.168.10.100", 26381);
    config.setPassword("root");
    return config;
}

七、缓存三大经典问题

7.1 过期淘汰机制

Redis 的 Key 过期删除策略:

1. 定期删除

  • 每 100ms 随机抽取设置了过期时间的 Key 进行检查
  • 随机抽取是为了避免扫描全部 key 造成 CPU 压力

2. 惰性删除

  • 获取 Key 时检查是否过期,过期则删除
  • 弥补定期删除可能遗漏的问题

3. 内存淘汰策略(6 种)

策略说明
volatile-lru过期 Key 中淘汰最近最少使用的 ⭐推荐
volatile-ttl过期 Key 中淘汰即将过期的
volatile-random过期 Key 中随机淘汰
allkeys-lru所有 Key 中淘汰最近最少使用的
allkeys-random所有 Key 中随机淘汰
no-enviction默认,内存不足时写入报错

7.2 缓存击穿

定义: 高并发下,某个热门 Key 突然过期,大量请求直接打到数据库

解决方案:

  1. 热点数据永不过期 + 异步更新
  2. 互斥锁保证缓存单线程写入
  3. 逻辑过期(对象内维护过期时间)

7.3 缓存穿透

定义: 查询缓存和数据库都不存在的数据(如恶意查询 id=-1)

解决方案:

  1. 布隆过滤器:快速判断 Key 是否可能存在于数据库
  2. 缓存空对象:查询为空也缓存,过期时间设短(如 60 秒)
  3. 请求参数校验:拦截非法参数

7.4 缓存雪崩

定义: 大量缓存同时过期,所有请求落到数据库

解决方案:

  1. 过期时间加随机值expire_time = base + random(0, 300)
  2. 热点数据永不过期
  3. 双缓存机制:缓存 A 设过期时间,缓存 B 不设过期时间
  4. 集群部署:热点数据均匀分布在不同的 Redis 节点

7.5 三种问题对比

问题原因特点
缓存击穿热点 Key 过期针对单个 Key
缓存穿透查询不存在的数据大量请求打到 DB
缓存雪崩大量 Key 同时过期范围广,压力大

面试回答参考

  • 第一段:开场 + 总述(15秒)
    “面试官您好,关于缓存穿透、击穿和雪崩这三个问题,我在项目中都有遇到过,也做过相应的优化方案。我分别来讲一下它们的定义、区别以及解决方案。”

  • 第二段:缓存穿透(40秒)
    “首先缓存穿透,它指的是查询一个缓存和数据库中都不存在的数据。

举个例子,我们商品查询的接口,正常id都是正数,但攻击者故意传一个负数的id去查,因为缓存里没有,每次请求都会穿透缓存直接打到数据库,导致数据库压力骤增。

针对穿透,我有两套解决方案:

第一套是布隆过滤器,我们可以在查询之前先用布隆过滤器判断这个key是否可能存在,如果判断不存在就直接返回,连缓存都不查了。但布隆过滤器有个特点,它只能100%判断不存在,不能100%判断存在,存在一定的误判率,所以需要定期重建。

第二套是缓存空对象,就是从数据库查不到数据时,也把这个key缓存起来,value设成空值,过期时间设短一些,比如60秒。这样短时间内同样的恶意请求就会命中缓存,不会再打到数据库。

这两种方案我们项目里是结合使用的,布隆过滤器做第一层拦截,缓存空值做兜底。”

  • 第三段:缓存击穿(50秒)
    “然后是缓存击穿,它和穿透不一样。击穿针对的是某一个热点key突然过期的情况。

比如微博上某个明星突然爆出大新闻,这个热点数据的key正好在那一瞬间过期了,那成千上万的请求就会同时绕过缓存,全部打到数据库上,瞬间造成数据库压力陡增。

解决击穿的核心思路就是保证热点数据永远可用。我了解两种方案:

第一种是互斥锁。当发现缓存失效时,不是所有请求都去查数据库,而是只有第一个请求去查,然后重建缓存,其他请求等待或者轮询。实现上可以用Redis的setnx命令来加锁,但要注意设置超时时间,防止持锁线程挂了导致死锁。

第二种是逻辑过期。就是不设置物理过期时间,而是在value里面存一个过期时间戳。查询的时候判断这个时间戳,如果发现逻辑过期了,就返回旧数据,同时异步发起一个线程去更新缓存。这种方案用户体验更好,因为不需要等待,但实现会复杂一些。

我在实际项目中,对于真正热点的数据,一般是直接设置永不过期,然后由定时任务主动去刷新缓存。”

  • 第四段:缓存雪崩(40秒)
    “然后是缓存雪崩,它和击穿的区别在于:击穿是一个热点key过期,雪崩是大量key在同一时间段集中过期。

比如我们给所有缓存数据都设置了统一的60分钟过期时间,那一个小时之后所有数据同时失效,所有请求全部打到数据库,数据库可能直接就宕机了。

针对雪崩,解决方案主要有三个方向:

第一个是过期时间加随机值。比如设置过期时间时,在60分钟基础上加上一个0到5分钟的随机数,让过期时间分散开,避免集体失效。

第二个是热点数据永不过期,核心数据不设过期时间,由后台任务主动刷新。

第三个是双缓存机制,就是缓存A设过期时间,缓存B不设过期时间。当A失效的时候从B读取数据,同时异步去更新A缓存。

我们项目里是三个方案结合使用,核心数据永不过期,一般数据加随机过期时间,同时做了多级缓存兜底。”

  • 第五段:熔断降级 + 兜底(30秒)
    “除了上面三个问题的针对性方案之外,我们还在应用层做了熔断降级作为兜底。

降级就是当系统压力过大或者缓存出现问题的时候,牺牲一些非核心功能,保证核心功能可用。比如查询商品详情的时候,如果缓存和数据库都出了问题,可以降级返回缓存的老数据或者默认数据,而不是直接报错。

熔断就是当某个服务的错误率超过一定阈值时,直接切断对这个服务的调用,快速失败返回,避免连锁反应导致整个系统崩溃。

这块我们用的是阿里开源的Sentinel,配置了错误率熔断和慢调用熔断,同时设置了降级返回的兜底数据。这样即使缓存彻底挂了,系统也不会完全不可用,用户体验的损失是可控的。”

  • 第六段:项目实践经验(40秒)- 高级加分项
    “我在实际项目中还积累了一些经验:

第一个是,我们当时上线之前做了压测,发现没有加随机过期时间的时候,QPS从1000瞬间跌到50左右,加了随机值之后,QPS稳定在800以上,效果非常明显。

第二个是,我们用布隆过滤器的时候,是用Google的Guava本地布隆,把热点key加载到本地缓存里,每次查询前先判断,误判率控制在1%以内。

第三个就是,我们还做了多级缓存,客户端缓存加上Nginx缓存加上Redis缓存再加上本地缓存,每一层都做降级兜底,这样即使Redis挂了,本地缓存还能撑一段时间。

第四个是,所有缓存key的过期时间,我们都集中管理在配置中心,方便统一调整和紧急刷新。

最后我觉得,这三个问题本质上都是缓存命中率下降导致的数据库压力问题,所以核心思路就是尽可能提高缓存命中率,同时做好降级和限流兜底,保证系统的高可用。”

  • 📋 关键词速记卡(自己背的时候用)
    穿透:查不存在的数据 → 布隆过滤器 + 缓存空值

击穿:热点key过期瞬间 → 互斥锁 + 逻辑过期 + 永不过期

雪崩:大量key同时过期 → 随机过期时间 + 永不过期 + 双缓存

熔断:出问题了直接拒绝请求,快速失败

降级:出问题了返回简化数据,保住核心功能

兜底:多级缓存 + Sentinel 熔断降级 + 默认值返回

关键点:压测数据 + 布隆实现 + 多级缓存 + 配置中心管理

八、总结

8.1 学习路径回顾

  1. ✅ Redis 基本概念与安装配置
  2. ✅ redis-cli 操作五种数据类型
  3. ✅ Jedis 原生 API 操作
  4. ✅ Spring Data Redis 集成
  5. ✅ 主从复制 + 哨兵高可用
  6. ✅ 缓存三大经典问题及解决方案

8.2 下一步:黑马点评项目

学习完以上内容后,你已经具备了:

  • Redis 核心 API 操作能力
  • Java 集成 Redis 的开发能力
  • 高可用架构的搭建能力
  • 缓存问题的应对能力

现在可以开始黑马点评项目了,重点实践:

  • 短信验证码登录(Session 共享 + Redis)
  • 缓存穿透/击穿/雪崩实战
  • 分布式锁(Redisson)
  • 秒杀系统(Lua 脚本 + 事务)
  • 附近商户(GEO 功能)
  • 关注推送(Feed 流)
  • 好友关注(Set 交集)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值