Redis面试题

Redis为什么快?

1,纯内存访问
2,单线程避免上下文切换
3,渐进式rehash(扩容时,只copy访问的key对应的索引位元素)

两个全局hash表用来存储KV,当hash表1没有查到元素时,再找hash表2
把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。
比如全局hash表1容量为6,索引为1的地方存放了三个元素ABC,
在哈希表的负载因子超过阈值时触发扩容操作,
初始化全局hash表2,当访问索引为1处元素时(get或者set),只将这三个元素copy到新的容量为12的全局hash表2.
直到全局hash表1全部copy到全局hash表2之后,将hash表2设置成hash表1,hash表1设置为null,等待下一次扩容。

4,缓存时间戳
定时任务将时间戳缓存起来,当redis需要设置key的过期时间时,直接从缓存中获取

IO多路复用

同步阻塞IO
单线程中,某个socket1阻塞,会影响其他已就绪socket处理,需要等socket1就绪,处理完后,才能处理别的socket。
多线程时,当客户端较多时,可能某一时刻只有几个socket准备就绪,造成资源浪费,上下文切换,线程调度,多个线程占用的内存都会造成瓶颈。
同步非阻塞IO
某个socket没有准备就绪时,切换至下一个,直到遇到准备就绪socket,进行处理。会不断轮询,哪怕同一时段没有一个准备就绪的socket。
单个socket阻塞,不会影响到其他socket,需要不断的轮询调用,有一定开销
系统会一直调用read()函数,去逐个检查fd是否准备好,会涉及到用户态到内核态的切换。
IO多路复用
IO多路复用指的是线程的复用,一个线程完成多个tcp网络连接
select和poll:都会把所有的io连接搜集到服务端,一个个轮询,有些连接只是建立了连接并没有发数据,会进行空轮询,select只能支持1024个连接,poll可以支持无限个连接。
select()函数在参数fd_set集合,入参时fd_set集合假如是[00001010]检查fd是2或者4是否就绪,检查后只有fd=2时准备就绪,就会将fd_set集合改为[00000010]。再遍历fd_set集合从而将准备就绪的fd进行读写操作。
select
创建fd_set文件描述符集合,每个文件描述符是一个无符号整数,假设监听fd=1,2,5,会将fd_set中相应bit位设置成1,执行select(5+1,fd_set,null,null,3)函数,5+1指要监听的最大fd+1,3指超时时间。select()函数会返回就绪的fd个数,并将fd_set集合中未就绪的bit位设置成0,然后线程就可以通过遍历fd_set,开始读取其中数据。
fd_set集合会从用户空间转移到内核空间,进行相应bit位设置。
优点:
不需要每个fd都进行一次系统调用,解决了频繁的用户态内核态切换问题
缺点:
单进程监听fd最大1024,有限制
每次调用需要将fd从用户态拷贝到内核态
不知道具体哪个文件描述符fd就绪,需要遍历全部文件描述符。
入参的3个fd_set集合每次调用都需要重置。
poll
poll()函数改进了fd_set,使用链表实现,fd_set集合可以存储无限个fd,但是还是返回就绪的fd个数,将fd_set集合中就绪的fd的属性设置成已就绪。然后线程就可以通过遍历fd_set,开始读取其中数据。
epoll
1,epoll_create()去创建一个事件搜集器selector
2,epoll_ctl()添加要监听的fd,到事件搜集器selector(ctl=control)
3,epoll_wait()监听有没有事件过来(也就是fd是否准备就绪),没有就处于阻塞状态,有就非阻塞。

Redis 是什么?都有哪些使用场景?

Redis 是一个使用 C 语言开发的高速缓存数据库。
Redis 使用场景:
记录帖子点赞数、点击数、评论数;
缓存近期热帖;
缓存文章详情信息;
记录用户会话信息。

Redis 有哪些功能?

数据缓存功能
分布式锁的功能
支持数据持久化
支持事务
支持消息队列

Redis 为什么是单线程的?

因为cpu不是Redis的瓶颈,Redis的瓶颈最有可能是内存或者网络带宽。
redis的单线程指的是网络IO和键值对读写是由一个线程完成的。
redis6.0以后引入多线程,是指网络IO采用了多线程,而内部命令依然是单线程完成的。而持久化、集群数据同步其实也是由其他线程完成的。

Redis 怎么实现分布式锁?

