我是靠谱客的博主 傻傻冰棍,最近开发中收集的这篇文章主要介绍3. redis (数据缓存中间件),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一、引言

1、什么是Redis?

Redis是一款基于内存键-值型NoSQL数据库。NoSQL - 非关系型数据库

特点:可以进行快速的数据读写,官方给的数据 11W/s 读 8W/s 写。

Memcache

2、Redis在实际开发中的运用场景

1)作为分布式系统的缓存服务器

在这里插入图片描述

2)应对数据高速读写的业务

在这里插入图片描述

3)作为分布式锁使用(Zookeeper、Redis)

在这里插入图片描述

4)数据共享

在这里插入图片描述

5)ID自增序列

在这里插入图片描述

二、Docker安装Redis

1)在合适位置准备好redis.conf配置文件

./redis/conf/redis.conf

注意:./表示docker-compose.yml所在的路径

2)编写docker-compose.yml

redis:
    image: redis:5
    container_name: redis
    restart: always
    ports:
      - 6379:6379
    volumes:
      - ./redis/conf/redis.conf:/etc/redis/redis.conf
      - ./redis/conf/data:/data
    command:
      ['redis-server', '/etc/redis/redis.conf']

3)修改宿主机中的redis.conf配置文件( 0.0.0.0在服务器的环境中,指的就是服务器上所有的ipv4地址, 所有机器上的地址都可以访问服务 )

把
bind 127.0.0.1
改成
bind 0.0.0.0

4)重启redis并且通过工具连接操作redis

三、Java通过API操作redis

1)添加依赖

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

2)编写代码操作redis

 //通过连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(100);
        poolConfig.setMaxIdle(50);
        poolConfig.setMinIdle(20);

        JedisPool jedisPool = new JedisPool(poolConfig,"192.168.195.188", 6379);
        Jedis jedis = jedisPool.getResource();

        //连接redis
//        Jedis jedis = new Jedis("192.168.195.188", 6379);
        //操作redis
//        jedis.set("money", "10000");
        String value = jedis.get("money");
        System.out.println(value);

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

四、Spring通过API操作redis

1)添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>2.2.3.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.7.RELEASE</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.0.1</version>
    </dependency>
</dependencies>

2)配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置redis连接池对象 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大空闲数 -->
        <property name="maxIdle" value="50"/>
        <!-- 最大连接数 -->
        <property name="maxTotal" value="100"/>
        <!-- 最大等待时间 -->
        <property name="maxWaitMillis" value="20000"/>
    </bean>

    <!-- 配置redis连接工厂 -->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!-- 连接池配置 -->
        <property name="poolConfig" ref="poolConfig"/>
        <!-- 连接主机 -->
        <property name="hostName" value="192.168.195.188"/>
        <!-- 端口 -->
        <property name="port" value="6379"/>
    </bean>


    <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
    <bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>

    <!-- 配置redis模板对象 -->
    <bean class="org.springframework.data.redis.core.RedisTemplate">
        <!-- 配置连接工厂 -->
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="keySerializer" ref="stringRedisSerializer"/>
        <property name="valueSerializer" ref="stringRedisSerializer"/>
    </bean>

</beans>

注意:Spring整合Redis提供的RedisTemplate默认对 key 和 value进行jdk的序列化操作。这样做的好处在于可以存放类型复杂的key-value,但是到了redis中会变成序列化的字符串,命令不能直接操作。
建议实际开发过程中,换成字符串的序列化方式,如果需要存放类型复杂的对象,可以手动的转换成JSON字符串。

五、SpringBoot整合Redis

1)添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2)配置application.yml

spring:
  redis:
    host: 192.168.195.188
    port: 6379

3)注入对象操作redis

@Autowired
private RedisTemplate redisTemplate;

@Autowired
private StringRedisTemplate stringRedisTemplate;

....

六、Redis常见的数据类型(https://www.redis.net.cn/)

1)String类型

结构:key - value

常用命令:
set key value
get key
incr key
incrby key xxx
decr key
decrby key xxx

2)Hash类型

结构:key - value(field, value)…

常用命令:
hset key field value
hget key field
hgetall key
hdel key field

运用场景:通常用来保存一个对象,field对应对象中的一个属性。
案列:gateway基于redis实现令牌桶算法限流:hash结构是key filed value 这个key就是桶的名字,filed就是令牌的数量 ,和桶的令牌最大的数量

3)List类型 - 链表

结构:key - value1,value2,value3…

特点:value有多个,可以重复,并且有序,底层是一个双向链表

