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}-1232−1)个键值对;每个键对应多个字段(Filed),字段都有和其对应的字段值

- 常用命令:
| 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),链表中的元素共享一个键。

列表中的元素有序,有索引,可以重复。每个元素都有两个索引号从左向右是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)

因为 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 会根据这个分数自动帮你排序。

哈希表存储每个Member对应的分数,保证查询特定元素权重的复杂度为 O(1)O(1)O(1)
跳表按照元素的Scoer存储Member,如果两个Member的Score重复,就按照Member的字典序来进行排序
跳表通过在元素上方建立“索引层”(Level),实现了类似二分查找的效果,使得插入、删除和查找的平均时间复杂度为 O(logN)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实现事务操作
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执行流程

优缺点
优点
缺点
AOF
与RDB快照不同,开启AOF后,Redis会将客户端发送的所有的更改的数据命令记录到磁盘中的AOF(.aof)文件。
Redis重启后,通过读取AOF文件,按顺序获取到记录的数据修改命令,即可完成数据恢复。
AOF开启
AOF开启需要修改redis.conf
# 是否开启AOF,默认为no
appendonly yes
#设置AOF文件名称
appendfilename appendonly.aof
AOF执行原理
AOF执行分为三个部分:命令追加、文件写入、文件同步

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

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对比
Redis高可用——主从复制
Redis的两种持久化解决了服务器宕机,Redis数据不能恢复的问题,但是现在加入服务器的磁盘损坏了,恢复文件损坏了,那又该怎么办呢?
我们可以将持久化的数据保存在多个服务器上,这样一台服务器出现了问题,仍可以从其他服务器上同步数据。
一个主从集群只能有一个主节点,可以有多个从节点。
我们规定,只能主节点(Master)写,从节点(Slave)读,也就是主写从读。
持久化优化
现在如果master和所有的slave都开启持久化的话,性能相对来说比较低。该如何优化提升性能呢?
我们可以在从节点上开启持久化、在主节点关闭持久化。 但是这样的话,数据不会丢失吗?在主从复制的结构下,无非要么主节点宕机,要么从节点宕机。
在主节点宕机后,为了服务正常进行,我们这样设定:
- 当从节点宕机重启后,主节点会自动的将数据同步到从节点上。所以不会出现数据丢失。
- 当主节点宕机后,可以手动将从节点提升为主节点(slaveof no one),继续对外提供服务。 并且当原先的主节点重启后,使用slaveof命令将其设置为新主节点的从节点,即可完成数据同步。
主从复制流程

主从复制的作用
Redis高可用——哨兵模式
我们前面说到,当主节点宕机后,可以选择一个从节点作为主节点继续进行服务,但是我们需要考虑几个问题:
为了解决这个问题,Redis提供了哨兵机制。在 Redis 主从集群中,哨兵机制是实现主从库自动切换的关键机制,它有效地解决了主从复制模式下故障转移的这三个问题。
哨兵的三个任务
哨兵节点也是一个Redis服务器,只是它不提供数据读写服务。哨兵节点的个数通常为单数。
如果哨兵节点只有一个,那么在哨兵节点宕机后,就不能正常进行主从节点的切换,因此需要哨兵集群
哨兵集群机制
主观下线和客观下线
主观下线
- 哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状
态。如果哨兵发现主库或从库对 PING 命令的响应超时了,那么,哨兵就会先把它标记
为“主观下线”。 - 如果检测的是从库,哨兵简单地把它标记为“主观下线”就行了,如果检测的是主库,那么,哨兵还不能简单地把它标记为“主观下线”,开启主从切换。
- 如果检测的是主库,哨兵还不能简单地把它标记为“主观下线”,开启主从切换。因为很有可能存在这么一个情况:那就是哨兵误判了,其实主库并没有故障。一旦启动了主从切换,后续的选主和通知操作都会带来额外的计算和通信开销,因此主观下线的判断并不可靠。
误判的原因有很多:集群网络压力较大、网络拥塞,或者是主库本身压力较大
客观下线
- 为了防止哨兵主观下线误判,我们需要多个哨兵进行相互验证。
- 哨兵之间通过主库频道__sentinel__:hello实现互相通信、交换主库信息,当一个哨兵认为主服务器下线了,就会去询问其他哨兵服务器下线情况。
- 若超过一半的哨兵认为主服务器下线了,就达成了主服务器下线的共识,可以将服务器标记为客观下线。这时就可以执行故障转移了。
哨兵集群切换主从流程

Redis高可扩——分片集群
在之前我们解决了Redis数据丢失的问题——主从集群;又实现了Redis高可用——哨兵集群
但是对于海量数据和高并发的情况,却并不能很好地解决。
当数据量达到单机内存瓶颈(例如超过 32GB),或者写并发高到单台 Master 无法承受时,一旦主服务器宕机,要恢复数据需要耗费的时间较长(往往以秒、分种为单位)
要解决这个问题,就需要搭建Redis Cluster分片集群
分片集群原理
- 哈希槽
- Redis Cluster 引入了哈希槽的概念,每个集群拥有16384个哈希槽,平均的分配给每台 Master使用。
- 当你存取一个 Key 时,Redis 对 Key 进行 CRC16 校验并对 16384 取模,根据算出的值就可以去指定的节点去操作。
- 去中心化
- 与哨兵模式不同,Redis Cluster 没有中心节点,也没有独立的哨兵进程。节点之间通过 Gossip 协议相互通信,确认相互的健康状况。
- 每个 Master 都可以挂载从节点。如果某个 Master 挂了,其他 Master 会集体投票,从其 Slave 中选出一个新的 Master。

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%,如果大于,就继续执行删除。

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 | 默认策略。不删除任何数据,内存满后写入报错。 |

