一、高可用问题
1,缓存穿透
1.1 概述
某key在redis中找不到,频繁访问这个key,缓存中没有就直接访问数据库了,频繁访问就导致数据库宕机了
1.2 解决方案
1.21 布隆过滤器
概述
请求时验证此key是否存在
创建一个二进制数组,都置为0(二进制数组只有0和1)
提供 key.hash() 运算找到对应二进制数组的下标,置为1
新来的key经过hash运算看对应下标的数组元素值是否为1就可以判断存在与否
力扣题目
题目描述:
请按照下列指示实现一个redis布隆过滤器:
① 首先构造n个哈希方法,每个哈希方法字符串到key值映射公式为:key = ( i * key + s) % mod,其中key从0开始,s为字符串中每个字符的ASCII值,i为这是第几个哈希方法(从1开始),mod为每个方法对应的随机数,取值在10000~20000之间。
② 实现add函数,向布隆过滤器中添加一个字符串:使用每个构造的哈希方法得到n个key值,相应key值标记。
③ 实现contains函数,检查给定字符串是否在布隆过滤器中:检查是否每个哈希方法的key值都有标记。
输入字符串数组s1表示先将这些字符串加入到布隆过滤器中,再检验字符串数组s2中的字符串是否在布隆过滤器中。
import java.util.*;
//hash运算可能存在位运算,异或等,参考HashMap的hash运算
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s1 string字符串ArrayList
* @param s2 string字符串ArrayList
* @param n int整型
* @return int整型ArrayList
*/
public ArrayList<Integer> BloomFilter (ArrayList<String> s1, ArrayList<String> s2, int n) {
ArrayList<Integer> list=new ArrayList<>();
//s1为需要添加的字符串集合
//s2为需要判断是否存在的字符串集合
//n为需要计算多少hash值,也就是看误判率判断
//申请一个足够长的二进制数组,默认值都为0
int[] boolFilter=new int[10000];
for (String str:s1){
add(str,n,boolFilter);
}
// s2数组中哪些已存在
for (String str:s2){
list.add(contains(str,boolFilter,n));
}
return list;
}
//mod应为随机数,一个过滤器一个,应在方法内,此处为了省事
int mod=10001;
//hash值计算应有<<或&等
public int hash(int key,int i,String s1){
for (int j = 0; j < s1.length(); j++) {
// 计算hash值 key = ( i * key + s) % mod
key=(i*key + s1.charAt(j)) % mod;
}
return key;
}
public void add(String s1,int n,int[] boolFilter){
for (int i = 1; i <= n; i++) {
int key2=0;
//计算出的key1下标对应的值变为1
boolFilter[hash(key2,i,s1)]=1;
}
}
//判断计算出的下标key的值是否为1
public int contains(String s2,int[] boolFilter,int n){
for (int i = 1; i <= n; i++) {
int key2=0;
if (boolFilter[hash(key2,i,s2)]!=1){
return 0;
}
}
return 1;
}
}
加了布隆过滤器还存在的问题
问题来源:不同key经过hash运算的结果可能相同
解决办法:误判率设置的低一点(误判率:每增加一个hash运算可减少一点误判率),不能完全避免,不能设置过低,过低的话,hash运算的次数就多,时间,空间的开销就大。
1.22 日志检测 --这个瞎猜的,不用纠结
业务层每个类中加个定时执行的接口
内容:
检测其他接口的访问时间(这段时间访问的尽可能多几次,算平均)
和预估时间比较,或者取之前接口平均访问时间比较(定时触发的第十次和第九次接口访问时间比较)
1.23 总结
在于是否用到缓存,不存在可以设置下
2,缓存击穿
2.1 概述
存在一个热点数据,用户频繁访问,该热点数据的key有过期时间,该过期到下一次设置服务器扛不住压力导致的宕机(例如:热搜)
2.2 解决方案
2.21 wait…
1)热点key时间到了失效
给key延长时间,或者设置永不过期
2)热点key时间没到,但是请求量太大了,redis响应不了那么多请求,导致一部分请求连接数据库查询结果响应 --瞎猜的,不用纠结
CountDownLatch压测,看看最大能接受多少并发请求
Semphore进行限流
2.22 分布式锁
set key nx px
看门狗延长key有效时间
3,缓存雪崩
3.1 概述
大量热点key失效
3.2 解决方案
预热key
key设置的过期时间适当增长,部分key设置永不过期,一段时间后手动清理
限流,分布式锁,Semphore
二、过期key删除
定时删除:
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。对内存最友好,对 CPU 时间最不友好。
惰性删除:
放任键过期不管,但是每次获取键时,都检査键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。对 CPU 时间最优化,对内存最不友好。
定期删除:
每隔一段时间,默认 100ms,程序就对数据库进行一次检査,删除里面的过期键。至 于要删除多少过期键,以及要检査多少个数据库,则由算法决定。前两种策略的折中,对 CPU 时间和内存的友好程度较平衡。
三、内存淘汰策略
当 redis 的内存空间(maxmemory 参数配置)已经用满时,redis 将根据配置的驱逐策略(maxmemory-policy 参数配置),进行相应的淘汰动作。
noeviction:默认策略,不淘汰任何 key,直接返回错误。
allkeys-lru:在所有的 key 中,使用 LRU 算法淘汰部分 key。
allkeys-lfu:在所有的 key 中,使用 LFU 算法淘汰部分 key,该算法于 Redis 4.0 新增。
allkeys-random:在所有的 key 中,随机淘汰部分 key。
volatile-lru:在设置了过期时间的 key 中,使用 LRU 算法淘汰部分 key。
volatile-lfu:在设置了过期时间的 key 中,使用 LFU 算法淘汰部分 key,该算法于 Redis 4.0 新增。
volatile-random:在设置了过期时间的 key 中,随机淘汰部分 key。
volatile-ttl:在设置了过期时间的 key 中,挑选 TTL(time to live,剩余时间)短的 key 淘汰。
1,LRU --最长时间未使用的
用LinkedHashMap
2,LFU --最不经常使用
题目:
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。
实现 LFUCache 类:
LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。
void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。
为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。
当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
/**
* 触发LRU,一次清理多少?
* 固定数量key,还是频率低于多少的
* 方案1:触发一次LFU清理一个key?感觉不太现实,应该其他清理策略
* HashMap存储key,value,useCount
* key达到上限,触发LRU
* 第一遍,遍历HashMap的节点,取出useCount最小值
* 第二遍,遍历HashMap的节点,删除useCount=useCount最小值的key
* HashMap的节点 --由数组,链表,红黑树组成
* 数组+链表:数组就是一个一个地址值连续的节点连接起来,链表就是地址值不连续的节点
* 红黑树:涉及前中后序遍历,此处不考虑
*/
class LFUCache {
//采用的策略:Hash(多存了时间戳+key使用次数) --题目看漏了一点,后来懒得换了
ConcurrentHashMap<Integer,Node> map=new ConcurrentHashMap<>();
int capacity;
public LFUCache(int capacity) {
this.capacity=capacity;
}
public int get(int key) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (map.containsKey(key)){
Node val=map.get(key);
int count = val.useCount + 1;
val.setUseCount(count);
val.setTime(System.currentTimeMillis());
return val.value;
}
return -1;
}
public void put(int key, int value) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//创建个Node对象
Node val=new Node();
val.setValue(value);
//put的时候要看容量
if (map.size()<capacity){
//如果put的key存在,更新value值,useCount++
putV(key,val);
}else {
deleteKey();
if(capacity==0){
}else{
putV(key,val);
}
//触发清理策略 --清理key
}
}
private void putV(int key,Node val) {
if (map.containsKey(key)){
int count = val.useCount + 1;
val.setUseCount(count);
val.setTime(System.currentTimeMillis());
map.put(key,val);
}else {
int count=1;
val.setUseCount(count);
val.setTime(System.currentTimeMillis());
map.put(key,val);
}
}
private void deleteKey() {
//迭代器找出最少使用次数
int minCount = Integer.MAX_VALUE;
Set<Integer> keys = map.keySet();
Iterator<Integer> iterator = keys.iterator();
while (iterator.hasNext()){
int key=iterator.next();
Node val = map.get(key);
minCount=Math.min(minCount,val.useCount);
}
//迭代器删除该useCount
Map<Long,Integer> deleteKey=new ConcurrentHashMap<>();
List<Long> longs=new ArrayList<>();
Iterator<Integer> it = keys.iterator();
while (it.hasNext()){
int key=it.next();
Node val = map.get(key);
if (val.useCount==minCount){
//添加key与时间戳
deleteKey.put(val.time,key);
longs.add(val.time);
}
}
if (longs.size()>1){
long minTime=longs.get(0);
for (int i = 0; i < longs.size(); i++) {
minTime=Math.min(minTime,longs.get(i));
}
map.remove(deleteKey.get(minTime));
}else if(longs.size()==1) {
map.remove(deleteKey.get(longs.get(0)));
}
}
//如果Node当key,这样会导致数组过长,建议useCount和value一起创建实体类,此处图个迭代方便
static class Node{
private int value;
private int useCount;
private long time;
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public int getUseCount() {
return useCount;
}
public void setUseCount(int useCount) {
this.useCount = useCount;
}
}
}