redit允许的数据结构
String、Hash、List、Set、SortedSet。
相关文章:https://juejin.im/post/5db66ed9e51d452a2f15d833
BloomFilter原理(布隆过滤器)
布隆过滤器(英语:Bloom Filter)是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
上述描述引自维基百科,特点总结为如下:
- 空间效率高的概率型数据结构,用来检查一个元素是否在一个集合中
- 对于一个元素检测是否存在的调用,BloomFilter会告诉调用者两个结果之一:可能存在或者一定不存在。
相关文章:http://oserror.com/backend/bloomfilter/
总结:
- BloomFilter 仅仅只能确定该元素可能存在,存在false positive现象。
- BloomFilter对于某些数据能确定一定不存在。(效率很高)
- BloomFilter不允许自删的情况
- BloomFilter的大小有公式可查
- 其优势相对于:set、hash、bit。在允许一定的错判的情况下,其效率很高
常见使用常见:防止击穿
参考:https://juejin.im/post/5db69365518825645656c0de
https://blog.csdn.net/jdsjlzx/article/details/43916241
Guava 中已经存在封装的布隆过过滤器
参考地址:https://juejin.im/post/5cc5aa7ce51d456e431adac5
https://www.jianshu.com/p/b60b0a3e8b9c
package com.ocean.cache;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import com.ocean.dao.UserDao;
import com.ocean.dto.UserDto;
/**
* 缓存击穿
* @author
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/spring/spring-dao.xml",
"classpath:config/spring/spring-bean.xml",
"classpath:config/spring/spring-redis.xml"})
public class CacheBreakDownTest {
private static final Logger logger = LoggerFactory.getLogger(CacheBreakDownTest.class);
private static final int THREAD_NUM = 100;//线程数量
@Resource
private UserDao UserDao;
@Resource
private RedisTemplate redisTemplate;
private int count = 0;
//初始化一个计数器
private CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
private BloomFilter<String> bf;
List<UserDto> allUsers;
@PostConstruct
public void init(){
//将数据从数据库导入到本地
allUsers = UserDao.getAllUser();
if(allUsers == null || allUsers.size()==0){
return;
}
//创建布隆过滤器(默认3%误差)
bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), allUsers.size());
//将数据存入布隆过滤器
for(UserDto userDto : allUsers){
bf.put(userDto.getUserName());
}
}
@Test
public void cacheBreakDownTest(){
for(int i=0;i<THREAD_NUM;i++){
new Thread(new MyThread()).start();
//计数器减一
countDownLatch.countDown();
}
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class MyThread implements Runnable{
@Override
public void run() {
try {
//所有子线程等待,当子线程全部创建完成再一起并发执行后面的代码
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//随机产生一个字符串
String randomUser = UUID.randomUUID().toString();
// String randomUser = allUsers.get(new Random().nextInt(allUsers.size())).getUserName();
String key = "Key:"+randomUser;
//如果布隆过滤器中不存在这个用户直接返回,将流量挡掉
if(!bf.mightContain(randomUser)){
System.out.println("bloom filter don't has this user");
return;
}
//查询缓存,如果缓存中存在直接返回缓存数据
ValueOperations<String,String> operation = (ValueOperations<String, String>) redisTemplate.opsForValue();
synchronized (countDownLatch) {
Object cacheUser = operation.get(key);
if(cacheUser!=null){
System.out.println("return user from redis");
return;
}
//如果缓存不存在查询数据库
List<UserDto> user = UserDao.getUserByUserName(randomUser);
if(user == null || user.size() == 0){
return;
}
//将mysql数据库查询到的数据写入到redis中
System.out.println("write to redis");
operation.set("Key:"+user.get(0).getUserName(), user.get(0).getUserName());
}
}
}
}
雪崩、穿透和击穿:
雪崩:缓存失效,大量请求打到数据库中。数据库也接着失效。
缓存穿透:缓存中查不到,每次你去数据库里查,也查不到。
缓存击穿:缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
参考文章:https://zhuanlan.zhihu.com/p/74880843
Redis 语法
参考文章:https://www.runoob.com/redis/redis-sorted-sets.html
redis多线程情况下避免读脏数据的悲观锁解决方案
学习地址:http://www.yelook.com/2227.html
redis 中能实现插旗子的语句叫做 setnx 。用法是 SETNX key value 。实现的功能是,当 key 值在 redis 库中不存在时,则建立这个 key 并设置对应的 value,且返回1。如果 key 值在库中存在时,则属于set失败,会返回0。
setnx 这个命令可以视为插旗子的动作,既可以检查旗子在不在,又能做到如果没有旗子在的时候插上去,此命令具有原子性。
所以思路图如下: