-
allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。 -
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 -
no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失。
### 10\. 秒杀常见问题
* 连接超时
* 使用连接池
* 超卖问题
* 使用事务
* 库存遗留
* 使用LUA脚本
### 11\. Redis发布订阅
* SUBSCRIBE c1 c2 c3
* PUBLISH c2 hello-redis
### 12\. Redis主从复制
* 配从不配主:slaveof 主库IP 主库端口,每次与master断开之后,都需要重新连接,除非配置redis.conf文件。
* 配置文件细节操作:
* 拷贝多个redis.conf文件
* 开启daemonize yes
* Pid文件名字
* 指定端口
* Log文件名字
* Dump.rdb名字
* 常用招式
* 一主二仆
* Info replication:查看信息
* SLAVEOF 127.0.0.1 6379:配置从库
![](https://img-blog.csdnimg.cn/img_convert/5cdaa7aaebe2ea66d3e2e0b04048c084.png)
### 13\. 哨兵模式
* 定义:反客为主(slaveof no one)自动化,能够监控主机是否故障,如果故障根据投票数自动将从库转为主库
* 使用步骤:
* 调整结构,6379带着80、81
* 自定义的/myredis目录下新建sentinel.conf文件
* 配置哨兵,填写内容:Sentinel monitor host637(被监控数据库名字(自己起名字))127.0.0.1 6379 1(多于1票则设为主机)
* 启动哨兵:Redis-sentinel /myredis/sentinel.conf
**更多Java学习资料、面试真题获得,请【[点击此处]( )】**
### 14\. Java使用redis
* 连接:Jedis jedis = new Jedis(“127.0.0.1”,6379);
* 插入:jedis.set(“k1”,”v1”);
* 事务:
Transaction transaction = jedis.multi();
transaction.set(“k2”,”v2”);
transaction.set(“k3”,”v3”);
transaction.exec();
* 加锁:
public class TestTransaction {
public boolean transMethod() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
int balance;// 可用余额
int debt;// 欠额
int amtToSubtract = 10;// 实刷额度
jedis.watch("balance");
//jedis.set("balance","5");//此句不该出现。模拟其他程序已经修改了该条目
balance = Integer.parseInt(jedis.get("balance"));
if (balance < amtToSubtract) {
jedis.unwatch();
System.out.println("modify");
return false;
} else {
System.out.println("***********transaction");
Transaction transaction = jedis.multi();
transaction.decrBy("balance", amtToSubtract);
transaction.incrBy("debt", amtToSubtract);
transaction.exec();
balance = Integer.parseInt(jedis.get("balance"));
debt = Integer.parseInt(jedis.get("debt"));
System.out.println("*******" + balance);
System.out.println("*******" + debt);
return true;
}
}
/**
-
通俗点讲,watch命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中重新再尝试一次。 -
首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作, -
如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。
*/
public static void main(String[] args) {
TestTransaction test = new TestTransaction();
boolean retValue = test.transMethod();
System.out.println("main retValue-------: " + retValue);
}
}
* 主从复制
* 配置从库:Jedis jedis\_s = new Jedis(“127.0.0.1”,6380);
* Jedis\_s.slaveof(“127.0.0.1”,6379);
* JedisPool
JedisPoolConfig poolConfig = new JedisPoolConfig( );
poolConfig.setMaxActive ( 1000);
poolconfig.setMaxIdle ( 32);
poolconfig. setMaxwait (100*1000);poolconfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, “127.0.0.1”,6379);
-
maxActive:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。 -
maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例; -
whenExhaustedAction:表示当pool中的jedis实例都被allocated完时,pool要采取的操作:默认有三种。 -
WHEN_EXHAUSTED_FAIL -->表示无jedis实例时,直接抛出NoSuchElementException; -
WHEN_EXHAUSTED_BLOCK -->则表示阻塞住,或者达到maxWait时抛出JedisConnectionException; -
WHEN_EXHAUSTED_GRoW -->则表示新建一个jedis实例,也就说设置的maxActive无用; -
maxWait:表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛JedisConnectionException; -
testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的:
### 15\. 解决session存储问题
* 方案一:存在cookie里
* 不安全
* 网络负担效率低
* 方案二:存在文件服务器或数据库里
* 大量的IO效率问题
* 方案三:session复制
* Session数据冗余
* 节点越多浪费越大
* 方案四:缓存数据库
* 完全存在内存中,速度快数据结构简单
### 16\. 单线程+多路IO复用
多路复用是指用一个线程来检查多个文件描述符(socket)的就绪状态,比如调用select、poll、epoll函数进行监视,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
* Select:每一个请求都进行询问,最多1024个
* Poll:每一个请求都进行询问不限制数量
* Epoll:监视请求时为每个请求设置标识符,不需要一一询问
### 17\. Select、poll、epoll
* select的几大缺点:
1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
3)select支持的文件描述符数量太小了,默认是1024
* 为什么epoll比select和poll更高效?
1)减少了用户态和内核态之间文件描述符的拷贝
2)减少了对就绪文件描述符的遍历
3)select和poll只支持LT模式,而epoll支持高效的ET模式,并且epoll还支持EPOLLONESHOT事件。
* 无论哪种情况下,epoll都比select和poll高效吗?
* epoll适用于连接较多,活动数量较少的情况。
1)epoll为了实现返回就绪的文件描述符,维护了一个红黑树和好多个等待队列,内核开销很大。如果此时监听了很少的文件描述符,底层的开销会得不偿失;
2)epoll中注册了回调函数,当有事件发生时,服务器设备驱动调用回调函数将就绪的fd挂在rdllist上,如果有很多的活动,同一时间需要调用的回调函数数量太多,服务器压力太大。
* select和poll适用于连接较少的情况。
1)当select和poll上监听的fd数量较少,内核通知用户现在有就绪事件发生,应用程序判断当前是哪个fd就绪所消耗的时间复杂度就会大大减小。
### 18\. REDIS缓存穿透,缓存击穿,缓存雪崩原因+解决方案
* 缓存穿透:key对应的数据在数据库和缓存并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据库,从而可能压垮数据库。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
* 解决方案:
1)最常见的则是采用布隆过滤器,在写入数据库时,将数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
2)简单粗暴的方法:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
* 缓存击穿:是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
* 解决方案
1)使用互斥锁:就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
2)设置永不过期
* 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
* 大量数据同时过期解决方案
1)用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。
2)将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
3)双key策略,主key设置过期时间,备key设置永久,主key过期时,返回备key内容
4)后台缓存更新,定时更新、消息队列通知更新
服务器宕机解决方案
1)服务熔断:在分布式系统中,我们往往需要依赖下游服务,不管是内部系统还是第三方服务,如果下游出现问题,我们不在盲目地去请求,在一个周期内失败达到一定次数,不在请求,及时失败。过一段时间,在逐步放开请求,这样既能防止不断的调用,使下游服务更坏,保护了下游方,还能降低自己的执行成本,快速的响应,减少延迟,增加吞吐量。
2)服务降级:降级就是为了解决资源不足和访问量增加的矛盾,在有限的资源情况下,为了能抗住大量的请求,就需要对系统做出一些牺牲,有点“弃卒保帅”的意思。放弃一些功能,保证整个系统能平稳运行。比如:抢购可以占时限流评论,将流量让给秒杀业务
3)请求限流:通过对并发访问进行限速。最简单的方式,把多余的请求直接拒绝掉,可以根据一定的用户规则进行拒绝策略
4)构建redis高可靠集群
![](https://img-blog.csdnimg.cn/img_convert/06c474fb64121d1288048d137a5f51b2.png)
### 19\. 布隆过滤器
布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
布隆过滤器会通过 3 个操作完成标记:
* 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
* 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。
* 第三步,将每个哈希值在位图数组的对应位置的值设置为 1;
举个例子,假设有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器。
![](https://img-blog.csdnimg.cn/img_convert/d50b912e9a6c23b5afdc2c155eb06a7e.png)
在数据库写入数据 x 后,把数据 x 标记在布隆过滤器时,数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 1、4、6,然后把位图数组的第 1、4、6 位置的值设置为 1。当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 1、4、6 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中。
布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,比如数据 x 和数据 y 可能都落在第 1、4、6 位置,而事实上,可能数据库中并不存在数据 y,存在误判的情况。
所以,查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。
### 20\. 为什么要用redis而不用map做缓存?
* Redis 可以用几十 G 内存来做缓存,Map 不行,一般 JVM 也就分几个 G 数据就够大了
* Redis 的缓存可以持久化,Map 是内存对象,程序一重启数据就没了
* Redis 可以实现分布式的缓存,Map 只能存在创建它的程序里
* Redis 可以处理每秒百万级的并发,是专业的缓存服务,Map 只是一个普通的对象
* Redis 缓存有过期机制,Map 本身无此功能
* Redis 有丰富的 API,Map 就简单太多了
### 21\. 如何保持缓存和数据库的一致性?
* 淘汰缓存还是更新缓存?
选择淘汰缓存,原因:数据可能为简单数据,也可能为较复杂的数据,复杂数据进行缓存的更新操作,成本较高,因此一般推荐淘汰缓存
* 先淘汰缓存还是先更新数据库?
选择先淘汰缓存,再更新数据库,原因:假如先更新数据库,再淘汰缓存,如果缓存淘汰失败,那么后面的请求都会得到脏数据,直至缓存过期。假如先淘汰缓存再更新数据库,如果数据库更新失败,只会产生一次缓存miss,相比较而言,后者对业务影响更小一点。
* 延时双删策略:解决数据库读写分离
public void write(String key,Object data){
redisUtils.del(key);
db.update(data);
Thread.Sleep(100);
redisUtils.del(key);
}
### 22\. Redis分布式锁的实现
* 加锁:使用setnx key value命令,如果key不存在,设置value(加锁成功)。如果已经存在lock(也就是有客户端持有锁了),则设置失败(加锁失败)。
* 解锁:使用del命令,通过删除键值释放锁。释放锁之后,其他客户端可以通过setnx命令进行加锁。
### 23\. Redis集群
# 最后
**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](https://codechina.csdn.net/m0_60958482/java-p7)**
学习视频:
![](https://img-blog.csdnimg.cn/img_convert/999c8f14cc9ec8c050be272d6b5e5b29.png)
大厂面试真题:
ring key,Object data){
redisUtils.del(key);
db.update(data);
Thread.Sleep(100);
redisUtils.del(key);
}
22. Redis分布式锁的实现
23. Redis集群
最后
CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】
学习视频:
[外链图片转存中…(img-Z4NasZ0s-1630856688065)]
大厂面试真题:
|