Redisson实现分布式锁和红锁
Redisson实现分布式锁和红锁

多个客户端去操作账户减钱,结算账户加钱操作时,需要额外在redis、zookeeper上获取锁,实现减钱加钱操作原子性

Redis分布式锁其实就是在系统里面占一个“坑”,其他程序也要占“坑”的时候,占用成功了就可以继续执行,失败了就只能放弃或稍后重试。

占坑一般使用 setnx(set if not exists)指令,只允许被一个程序占有,使用完调用 del 释放锁。

设置锁时设置过期时间,使用uuid设置锁值。
解锁时使用lua脚本保证判断加锁和加锁是同一客户端也就是判断uuid和释放锁delete操作的原子性

/**
 为了确保分布式锁可用,我们至少要保证锁满足四个条件
 互斥性。在任意时刻,只有一个客户端能持有锁
 不会发生死锁。及时有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
 加锁和解锁必须具有原子性。
 */

@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("testLock")
    public void testLock() throws InterruptedException {
        String uuid = UUID.randomUUID().toString();
        //获取锁,setnx
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        //获取锁成功、查询num的值
        if (lock){
            Object num = redisTemplate.opsForValue().get("num");
            //判断num为空return
            if(StringUtils.isEmpty(num)){
                return;
            }
            //有值就转成int
            int parseInt = Integer.parseInt(num + "");
            //把redis的mum加1
            redisTemplate.opsForValue().set("num",++parseInt);
            //释放锁,del
            /**
             * 使用lua脚本删除锁释放锁,保证原子性
             */
            //判断比较uuid值是否一样
            String lockUUID = (String)redisTemplate.opsForValue().get("lock");
            if (uuid.equals(lockUUID)){
                redisTemplate.delete("lock");
            }
        }else {
            //获取锁失败,每隔0.1秒再获取
            Thread.sleep(100);
            testLock();
        }
    }
}

zookeeper实现分布式锁:

abcd4个客户端分别创建临时有序节点,创建节点编号最小的客户端获取锁,b客户端监听a是否删除了临时节点,c客户端监听b是否删除了临时节点
释放锁,a客户端释放锁,也就是有序节点01,b客户端监听到a客户端删除了临时节点,
b客户端判断自己生成的临时有序节点是不是最小的,如果是最小的就获取到锁,如果不是最小的就继续等待,对客户端c,d没有影响

怎么保证缓存和数据库数据的一致性?

  • 延时双删,先删除redis缓存,更新数据库,延时几百毫秒再删除redis缓存。
  • 队列+重试机制,先更新数据库,再删除redis缓存,如果失败,给mq发消息,mq消费端再次删除redis缓存,失败就重试直到删除成功。
  • 订阅binlog日志,提取出更新操作,先删除redis缓存,如果失败,给mq发消息,mq消费端再次删除redis缓存,失败就重试直到删除成功。canal框架可以对mysql的binlog进行订阅。

缓存数据库双写不一致?

比如数据库库存总量是10,
线程1:查数据库,设置redis为10
线程2:修改数据库-1,设置redis为9
但是线程1在查询数据库之后发生意外,线程2操作完成了,然后线程1将redis缓存改成了10
更新数据库时加写锁,读取数据库时加读锁,当发生写操作时线程串行,别的线程等待,写操作完成后再运行。当多个读操作时,读锁会线程并行。

//热点数据创建
private static final String LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX = "Lock:product:hot_cache_create:";
//双写一致性
private static final String LOCK_PRODUCT_UPDATE_PREFIX = "Lock:product:update:";

public Product get(Long productId){
    String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE+ productId;
    Product product = getProductFromCache(productCacheKey);
    if(product != null){
        return product;
    }
    //热点数据查询加锁
    RLock hotCatchCreateLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX+productId);
    hotCatchCreateLock.lock();
    try {
        product = getProductFromCache(productCacheKey);
        if(product!= null){
            return product;
        }

        //双写一致性加锁,当读取时加读锁
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
        RLock readLock = readWriteLock.readLock();
        try {
            Product product1 = iProductMapper.selectById(productId);
            if(product1!=null){
                //重新设置redis缓存
                redisUtils.set(productCacheKey, product1, AuthConstant.EXPIRATION_TIME_IN_SECOND, TimeUnit.SECONDS);
            }else {
                redisUtils.set(productCacheKey, RedisKeyPrefixConst.PRODUCT_EMPTY_CACHE, AuthConstant.EXPIRATION_TIME_IN_SECOND, TimeUnit.SECONDS);
            }
        } finally {
            readLock.unlock();
        }
    } finally {
        hotCatchCreateLock.unlock();
    }
    return product;

}

private Product getProductFromCache(String productCacheKey) {
    Product product = null;
    Object o = redisUtils.get(productCacheKey);
    if (o!= null){
        if(StringUtils.equals(RedisKeyPrefixConst.PRODUCT_EMPTY_CACHE,o.toString())){
            return new Product();
        }
        product = (Product)o;
    }
    return product;
}

/**
 * update数据库和get方法当缓存失效重新设置缓存时,抢占一把分布式锁
 */
public Integer update(Product product){
    //双写一致性加锁,当写时加写锁
    RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getProductId());
    RLock writeLock = readWriteLock.writeLock();
    int updateById;
    try {
        updateById = iProductMapper.updateById(product);
        redisUtils.set(RedisKeyPrefixConst.PRODUCT_CACHE+ product.getProductId(), product, AuthConstant.EXPIRATION_TIME_IN_SECOND, TimeUnit.SECONDS);
    } finally {
        writeLock.unlock();
    }
    return updateById;
}

什么是缓存穿透?怎么解决?

redis命中率降低,一直查询数据库。

缓存穿透有两种解决方案:其一是把不存在的key设置null值到缓存中。其二是使用布隆过滤器,在查询缓存前先通过布隆过滤器判断key是否存在,存在再去查询缓存。

设置null值可能被恶意针对,攻击者使用大量不存在的不重复key ,那么方案一就会缓存大量不存在key数据。此时我们还可以对Key规定格式模板,然后对不存在的key做正则规范匹配,如果完全不符合就不用存null值到redis,而是直接返回错误。

BloomFilter(布隆过滤):将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。在缓存之前在加一层 BloomFilter,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查缓存 -> 查 DB。

redis中没有需要的key,直接穿过redis访问数据库了。
bitmap是一组二进制数组,每个需要存储到redis的key,使用若干个哈希算法计算出若干个数组下标;
比如hello的下标是hash1()->1,hash2()->2,hash3()->3,hash4()->4。那么就会把下标为1,2,3,4
的数组元素改为1,
比如hi的下标是2,3,4,5那么也会把2,3,4,5的数组元素改为1。
这样就可以通过相应位置数组值是否为1判断redis中是否存在这个key。
当然也会有误判出现,当不同的key通过若干哈希算法得到数组下标一样时,就会造成误判。
设置布隆过滤器时需要设置	1,需要存储的key数量 2,误差率,因为误差率越小,需要的hash算法的数量也就越多,bitmap的长度也就越长。

布隆过滤器

什么是缓存击穿?

少量key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
当这个key在失效的瞬间,redis查询失败,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决思路:双重检测+分布式锁,第一次查redis发现没有缓存,使用分布式锁,当获取到分布式锁的线程第二次查redis再次发现没有缓存,再查数据库,将缓存设置回redis,释放锁,其他线程抢到锁之后先检查redis缓存发现存在值直接返回,因为第一个线程已经将数据库值设置回redis,所以不需要再次请求数据库

public Product get(Long productId){
    String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE+ productId;
    Product product = getProductFromCache(productCacheKey);
    if(product != null){
        return product;
    }
    //加锁
    RLock lock = null;
    try {
        lock = redisson.getLock(productCacheKey);
        lock.lock();
        //双重检测解决缓存击穿问题,当热点缓存失效时,使用分布式锁重新设置缓存
        product = getProductFromCache(productCacheKey);
        if(product!= null){
            return product;
        }

		//有双写不一致问题,在双写不一致问题处解决
        Product product1 = iProductMapper.selectById(productId);
        if(product1!=null){
            //重新设置redis缓存
            redisUtils.set(productCacheKey, product1, AuthConstant.EXPIRATION_TIME_IN_SECOND, TimeUnit.SECONDS);
        }else {
            redisUtils.set(productCacheKey, RedisKeyPrefixConst.PRODUCT_EMPTY_CACHE, AuthConstant.EXPIRATION_TIME_IN_SECOND, TimeUnit.SECONDS);
        }
    } finally {
        lock.unlock();
    }
    return product;

}
private Product getProductFromCache(String productCacheKey) {
    Product product = null;
    Object o = redisUtils.get(productCacheKey);
    if (o!= null){
        if(StringUtils.equals(RedisKeyPrefixConst.PRODUCT_EMPTY_CACHE,o.toString())){
            return new Product();
        }
        product = (Product)o;
    }
    return product;
}

