欢迎光临
我们一直在努力

Redis 基础到进阶

Redis基础

Redis 是什么

要点说明
Redis 是什么 Key-Value 内存数据库
为什么快 内存存储 + 单线程
常见用途 缓存、排行榜、分布式锁

5种数据类型

Redis的键值是String类型,值有五种类型

值的数据类型说明
string 字符串类型
hash 由键值对构成,类似java中的map
list 列表类型,元素有序可重复
set 集合类型,元素无序不可重复
zset 有序的集合类型

String

字符串数据是最基本的存储数据类型,以二进制保存,字符串、整数、浮点数都会以字符串类型存入,string类型的数据最大为512M

  • 常用命令:
命令功能
set key value 添加或修改一个键值对,键不存在就是添加,存在就是修改值
get key 获取值,如果存在就返回值,不存在就返回空值
del key 删除指定的键值对,返回删除的个数
SETEX key secondes value 设定过期时间为secondes秒的键值对
EXPRIE key secondes 给存在的键设置一个过期时间
SETNX key value 保存键值对,如果key存在就不保存,不存在就保存
  • 适用场景:用户登录时,后端保存验证码,并设置失效时间
  • 实践记录:

# 练习代码
set name xiaoming
get name
del name

Hash

Hash类型可以看作是键值对都是String类型的map容器,每个Hash可以存储4G(232−12^{32}-12321)个键值对;每个键对应多个字段(Filed),字段都有和其对应的字段值
Pasted image 20260224204010

  • 常用命令:
命令功能
hset key filed value 添加键、字段、值
hget key filed 通过键和字段获得值
hmset key filed value filed value 一次添加多个字段和值
hmget key filed field 通过键和字段获取多个值
hdel key filed value 删除一个或多个字段的值
hgetall 得到这个键下所有的字段和值
HKEYS key 获取hash表中所有的字段
HVALS key 获取hash表中所有的值
  • 适用场景:存储对象数据时使用
  • 实践记录:

# 练习代码
hset person name xiaoming
hmset shcool AHUT age 20
hmget person name school age
hdel person name
hgetall person
HKEYS person
HVALS person
hdel person school age

List

List(列表)的本质是一个双向链表(Doubles Linked List),链表中的元素共享一个键。
Pasted image 20260224205943|750
列表中的元素有序,有索引,可以重复。每个元素都有两个索引号从左向右是0~n
从右向左是-1~-(n+1)。
它的核心特点就是支持从左端(Head)或右端(Tail)进行高效的插入和弹出操作。

  • 常用命令:
命令功能
lpush key value value 从列表左边向指定键添加一个或多个元素,返回列表中的元素个数
rpush key value 从列表右边指定的键添加一个或多个元素,返回列表中的元素个数
lpop key 从列表左边删除一个元素,返回被删除的元素
rpop key 从列表右边删除一个元素,返回被删除的元素
lrange key begin end 得到指定键中指定范围的数据,如果要取出所有的元素,索引号的范围是0~-1
lindex key index 获取指定索引的值
llen key 获取列表的长度
BRPOP key timeout 移出并获取列表的最后一个元素,如果没有元素会阻塞列表知道等待超时或者发现可弹出元素为止,超时时间单位为秒
LREM key value count 从表头删除值相等元素的指定个数
  • 适用场景:朋友圈点赞,按照点赞顺序显示好友信息;取消点赞,就移除好友信息
  • 实践记录:

# 练习代码
lupsh mylist a b c
rpush mylist one two three
lrange mylist 0 2
lrange 0 -1
lindex mylist 3
llen mylist
bprop mylist 10
lerm mylist b 1 #删除一个值为b的元素

Set

在 Redis 内部,Set 的实现其实是一个 Value 全部为空的哈希表(Hashtable)。这就保证了查找任何一个元素的时间复杂度都是 O(1)O(1)O(1)
Pasted image 20260224232803
因为 Set 没有索引(Index),所以你不能说“给我拿第二个元素”,你只能“点名”或者“随机抽”

  • 常用命令:
命令功能
sadd key 向一个键中添加一个或者多个元素
smembers key 得到这个集合中的所有元素
sismembers key value 判断指定元素在集合中是否存在,存在返回1,不存在返回0
srem key item item 通过键删除一个或者多个元素
sinter key1 [key2] 获得给定所有集合的交集
  • 适用场景:需要去重的少量信息,比如手机号、身份证信息
  • 实践记录:

