Redis学习笔记

redis启动服务的细节

直接使用./redis-server方式启动,使用的是redis-server这个shell脚本中默认配置。如果想要改配置文件,需要在源码目录中复制redis-conf配置文件到安装目录,启动时:./redis-server …/redis-conf

redis中库的概念

database用来存放数据的基本单元,可以放key-value键值对。每一个库都有一个唯一编号,从0开始,默认库的个数为16,库与库之间是隔离的,默认使用0号库,可以在配置文件中配置。使用select index进行切换,比如select 1

如果redis客户端显示中文,需要启动时加 redis-cli --raw

redis中清除库的指令
  • flushDB 清空当前库
  • flushall 清空所有库

一、常见redis命令

keys pattern 查找所有符合给定模式的key,比如*匹配任意多个,?匹配1个,[]匹配[]中的任意一个

del key [key…] 删除给定的一个或多个key,不存在的key会被忽略,返回被删除key的数量

exists key [key…] 检查给定key是否存在,存在返回1,否则返回0,多个时只要有一个存在就返回1

expire key seconds 设置过期时间,默认永久存储,时间为秒

move key db 把key移动到指定的库

pexpire key milliseconds和expire类似,只是单位为毫秒

ttl 剩余过期时间,时间返回为秒,-1表示永久存储,-2表示key不存在,其余表示过期时间

pttl 和ttl类似,返回毫秒

randomkey 随机返回一个key,如果数据库为空,返回nil

rename key newkey 将key改名为newkey,当key和newkey相同或者key不存在时,返回一个错误,如果newkey存在,覆盖值

type key 返回key所存储值的类型,none(key不存在),string,list,set,zset,hash

二、Redis数据类型

1. string类型

set 设置一个值

get 获取一个值

mset 一次设置多个值 mset name zhangsan age 18 bir 2021-12-12

mget 一次获取多个值 mget name age bir

getset 获取原始key的值,同时设置新值

strlen 获得对应key存储value的长度

append 为对应key的value追加内容

getrange 索引0开始,截取value的内容,不会修改原值。比如 getrange name 2 -1 返回2到末尾的值

setex 设置值的时候就设置一个超时时间(秒),比如setex name 10 zhangsan

psetex 设置值的时候就设置一个超时时间(毫秒)

setnx 存在不做任何操作,不存在添加。成功返回1,否则返回0

msetnx 可以同时设置多个key,原子操作(只要有一个key存在,就不做任何操作)

decr 自减1

decrby 自减给定的值

incr 自增

incrby 自增给定的值

incrbyfloat 增加浮点数,比如 incrbyfloat age 1.3333333344444444444444

2. list

list是有序,可重复的,可以从左边添加元素,也可以从右边添加元素

lpush key [value…] 将值加入到一个key的列表头部,比如:lpush lists a b c d e

lpushx key value 同lpush,但必须保证key存在,比如: lpushx lists f

rpush key [value…] 将值加入到一个key的列表尾部,比如:rpush list g h i j k

rpushx key value 同rpush,但必须保证key存在,比如:rpushx list l

lpop 返回和移除列表左边的第一个元素,比如: lpop lists 返回 “f”

rpop 返回和移除列表右边的第一个元素,比如: rpop lists 返回"a"

lrange 获取某一个下标区间内的元素,比如:lrange list 0 -1 表示遍历所有

llen 获取列表元素的个数,比如: llen lists

lset 设置某一个指定索引的值(索引必须存在),比如: lset list 0 gg

lindex 获取某一个指定索引位置的元素,比如: lindex list 0

lrem 删除重复元素,比如lrem list 2 gg 表示删除2个gg

ltrim 保留列表中特定区间内的元素,会操作原数组,比如 ltrim list 0 2

linsert在某一个元素之前,之后插入新元素,重复时从左到右匹配,比如 linsert list before i h

3. Set类型

Set类型也是集合,元素无需,不可以重复

sadd: 为集合添加元素, 比如: sadd sets aa bb cc dd ee

smembers:显示集合中的所有元素,比如: smembers sets

scard:返回集合中元素的个数,比如:scard sets

spop:随机返回一个元素,并将元素在集合中删除,比如:spop sets

smove:从一个集合中向另一个集合移动元素,比如: smove set1 sets xx

srem:从集合中删除一个或多个元素, 比如:srem set1 zz cc

sismember:判断一个集合中是否含有这个元素,含有返回1,否则为0,比如 sismember setx aa

srandmember:随机返回元素,不会删除,比如:srandmember sets 2

sdiff:去掉第一个集合中其他集合含有的相同元素,比如:sdiff sets1 sets2

sinter:求交集

sunion:求并集

4. ZSet类型

特点:可排序的set集合,可以排序,不可重复。每个元素都带了一个分数,排序是靠分数实现的,又称可排序Set或者SortSet

zadd 添加一个有序集合元素,比如 zadd zset1 10 zhangsan 9 xiaochen 8 xiaoming

zcard 返回集合的元素个数,比如 zcard zset1

zrange 升序 返回一个范围内的元素,比如 zrange zset1 0 -1 withscores 会展示分数

zrangebyscore 按照分数查找一个范围内的元素,包含边界,比如 zrangebyscore zset1 9 10 limit 0 1 进行分页

zrevrange 降序 返回一个范围内的元素

zrank 返回排名,比如 zrank zset1 zhangsan

zrevrank 倒序排名,比如 zrevrank zset1 zhangsan

zscore 显示一个元素的分数,比如 zscore zset1 zhangsan

zrem 移除某一个或多个元素,比如 zrem zset1 zhangsan xiaochen

zincrby 给某个元素加分,比如 zincrby zset1 90 xiaoming

5. hash类型

特点:value是一个map结构,存在key-value,key无序


hset 设置一个key/value对,比如 hset hash1 name zhangsan

hget 获得一个key对应的value,比如 hgge hash1 name

hgetall 获得所有的key/value对,比如 hgetall hash1

hdel 删除某一个key/value 对,比如 hdel hash1 name height

hexists 判断一个key是否存在,比如 hexists hash1 name

hkeys 获得所有的key,比如 hkeys hash1

hvals 获得所有的value,比如 hvals hash1

hmset 设置多个key/value,比如 hmset hash1 bir 2020-12-12 address beijing clazz 2011

hmget 获得多个key的value,比如 hmget hash1 name age bir

hsetnx 设置一个不存在的key的值,比如 hsetnx hash1 jiguan chq

hincrby为value进行加法运算,比如 hincrby hash1 age 100

hincrbyfloat 为value加入浮点值,比如 hincrbyfloat hash1 age 1.455555555555555555

三、redis可视化工具-redis-desktop-manager

需要开启redis远程连接,默认redis服务器是没有开启远程连接的,需要修改如下配置:bind 0.0.0.0 表示允许一切客户端连接,如果还是无法连接,说明防火墙未关闭

四、持久化机制

Redis官方提供了两种不同的持久化方法来将数据存储到硬盘里面,分别是:快照和AOF

1. 快照

这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是redis的默认开启持久化方式,保存的文件是以.rdb形式结尾的文件,因此这种方式也称为RDB方式。生成快照的方式有2种,客户端发送命令生成和服务端自动触发生成。

客户端方式生成快照:BGSAVE和SAVE指令

客户端可以使用BGSAVE命令来创建一个快照,当接收到客户端BGSAVE命令时,redis会调用fork来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求。

客户端还可以使用SAVE命令来创建一个快照,接收到SAVE命令的redis服务器在快照创建完毕之前将不再响应任何其他的命令。

服务器配置自动触发生成快照

如果用户在redis.conf中配置了save,redis会在save选项条件满足之后自动触发一次BGSAVE命令,如果设置了多个save配置选项,当任意一个save配置选项条件满足,redis也会触发一次BGSAVE命令

服务器接收客户端shutdown指令,关闭服务器请求时,会执行一个save命令,执行完后才会关闭服务器。

配置快照生成的位置

快照可能在保存某次快照后,还未触发下一次快照,然后突然宕机,会出现数据丢失的问题。

2. AOF(append only file)

这种方式是将所有客户端执行的写命令写到AOP文件末尾,以此来记录数据发生的变化,因此只需要redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集。以下配置中的appendonly即为设置是否开启AOF持久化,默认为 no(关闭状态)。

日志同步频率

always 【谨慎使用】每个redis写命令都要同步写入硬盘,严重降低redis速度