常用命令:
lpush key value1 value2…
rpush key value1 value2…
llen key
lpop key
rpop key
lrange key beginIndex endIndex (含头含尾)

4)Set类型 - 集合

结构:key - value3,value2,value4,value1

特点:value有多个,无序不可重复,底层是一张哈希表

常用命令:
sadd key value1 value2…
smembers key
scard key

运用场景:去重、判断唯一、判定是否存在
案列:
防止提前下单的操作: reids有一个数据set集合,set集合底层用的是哈希表,哈希表可以用来,判断是否存在。

5)Zset类型 - 有序集合

结构:key - (score1, value1) (score2, value2)…

特点:value有多个,每个value都携带一个评分,根据评分排序,元素不能重复,底层是一张跳跃表

常用的命令:
zadd key score1 value1 score2 value2 …
zscan key cursor
zcard key
zrange key startindex endindex
zrevrangebyscore key maxScore minScore

运用场景:热门搜索词 - 搜索词的搜索频率设置score
案列: 客户端发送一个消息,在抢卷服务设置提醒计算开抢时间,然后存到redis的有序集合里,以年月日为key,value就是用户id和优惠券id(时间段)绑定在一起。系统时间一到就会触发定时任务,获得当前时间,把这个时间在redis查询,然后循环的通知netty服务的消息中心在通知到客户端。

6)redis中非类型相关的常见命令

keys * - 查看当前redis库中所有的key
type key - 返回key对应的数据类型
del key - 删除指定的key,无论数据类型
select index - 选择指定的数据库(0 ~ 15)
flushdb - 清空当前的数据库的所有key
flushall - 清空所有数据库的所有key

#对应的SpringBoot中的API操作代码
stringRedisTemplate.opsForValue();//操作String类型
stringRedisTemplate.opsForHash();//操作hash类型
stringRedisTemplate.opsForList();//操作list类型
stringRedisTemplate.opsForSet();//操作set类型
stringRedisTemplate.opsForZSet();//操作zset类

stringRedisTemplate.expire("", 5, TimeUnit.MINUTES);
stringRedisTemplate.delete("");       

七、Redis的超时时间

7.1 redis的超时时间有什么用?

redis通常作为缓存服务器存在,数据库中的很多数据都会缓存到redis中,以此减少数据库的访问压力。但是因为存储单位的不同,不可能将所有的数据库数据都缓存到redis中,只能缓存局部数据。实际开发中,肯定希望redis尽可能的缓存热点数据,如何判断哪些数据是热点数据? 通过给key设置超时时间可以帮助redis过滤掉很多的冷门数据。

7.2 超时时间的相关命令

expire key - 给key设置超时时间,单位是秒
persist key - 移除key的超时时间
ttl key - 查看key的剩余超时时间
	-2 表示当前key不存在
	-1 表示当前key永生

注意:redis中,key如果过期的话,并不会立刻从内存中移除,在redis中移除过期key的机制:

1)当客户端需要使用到某个key时
2)后台有个线程间隔的扫描所有过期的key,再进行移除

八、redis的内存淘汰策略

8.1 什么是redis的内存淘汰策略?

简单来说,就是当redis发现自己内存满了之后,怎么办?

8.2 如何配置Redis的内存淘汰策略?

在这里插入图片描述

内存淘汰策略的可选项:
volatile-lru -> Evict using approximated LRU among the keys with an expire set.
allkeys-lru -> Evict any key using approximated LRU.
volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
allkeys-lfu -> Evict any key using approximated LFU.
volatile-random -> Remove a random key among the ones with an expire set.
allkeys-random -> Remove a random key, any key.
volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
noeviction -> Don’t evict anything, just return an error on write operations.

可选值:
noeviction -> redis变成只读模式
volatile-lru -> 在所有设置了过期时间的key中,淘汰最近最少被使用的key
allkeys-lru -> 在所有的key中,淘汰最近最少被使用的key
volatile-lfu -> 在所有设置了过期时间的key中,淘汰总共最少被使用的key
allkeys-lfu -> 在所有的key中,淘汰总共最少被使用的key
volatile-random -> 在所有设置了过期时间的key中,随机淘汰key
allkeys-random -> 在所有的key中,随机淘汰key
volatile-ttl -> 在所有设置了过期时间的key中,淘汰存活时间最短的key

前缀后缀:
allkeys - 表示所有的key
volatile - 表示设置了过期时间的key

lru - 最近最少被使用
lfu - 总共最少被使用
random - 随机
ttl - 存活时间最短