public class RedisKeyPrefixConst {
    public final static String PRODUCT_CACHE = "product:id";
    public final static String PRODUCT_EMPTY_CACHE = "{}";
}

什么是缓存雪崩?

缓存雪崩,是指在某一个时间段,缓存集中过期失效。对这批数据的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。

解决缓存雪崩问题的关键是让缓存Key的过期时间分散。因此我们可以把数据按照业务分类,然后设置不同过期时间。相同业务类型的key,设置固定时长加随机数。尽可能保证每个Key的过期时间都不相同。

另外,Redis宕机也可能导致缓存雪崩,因此我们还要搭建Redis主从集群及哨兵监控,多级缓存,请求限流,保证Redis的高可用。

大量的缓存key,在同一时间大量失效
随机设置缓存失效时间,不让大量key在同一时间失效,把所有的请求都打到数据库上。
把热点的key放置在不同的节点中,让热点key平均分布在不同节点中。
热点key不设置过期时间。
设置定时任务去更新热点key的缓存。

Redis集群

Redis集群

多级缓存?

使用本地缓存的好处:
减少网络请求,提高性能
分布式系统中,天然分布式缓存
减少远程缓存的读压力
使用本地缓存的缺点:
进程空间大小有限,不支持大数据量存储(解决思路:只把最热的数据存储在本地缓存中)
重启程序会丢失数据(解决思路:重启时,从远程缓存重新把最热的数据缓存在本地缓存中)
分布式场景下,系统之间数据可能存在不一致
和远程缓存数据可能会存在不一致(解决思路:本地缓存过期时间设置短一些,比如几秒过期,过期之后,再访问远程缓存来重新保存到本地缓存中,达到数据的最终一致性)

Redis 支持的数据类型有哪些?

Redis 支持的数据类型:string(字符串)、list(列表)、hash(字典)、set(集合)、zset(有序集合)。

redis存储的类型?
string(单key,单value)list(底层linkedlist,单key多value)set(底层hashset存储的value不重复)zset(sorted set 给每一个value前设置一个分数)hash(hashmap,kv模式不变,但v是一个键值对)

hash(hashmap,kv模式不变,但v是一个键值对)
底层hash表或压缩列表

操作hash类型数据:hset user id 11,key是user,value是id 11;hget user id
				  hmset customer id 11 name zhangsan age 16将不同字段存入customer;hgetall customer
				  hkeys,hvals获取所有key,value

使用情景:
购物车,将用户id作为key,value中的k是商品id,value中v是商品数量
	添加商品:hset cart:101 1001 1
	增加数量:hincrby cart:101 1001 1
	商品总数:hlen cart:101
	删除商品:hdel cart:101 1001
	获取购物车所有商品 hgetall cart:101

list类型(一个key,多个value)
底层双向链表

操作list类型数据:lpush(left,比如左边进1~5 lpush key01 1 2 3 4 5,lrange读取时是5~1)rpush(right)、lrange
				  lpop/rpop压栈弹栈、lindex按照索引下标获取元素、llen获取list中元素个数、lrem删除n个value、ltrim截取指定范围的值赋值给key

list应用场景(常见分布式数据结构)
Stack=LPUSH+LPOP 先进后出
Queue队列=LPUSH+RPOP 先进先出
Blocking MQ阻塞队列=LPUSH+BRPOP

微信公众号消息
比如我关注了备车说车、西安房哥、苏群
备车说车12:00发了一篇文章(文章id为1) lpush wechat-msg:userid001 1
西安房哥12:05发了一篇文章(文章id为2) lpush wechat-msg:userid001 2
苏群12:10发了一篇文章(文章id为3) lpush wechat-msg:userid001 3
查看最新消息 lrange wechat-msg:userid001 0 10,查看最近10条消息,刚好满足最新的消息在前面

set类型(一个key,多个value,value不允许重复)

操作set类型数据:sadd set01 1 1 2 3只能存进去1,2,3因为set类型不允许重复、
scard获取集合中元素个数、srem删除集合中元素、srandmember随机出几个数、spop随机出栈