everysec 【推荐】【默认】每秒执行一次,同步显示的将多个写命令同步到磁盘,即使系统崩溃,也只丢失1s产生的数据

no 【不推荐】由操作系统决定何时同步,当系统发生崩溃时,可能丢失不定数量的数据,另外如果写的频率太低,一次性会写入大量数据,会导致redis阻塞

如果两种持久化都开启,则以AOF为主

AOF文件的重写

AOF的方式也同时带来了另一个问题,持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其中有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。

为了压缩aof持久化文件,redis提供了AOF重写机制。

触发重写方式

1.客户端方式触发重写 - 执行BGREWRITEAOF命令,不会阻塞redis的服务

2.服务器配置方式自动触发 - 配置redis.conf中的auto-aof-rewrite-percentage选项

如果设置auto-aof-rewrite-percentage值为100和auto-aof-rewrite-min-size 64mb,并且启用aof持久化时,那么当aof文件体积大于64m,并且aof文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-percentage设置为更大。

举个例子:第一次aof达到64m会重写,加入重写后变为20m,当aof达到40m时就会触发第二次重写,依次类推。。。。

重写原理

重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件。

两种方案既可以同时使用,也可以单独使用,甚至可以都不用,具体取决于用户的数据和应用决定;
无论哪种,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应对持久化的文件进行备份(最好备份到多个不同的地方);

五、redis主从复制

主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据,不能在主节点宕机时,顶上来提供服务,所以不常用。

配置方式

1、修改端口
2、修改# bind 127.0.0.1为 bind 0.0.0.0
3、在从节点中加入:replicaof ip 端口

六、哨兵机制

Sentinel(哨兵)是Redis的高可用解决方案:

由一个或多个sentinel实例组成的sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。

简单来说哨兵就是带有自动故障转移功能的主从架构。但是哨兵机制无法解决单节点并发压力问题以及单节点内存和磁盘物理上限问题。

配置方式

1.在主节点上创建哨兵配置(在Master对应redis.conf同目录下新建sentinel.conf文件,名字绝对不能错;)
2.配置哨兵,在sentinel.conf文件中填入内容:

sentinel monitor 被监控数据库名字(自己起名) ip port 哨兵数量
bind:0.0.0.0 #开启远程访问模式

3.启动哨兵模式进行测试
redis-sentinel sentinel.conf

使用哨兵机制后Spring-Boot如何处理

使用哨兵后,master可能会换,如果之前的slave节点就可以进行set操作了,配置改为如下:

#哨兵名称
spring.redis.sentinel.master=mymaster
#哨兵节点,多个用逗号分隔
spring.redis.sentinel.nodes=127.0.0.1:26379,127.0.0.1:26380

七、集群

Redis在3.0后开始支持Cluster(集群)模式,目前redis的集群支持节点的自动发现,支持slave-master选举和容错,支持在线分片(sharding shard)等特性。

所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽,节点的fail是通过集群中超过半数的节点检测失效时才生效。客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责维护node<->slot<->value

判断一个集群中的节点是否可用,是集群中的所有主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉,所以搭建redis集群时建议节点数最好为基数,至少需要三个主节点,三个从节点,所以一个集群最少需要6个节点。

八、redis 实现分布式session

redis的session管理利用spring提供的session管理解决方案,将一个应用session交给Redis存储,整个应用中所有session的请求都会去redis中获取对应的session数据。

Spring Boot中使用也很简单,只需引入spring-session-data-redis依赖,然后启动类上加上@EnableRedisHttpSession即可。

九、使用Redis实现分布式锁

假设有一个秒杀程序,库存为50,代码如下:

    @GetMapping("/lock")
    public String Redis() {
        String retVal;
        synchronized (this) {
            int stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int remainStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(remainStock));
                retVal = "剩余库存:" + remainStock;
            } else {
                retVal = "库存不足";
            }
            log.info(retVal);
        }
        return retVal;
    }

单机下,上面代码没有任何问题,但是在集群下,使用synchronized 就不好使了,启动2台机器,分别是8001,8002,压测情况如下:

可以看到,出现了重复消费的情况,接下来使用分布式锁来解决上面的问题。