注意:redis中的所有淘汰策略,都是近似算法(lru、lfu、ttl)
在这里插入图片描述

九、Redis的lua脚本

9.1 Redis的线程模型

redis是单线程模型的服务。所有的Redis单线程,并不是说Redis服务只有一个线程,确切来说,只有一个主线程在接收执行客户端的命令。

为什么Redis要设计成单线程?

Redis的执行效率瓶颈不在于CPU,Redis的效率瓶颈在于网络,多线程执行命令对于Redis来说,反而要额外的进行线程切换的开销。

单线程的Redis是否意味着线程安全?

Redis内部确实是线程安全的,但是调用Redis的服务不一定线程安全?

往往可以基于 单线程特性 + Lua脚本 实现无锁化的线程安全业务。

注意:
1、Lua脚本中不能执行耗时操作,如果执行超过5S的lua脚本,Redis服务会自动停止lua脚本运行。
2、Redis 6.0之后,开始支持多线程。主线程 + n个IO线程,IO线程主要负责连接的处理,数据的读写,所有IO线程的命令会交给主线程执行。

9.2 Lua脚本的基本语法

Redis执行lua脚本的语法:

eval SCRIPT numkeys [key …] [arg …]

解释:
eval 固定写法,表示执行一个lua脚本
SCRIPT 脚本内容
numkeys 脚本中key变量的数量
[key …] 传入脚本的key变量的实际值
[arg …] 传入脚本中的附加参数的实际值

在lua脚本中,操作redis的语法:

redis.call(‘…’, ‘…’, ‘…’);

比如:set name xiaoming -> redis.call(‘set’, ‘name’, ‘xiaoming’)

案例:
eval “return redis.call(‘get’, ‘name’)” 0
eval “return redis.call(‘set’, KEYS[1], ARGV[1])” 1 name xiaohong
eval “redis.call(‘hset’, KEYS[1], ARGV[1], ARGV[2])” 1 person sex man

练习:求取一个链表所有元素的平均值

--获得redis链表的总长度
local agesLength = redis.call('llen', KEYS[1])

--获得redis链表的所有元素放入一个对象(数组)
local ages = redis.call('lrange', KEYS[1], 0, agesLength)

--临时变量
local ageSum = 0

--循环数组,年龄的累加
for i=1,#ages do
 --循环体
 ageSum = ages[i] + ageSum
end

--求取平均值
return ageSum/#ages

lua脚本的缓存

缓存脚本:script load “SCRIPT” 返回一个sha签名字符串
执行缓存脚本:evalsha “脚本签名” numkeys [key …] [arg …]

9.3 SpringBoot中执行lua脚本

/**
 * 执行lua脚本
 */
@Test
void script(){
    
    String str = "--获得redis链表的总长度n" +
            "local agesLength = redis.call('llen', KEYS[1])n" +
            "n" +
            "--获得redis链表的所有元素放入一个对象(数组)n" +
            "local ages = redis.call('lrange', KEYS[1], 0, agesLength)n" +
            "n" +
            "--临时变量n" +
            "local ageSum = 0n" +
            "n" +
            "--循环数组,年龄的累加n" +
            "for i=1,#ages don" +
            "    --循环体n" +
            "    ageSum = ages[i] + ageSumn" +
            "endn" +
            "n" +
            "--求取平均值n" +
            "return ageSum/#ages";

    //创建一个脚本对象
    DefaultRedisScript script = new DefaultRedisScript(str, Long.class);

    List<String> keys = new ArrayList<>();
    keys.add("ages");
    //执行脚本
    Long result = (Long) stringRedisTemplate.execute(script, keys);
    System.out.println("脚本执行的结果:" + result);
}

十、使用Redis的lua脚本,实现分布式锁

package com.qf.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;

@Component
public class LockUtil {

    @Autowired
    private StringRedisTemplate redisTemplate;

    //加锁的lua脚本
    private String lockLua = "--锁的名称n" +
            "local lockName=KEYS[1]n" +
            "--锁的valuen" +
            "local lockValue=ARGV[1]n" +
            "--过期时间 秒n" +
            "local timeout=tonumber(ARGV[2])n" +
            "--尝试进行加锁n" +
            "local flag=redis.call('setnx', lockName, lockValue)n" +
            "--判断是否获得锁n" +
            "if flag==1 thenn" +
            "--获得分布式锁,设置过期时间n" +
            "redis.call('expire', lockName, timeout)n" +
            "endn" +
            "--返回标识n" +
            "return flag ";