# 练习代码
sadd myset A B C 1 2 3 #存储无序
sadd myset B #已存在B,无法插入
smember mylist
srem myset C #删除指定元素
sismember myset A #判定元素是否存在
sinter myset otherset #求两个set的交集

ZSet

ZSet称为有序表,在 ZSet 中,每个元素由两部分组成:

  • Member(成员): 相当于 Hash 的 Field,必须是唯一的(比如玩家 ID)。
  • Score(分数): 相当于 Hash 的 Value,但必须是浮点数,Redis 会根据这个分数自动帮你排序。
    Pasted image 20260224234949
    哈希表存储每个Member对应的分数,保证查询特定元素权重的复杂度为 O(1)O(1)O(1)
    跳表按照元素的Scoer存储Member,如果两个Member的Score重复,就按照Member的字典序来进行排序
    跳表通过在元素上方建立“索引层”(Level),实现了类似二分查找的效果,使得插入、删除和查找的平均时间复杂度为 O(log⁡N)O(\\log N)O(logN)
  • 常用命令:
命令功能
zadd key socre value 添加一个或者多个成员,每个成员都有一个分数
zrange key begin end 获取指定范围的成员,0~-1可以得到所有的成员
zrange key begin end withscore 获取指定范围的成员和对应分数
zrevrange key begin end withscore 按分数倒序获取指定范围的成员和对应分数
zerm key value value 删除一个或者多个成员
zcard key 得到有序表成员的个数
zrank key value 得到成员的索引号
zscore key value 得到成员的分数
  • 适用场景:
  • 实践记录:

# 练习代码
zadd country 10 Jpan
zadd country 5 USA 50 China
zcard country
zrank country 0 2 withscore
zrem country USA

Redis其他常见命令

命令功能
keys 匹配字符串 查询所有的键,可以使用通配符
del key1 del2 删除任意类型的指定的键
exists key 判断指定的键是否存在
select dbname 切换指定的数据库
move key dbname 将当前的键切换到指定的数据库
TTL key 返回给定key的剩余生存时间,不存在或者已过期返回-2,永久存在返回-1
flushall 删库

Java操作Redis

使用Jedis客户端(了解即可)

Jedis是Redis官方推荐的Java客户端之一,提供了全面的Redis命令支持

1. 添加Maven依赖

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.6</version>
</dependency>
<!– 连接池 (使用JRedis可选,Spring Boot 2.7+ 默认使用 Lettuce) –>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

2. 基本连接

import redis.clients.jedis.Jedis;

public class RedisDemo {
public static void main(String[] args) {
// 连接Redis服务器,默认端口6379
Jedis jedis = new Jedis("localhost", 6379);

// 认证(如果设置了密码)
// jedis.auth("password");

// 测试连接
System.out.println("连接成功:" + jedis.ping());

// 设置键值
jedis.set("key1", "Hello Redis");

// 获取值
String value = jedis.get("key1");
System.out.println("key1的值:" + value);

// 关闭连接
jedis.close();
}
}

3. 操作不同数据类型

String类型

jedis.set("name", "张三");
jedis.setex("code", 300, "123456"); // 设置5分钟过期
String name = jedis.get("name");

Hash类型

jedis.hset("user:1001", "name", "李四");
jedis.hset("user:1001", "age", "25");
Map<String, String> user = jedis.hgetAll("user:1001");

List类型

jedis.lpush("mylist", "item1", "item2");
List<String> list = jedis.lrange("mylist", 0, 1);

Set类型

jedis.sadd("tags", "Java", "Redis", "Database");
Set<String> tags = jedis.smembers("tags");

ZSet类型

jedis.zadd("rank", 100, "player1");
jedis.zadd("rank", 200, "player2");
Set<String> topPlayers = jedis.zrange("rank", 0, 1);

4. JRedis连接池配置

对于生产环境,建议使用连接池:

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisPool {
public static void main(String[] args) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(10);
poolConfig.setMaxIdle(5);

JedisPool jedisPool = new JedisPool(poolConfig, "localhost", 6379);

try (Jedis jedis = jedisPool.getResource()) {
jedis.set("pool_test", "连接池测试");
System.out.println(jedis.get("pool_test"));
}

jedisPool.close();
}
}

5. 注意事项

  • 确保Redis服务已启动且可访问
  • 生产环境建议使用连接池管理连接
  • 注意资源释放,避免连接泄漏
  • 根据业务需求合理设置超时时间和连接数