redis有一个setnx操作,如果key存在,就不进行操作,否则就操作,使用setnx后代码如下:

    @GetMapping("/lock1")
    public String RedisTest1() {
        String retVal;
        String lockKey="lockKey";
        //加锁
        Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "");
        if(!isLock){
            retVal="服务器繁忙";
        }
        int stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            int remainStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", String.valueOf(remainStock));
            retVal = "剩余库存:" + remainStock;
        } else {
            retVal = "库存不足";
        }
        log.info(retVal);
        //解锁
        stringRedisTemplate.delete(lockKey);
        return retVal;
    }

上面的代码表面上看似实现了加锁,实际上有很多问题,假设有A,B两个请求同时到达,由于redis是执行命令时是单线程,所以只会有一个请求拿到锁,假设A拿到,存在的问题有:

1、如果A线程在执行的过程中发生了异常,锁就不会释放;针对这个问题使用try{}finally{};
2、如果A线程还未释放锁,但所在的机器突然宕机了,锁也不会释放;针对这个问题设置过期时间;

为了解决以上问题,改进后的代码如下:

 @GetMapping("/lock2")
    public String RedisTest2() {
        String retVal;
        String lockKey="lockKey";
        //加锁
        Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "");
        stringRedisTemplate.expire(lockKey, 10,TimeUnit.SECONDS);
        if(!isLock){
            retVal="服务器繁忙";
        }
        try{
            int stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int remainStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(remainStock));
                retVal = "剩余库存:" + remainStock;
            } else {
                retVal = "库存不足";
            }
            log.info(retVal);
        }finally {
            //解锁
            stringRedisTemplate.delete(lockKey);
        }
        return retVal;
    }

仔细分析上面的程序,还是会有一系列问题:

1.由于设置过期时间不是原子性的,如果刚拿到锁,还未来得及设置过期时间,机器宕掉了,锁不会释放;
2.加锁A线程先拿到锁,还未执行完成,时间到期,然后B线程也拿到了锁,过一段时间后,A执行结束,释放锁,但B还未结束,此时其他请求也可以拿到锁了;

针对第一个问题,可以使用set命令,可以同时到达setnx和设置过期时间的效果,由于只有jedis才有相应的api,RedisTemplate未提供相应的功能,所以需要自己拿到jedis实例,然后调用set方法;

当然也可以使用lua脚本来实现原子性操作。

针对第二个问题,可以加锁后设置一个标识,只有锁是自己的,才释放;

改进代码如下:

  @GetMapping("/lock2")
    public String RedisTest2() {
        String retVal;
        String lockKey="lockKey";
        String clientId= UUID.randomUUID().toString();
        //加锁
       Boolean isLock = stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(lockKey, clientId, "NX", "EX", 10);
            if ("OK".equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
        if(!isLock){
            retVal="服务器繁忙";
        }
        try{
            int stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int remainStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(remainStock));
                retVal = "剩余库存:" + remainStock;
            } else {
                retVal = "库存不足";
            }
            log.info(retVal);
        }finally {
            //解锁
            if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete("stock");
            }
        }
        return retVal;
    }

实际上以上代码仍然有问题,体现如下:

1.解锁不是原子性的,仍会刚判断了是自己的锁,还未来得及释放就宕机了;针对这个问题要实现原子操作,需要写脚本解决;
2.过期时间到底设置多少合适,如果设置短了,可能程序还未执行完,锁就释放了,如果设置长了,万一机器宕掉了,其他机器就会等待很长的时间才能获取锁;针对这个问题,可以拿到锁后,开启一个线程定时检测是否程序持有锁,未完成就把过期时间延迟(重新设置),具体实现自己动手比较麻烦,后面会使用redisson框架来解决该问题。

改进后的代码如下:

   @GetMapping("/lock2")
    public String RedisTest2() {
        String retVal;
        String lockKey="lockKey";
        String clientId= UUID.randomUUID().toString();
        //加锁
       Boolean isLock = stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(lockKey, clientId, "NX", "EX", 10);
            if ("OK".equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
        if(!isLock){
            retVal="服务器繁忙";
            return retVal;
        }
        try{
            int stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int remainStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(remainStock));
                retVal = "剩余库存:" + remainStock;
            } else {
                retVal = "库存不足";
            }
            log.info(retVal);
        }finally {
            //解锁
            String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
                Jedis jedis = (Jedis) redisConnection.getNativeConnection();
                Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                        Collections.singletonList(clientId));
                Long RELEASE_SUCCESS = 1L;
                if (RELEASE_SUCCESS.equals(result)) {
                    return Boolean.TRUE;
                }
                return Boolean.FALSE;
            });
        }
        return retVal;
    }

