没有缓存,直接请求数据库。
现在也有很多内部系统,教务系统等等都是这样的
最明显的问题就是: 慢!。
大家没发现我们学校的抢课系统每次都很慢吗?我猜大概率就是没用缓存,或者说服务做得不好,再或者代码写得有问题等等。
大家需要知道一个知识:
所以我们需要redis, 因为它高性能,高并发
凡事有利也有弊
缓存穿透,雪崩,击穿
/**
* HashMap模拟数据库数据
*/
private static Map<Long, DataObject> dbDataList = new HashMap<>();
/**
* 初始化数据库,值为1~10000
*/
@PostConstruct
private void initData() {
for (long i = 1; i <= maxIdnum; i++) {
//addToBloomFilter(String.valueOf(i));
dbDataList.put(i, new DataObject(i, "name:" + i));
}
}
观察输出结果,可以发现3000次请求全部打到数据库去了,我们这里只是模拟了一下,实际黑客攻击是,并发量可能更大,直接会把你的数据库打崩的。
对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,等下次再次查询缓存的时候就会有这个值,就不再向数据库请求了。设置空结果的过期时间会很短,最长不超过五分钟。
设置可访问的白名单:使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
采用布隆过滤器:布隆过滤器可以用于检索一个元素是否在一个集合中,将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
导入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
BloomFilterHelper
package com.jyt.test;
import com.google.common.base.Preconditions;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;
public class BloomFilterHelper<T> {
private int numHashFunctions;
private int bitSize;
private Funnel<T> funnel;
public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
Preconditions.checkArgument(funnel != null, "funnel不能为空");
this.funnel = funnel;
// 计算bit数组长度
bitSize = optimalNumOfBits(expectedInsertions, fpp);
// 计算hash方法执行次数
numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
}
public int[] murmurHashOffset(T value) {
int[] offset = new int[numHashFunctions];
long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
int hash1 = (int) hash64;
int hash2 = (int) (hash64 >>> 32);
for (int i = 1; i <= numHashFunctions; i++) {
int nextHash = hash1 + i * hash2;
if (nextHash < 0) {
nextHash = ~nextHash;
}
offset[i - 1] = nextHash % bitSize;
}
return offset;
}
/**
* 计算bit数组长度
*/
private int optimalNumOfBits(long n, double p) {
if (p == 0) {
// 设定最小期望长度
p = Double.MIN_VALUE;
}
int sizeOfBitArray = (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
return sizeOfBitArray;
}
/**
* 计算hash方法执行次数
*/
private int optimalNumOfHashFunctions(long n, long m) {
int countOfHash = Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
return countOfHash;
}
}
RedisBloomFilter
package com.jyt.test;
import com.google.common.base.Preconditions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* @Classname RedisBloomFilter
* @Description TODO
* @Date 2021/11/1 19:36
* @Created by Echo
*/
@Service
public class RedisBloomFilter {
@Autowired
private RedisTemplate redisTemplate;
/**
* 根据给定的布隆过滤器添加值
*/
public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
int[] offset = bloomFilterHelper.murmurHashOffset(value);
for (int i : offset) {
// System.out.println("key : " + key + " " + "value : " + i);
redisTemplate.opsForValue().setBit(key, i, true);
}
}
/**
* 根据给定的布隆过滤器判断值是否存在
*/
public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
int[] offset = bloomFilterHelper.murmurHashOffset(value);
for (int i : offset) {
// System.out.println("key : " + key + " " + "value : " + i);
if (!redisTemplate.opsForValue().getBit(key, i)) {
return false;
}
}
return true;
}
}
简单看看代码逻辑
public DataObject getData(Long id) {
//布隆过滤器中不存在,则直接返回空
if (!bloomFilteExists(String.valueOf(id))) {
System.out.println("布隆过滤器中不存在, id: " + id);
return null;
}
//从缓存读取数据
DataObject result = getDataFromCache(id);
if (result == null) {
//缓存不存在,从数据库查询数据的过程加上锁,避免缓存击穿导致数据库压力过大
RLock lock = redissonClient.getLock(DATA_LOCK_NAME + id);
lock.lock(15, TimeUnit.SECONDS);
if (lock.isLocked()) {
try {
//双重判断,第二个以及之后的请求不必去找数据库,直接命中缓存
//再次查询缓存
result = getDataFromCache(id);
if (result == null) {
// 从数据库查询数据
result = getDataFromDB(id);
// 将查询到的数据写入缓存
setDataToCache(id, result);
}
} finally {
//锁只能被拥有它的线程解锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
} else {
if (result.getId() == ID_NOT_EXISTS) {
return null;
}
}
return result;
}
数据库中有1-10000 的数据,我们手动删除1-100的数据,,模拟缓存大面积失效。
利用jmeter测试:300并发量,循环10次
查询了数据库200次数据库
public DataObject getData(Long id) {
//布隆过滤器中不存在,则直接返回空
if (!bloomFilteExists(String.valueOf(id))) {
System.out.println("布隆过滤器中不存在, id: " + id);
return null;
}
//从缓存读取数据
DataObject result = getDataFromCache(id);
if (result == null) {
//缓存不存在,从数据库查询数据的过程加上锁,避免缓存击穿导致数据库压力过大
RLock lock = redissonClient.getLock(DATA_LOCK_NAME + id);
lock.lock(15, TimeUnit.SECONDS);
if (lock.isLocked()) {
try {
//双重判断,第二个以及之后的请求不必去找数据库,直接命中缓存
//再次查询缓存
result = getDataFromCache(id);
if (result == null) {
// 从数据库查询数据
result = getDataFromDB(id);
// 将查询到的数据写入缓存
setDataToCache(id, result);
}
} finally {
//锁只能被拥有它的线程解锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
} else {
if (result.getId() == ID_NOT_EXISTS) {
return null;
}
}
return result;
}
假设id值为50的数据是一个高热点数据,我们手动删除它,模仿突然过期
删除:
curl http://127.0.0.1:8080/data/deleteCache?id=50
查询:
curl http://127.0.0.1:8080/data/getData1?id=50
与缓存雪崩类似
本人是菜鸟学生,在准备redis的技术分享,很多东西都是在网上查的加上自己的讲解,代码是从网上大佬趴下来改的。
参考:
https://blog.csdn.net/huchao_lingo/article/details/105552004
https://mp.weixin.qq.com/s/knz-j-m8bTg5GnKc7oeZLg
https://space.bilibili.com/130763764/search/video?keyword=%E7%BC%93%E5%AD%98
https://blog.csdn.net/qq_28743877/article/details/104721672
https://blog.csdn.net/weixin_43748936/article/details/110225696