Spring Data Redis

Spring Data Redis 是 Spring 生态中用于操作 Redis 的框架,提供了高级抽象和简化操作。它提供了一个高度封装的类RedisTemplate,针对类似JRedis客户端中大量的api进行封装,将同一类型操作封装为operation接口,具体分类如下:

接口功能
ValueOperation 操作String类型
SetOperation 操作Set类型
ZsetOperation 操作ZSet类型
HashOperation 操纵Hash类型
ListOperation 操作List类型

1. 添加依赖

<!– Spring Boot Starter Data Redis –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.0</version>
</dependency>

2. Spring配置文件

spring:
redis:
# Redis 服务器地址
host: localhost
# Redis 服务器端口
port: 6379
# Redis 数据库索引 (默认 0)
database: 0
# 连接超时时间 (毫秒)
timeout: 3000ms
# Redis 访问密码 (如果设置了密码)
password: yourpassword

# 连接池配置 (使用 Lettuce 时)
lettuce:
pool:
# 最大连接数 (默认 8,-1 表示无限制)
max-active: 8
# 最大空闲连接数
max-idle: 8
# 最小空闲连接数
min-idle: 0
# 最大等待时间 (毫秒)
max-wait: 1ms

# 或者使用 Jedis (需要额外依赖)
# jedis:
# pool:
# max-active: 8
# max-idle: 8
# min-idle: 0
# max-wait: -1ms

3. RedisTemplate 配置类

redis默认的key和value的默认序列器是JdkSerializationRedisSerializer,这会导致java的类型数据在存入redis中出现乱码,因此需要专门配置序列器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

/**
* 配置 RedisTemplate,使用 JSON 序列化
*/

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

// 设置 key 的序列化器
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());

// 设置 value 的序列化器 (使用 JSON)
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

template.afterPropertiesSet();
return template;
}

/**
* StringRedisTemplate 专用于字符串操作
*/

@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}

4. 操作不同数据类型

String 类型操作

import org.springframework.data.redis.core.ValueOperations;
import javax.annotation.Resource;

@Resource
private ValueOperations<String, String> valueOperations;

// 基本操作
valueOperations.set("username", "张三");
valueOperations.set("age", "25", 10, TimeUnit.MINUTES); // 10分钟过期
String username = valueOperations.get("username");

//存入键值对,如果存在就修改,不存在就不执行
valueOperation.setIfPresent("age","30")
//存入键值对,如果不存在就存入,存在就不执行
valueOpreation.seIFAbsent("age","30")

// 原子操作
Long increment = valueOperations.increment("counter"); // 自增1
Long decrement = valueOperations.decrement("counter"); // 自减1
Long add = valueOperations.increment("counter", 5); // 增加5

Hash 类型操作

import org.springframework.data.redis.core.HashOperations;

@Resource
private HashOperations<String, String, Object> hashOperations;

// 设置单个字段
hashOperations.put("user:1001", "name", "李四");
hashOperations.put("user:1001", "age", 30);

// 批量设置字段
Map<String, Object> userMap = new HashMap<>();
userMap.put("email", "lisi@example.com");
userMap.put("phone", "13800138000");
hashOperations.putAll("user:1001", userMap);

// 获取字段值
String name = (String) hashOperations.get("user:1001", "name");
Map<String, Object> allFields = hashOperations.entries("user:1001");

// 判断字段是否存在
Boolean hasField = hashOperations.hasKey("user:1001", "email");

// 删除字段
hashOperations.delete("user:1001", "phone");

List 类型操作

import org.springframework.data.redis.core.ListOperations;

@Resource
private ListOperations<String, Object> listOperations;

// 从左边插入
listOperations.leftPush("mylist", "item1");
listOperations.leftPushAll("mylist", "item2", "item3");

// 从右边插入
listOperations.rightPush("mylist", "item4");

// 获取范围
List<Object> items = listOperations.range("mylist", 0, 1); // 获取所有元素

// 获取列表长度
Long size = listOperations.size("mylist");

// 弹出元素
Object leftItem = listOperations.leftPop("mylist");
Object rightItem = listOperations.rightPop("mylist");

Set 类型操作

import org.springframework.data.redis.core.SetOperations;

@Resource
private SetOperations<String, Object> setOperations;

// 添加元素
setOperations.add("tags", "Java", "Redis", "Spring");