抽奖小程序
1,参与抽奖加入集合
sadd key {userId}
2,查看参与抽奖用户
smembers key
3,抽取count名中奖者
srandmember key [count] 从集合中选出count个随机对象
spop key [count] 随机弹出count个对象

朋友圈点赞
1,点赞 sadd pyqLike:{消息id} {用户id}
2,取消点赞 srem pyqLike:{消息id} {用户id}
3,检查用户是否点过赞 sismember pyqLike:{消息id} {用户id}
4,获取点赞用户列表	smembers pyqLike:{消息id}
5,获取点赞用户数	scard pyqLike:{消息id}

关注模型:set集合求交集、并集、差集
set1={a,b,c} 	set2={b,c,d} 	set3={c,d,e}
sinter set1 set2 set3 	-> 交集{c}
sunion set1 set2 set3 	-> 并集{a,b,c,d,e}
sdiff set1 set2 set3 	-> 差集{a}
举例:
1,刘备关注的人 liubeiSet={caocao,sunquan,zhugeliang,guanyu,zhangfei,zhaoyun}
2,曹操关注的人 caocaoSet={liubei,sunquan,guanyu,xiahouyuan}
3,孙权关注的人 sunquanSet={liubei,caocao,lusu}
3,我关注的人 mySet={liubei,caocao,xiaoqiao}
刘备&曹操共同关注 sinter liubeiSet caocaoSet -->{sunquan,guanyu}
我关注的人也可能关注刘备	sismember caocaoSet liubei
我可能认识的人  sdiff mySet liubeiSet -->{xiaoqiao}

zset集合(一个key,多个value,value不允许重复,并且有分数)
zset通过有序链表实现,因为链表增删快,查找慢的特性,底层加入跳表查询,通过加入冗余索引层,每2个建立一个冗余索引,近似于折半查找(以空间换时间)。
当存储元素较少时采用压缩列表(以时间换空间)

redis配置文件中zset-max-ziplist-entries 128可以配置存储多少元素时使用压缩列表,超过多少元素使用跳表
操作zset类型数据:zadd zset01 60 v1 70 v2 80 v3(给每个value都设置一个分数)
排行榜相关业务:
朋友圈点赞按时间排序
zset pyqZan:{消息id} {score也就是时间} {用户id}
keys * 当前存在的所有key
exists key 判断某个key是否存在
move key dbname 剪切某个key到另外一个库db中
select dbname 切换到另一个库
ttl key 查看key还有多少秒过期,ttl time to leave-1表示永不过期,-2表示已过期
expire key 秒钟 为key指定一个过期时间
type key 查看key数据类型
操作string类型数据:get、set、del、append、strlen(string类型数据的长度)、incr、incrby、decr、decrby(key+1,key+n,key-1,key-n)
					getrange获取key对应value的范围内取值、setrange范围内设置值、setex key time value设置值时顺便设置过期时间
					setnx(set if not exist)不存在时才能设置进去值、mset(more set)一次性设置多个k,v、msetnx(more set if not exist)如果有一个key没有寸成功那么就全部存不进去

Redis 支持的 Java 客户端都有哪些?

支持的 Java 客户端有 Redisson、jedis、lettuce 等。

Redis 持久化有几种方式?

Redis 的持久化有两种方式,或者说有两种策略:

RDB(Redis Database):指定的时间间隔能对你的数据进行快照存储。
AOF(Append Only File):每一个收到的写命令都通过write函数追加到文件中。

RDB(redis database)
指定时间间隔内将内存中的数据集快照写入磁盘dump.rdb
snapshot快照,恢复是将快照文件直接读到内存里
redis.conf文件中save <seconds> <changes>
save "" 禁用rdb
比如set了一条数据,直接使用save命令可以直接备份,或者使用bgsave命令(异步开启save)
适合大规模数据恢复,对数据完整性和一致性要求不高
间隔一段时间做一次备份,如果redis意外宕机,会丢失最后一次快照后的所有修改
RDB需要fork子进程来做快照工作,fork的时候,内存中的数据被克隆一份,大致2倍的膨胀性需要考虑