    //解锁的lua脚本
    private String unLockLua = "--锁的名称n" +
            "local lockName=KEYS[1]n" +
            "--锁的valuen" +
            "local lockValue=ARGV[1]n" +
            "--判断锁是否存在,以及锁的内容是否为自己加的n" +
            "local value=redis.call('get', lockName)n" +
            "--判断是否相同n" +
            "if value == lockValue thenn" +
            "     redis.call('del', lockName)n" +
            "     return 1n" +
            "endn" +
            "return 0";

    private ThreadLocal<String> tokens = new ThreadLocal<>();

    /**
     * 加锁
     * @return
     */
    public void lock(String lockName){
        lock(lockName, 30);
    }

    public void lock(String lockName, Integer timeout){

        String token = UUID.randomUUID().toString();
        //设置给threadLocal
        tokens.set(token);

        //分布式锁 - 加锁
        Long flag = (Long) redisTemplate.execute(new DefaultRedisScript(lockLua, Long.class),
                Collections.singletonList(lockName),
                token, timeout + ""
        );

        System.out.println("获得锁的结果:" + flag);

        //设置锁的自旋
        if (flag ==0) {
            //未获得锁
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lock(lockName, timeout);
        }
    }

    /**
     * 解锁
     * @return
     */
    public boolean unlock(String lockName){

        //获得ThreadLocal
        String token = tokens.get();

        //解锁
        Long result = (Long) redisTemplate.execute(new DefaultRedisScript(unLockLua, Long.class),
                Collections.singletonList(lockName),
                token);

        System.out.println("删除锁的结果:" + result);

        return result == 1;
    }
}

十一、Redis的持久化

为什么要持久化?
  因为,redis本身运行时数据保存在内存中,如果不进行持久化,那么在redis出现非正常原因宕机或者关闭redis的进程或者关闭计算机后数据肯定被会操作系统从内存中清掉。

11.1 Redis持久化的方式

1)RDB - 默认

快照 - 将redis当前瞬间的内存结构记录并且保存到硬盘上

2)AOF - 默认关闭

AOF (Append only file) - 记录所有redis执行过的写命令,当需要恢复数据时,重放这些写命令即可

11.2 RDB相关命令和配置

快照相关的命令

save - 手动执行一个前台快照,由当前主线程执行快照,在快照的过程当中,不能执行任何命令
bgsave - 手动执行一个后台快照,会开启一个新的线程进快照操作,但是开发者没办法获得快照的结果

快照的相关配置

快照的执行频率,每xx秒后,有xx数据发生变化,就执行一个快照
save 900 1
save 300 10
save 60 10000

当bgsave错误时,前台是否需要停止写入操作
stop-writes-on-bgsave-error yes

配置快照文件的名字和路径
dbfilename dump.rdb
dir ./

11.3 AOF的相关配置

AOF的相关配置

配置是否开启AOF
appendonly no - 默认关闭

配置AOF的文件名称
appendfilename “appendonly.aof”

配置AOF的记录频率
appendfsync always - 每执行一个写命令,就记录一次
appendfsync everysec - 默认行为,每秒记录当前这一秒钟所有的写命令
appendfsync no - 根据操作系统的配置决定什么时候快照

配置AOF文件的重写的条件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

AOF文件的优化策略:

Bgrewriteaof命令 - 手动触发后台对aof文件的重写,重写后会得到一个体积优化版
redis会自动的触发AOF文件重写

11.4 快照和AOF的选择策略

1、找时机开发过程中,AOF往往比快照更安全,AOF最多只会丢失1S以内的数据
2、redis服务启动后,先根据AOF文件恢复数据,再恢复快照的数据
3、当数据量很大时,快照恢复的速度要快与AOF
4、如果希望数据相对更安全,官方推荐同时开启快照和AOF,但是仍然不能保证数据的绝对安全
5、如果Redis仅仅只是作为分布式缓存服务器使用,快照和AOF都可以关闭

十二、Redis作为缓存服务器的使用

12.1 自定义缓存策略

@Override
public  List<Student> queryAll() {
    //获取缓存中的数据
    List<Student> stulist = (List<Student>) redisTemplate.opsForValue().get("stulist");
    if(stulist == null) {
        //如果缓存中没有,再查询数据
        System.out.println("查询了数据库?");
        stulist = stuDao.selectList(null);

        //缓存重建
        redisTemplate.opsForValue().set("stulist", stulist, 10, TimeUnit.MINUTES);
    }

    return stulist;
}