// 获取所有元素
Set<Object> tags = setOperations.members("tags");

// 判断元素是否存在
Boolean isMember = setOperations.isMember("tags", "Java");

// 获取集合大小
Long size = setOperations.size("tags");

// 删除元素
Long removed = setOperations.remove("tags", "Redis");

// 集合运算
setOperations.add("set1", "A", "B", "C");
setOperations.add("set2", "B", "C", "D");

Set<Object> intersection = setOperations.intersect("set1", "set2"); // 交集
Set<Object> union = setOperations.union("set1", "set2"); // 并集
Set<Object> difference = setOperations.difference("set1", "set2"); // 差集

ZSet 类型操作

import org.springframework.data.redis.core.ZSetOperations;

@Resource
private ZSetOperations<String, Object> zSetOperations;

// 添加元素 (分数,成员)
zSetOperations.add("ranking", "player1", 100.0);
zSetOperations.add("ranking", "player2", 200.0);
zSetOperations.add("ranking", "player3", 150.0);

// 获取排名 (按分数升序,从0开始)
Long rank = zSetOperations.rank("ranking", "player2"); // 获取正序排名
Long reverseRank = zSetOperations.reverseRank("ranking", "player2"); // 获取倒序排名

// 获取分数
Double score = zSetOperations.score("ranking", "player1");

// 获取指定范围的元素 (按分数升序)
Set<ZSetOperations.TypedTuple<Object>> range = zSetOperations.rangeWithScores("ranking", 0, 2);

// 获取指定分数范围的元素
Set<Object> byScore = zSetOperations.rangeByScore("ranking", 100.0, 200.0);

// 增加分数
Double newScore = zSetOperations.incrementScore("ranking", "player1", 50.0);

5. 其他常见操作

//获取所有的键
Set keys = redisTemplate.keys("*");

//判断key是否存在
redisTemplate.hasKey("key");

//删除指定key
redisTepmlate.delete("key");

//获取key对应的value的数据类型
DataType type = redisTemlate.type("key");

Spring Redis进一步封装

类似于Service层封装Mapper层,实现某个操作需要执行多个mapper查询;我们也将redis


Redis进阶

Redis事务

Redis是支持事务的,事务内的操作要么都成功,要么都失败;
事务基于队列实现,创建一个事务队列,然后将事务放入队列中,最后依次执行。

#开启事务
multi

#添加命令
sadd user xiaoming
sadd user Jack
sadd user Tom
sadd user Jery

#执行事务
exec
# 取消事务
discard

Redis事务处理机制

事务执行不成功有两种情况:语法错误和执行错误。
当出现语法错误的时候,redis能看出来错误,所有的操作都不会进行执行,这也符合我们对事务的理解。

但是出现执行错误时,比如:

#开启事务
multi

#命令
set lesson java

#错误地使用了rpush操作
rpush lesson eureka feign nacos
set lesson redis

#执行事务
exec

#获取数据
get lesson

语法本身是没有问题的,所以运行之前redis无法发现错误,但是在执行时出现了错误,因此只会错误的命令不执行, 而正确的命令仍然能够正常执行,我们称此为弱事务。