AOF(append only file)
以日志的形式来记录每个写操作。只许追加文件但不可以改写文件。redis启动之初会读取该文件重新构建数据。
写入appendonly.aof文件,redis.conf中appendonly no(yes)开启关闭aof;appendfsync always、everysec每秒记录默认、no
redis-check-aof(redis-check-dump) --fix appendonly.aof(dump.rdb)修复被改动的aof(rdb)文件
rewrite优化重复的修改同一key指令 bgrewriteaof。记录上次aof文件大小,默认当这次比上次大一倍且大于64M时触发
相同数据集数据而言,aof文件远大于rdb文件,恢复速度慢于rdb。aof运行效率慢于rdb,每秒同步策略较好

当appendonly.aof和dump.rdb共存时,优先加载appendonly.aof,因为aof文件比rdb数据要完整

Redis 如何做内存优化?

尽量使用 Redis 的散列表,把相关的信息放到散列表里面存储,而不是把每个字段单独存储,这样可以有效的减少内存使用。比如将 Web 系统的用户对象,应该放到散列表里面再整体存储到 Redis,而不是把用户的姓名、年龄、密码、邮箱等字段分别设置 key 进行存储。

Redis事务?

multi开启事务
get、set操作一堆命令
exec批处理执行以上命令
discard放弃事务操作
exec比如get、set过程中有一条命令书写错误,整个事务将失效;
	再比如给abc,incr命令加+,整个事务命令中只有incr这一条会出错
	所以说redis部分支持事务,不保证原子性,redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行。
开启事务,所有的get、set等操作都进入队列queue中,执行exec时才被执行
	
开启:multi开始一个事务
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
执行:由exec命令触发事务

watch指令,类似乐观锁,事务提交后,如果key已被别的客户端改变,整个事务队列都不会被执行
通过watch命令在事务执行之前监控了多个keys,倘若在watch之后在任何key的值发生了变化,exec命令执行的事务都将被放弃,
同时返回nullmulti-bulk应答以通知事务执行失败

redis过期策略以及内存淘汰机制

  • 定期删除:将redis key做成一个过期字典,每次随机抽取20个key,删除其中过期的key,如果删除比例超过1/4,就再抽取20个key,继续删除。
  • 惰性删除:当访问一个key时,发现key已经过期,直接删除缓存,不返回任何东西

redis的bigkey?

key对应的value特别大。

一个字符串类型,一般认为超过10kb;一个非字符串类型value存储了过多元素。
hash、list、set、zset元素个数不要超过5000

如何查找大key?
将rdb文件转成csv文件
rdb -c memory /mnt/data/redis/dump.rdb >  /mnt/data/redis/memory.csv

导出内存中排名前3的keys
rdb --command memory --largest 3 dump.rdb

导出大于 10 kb 的  key  输出到一个表格文件
rdb dump.rdb -c memory --bytes 10240 -f redis.csv
bigkey危害:

造成内存使用非常不均匀:例如在redis cluster中,bigkey会造成节点的内存空间使用不均匀。
造成超时阻塞:由于redis单线程的特性,操作bigkey比较耗时,也就意味着阻塞redis可能性增大。
造成网络拥塞:每次获取bigkey产生的网络流量较大。

解决方式:

value如果过大,能拆分尽量拆分。

一次性删除的后果

大Key如果一次性执行删除操作,会立即触发大量内存的释放过程。这个过程中,操作系统需要将释放的内存块重新插入空闲内存块链表,以便之后的管理和再分配。由于这个过程是同步进行的,并且可能涉及大量的内存块操作,因此它将占用相当一部分处理时间,并可能造成Redis主线程的阻塞。
这种阻塞会导致Redis无法及时响应其他命令请求,从而引起请求超时,超时的累积可能会导致Redis连接耗尽,进而产生服务异常。
因此删除大key,一定要慎之又慎,可以选择异步删除或批量删除。

如何删除bigkey:

1,低峰时删除(比如凌晨)
2,异步删除
Redis 4.0及以后版本提供了UNLINK命令,该命令与DEL命令类似,但它会在后台异步删除key,不会阻塞当前客户端,也不会阻塞Redis服务器的主线程,因此可以更安全地删除大key。

UNLINK mybigstring

UNLINK的工作原理是:
立即将 key 标记为已删除,从所有数据结构中移除,这个操作是立即返回的,不阻塞主线程。
实际的空间释放操作则是在后台线程异步进行。

3,scan扫描Redis中的大key,分批次删除大key
redis中的大key要如何删除?

Redis解决key冲突?

业务隔离:多种业务存储在不同的redis中
key组成:业务模块+系统名称+关键字
分布式系统中,使用分布式锁,确保同一时间只能有一个redis操作key

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值