redis在我们实际的场景中用到的地方非常多,经常的用于缓存。
大面积的使用缓存的时候,我们要考虑到:缓存的穿透、雪崩,还有要注意key的设计
* 1:模拟登陆的场景---用户输入账号登陆 第一次登陆的时候
* 缓存里肯定没有,这个时候从数据库去查询,如果查询到了,则返回的同时,并且把姓名放到缓存里去 这样下次再去登陆 的 时候,则从缓存里拿了
* (放到缓存里的key就是这个账号 value是客户的姓名) 2:如果有这样的一种场景:用户传进来的 账号,在缓存里也没 有,然后再数据库里也没有
* 在多线程并发的情况下,就会出现不停的查询数据库的场景,这个时候DB可能撑不住会直接挂了(最简单的就是连接池满 了,直接蹦了)
* 3:我们这个时候就需要去对这个key 进行处理,
* 如果根据这个key查不到,并且数据库里也查不到这个时候就给这个key缓存下,放一个空的值到redis里 这样下次 在进来
* 查询就可以直接从缓存里查询,发现返回一个空的,这个时候直接告诉客户的不存在即可。
* 4:但是 实际中还有这样的一种场景,就是这个key不是固定的随机的在变换 (===========有的人比较坏====比如随机 产生UUID的这样key,这个key根本就不存在),我们发现这个key不在缓存里,每次都不一样,这样还是会每次不停的去 查 询数据库,给DB造成相当大的压力
* 这就比较麻烦了。所以这里我们在查询缓存之前,在做一层拦截,确定一下这个key到底在不在数据库里,在数据库里,再去执行我们前面的逻辑,那么如何确定一个元素,在百万级别的数据库里到底是否存在呢?很显然我们不可能每次都要去数据库查询出在还是不在,这对DB同样是超级大的压力,我们需要一个方法或者工具来帮我们判断,这个元素到底在还是不在,这个时候我们引入一个过滤器-----布隆过滤器(底层是位图运算,大家感兴趣可以去搜索下,主要的应用是在海量的元素中寻找某一个元素是否存在)
* 5:在查询redis缓存之前,实现用布隆过滤器拦截一下
=========================分割线===============话不多说直接上我的演示代码=====================
附上一些伪代码,其实大部分代码都是可以用的,大家自己自行补上中文部分
import java.util.UUID;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test {
/**
* 由于我这个代码是拆分到 main方法里来的,大家实际的时候记得不要这么玩,代码会有报错的
*
* 部分中文的地方 请大家自行补上自己的代码
* */
static BloomFilter<String> bf;
@Autowired
@Qualifier("forObject")
private RedisTemplate<String, Object> redisTemplate;
public static void main(String[] args) {
innt();
String keyAccount = UUID.randomUUID().toString();
// 如果 布隆过滤器 认为可能含有 这个key ,才去执行 之前的操作
if (bf.mightContain(keyAccount)) {
createThread(keyAccount);
} else {
log.debug("不存在该账号:请检查数据================================");
}
}
/**
* 模拟 100个线程 同时访问登陆的方法 CyclicBarrier 用到JDK里这个的工具类
*/
public static void createThread(String key) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(100);
ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
getMyUserName(key);
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
}
public static String getMyUserName(String keyAccount) {
String name = "";
// 如果存在则再去执行原先的逻辑 ,否则直接返回不存在
// 当我们随机传一个key的时候 这个肯定肯定在 redis里不存,在数据库也不存在,这个时候redis就发生了缓存穿透
// 查询缓存
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
name = (String) redisTemplate.opsForValue().get(keyAccount);
// 双层检测锁
if (StringUtils.isBlank(name)) {
synchronized (this) {// 双层加锁 --
name = (String) redisTemplate.opsForValue().get(keyAccount);
if (StringUtils.isBlank(name)) {
// 缓存为空,查询数据库
返回值 result = 从数据库根据账号查询出姓名来
if (result != null && StringUtils.isNotBlank(result.getName())) {
// 把数据库查询出来的数据放入redis
redisTemplate.opsForValue().set(keyAccount, result.getName());
} else {
System.out.println("这个时候就发生缓存穿透了--------------------------------------》");
}
}
}
} else {
System.out.println("这个时候我们能从缓存里拿到的,不会直接去访问数据库");
System.out.println(name);
}
return name;
}
/**
* 初始化数据库的数据到 布隆过滤器里来
**/
public static void innt() {
返回值 result = 从数据库查询 实现的100万条数据到,然后加载到布隆过滤器里
// 从数据库里拿到数据 加载到布隆过滤器
bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), reult.size());
// 把数据放到布隆过滤器里
for (Useruser : reult) {
bf.put(user.getAccount());
}
}
}
=====================================user=================实体类===================
import java.io.Serializable;
import lombok.Data;
@Data
public class User implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private String account;
}
另外附上:redis序列化对象的以及基本的设置类
===============================RedisConfig类======================================
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean("forObject")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
// Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// ObjectMapper om = new ObjectMapper();
// om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
// template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
// template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
// template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
// template.setHashValueSerializer(jackson2JsonRedisSerializer);
// 采用 key 是stirng ,value是对象的方式
JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer();
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
template.setConnectionFactory(factory);
template.setKeySerializer(stringSerializer);
template.setValueSerializer(redisSerializer);
return template;
}
}
===============================RedisObjectSerializer 类======================================
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
/**
* 虽然以上的代码实现了 Redis 基础的数据操作,但是遗憾的是在 Java 开发领域内必须要考虑一个实际的问题,
* 那么就是对象 的序列化保存问题,毕竟 Redis 数据库的读写速度是非常快的,但是如果不能够进行对象的存储,
* 这样的存储意义就不大了,这样 就需要准备一个对象的序列化处理程序类,通过对象的形式进行数据的存储。
* 如果要想进行 Redis 对象序列化操作则一定要首先准备一个序列化处理程序类,这个程序类有实现要求:
* */
public class RedisObjectSerializer implements RedisSerializer<Object> {
// 为了方便进行对象与字节数组的转换,所以应该首先准备出两个转换器
private Converter<Object, byte[]> serializingConverter = new SerializingConverter();
private Converter<byte[], Object> deserializingConverter = new DeserializingConverter();
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; // 做一个空数组,不是null
@Override
public byte[] serialize(Object obj) throws SerializationException {
if (obj == null) { // 这个时候没有要序列化的对象出现,所以返回的字节数组应该就是一个空数组
return EMPTY_BYTE_ARRAY;
}
return this.serializingConverter.convert(obj); // 将对象变为字节数组
}
@Override
public Object deserialize(byte[] data) throws SerializationException {
if (data == null || data.length == 0) { // 此时没有对象的内容信息
return null;
}
return this.deserializingConverter.convert(data);
}
}
redis的连接配置,自己在项目里配置就好了,本身我这里是springCloud项目,所以只要在yaml里文件配置好了