SpringBoot实现事务操作

  • 修改RedisConfig配置类,开启事务控制
  • redisTemplate.setEnableTransactionSupport(true);

  • 测试一下事务效果
  • @Test
    public void multiTest(){
    //开启事务
    redisTemplate.multi();
    try{
    redisTemplate.opsForValue().set("lesson","java");
    redisTemplate.opsForSet().add("lesson","eureka","feign","gateway");
    redisTemplate.opsForValue().set("lesson","redis");
    System.out.println(redisTemplate.opsForValue().get("lesson"));
    }catch (Exception e){
    //回滚
    System.out.println("出现异常");
    redisTemplate.discard();
    }finally {
    redisTemplate.exec();
    }
    }

    Redis持久化机制

    Redis将内存保存在内存中,但是内存并不能将数据持久化保存,一旦服务器宕机,数据就会丢失。当出现这种情况后,为了能够让Redis进行数据恢复,因此Redis提供了持久化机制,将内存中的数据保存到磁盘中,避免数据意外丢失。

    持久化机制有两种:RDB和AOF ,可以选择其中一种,也可以一起使用。

    RDB快照

    RDB是Redis默认的存储方式,其基于快照思想,当符合一定条件后,Redis会将这一刻内存的数据进行快照并保存在磁盘上,产生一个二进制文件,后缀名.rdb

    快照触发方式

    配置文件中设置
    关于快照触发机制,配置文件已经给出了一些
    我们也可以根据项目自己设置触发频率

    save "" # 不使用RDB存储 不能主从

    # 记忆
    save 3600 1 #表示1小时内至少1个键被更改则进行快照。
    save 300 100 #表示5分钟(300秒)内至少100个键被更改则进行快照。
    save 60 10000 #表示1分钟内至少10000个键被更改则进行快照。

    手动执行

    #执行save命令(同步执行)
    save

    #执行bgsave命令(异步子线程执行)
    bgsave

    • save:同步处理,阻塞Redis服务进程,服务器不会处理任何命令,直到RDB文件保存完毕。
    • bgsave:会fork一个和主进程一致的子线程负责操作RDB文件,在创建子线程期间,Redis不处理任何命令。创建子线程后,不会阻塞Redis服务进程,操作RDB文件的同时仍然可以处理命令。

    Redis 的网络 IO 和键值对读写是由一个主线程完成的。
    但为了处理耗时操作(如持久化、异步删除),Redis 内部会利用多线程或子进程来分担压力。
    进程是资源分配的最小单位,各各进程之间的内存资源相互独立。

    Redis默认使用的是bgsave来保存快照,手动也推荐使用这种方式。

    RDB执行流程

    Pasted image 20260226232327

    优缺点

    优点

  • 基于二进制文件完成文件备份,占用空间少,便于文件运输
  • 能自定义快照规则
  • 缺点

  • 无法保证数据完整性,会丢失最后一次快照前的所有数据
  • bgsave每次执行时都会创建子进程时,会阻塞服务器进程,频繁执行会影响效率
  • AOF

    与RDB快照不同,开启AOF后,Redis会将客户端发送的所有的更改的数据命令记录到磁盘中的AOF(.aof)文件。
    Redis重启后,通过读取AOF文件,按顺序获取到记录的数据修改命令,即可完成数据恢复。

    AOF开启

    AOF开启需要修改redis.conf

    # 是否开启AOF,默认为no
    appendonly yes

    #设置AOF文件名称
    appendfilename appendonly.aof

    AOF执行原理

    AOF执行分为三个部分:命令追加、文件写入、文件同步
    Pasted image 20260226231414|475

    三个部分中最关键的就是何时将AOF文件同步到磁盘,根据redis.conf文件中appendfsync属性值分为三种策略:

  • always:每次执行写入命令时都会将缓冲区内容写入AOF文件,并同步到磁盘中,效率最低,但是最安全,最多丢失一个命令的数据。
  • everyces:每次执行写入命令时都会将缓冲区内容写入AOF文件,并且每隔一秒就同步到磁盘中,最多丢失不超过两秒的数据,兼备安全与效率。
  • no:每次执行写入命令都会将缓冲区文件写入AOF文件,但并不对AOF文件进行同步磁盘。 同步操作交由操作系统完成(每30秒一次),该方式最快,但最不安全。
  • AOF执行流程

    Pasted image 20260226231831

    AOF文件重写

    AOF文件会将对Redis操作的所有写命令都记录下来,随着服务器的运行,AOF文件内保存的内容会越来越多。这样就会造成两个比较严重的问题:占用大量存储空间、数据还原花费的时间多。

    为了解决这个问题,Redis提供了AOF优化,当AOF文件超过阈值大小后,会单独开一个子线程来重写AOF文件替代现有的文件。新文件不会包含任何冗余命令,新文件是恢复Redis数据的最小命令集合。

    文件大小的阈值需要我们在配置文件中设置

    #当前文件大小超过旧文件大小的百分之多少时进行重写。如果之前没有重写过,以启动时aof文件大小为准
    auto-aof-rewrite-percentage 100

    #限制允许重写最小aof文件大小,也就是文件小于64mb的时候,不需要进行优化
    auto-aof-rewrite-min-size 64mb

    当然我们也能手动执行重写,使用命令bgrewriteaof即可

    RDB和AOF对比

  • RDB默认开启,AOF需手动开启
  • RDB存储的是二进制文件,AOF存储的是文本文件,RDB性能优于AOF
  • AOF安全性优于RDB
  • AOF优先级高于RDB
  • RDB存储某个时刻的数据快照,AOF存储写命令
  • RDB在配置触发状态会丢失最后一次快照以后更改的所有数据,AOF默认使用everysec,每秒保存一次,最多丢失两秒以内的数据
  • Redis高可用——主从复制

    Redis的两种持久化解决了服务器宕机,Redis数据不能恢复的问题,但是现在加入服务器的磁盘损坏了,恢复文件损坏了,那又该怎么办呢?

    我们可以将持久化的数据保存在多个服务器上,这样一台服务器出现了问题,仍可以从其他服务器上同步数据。

    一个主从集群只能有一个主节点,可以有多个从节点。
    我们规定,只能主节点(Master)写,从节点(Slave)读,也就是主写从读。

    持久化优化

    现在如果master和所有的slave都开启持久化的话,性能相对来说比较低。该如何优化提升性能呢?
    我们可以在从节点上开启持久化、在主节点关闭持久化。 但是这样的话,数据不会丢失吗?在主从复制的结构下,无非要么主节点宕机,要么从节点宕机。

    在主节点宕机后,为了服务正常进行,我们这样设定:

    • 当从节点宕机重启后,主节点会自动的将数据同步到从节点上。所以不会出现数据丢失。
    • 当主节点宕机后,可以手动将从节点提升为主节点(slaveof no one),继续对外提供服务。 并且当原先的主节点重启后,使用slaveof命令将其设置为新主节点的从节点,即可完成数据同步。

    主从复制流程

    Pasted image 20260225142436

    主从复制的作用

  • 读写分离:主写从读,提高服务器的读写负载能力
  • 负载均衡:可以根据服务的需求改变slave的数量,通过多个从节点分担读数据的负载
  • 故障回复:主节点宕机时,可以由salve提供服务,可以快速恢复故障
  • 数据冗余:数据的热备份,持久化外的一种数据冗余方式
  • 高可用基石:哨兵模式和集群都是基于主从复制实现的,是Redis的高可用方案的实现基石
  • Redis高可用——哨兵模式

    我们前面说到,当主节点宕机后,可以选择一个从节点作为主节点继续进行服务,但是我们需要考虑几个问题:

  • 主库真的挂了吗?
  • 该选择哪个从库作为主库?
  • 怎么把新主库的相关信息通知给从库和客户端呢?
  • 为了解决这个问题,Redis提供了哨兵机制。在 Redis 主从集群中,哨兵机制是实现主从库自动切换的关键机制,它有效地解决了主从复制模式下故障转移的这三个问题。

    哨兵的三个任务

  • 监控:哨兵会不断检查你的主服务器和从服务器是否正常运行
  • 提醒:当某个Redis服务器出现问题时,哨兵可以向管理员发送通知
  • 自动故障迁移:当主服务器宕机后,哨兵就会开始一次自动故障迁移,将失效主服务器的一个从服务器升级为新的主服务器,并让其他主服务器复制新的主服务器
  • 哨兵节点也是一个Redis服务器,只是它不提供数据读写服务。哨兵节点的个数通常为单数。
    如果哨兵节点只有一个,那么在哨兵节点宕机后,就不能正常进行主从节点的切换,因此需要哨兵集群

    哨兵集群机制

    主观下线和客观下线

    主观下线

    • 哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状
      态。如果哨兵发现主库或从库对 PING 命令的响应超时了,那么,哨兵就会先把它标记
      为“主观下线”。
    • 如果检测的是从库,哨兵简单地把它标记为“主观下线”就行了,如果检测的是主库,那么,哨兵还不能简单地把它标记为“主观下线”,开启主从切换。
    • 如果检测的是主库,哨兵还不能简单地把它标记为“主观下线”,开启主从切换。因为很有可能存在这么一个情况:那就是哨兵误判了,其实主库并没有故障。一旦启动了主从切换,后续的选主和通知操作都会带来额外的计算和通信开销,因此主观下线的判断并不可靠。

    误判的原因有很多:集群网络压力较大、网络拥塞,或者是主库本身压力较大

    客观下线

    • 为了防止哨兵主观下线误判,我们需要多个哨兵进行相互验证。
    • 哨兵之间通过主库频道__sentinel__:hello实现互相通信、交换主库信息,当一个哨兵认为主服务器下线了,就会去询问其他哨兵服务器下线情况。
    • 若超过一半的哨兵认为主服务器下线了,就达成了主服务器下线的共识,可以将服务器标记为客观下线。这时就可以执行故障转移了。
    哨兵集群切换主从流程

    Pasted image 20260225140650

    Redis高可扩——分片集群

    在之前我们解决了Redis数据丢失的问题——主从集群;又实现了Redis高可用——哨兵集群

    但是对于海量数据和高并发的情况,却并不能很好地解决。
    当数据量达到单机内存瓶颈(例如超过 32GB),或者写并发高到单台 Master 无法承受时,一旦主服务器宕机,要恢复数据需要耗费的时间较长(往往以秒、分种为单位)

    要解决这个问题,就需要搭建Redis Cluster分片集群

    分片集群原理

    • 哈希槽
      • Redis Cluster 引入了哈希槽的概念,每个集群拥有16384个哈希槽,平均的分配给每台 Master使用。
      • 当你存取一个 Key 时,Redis 对 Key 进行 CRC16 校验并对 16384 取模,根据算出的值就可以去指定的节点去操作。
    • 去中心化
      • 与哨兵模式不同,Redis Cluster 没有中心节点,也没有独立的哨兵进程。节点之间通过 Gossip 协议相互通信,确认相互的健康状况。
      • 每个 Master 都可以挂载从节点。如果某个 Master 挂了,其他 Master 会集体投票,从其 Slave 中选出一个新的 Master。

    Pasted image 20260225160658

    Key过期删除策略

    我们知道,可以对Redis键设置过期时间,过期时间到了就会删除这个键,这是直观的删除方式。但是Redis删除Key的策略并不止这一种,而是三种:

  • 定时删除
  • 惰性删除
  • 定期删除
  • 定时删除

    在我们设置键的过期时间的同时,Redis会创建一个定时任务,到了过期时间后,就会删除这个键。这个策略能保证过期的键能尽快删除,释放内存空间

    但是这种策略会导致大量定时器任务执行,严重降低系统性能。对内存友好,对CPU不友好,因此不建议使用。

    惰性删除

    惰性删除策略:它不持续关注key的过期时间, 而是在获取key时,才会检查key是否过期,如果过期则删除该key。
    虽然不怎么消耗CPU资源,但是假设Redis中现在存在大量过期key,而这些过期key如果都不被使用,它们就会保留在redis中,造成内存空间一直被占用。
    这种方式对CPU友好,但是对内存不友好。

    定期删除

    为了解决上面两个策略的弊端,我们采用定期删除策略。

    定期删除,顾名思义,就是每隔一段时间进行一次删除。 那么大家想一下,应该隔多久删一次? 一次又删除多少过期key呢?

    • 如果删除操作执行次数过多、执行时间过长,就会导致和定时删除同样的问题:占用大量CPU资源去进行删除操作。
    • 如果删除操作执行次数过少、执行时间过短,就会导致和惰性删除同样的问题:内存资源被持续占用,得不到释放。

    设置频率和执行时长就是需要仔细考量的事情。

    Redis默认的频率是每秒删除10次,每次随机获取20个key,删除完这些key后,会判断删除的key数量是否大于过期key的25%,如果大于,就继续执行删除。

    Pasted image 20260225164554|450

    Redis内存淘汰策略

    当启用定期清理是,总是会有一些已经过期的key,定期扫描一直都没有扫描到它,而且这些key也一直没有被使用。 那么它们就会一直在内存中存在。同时继续向Redis不断插入新数据,最终造成内存空间不足的问题。

    为了解决这个问题,我们就需要用到内存淘汰策略

    Redis提供了8种内存淘汰策略

    LRU (Least Recently Used——最近最少使用) 和 LFU (Least Frequently Used——使用频率最低)

    阵营策略名称具体淘汰逻辑
    设置了过期时间的数据 (volatile-*) 1. volatile-lru 在设置了过期时间的数据中,淘汰最近最少使用的。
    2. volatile-lfu 在设置了过期时间的数据中,淘汰访问频率最低的。
    3. volatile-random 在设置了过期时间的数据中,随机淘汰。
    4. volatile-ttl 在设置了过期时间的数据中,淘汰最快要过期的。
    所有数据 (allkeys-*) 5. allkeys-lru 在所有数据中,淘汰最近最少使用的。
    6. allkeys-lfu 在所有数据中,淘汰访问频率最低的。
    7. allkeys-random 在所有数据中,随机淘汰。
    拒绝写入 8. noeviction 默认策略。不删除任何数据,内存满后写入报错。
    赞(0)
    未经允许不得转载:171主机测评 » Redis 基础到进阶
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址