Redis

Redis

redis认识

image-20241007223906853

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


  • 内存数据库,速度快,也支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等多种数据结构的存储。
  • Redis支持数据的备份(master-slave)与集群(分片存储),以及拥有哨兵监控机制。
  • 支持事务

Redis 性能高、原子操作、支持多种数据类型,主从复制与哨兵监控,持久化操作等。

Redis 的高并发实现

Redis 是 纯内存 数据库

Redis 使用的是 非阻塞 IOIO 多路复用

Redis 采用了 单线程 的模型

优点

不需要各种锁的性能消耗

不需要各种锁的性能消耗

CPU 消耗

redis数据类型

基本数据结构包含:字符串(strings)、 散列(hashes)、 列表(lists)、 集合(sets)、 有序集合(sorted sets)五种。

image-20241007224006260

字符串

Redis 的字符串是一个由字节组成的序列,跟 Java 里面的 ArrayList 有点类似,采用预分配冗余空间的方式来减少内存的频繁分配

img

实际分配的空间 capacity 一般要高于实际字符串长度 len。

应用场景

字符串类型在工作中使用广泛,主要用于缓存数据,提高查询性能。比如存储登录用户信息、电商中存储商品信息、可以做计数器(想知道什么时候封锁一个IP地址(访问超过几次))等等。

指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 添加一条String类型数据
set key value
# 获取一条String类型数据
get key
# 添加多条String类型数据
mset key1 value1 key2 value2
# 获取多条String类型数据
mget key1 key2
# 自增(+1
incr key
# 按照步长(step)自增
incrby key step
# 自减(-1
decr key
# 按照步长(step)递减
decrby key step
# 删除
del key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
>set username zhangsan  
"OK"

# 获取字符串
>get username
"zhangsan"

# 插入多个字符串
>mset age 18 address bj
"OK"

# 获取多个字符串
>mget username age
1) "zhangsan"
2) "18"

# 自增
>incr num
"1"
>incr num
"2"

# 自减
>decr num
"1"

# 指定步长自增
>incrby num 2
"3"
>incrby num 2
"5"

# 指定步长自减
>decrby num 3
"2"

# 删除
>del num
"1"
散列

散列相当于 Java 中的 HashMap,内部是无序字典。实现原理跟 HashMap 一致。

个哈希表有多个节点,每个节点保存一个键值对。

HashMap 中的键是唯一的,每个键都映射到一个对应的值。它的底层实现使用了哈希函数,通过将键映射为数组中的索引位置,来实现高效的查找、插入和删除操作。

应用场景

Hash 也可以同于对象存储,比如存储用户信息,与字符串不一样的是,字符串是需要将对象进行序列化(比如 JSON 序列化)之后才能保存,而 Hash 则可以将用户对象的每个字段单独存储这样就能节省序列化和反序列的时间。

可以保存用户的购买记录,比如 key 为用户 id,field 为商品 id,value 为商品数量。同样还可以用于购物车数据的存储,比如 key 为用户 id,field 为商品 id,value 为购买数量等等。

也就是设计结构单对单-优先散列

操作指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 设置属性
hset keyname field1 value1 field2 value2
# 获取某个属性值
hget keyname field
# 获取所有属性值
hgetall keyname
# 删除某个属性
hdel keyname field
# 获取属性个数
hlen keyname
# 按照步长自增/自减某个属性(该属性必须是数字)
hincrby keyname field step
# 删除整个 hash
del keyname

image-20241005152410795

列表

Redis 中的 lists 相当于 Java 中的 LinkedList,实现原理是一个双向链表(其底层是一个快速列表),即可以支持反向查找和遍历,更方便操作。插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。

应用场景

ists 的应用场景非常多,可以利用它轻松实现热销榜。

可以实现工作队列(利用 lists 的 push 操作,将任务存在 lists 中,然后工作线程再用 pop 操作将任务取出进行执行 )。

可以实现最新列表,比如最新评论等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 左进
lpush key value1 value2 value3...

# 左出
lpop key

# 右进
rpush key value1 value2 value3...

# 右出
rpop key