最终把代码整理后如下:

  @GetMapping("/lock")
    public String RedisTest() {
        String retVal;
        String lockKey = "lockKey";
        String stockKey = "stock";
        String clientId = UUID.randomUUID().toString();
        //加锁
        Boolean isLock = lockService.tryLock(lockKey, clientId, 10);
        if (!isLock) {
            retVal = "服务器繁忙";
            return retVal;
        }
        try {
            BoundValueOperations<String, String> valueOps = stringRedisTemplate.boundValueOps(stockKey);
            Integer stock = Integer.valueOf(valueOps.get());
            if (stock > 0) {
                int remainStock = stock - 1;
                valueOps.set(String.valueOf(remainStock));
                retVal = "剩余库存:" + remainStock;
            } else {
                retVal = "库存不足";
            }
            log.info(retVal);
        } finally {
            //解锁
            lockService.releaseLock(lockKey,clientId);
        }
        return retVal;
    }
package com.yyb.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.scripting.support.StaticScriptSource;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 */
@Component
public class NewLockService {
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     *
     * @param lockKey  加锁键
     * @param clientId 加锁客户端唯一标识(采用UUID)
     * @param seconds  锁过期时间
     * @return
     */
    public Boolean tryLock(String lockKey, String clientId, long seconds) {
        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            return redisConnection.set(lockKey.getBytes(), clientId.getBytes(), Expiration.seconds(seconds), RedisStringCommands.SetOption.ifAbsent());
        });
    }

    /**
     * 释放锁操作
     * @param key
     * @param value
     * @return
     */
    public boolean releaseLock(String key, String value) {
        DefaultRedisScript<Boolean> lockScript = new DefaultRedisScript<>();
        lockScript.setScriptSource(
                new StaticScriptSource(RELEASE_LOCK_SCRIPT));
        lockScript.setResultType(Boolean.class);
        Boolean result = redisTemplate.execute(lockScript,Collections.singletonList(key),value);
        return result;
    }
}

上述代码实现,仅对 redis 单实例架构有效,当面对 redis 哨兵模式或集群时就无效了。原因是当在主机宕机,从机被升级为主机的一瞬间的时候,如果恰好在这一刻,由于 redis 主从复制的异步性,导致从机中数据没有即时同步,那么上述代码就会无效,导致同一资源有可能会产生两把锁,违背了分布式锁的原则。

使用Redisson解决分布式问题

Redisson实现分布式锁

  @GetMapping("/lock")
    public String RedisTest() {
        String retVal = "";
        String stockKey = "stock";
        //加锁
        RLock lock = redissonClient.getLock("myLock");
        lock.lock();
        try {
            BoundValueOperations<String, String> valueOps = stringRedisTemplate.boundValueOps(stockKey);
            Integer stock = Integer.valueOf(valueOps.get());
            Thread.sleep(3000);
            if (stock > 0) {
                int remainStock = stock - 1;
                valueOps.set(String.valueOf(remainStock));
                retVal = "剩余库存:" + remainStock;
            } else {
                retVal = "库存不足";
            }
            log.info(retVal);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //解锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return retVal;
    }


@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

十一、缓存问题

缓存穿透

客户端查询了数据库中没有的数据,导致这种情况下缓存无法利用,直接穿过redis到数据库了

解决方案:将数据库没有查询到结果也进行缓存

缓存击穿

大量请求访问热点数据,然后这个key突然失效,这些请求就会涌向数据库导致极端情况,数据库阻塞或挂起

解决方案:
1、让缓存永不过期
2、使用分布式锁,在访问数据时加锁,然后存入redis,推荐的方式

缓存雪崩

在系统运行的某一时刻,缓存全部失效,恰好这一时刻涌来了大量请求,导致缓存无法使用,请求涌向数据库导致极端情况,数据库阻塞或挂起

解决方案:
1、缓存永久存储【不推荐】
2、针对不同业务数据设置不同的超时时间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值