Java --- 面试题

一、Redis应用场景

1.1、缓存

热点数据(高频查询,但不经常修改和删除的数据)首选redis作为缓存,性能优秀。

案例:如仓储业务中的商品信息,用户从redis的查询商品信息,没有在去数据库中查询。

1.2、分布式锁

在多线程环境下,对共享资源访问的线程问题,需要通过锁的机制实现访问的互斥。

案例:在集群环境下的库存扣减功能,普通方式实现,步骤:①、获取锁:setnx key vlue ②、设置好锁的过期时间:expire key 10 ③、执行业务代码 ④、释放锁:del key

使用redisson来实现分布式锁:①、获取锁 ②、执行业务代码 ③、释放锁

redisson加锁、锁续期、释放锁底层都是通过lua脚本实现的,这样保证了操作的原子性。

redisson的锁为可重入锁。

1.2.1、分布式锁的坑 -- 非原子操作

解决办法:①、将加锁与设置锁的过期时间进行统一操作。②采用lua脚本。

1.2.2、分布式锁的坑 -- 锁未释放

解决办法:锁必须加过期时间。

1.2.3、分布式锁的坑 -- 锁提前释放

解决办法:给锁续期

1.2.4、分布式锁的坑 -- 释放其他线程的锁

解决办法:给每个锁添加唯一的id

1.2.5、分布式锁的坑 -- 大量请求竞争锁失败

解决办法:①、使用自旋锁重试。②、优化业务代码,将执行时间变短。③、进行限流。

1.2.6、分布式锁的坑 -- 主从复制

解决办法:①、红锁机制(不采用) 

1.2.7、分布式锁的坑 -- 锁的性能

解决办法:分段锁(大多数不采用,需要根据场景)

1.2.8、分布式锁的坑 -- 锁可重入性

解决办法:redisson自身实现

1.3、使用场景案例

1.3.1、Token存储

过程:前端发送登录请求给后端,后端生成一个token返回给前端,并存储在redis中。当前端发送请求是携带token需要给跟redis存储中token进行验证,通过才能返回结果给前端。

1.3.2、短信验证码存储

 过程:用户发送获取短信验证码,后端获取并写入redis中,前端用户输入验证码与redis中进行比较。

1.3.3、计数器

如:文章阅读数,视频点赞数等,使用hset类型。

1.3.4、全局唯一ID

主要使用redis自增id的特性。

1.3.5、排行榜

如积分排行榜,英雄战力排行榜。使用zset类型数据。

1.3.6、限流

local key = KEYS[1] --限流KEY(一秒一个)"limit:" + System.currentTimeMillis() / 1000;
local limit = tonumber(ARGV[1]) --限流大小  10

local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
    return 0
else  --请求数+1,并设置2秒过期
    redis.call("INCRBY", key, "1")
    redis.call("EXPIRE", key, "15")
    return 1
end
@Test
    void testLimit() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limit.lua")));

        for (int i = 0; i < 112; i++) {
            String key = "limit:" + System.currentTimeMillis() / 1000; // limit:1705664721
            Long result = stringRedisTemplate.execute(redisScript, Arrays.asList(key), String.valueOf(100));
            if (result == 0) {
                //需要限流
                System.out.println("限流了......");
            }
        }
    }

1.3.7、购物车

 使用hset类型实现

1.4、缓存穿透

 原因:用户访问一个不存在的数据,而redis就会去请求后端数据库,当这种大量不存在的数据访问,导致redis频繁去查询后端数库以至于后端数据库崩溃。

解决办法:①、缓存空结果,将所有查询的数据都存在redis的缓存中,减少对数据库的查询,优点:实现简单。缺点:会导致redis的内存被大量占用,出现缓存与数据库不一致的情况。

②、布隆过滤器。优点:不会缓存无效数据,缺点:实现比较复杂,存在一定误判。

1.4.1、布隆过滤器

使用方案:在用户请求reids缓存之前,先经过布隆过滤器,经过筛选在去查询redis缓存。

原理:布隆过滤器主要使用一个很长的二进制数组,在由一系列hsah函数来确定请求数据是否存在于该集合中。

案例:布隆过滤器BloomFilter

 缓存预热+雪崩+穿透+击穿

1.5、缓存击穿

原因:在高并发条件下,对于热点数据,当热点数据失效的瞬间,或刚开始还没有对热点数据进行缓存,所有请求都被发送到数据库去查询,数据库就会崩溃。

解决办法:①、使用全局锁:在访问数据库之前都先请求全局锁,获取锁的线程才有资格去访问数据库,其他线程必须等待。由于现在的业务都是分布式的,本地锁没法控制其他服务器的线程等待,所以要用全局锁。如分布式锁。

②、热点数据,设置不过期时间。实现方式一:redis中的热点key不设置过期时间,缺点:缓存热点数据是静态的,得不到更新。实现方式二:给热点key设置一个逻辑过期时间得字段,跟逻辑删除思路一致。

1.6、缓存雪崩

原因:①、由于大量得key或整个缓存数据全部过期,同时又有大量请求访问这些过期缓存而导致请求落地数据库上,而导致数据库崩溃。②、缓存发生故障,导致大量请求落入到数据库,导致数据崩溃。

解决办法:①、建立高可用得redis集群。②、给不同的key设置不同的过期时间。③、本地缓存+限流或降级,避免数据库被压垮。

1.7、内存使用完怎么办?

 redis7的缓存淘汰策略

1.8、String类型的值最大能放多大数据?

String类型:512MB

List,Set,Hash类型:2^32-1(4294967295) 个元素。

1.9、如何保证数据库与redis的数据一致性?

解决办法:使用缓存双写一致性

 redis7缓存双写一致性

1.10、redis集群最大能部署多少主节点?

redis集群中内置了16384个哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同节点 

二、Spring 

2.1、Spring容器中的bean是线程安全的吗?

答:不是线程安全的

原因:①、spring容器中的bean默认是singleton单例的,所有线程都共享一个单例bean,因此是存在资源竞争的。②、实际开发中,单例bean一般以无状态的方式来使用,即线程之间的操作不会对bean的成员执行除查询以外的操作,所以这个单例bean也可以说是线程安全的。如c

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸭鸭老板

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值