# 从左往右读取 startend是下标
lrange key start end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 从 list 左边依次插入
>lpush student zhangsan lisi wangwu
"3"

# 从 list 右边插入
>rpush student tianqi
"4"

# 从 list 左边弹出一个
>lpop liangshan
"wangwu"

# 从 list 右边弹出一个
>rpop liangshan
"tianqi"

# 获取 list 下标 0 ~ 1 的数据(左闭右闭)
>lrange liangshan 0 1
1) "lisi"
2) "zhangsan"
集合

集合类似 Java 中的 HashSet,(HashSet 的内部 HashMap代表集合中的元素,而可以认为是无意义的,只用于填充。它并不一定是 null,而是同一个固定的占位对象。key-value-只有key有值) HashMap,实际就是通过计算 hash 的方式来快速排重的,这 也是 set 能提供判断一个成员是否在集合内的原因。

Redis 的 sets 类型是使用哈希表构造的,因此复杂度是 0(1)。它支持集合内的增删改查,并且支持多个集合间的交集、并集、 差集操作。可以利用这些集合操作,解决程序开发过程当中很多数据集合间的问题。比如计算网站独立 ip,用户画像中的用 户标签,共同好友等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 添加内容
sadd key value1 value2
# 查询 key 里所有的值
smembers key
# 移除 key 里面的某个 value
srem key value
# 随机移除某个 value
spop key
# 返回两个 set 的并集
sunion key1 key2
#返回 key1 剔除交集的那部分(差集)
sdiff key1 key2
#跟 siffer 相反,返回交集
sinter key1 key2
有序集合

sorted sets 是 Redis 类似于 SortedSetHashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重

image-20210412221232375

主要应用于根据某个权重进行排序的队列的场景,比如游戏积分排行榜,设置优先级的任务列表,学生成绩表等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 添加元素
zadd key score value [score value...]

# 获取集合的值并按照score从小到大排列, 最小的是最上面
zrange key start end

# 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列, 最小的是最上面
zrangeByScore key score_min score_max

# 删除
zrem key value

# 获取key的集合有多少元素
zcard key

# 统计分数从小到大有多少元素 (闭区间)
zcount key score_min score_max

# 获取value所在位置(从小到大排序,最小的是0
zrank key value

# 获取value所在的位置(从大到小排列, 最大的是0
zrevrank key value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 插入多条数据和分数并去重及排序
>zadd rank 66 zhangsan 88 lisi 77 wangwu 99 zhaoliu
"4"

# 插入多条数据及分数并去重及排序
>zadd rank 66 zhangsan 88 lisi 77 wangwu 99 zhaoliu
"0"

# 获取下标 0 ~ 3 的数据(左闭右闭)
>zrange rank 0 3
1) "zhangsan"
2) "wangwu"
3) "lisi"
4) "zhaoliu"

# 获取分数在 77 ~ 99 之间的数据(左闭右闭)
>zrangeByScore rank 77 99
1) "wangwu"
2) "lisi"
3) "zhaoliu"

# 删除一条数据
>zrem rank zhaoliu
"1"

# 查询元素的个数
>zcard rank
"3"

# 统计分数在 77 ~ 88 之间的数据(左闭右闭)
>zcount rank 77 88
"2"

# 获取指定元素的下标
>zrank rank zhangsan
"0"

# 获取指定元素的下标并反转
>zrevrank rank zhangsan
"2"

redis高级数据结构

位图
基数统计
地理位置
发布订阅

redis持久化

通常数据库存在三种用于持久操作以防止数据损坏的常见策略

  1. 数据库不关心故障,而是在数据文件损坏后从数据备份或快照中恢复。RDB 就是这种情况。
  2. 使用日志记录每个操作的操作行为,以在失败后通过日志恢复一致性。由于操作日志是按顺序追加写入的,因此不会出现无法恢复操作日志的情况。类似于 MySQL 的 redo log 和 undo log。
  3. 数据库不修改旧数据,而仅通过追加进行写入,因为数据本身就是日志,因此永远不会出现数据无法恢复的情况,CouchDB 是一个很好的例子。AOF 类似这种情况。

image-20241005153226509

RDB 持久化

  • Redis 生成某一时刻的数据快照,保存到磁盘的 RDB 文件中。可以手动触发生成,也可以通过配置让 Redis 在特定的时间间隔生成。
  • 适合冷备份或者在不特别关注数据丢失的场景下使用,因为在快照之间的写操作数据不会保存。

AOF 持久化

  • Redis 将每个写命令以日志的形式顺序追加到 AOF 文件中,恢复时则从头开始重放日志文件的命令来还原数据。可以通过配置控制日志写入磁盘的频率,比如每秒一次、每次写入、由操作系统控制等。
  • 适合对数据安全性要求高的场景,但文件大小可能较大。

混合持久化

  • 这是 Redis 4.0 引入的一种持久化方式,它结合了 RDB 和 AOF 的优点。恢复时可以使用 RDB 快速加载大部分数据,AOF 文件补充最新的增量操作。
  • 适用于既要快速恢复又希望减少数据丢失的场景。
开启方式-案例
1
2
3
4
5
6
# 创建配置目录
mkdir -p /usr/local/redis/conf
# 创建数据目录
mkdir -p /usr/local/redis/data
# 创建日志目录
mkdir -p /usr/local/redis/log
1
vim /usr/local/redis/conf/redis.conf
1
2
3
4
5
6
7
8
9
10
11
12
# 放行 IP 访问限制
bind 0.0.0.0
# 后台启动
daemonize yes
# 日志存储目录及日志文件名
logfile "/usr/local/redis/log/redis.log"
# RDB 数据文件名
dbfilename dump.rdb
# RDB 数据文件和 AOF 数据文件的存储目录
dir /usr/local/redis/data
# 设置密码
requirepass 123456

redis.conf 文件末尾加上:

1
2
3
4
5
6
# 900 秒内如果超过 1 个key改动,则发起快照保存
save 900 1
# 300 秒内如果超过 10 个 key 改动,则发起快照保存
save 300 10
# 60 秒内如果超过 1W 个 key 改动,则发起快照保存
save 60 10000

快照,顾名思义可以理解为拍照一样,把整个内存数据映射到硬盘中,保存一份到磁盘,因此恢复数据起来比较快,把数据映射回去即可,不像 AOF 一样,一条条的执行操作指令

快照是默认的持久化方式。这种方式就是将内存中数据以快照的放入写入二进制文件中,默认的文件名为 dump.rdb,可以通过配置设置自动做快照持久化的方式。


aof

通过配置 redis.conf 进行启动,默认是关闭的

1
2
3
4
5
# 默认 appendonly 为 no
appendonly yes
appendfilename "appendonly.aof"
# RDB 文件和 AOF 文件所在目录
dir /usr/local/redis/data
优化方案
  1. 独立部署,硬盘优化
    • 持久化过程是密集计算的过程,所以最好把 Redis 单独部署到一台服务器上。
    • 写入请求多的话,可以考虑性能更高的 SSD 磁盘。
  2. 缓存禁用持久化
    • 缓存丢失了也不要紧。
  3. 主从模式,从持久化
    • 从节点一般只提供读功能,把持久化的工作交给从节点,减轻主节点的压力。
    • AOF 可以考虑禁用,因为多个从节点我们只需要选取追新的那份备份就可以了。
  4. 优化 fork 处理
    • 降低 AOF 的重写频率。
    • 重写期间如果已经阻塞的时候,那就不接收重写期间的数据了。

redis spring集成

1.坐标
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置文件
1
2
3
4
5
6
spring:
redis:
host: 121.4.146.25
port: 6379
password: 123456
database: 5
3.配置序列化

在不指定序列化方式时,Spring 组件通常会使用Java 默认的序列化机制,即 Serializable 接口进行序列化。这种方式虽然可以工作,但效率较低,且生成的序列化结果较大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

// 设置键的序列化方式
template.setKeySerializer(new StringRedisSerializer());

// 设置值的序列化方式
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));

return template;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);

// 使用 Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

// 设置 key 和 value 的序列化规则
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
4.代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@SpringBootTest(classes = {RedisSpringbootApplication.class})
class RedisSpringbootApplicationTests {

// 注入模板对象
@Autowired
private RedisTemplate redisTemplate;

@Test
public void testRedis(){
System.out.println(redisTemplate.getConnectionFactory().getConnection().ping());
// 获取 Redis 操作对象
redisTemplate.opsForValue().set("key", "value");
String value = redisTemplate.opsForValue().get("key");

redisTemplate.opsForHash().put("hashKey", "field", "value");
String hashValue = (String) redisTemplate.opsForHash().get("hashKey", "field");

redisTemplate.opsForList().leftPush("listKey", "value1");
String listValue = redisTemplate.opsForList().rightPop("listKey");

redisTemplate.opsForSet().add("setKey", "value1");
Boolean isMember = redisTemplate.opsForSet().isMember("setKey", "value1");

redisTemplate.opsForZSet().add("zsetKey", "value1", 1.0);
Set<String> zSetValues = redisTemplate.opsForZSet().range("zsetKey", 0, -1);





}

RedisTemplate 的工作原理

当你使用 RedisTemplate 时,具体的操作大致可以分为以下几步:

  1. 获取 Redis 连接:通过 RedisConnectionFactory 获取 Redis 的连接。
  2. 序列化键/值:使用配置的序列化器将键和值序列化为 Redis 可以识别的格式(如字符串或二进制数据)。
  3. 执行 Redis 命令:通过底层的 Redis 客户端库(如 JedisLettuce)向 Redis 服务器发送命令。
  4. 反序列化返回数据:Redis 返回的结果通过反序列化器转换回 Java 对象供应用使用。
  • RedisTemplate
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    本质上依赖于底层的 Redis 客户端库,与 Redis 服务器进行通信。Spring Data Redis 支持多个 Redis 客户端,例如:

    - **Jedis**:一个常用的 Redis Java 客户端,适用于传统阻塞式操作。
    - **Lettuce**:一个支持异步和同步通信的非阻塞 Redis 客户端,默认用于 Spring Data Redis。

    当我们使用 `RedisTemplate` 进行操作时,它会调用 Redis 客户端库的 API 来与 Redis 服务器通信。

    ###### 5.reids配合日期配置

    1.redis配置