@Override
@Transactional
public Student insert(Student student) {
    stuDao.insert(student);

    //删除必要的缓存
    redisTemplate.delete("stulist");
    //新建学生缓存
    redisTemplate.opsForValue().set("stuone_" + student.getId(), student, 10, TimeUnit.MINUTES);

    return student;
}

@Override
public Student queryOne(Integer id) {

    Student student = (Student) redisTemplate.opsForValue().get("stuone_" + id);
    if(student == null){
        System.out.println("查询数据了!!!!");
        student = stuDao.selectById(id);

        redisTemplate.opsForValue().set("stuone_" + id, student, 10, TimeUnit.MINUTES);
    }

    return student;
}

@Override
public int deleteById(Integer id) {
    int result = stuDao.deleteById(id);

    //删除必要的缓存
    redisTemplate.delete("stulist");
    redisTemplate.delete("stuone_" + id);

    return result;
}

/**
 * 修改数据时,数据库和缓存服务器一致性问题
 * 1、修改数据库,修改缓存 - 相对比较大的概率引起数据不一致
 * 在A线程 - 修改数据库学生1,即将修改缓存的时候这里被打断进入B线程,修改数据库,修改缓存;这个时候的a线程在去修改数据库已经没有意义了,数据库已经被修改过了。
 * 2、修改缓存,修改数据库 - 相对比较大的概率引起数据不一致
 * 在A线程 - 修改缓存学生1,即将修改数据库的时候这里被打断进入B线程,修改缓存这个时候就会把a线程修改的缓存给覆盖掉再去修改数据库;(所以以后操作redis不要修改自己删除就没有覆盖的问题了)
 * 3、删除缓存,修改数据库 - 相对比较大的概率引起数据不一致
 * 在A线程 - 删除缓存学生1,即将修改数据库的时候这里被打断进入B线程,删除缓存再去修改数据库,这个是没问题的,怕就怕在B线程是去查询缓存的,这个时候缓存被删了去查询数据库然后在重建缓存,这个时候到了a线程去修改数据库这个时候数据又不一致了
 * 4、修改数据库,删除缓存 - 相对来说出问题的概率较小
 * B线程,查询学生1,先查缓存,缓存失效了,缓存没有再查数据库这个时候(A线程这里被打断进入 - 修改数据库学生删除缓存),然后b线程重建缓存(这里重建的数据就是a线程的数据),
 * 
所以这个时候就可以看出一般都是删缓存放在数据的后面,出问题概率比较小所以得用缓存注解
 * 延时双删:修改数据 -> 删除缓存 -> 延迟2s -> 删除缓存
 *
 *
 * @param id
 * @return
 */
@Override
public int updateById(Integer id) {

    Student student = this.queryOne(id);
    student.setName(student.getName() + 1);
    int result = stuDao.updateById(student);

    //删除必要的缓存
    redisTemplate.delete("stulist");
    redisTemplate.delete("stuone_" + id);
    
    return result;
}

12.2 Spring提供的缓存操作注解

1)添加依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2)启动类配置注解

@EnableCaching

3)配置application.yml

spring:
  redis:
    host: 192.168.195.188
  #配置缓存的过期时间       
  cache:
    redis:
      time-to-live: 600000

4)使用注解完全缓存的操作

@Cacheable 
	- 通常用于查询业务,先去缓存服务器查询,如果有结果直接返回,如果没有,再调用目标方法执行业务,并且将目标方法的返回值重建到缓存中
	
@CachePut
	- 标记了该注解的方法,一定会被执行,将方法的返回值重建到缓存中,多用于添加的业务(就是添加的到缓存中,查询就直接在缓存中查)

@CacheEvict
 	- 表示删除某个缓存,多用于新增、修改、删除的业务

@Caching
	- 以上3个注解的数组集合体
	
@CacheConfig
	- 进行缓存的统一配置,标记在类上

12.3 分布式缓存的常见问题

1)缓存击穿

客户端恶意的访问一个不存在的数据,从而穿透缓存,请求到达数据库,频繁的发送这类请求,直接查询数据库,增加数据库的压力。

解决:

1、在缓存服务中缓存null值
2、用户黑名单(ip拉黑)-》但是如果黑客用木马植入很多电脑去访问这个方法也是没办法的
3、使用布隆过滤器(判断,一个数据可能存在或者一定不存在)他其实是一张大的哈希表,他的能够判断一个数据可能存在或者一定不存在,在大量数据面前,我只要设置好布隆过滤器之后,访问数据布隆过滤器非常快的判断这个数据,一定不存在就一定不存在,可能存在就不一定了,可能存在的误差就很低他还可以帮我们实现点击率

2)缓存失效

某个缓存失效之后,大量的请求访问该数据,在第一个请求重建缓存回来之前,大量的请求发现缓存失效,都会去尝试重建缓存,从而导致数据库压力增加。

解决:

1、在重建缓存时,添加分布式锁

3)缓存雪崩

大量的缓存,在同一时刻一起失效。

解决:

1、新建缓存时,采用固定 + 随机数的方式设置过期时间;错开他们的过期时间

4)缓存预热

在项目运行之前,分析出热点数据,直接添加到缓存服务器中(统计结果或者大数据分析)

十三、Redis集群结构

13.1 主从集群

主从复制:
1、简单来说,是使用两个或两个以上相同的数据库,将一个数据库当做主数据库,而另一个数据库当做从数据库。在主数据库中进行相应操作时,从数据库记录下所有主数据库的操作,使其二者一模一样。
2、当主数据库出现问题时,可以当从数据库代替主数据库,可以避免数据的丢失。
3、可以进行读写分离

读写分离:
1、是一种让数据库更稳定的的使用数据库的方法。是在有从数据库的情况下使用,当主数据库进行对数据的增删改也就是写操作时,将查询的任务交给从数据库。
2、减轻主数据库的压力。因为进行写操作更耗时,所以如果不进行读写分离的话,写操作将会影响到读操作的效率

哨兵:主从故障切换、请求读写分离

使用docker搭建Redis主从复制集群(6个容器、3个redis、3个哨兵)
在这里插入图片描述

1)准备配置文件和文件夹的路径

2)编写docker-compose.yml

version: "3.1"
services:
  master:
    image: redis:5
    container_name: master
    restart: always
    network_mode: host
    volumes:
      - ./redis_master/conf/redis.conf:/etc/redis/redis.conf
      - ./redis_master/data:/data
    command:
      ['redis-server', '/etc/redis/redis.conf']
  slave1:
    image: redis:5
    container_name: slave1
    restart: always
    network_mode: host
    volumes:
      - ./redis_slave1/conf/redis.conf:/etc/redis/redis.conf
      - ./redis_slave1/data:/data
    command:
      ['redis-server', '/etc/redis/redis.conf']
  slave2:
    image: redis:5
    container_name: slave2
    restart: always
    network_mode: host
    volumes:
      - ./redis_slave2/conf/redis.conf:/etc/redis/redis.conf
      - ./redis_slave2/data:/data
    command:
      ['redis-server', '/etc/redis/redis.conf']

3)配置slave从机的redis.conf

replicaof 192.168.195.188 6379

主从复制搭建完成

4)搭建哨兵模式(docker-compose.yml)

sentinel1:
    image: redis:5
    container_name: sentinel1
    restart: always
    network_mode: host
    volumes:
      - ./redis_sentinel1/conf/sentinel.conf:/etc/redis/sentinel.conf
      - ./redis_sentinel1/data:/data
    command:
      ['redis-sentinel', '/etc/redis/sentinel.conf']
sentinel2:
    image: redis:5
    container_name: sentinel2
    restart: always
    network_mode: host
    volumes:
      - ./redis_sentinel2/conf/sentinel.conf:/etc/redis/sentinel.conf
      - ./redis_sentinel2/data:/data
    command:
      ['redis-sentinel', '/etc/redis/sentinel.conf']
sentinel3:
    image: redis:5
    container_name: sentinel3
    restart: always
    network_mode: host
    volumes:
      - ./redis_sentinel3/conf/sentinel.conf:/etc/redis/sentinel.conf
      - ./redis_sentinel3/data:/data
    command:
      ['redis-sentinel', '/etc/redis/sentinel.conf']

5)配置哨兵的sentinel.conf

sentinel monitor mymaster 192.168.195.188 6379 2
sentinel down-after-milliseconds mymaster 10000 #多久未收到master的心跳,认为master下线

6)SpringBoot连接哨兵,进行读写分离

Spring:
  redis:
    sentinel:
      master: mymaster
      nodes:
        - 192.168.195.188:26379
        - 192.168.195.188:26380
        - 192.168.195.188:26381

13.2 分片集群

在这里插入图片描述

最后

以上就是傻傻冰棍为你收集整理的3. redis (数据缓存中间件)的全部内容,希望文章能够帮你解决3. redis (数据缓存中间件)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部