    @Configuration public class RedisConfig<V> {
@Autowired
private RedisProperties redisProperties;

@Bean
public LettuceConnectionFactory connectionFactory() {
    RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
    config.setHostName(redisProperties.getHost());
    config.setPort(redisProperties.getPort());
    return new LettuceConnectionFactory(config);
}
@Bean
public RedisTemplate<String, V> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, V> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    // 设置key的序列化方式
    template.setKeySerializer(RedisSerializer.string());
    // 设置value的序列化方式
    template.setValueSerializer(RedisSerializer.json());
    // 设置hash的key的序列化方式
    template.setHashKeySerializer(RedisSerializer.string());
    // 设置hash的value的序列化方式
    template.setHashValueSerializer(RedisSerializer.json());
    template.afterPropertiesSet();
    return template;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

2.

```java
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("coupon")
@ApiModel(value="Coupon对象", description="")
public class Coupon implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;

@TableField("start_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;

@TableField("end_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;

}

缓存

1.缓存方式选择

image-20241007223535709

字符串-需要序列化适合小数据-拿取需要取出所有字符串-大数据麻烦

哈希-拿去根据key拿去-不需要序列化-适合大数据

2.数据类型支持缓存

7468e1fc2e0e15018ebbd9c9677284e7

缓存异常解决

image-20241007224145594

1.缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

  1. 设置热点数据永远不过期。

  2. 设置随机过期时间-不会一下子大家一下到期

  3. 读mysql数据库-加互斥锁

    理论上如果能根据 key 值加锁就更好了,就是线程 A 从数据库取 key1 的数据并不妨碍线程 B 取 key2 的数据, –优化方向

    —-要做好降级处理哦

    4.提前缓存预热-每天预热多少条数据-使用游标

img

2.缓存穿透

当查询缓存是无此 key 对应的值,后去数据库查询,数据库有值时存入缓存无值时返回无此值,但再一次查此 key 是还是一样的结果,但大量的访问此 key 是对数据库会造成更大的压力。

缓存空值-第一次查mysql后无数据-设置较低有效期(防止对方进行攻击-不建议)

布隆过滤器

  • 当客户端进行查询时,先经过布隆过滤器,判断要查询的数据 key 是否在布隆数组当中,如果可能存在,则查询数据库,如果不可能存在,则返回空。

  • 布隆过滤器(Bloom Filter)是一种数据结构,用于快速判断一个元素是否属于一个集合中。

    它使用多个Hash函数将一个元素映射成一个位阵列(Bit array)中的一个点,将Bit array理解为一个二进制数组,数组元素是0或1。

    当一个元素加入集合时,通过N个散列函数将这个元素映射到一个Bit array中的N个点,把它们设置为1。

image-20241007224925550

检索某个元素时再通过这N个散列函数对这个元素进行映射,根据映射找到具体位置的元素,如果这些位置有任何一个0,则该元素一定不存在,如果都是1很可能存在误判。

**哈希函数的基本特性:

同一个数使用同一个哈希函数计算哈希值,其哈希值总是一样的。

对不同的数用相同的哈希函数计算哈希值,其哈希值可能一样,这称为哈希冲突。

哈希函数通常是单向的不可逆的,即从哈希值不能逆向推导出原始输入。这使得哈希函数适用于加密和安全应用。

为什么存在哈希误判

主要原因是哈希冲突。布隆过滤器使用多个哈希函数将输入的元素映射到位数组中的多个位置,当多个不同的元素通过不同的哈希函数映射到相同的位数组位置时就发生了哈希冲突。

由于哈希函数的有限性,不同的元素可能会映射到相同的位置上,这种情况下即使元素不在布隆过滤器中可能产生误判,即布隆过滤器判断元素在集合中。

如何降低误判率?

增加Bit array空间,减少哈希冲突,优化散列函数,使用更多的散列函数。


使用

将要查询的元素通过N个散列函数提前全部映射到Bit array中,比如:查询服务信息,需要将全部服务的id提前映射到Bit array中,当去查询元素是否在数据库存在时从布隆过滤器查询即可,如果哈希函数返回0则表示肯定不存在。

image-20241007224956276

布隆过滤器的优点是:二进制数组占用空间少,插入和查询效率高效。

缺点是存在误判率,并且删除困难,因为同一个位置由于哈希冲突可能存在多个元素,删除某个元素可能删除了其它元素。

使用场景

1、海量数据去重,比如URL去重,搜索引擎爬虫抓取网页,使用布隆过滤器可以快速判定一个URL是否已经被爬取过,避免重复爬取。

2、垃圾邮件过滤:使用布隆过滤器可以用于快速判断一个邮件地址是否是垃圾邮件发送者,对于海量的邮件地址,布隆过滤器可以提供高效的判定。

3、安全领域:在网络安全中,布隆过滤器可以用于检查一个输入值是否在黑名单中,用于快速拦截一些潜在的恶意请求。

4、避免缓存穿透:通过布隆过滤器判断是否不存在,如果不存在则直接返回。

实现

使用redit的bitmap位图结构实现。

使用redisson实现。

使用google的Guava库实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
void test1()
{
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000, 0.01);

// 添加元素到布隆过滤器
bloomFilter.put("example1");
bloomFilter.put("example2");
bloomFilter.put("example3");

// 测试元素是否在布隆过滤器中
System.out.println(bloomFilter.mightContain("example1")); // true
System.out.println(bloomFilter.mightContain("example4")); // false
}
3.缓存雪崩

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。

比如对某信息设置缓存过期时间为30分钟,在大量请求同时查询该类信息时,此时就会有大量的同类信息存在相同的过期时间,一旦失效将同时失效,造成雪崩问题。

1.雪崩后关闭外网服务,对数据库 预热缓存 再开启外网服务。

  1. 将缓存的 key 的到期时间设置为不同个的时间,避免同一个时间段大规模的缓存失效。
  2. 将缓存备份。
  3. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  4. 设置热点数据永远不过期。
4.缓存淘汰

Redis 的最大缓存、主键失效、淘汰机制等参数都是通过 redis.conf 配置文件来配置的。

通过设置最大缓存-还有淘汰机制-减少无效数据

缓存不一致问题
1.缓存不一致问题

缓存不一致问题是指当发生数据变更后该数据在数据库和缓存中是不一致的,此时查询缓存得到的并不是与数据库一致的数据。

写数据库和写缓存导致不一致称为双写不一致,比如:先更新数据库成功了,更新缓存时失败了,最终导致不一致。

image-20241007230658977

CPU划分时间片

线程1 写数据库-写完 key 123

此时cpu划分时间片到线程2

线程2-写数据-写完-写redis写完 key 456

线程1继续执行-直接替换redis缓存 导致缓存不一致问题-key123

先缓存后数据 照样线程安全问题

数据库 key456

redis key123

解决方案-1.分布式锁

将缓存-和数据库操作 同时上锁

image-20241007230740088

线程1申请分布式锁,拿到锁。此时其它线程无法获取同一把锁。

线程1写数据库,写缓存,操作完成释放锁。

线程2申请分布锁成功,写数据库,写缓存。

对双写的操作每个线程顺序执行。

对操作异常问题仍需要解决:写数据库成功写缓存失败了,数据库需要回滚,此时就需要使用分布式事务组件。

使用分布式锁解决双写一致性不仅性能低下,复杂度增加。

2.延迟双删

image-20241007230801582

主从复制还没完成-线程2就去读了-导致读了旧数据-只要不更新-后面的人都读旧数据-出错

3.最终一致

image-20241007230853382

延迟多长时间呢?

延迟主数据向从数据库同步的时间间隔,如果延迟时间设置不合理也会导致数据不一致。

—定时器删除缓存最好—

也许有人还能查询到旧数据-

只要过了删除时间

有人再去查-那么就能保证肯定拿到的新缓存数据

4.Canal Mq方案

延迟双删的目的也是为了保证最终一致性,即允许缓存短暂不一致,最终保证一致性。

保证最终一致性的方案有很多,比如:通过MQ、Canal、定时任务都可以实现。

Canal是一个数据同步工具,读取MySQL的binlog日志拿到更新的数据,再通过MQ发送给异步同步程序,最终由异步同步程序写到redis。此方案适用于对数据实时性有一定要求的场景。

image-20241007231258473

线程1写数据库

canal读取binlog日志,将数据变化日志写入mq

同步程序监听mq接收到数据变化的消息

同步程序解析消息内容写入redis,写入redis成功正常消费完成,消息从mq删除。

5.定时器解决

专门启动一个数据同步任务定时读取数据同步到redis,此方式适用于对数据实时性要求不强更新不频繁的数据。

image-20241007231347023

从思路来看-定时器和Canal差不多 但是别人更节省cpu资源

Redis使用场景

1.缓存优化

最基础的使用-需要考虑缓存异常解决场景-还有缓存不一致情况

https://gitee.com/laomaodu/anli01

https://gitee.com/laomaodu/kaqi-pan

https://gitee.com/yun-lan-arrives-home/jzo2o-customer

2.延迟任务-消息队列

黑马头条中-延迟文字的发布-后续会自写项目-现在先空着

3.秒杀抢购

https://gitee.com/laomaodu/anli01 非常经典的一个秒杀抢购

4.实时统计
5.限流处理
6.排行榜

https://gitee.com/laomaodu/music-platform-demo

1
2
redisTemplate.opsForZSet().add(HotArticleConstants.HOT_ARTICLE_REDIS_QUEUE,mess.getMuicid(),score);//分数

1
2
3
//降序reverseRange 升序 range

redisTemplate.opsForZSet().reverseRange(HotArticleConstants.HOT_ARTICLE_REDIS_QUEUE, onlineMusic.getPage()*10-10, onlineMusic.getPage()*10);

拿出

7.会话管理

session-id->对应数据-可防止服务器失效后数据丢失

Redis优化

空着-如果12月份无实习就继续学习


Redis
http://example.com/2024/10/05/Middleware/redis/Redis/
作者
John Doe
发布于
2024年10月5日
许可协议