现有50亿个电话号码,现有10万个电话号码,
如何要快速准确的判断这些电话号码是否已经存在?
1、通过数据库查询-------实现快速有点难。
2、数据预放到内存集合中:50亿*8字节大约40G,内存太大了。
布隆过滤器(英语:Bloom Filter)是 1970 年由布隆提出的。
它实际上是一个很长的二进制数组+一系列随机hash算法映射函数,主要用于判断一个元素是否在集合中。
通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景,一般想到的是将集合中所有元素保存起来,然后通过比较确定。
链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。
但是随着集合中元素的增加,我们需要的存储空间也会呈现线性增长,最终达到瓶颈。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为O(n),O(logn),O(1)。这个时候,布隆过滤器(Bloom Filter)就应运而生
结论备注
缓存穿透是什么
一般情况下,先查询缓存redis是否有该条数据,缓存中没有时,再查询数据库。
当数据库也不存在该条数据时,每次查询都要访问数据库,这就是缓存穿透。
缓存透带来的问题是,当有大量请求查询数据库不存在的数据时,就会给数据库带来压力,甚至会拖垮数据库。
可以使用布隆过滤器解决缓存穿透的问题
把已存在数据的key存在布隆过滤器中,相当于redis前面挡着一个布隆过滤器。
当有新的请求时,先到布隆过滤器中查询是否存在:
如果布隆过滤器中不存在该条数据则直接返回;
如果布隆过滤器中已存在,才去查询缓存redis,如果redis里没查询到则穿透到Mysql数据库
发现存在黑名单中的,就执行特定操作。比如:识别垃圾邮件,只要是邮箱在黑名单中的邮件,就识别为垃圾邮件。
假设黑名单的数量是数以亿计的,存放起来就是非常耗费存储空间的,布隆过滤器则是一个较好的解决方案。
把所有黑名单都放在布隆过滤器中,在收到邮件时,判断邮件地址是否在布隆过滤器中即可。
哈希函数的概念是:将任意大小的输入数据转换成特定大小的输出数据的函数,转换后的数据称为哈希值或哈希编码,也叫散列值
如果两个散列值是不相同的(根据同一函数)那么这两个散列值的原始输入也是不相同的。
这个特性是散列函数具有确定性的结果,具有这种性质的散列函数称为单向散列函数。
散列函数的输入和输出不是唯一对应关系的,如果两个散列值相同,两个输入值很可能是相同的,但也可能不同,这种情况称为“散列碰撞(collision)”。
用 hash表存储大数据量时,空间效率还是很低,当只有一个 hash 函数时,还很容易发生哈希碰撞。
布隆过滤器原理
布隆过滤器(Bloom Filter) 是一种专门用来解决去重问题的高级数据结构。
实质就是一个大型位数组和几个不同的无偏hash函数(无偏表示分布均匀)。由一个初值都为零的bit数组和多个个哈希函数构成,用来快速判断某个数据是否存在。但是跟 HyperLogLog 一样,它也一样有那么一点点不精确,也存在一定的误判概率
添加key时
使用多个hash函数对key进行hash运算得到一个整数索引值,对位数组长度进行取模运算得到一个位置,
每个hash函数都会得到一个不同的位置,将这几个位置都置1就完成了add操作。
查询key时
只要有其中一位是零就表示这个key不存在,但如果都是1,则不一定存在对应的key。
结论:
有,是可能有
无,是肯定无
当有变量被加入集合时,通过N个映射函数将这个变量映射成位图中的N个点,把它们置为 1(假定有两个变量都通过 3 个映射函数)。
查询某个变量的时候我们只要看看这些点是不是都是 1, 就可以大概率知道集合中有没有它了
如果这些点,有任何一个为零则被查询变量一定不在,
如果都是 1,则被查询变量很可能存在,
为什么说是可能存在,而不是一定存在呢?那是因为映射函数本身就是散列函数,散列函数是会有碰撞的。
布隆过滤器 本质上 是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0
当我们向布隆过滤器中添加数据时,为了尽量地址不冲突,会使用多个 hash 函数对 key 进行运算,算得一个下标索引值,然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。
例如,我们添加一个字符串wmyskxz
向布隆过滤器查询某个key是否存在时,先把这个 key 通过相同的多个 hash 函数进行运算,查看对应的位置是否都为 1,
只要有一个位为 0,那么说明布隆过滤器中这个 key 不存在;
如果这几个位置全都是 1,那么说明极有可能存在;
因为这些位置的 1 可能是因为其他的 key 存在导致的,也就是前面说过的hash冲突。。。。。
就比如我们在 add 了字符串wmyskxz数据之后,很明显下面1/3/5 这几个位置的 1 是因为第一次添加的 wmyskxz 而导致的;
此时我们查询一个没添加过的不存在的字符串inexistent-key,它有可能计算后坑位也是1/3/5 ,这就是误判了…
布隆过滤器的误判是指多个输入经过哈希之后在相同的bit位置1了,这样就无法判断究竟是哪个输入产生的,
因此误判的根源在于相同的 bit 位被多次映射且置 1。
这种情况也造成了布隆过滤器的删除问题,因为布隆过滤器的每一个 bit 并不是独占的,很有可能多个元素共享了某一位。
如果我们直接删除这一位的话,会影响其他的元素
特性
一个元素判断结果为没有时则一定没有,
如果判断结果为存在的时候元素不一定存在。
布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。
优点:
高效地插入和查询,占用空间少
缺点:
为了解决布隆过滤器不能删除元素的问题,布谷鸟过滤器横空出世。论文《Cuckoo Filter:Better Than Bloom》
作者将布谷鸟过滤器和布隆过滤器进行了深入的对比。相比布谷鸟过滤器而言布隆过滤器有以下不足:
查询性能弱、空间利用效率低、不支持反向操作(删除)以及不支持计数
在某一时刻出现大规模的key失效,导致大量的请求落在数据库上,导致数据库压力过大,可能瞬间就导致数据库宕机。
缓存穿透是指用户请求的数据再缓存中不存在,数据库中也不存在,导致请求都要去数据库中查一遍。如果有恶意攻击不断请求系统中不存在的数据,会导致大量请求落在数据库上,可能导致数据库宕机。
一般都OK
But黑客或者恶意攻击
.java
<!--guava Google 开源的 Guava 中自带的布隆过滤器--><dependency><groupId&le.guava</groupId><artifactId>guava</artifactId><version>23.0</version></dependency>
server.port=5555# =====================druid相关配置=======================
pe=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-namesql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://yourip/redis?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.st-while-idle=false# ====================redis相关配置======================
# Redis数据库索引(默认为0)
dis.database=0
# Redis服务器地址
dis.host=yourip
# Redis服务器连接端口
dis.port=6379
# Redis服务器连接密码(默认为空)
dis.password=
# 连接池最大连接数(使用负值表示没有限制) 默认-1,记得加入单位ms,不然idea报红色
dis.lettuce.pool.max-active=9
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1,记得加入单位ms,不然idea报红色
dis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接 默认8
dis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认0
dis.lettuce.pool.min-idle=0
@Testpublic void bloomFilter(){
// 创建布隆过滤器对象BloomFilter<Integer> filter = ate(Funnels.integerFunnel(), 100);
// 判断指定元素是否存在System.out.println(filter.mightContain(1));System.out.println(filter.mightContain(2));
// 将元素添加进布隆过滤器filter.put(1);filter.put(2);System.out.println(filter.mightContain(1));System.out.println(filter.mightContain(2));}
public class BloomfilterDemo{public static final int _1W = 10000;//布隆过滤器里预计要插入多少数据public static int size = 100 * _1W;//误判率,它越小误判的个数也就越少(思考,是不是可以设置的无限小,没有误判岂不更好)public static double fpp = 0.03;// 构建布隆过滤器private static BloomFilter<Integer> bloomFilter = ate(Funnels.integerFunnel(), size,fpp);public static void main(String[] args){//1 先往布隆过滤器里面插入100万的样本数据for (int i = 0; i < size; i++) {bloomFilter.put(i);}//故意取10万个不在过滤器里的值,看看有多少个会被认为在过滤器里List<Integer> list = new ArrayList<>(10 * _1W);for (int i = size+1; i < size + 100000; i++) {if (bloomFilter.mightContain(i)) {System.out.println(i+"t"+"被误判了.");list.add(i);}}System.out.println("误判的数量:" + list.size());}}
现在总共有10万数据是不存在的,误判了3033次,
原始样本:100W
不存在数据:101W—110W
我们计算下误判率:===========》
从这里我们可以看到源码中默认会设置0.03的误判率,并且0.03的误判率会使用5个hash函数
fpp(误判率): False positive probability
查看误判率为0.03的100W个元素的布隆过滤器的创建:
查看误判率为0.01的100W个元素的布隆过滤器的创建:
对比:
误判率越高,所需bit数组就越大,hash函数就越多,程序效率越低。
Guava 提供的布隆过滤器的实现还是很不错的 (想要详细了解的可以看一下它的源码实现),但是它有一个重大的缺陷就是只能单机使用 ,而现在互联网一般都是分布式的场景。
为了解决这个问题,我们就需要用到 Redis 中的布隆过滤器了
<!-- redisson --><dependency><groupId&disson</groupId><artifactId>redisson</artifactId><version>3.13.4</version></dependency>
public class RedissonBloomFilterDemo {public static final int _1W = 10000;//布隆过滤器里预计要插入多少数据public static int size = 100 * _1W;//误判率,它越小误判的个数也就越少public static double fpp = 0.03;static RedissonClient redissonClient = null;//jedisstatic RBloomFilter rBloomFilter = null;//redis版内置的布隆过滤器@ResourceRedisTemplate redisTemplate;static {Config config = new Config();config.useSingleServer().setAddress("redis://xxx:6379").setDatabase(0);//构造redissonredissonClient = ate(config);//通过redisson构造rBloomFilterrBloomFilter = BloomFilter("phoneListBloomFilter",new StringCodec());Init(size,fpp);// 1测试 布隆过滤器有+redis有rBloomFilter.add("10086");Bucket("10086",new StringCodec()).set("chinamobile10086");// 2测试 布隆过滤器有+redis无//rBloomFilter.add("10087");//3 测试 ,布隆过滤器无+redis无}private static String getPhoneListById(String IDNumber) {String result = null;if (IDNumber == null) {return null;}//1 先去布隆过滤器里面查询if (ains(IDNumber)) {//2 布隆过滤器里有,再去redis里面查询RBucket<String> rBucket = Bucket(IDNumber, new StringCodec());result = ();if(result != null) {return "i come from redis: "+result;}else{result = getPhoneListByMySQL(IDNumber);if (result == null) {return null;}// 重新将数据更新回Bucket(IDNumber, new StringCodec()).set(result);}return "i come from mysql: "+result;}return result;}private static String getPhoneListByMySQL(String IDNumber){return "chinamobile"+IDNumber;}public static void main(String[] args) {String phoneListById = getPhoneListById("10086");//String phoneListById = getPhoneListById("10087"); //请测试执行2次//String phoneListById = getPhoneListById("10088");System.out.println("------查询出来的结果: "+phoneListById);//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }redissonClient.shutdown();}}
布隆过滤器有,redis中也有
布隆过滤器有,redis也没有
第一次测试:
第二次测试
布隆过滤器没有,redis也没有
docker run -p 6378:6378 --name=bloomfilter -d redislabs/rebloom
docker exec -it bloomfilter /bin/bash
redis-cli
# 下载 编译 安装Rebloom插件
wget .2.
# 解压
tar -zxvf v2.2.
cd RedisBloom-2.2.2
# 若是第一次使用 需要安装gcc++环境
make
# redis服启动添加对应参数 这样写还是挺麻烦的
# rebloom_module="/usr/local/rebloom/rebloom.so"
# daemon --user ${REDIS_USER-redis} "$exec $REDIS_CONFIG --loadmodule # $rebloom_module --daemonize yes --pidfile $pidfile"
# 记录当前位置
pwd
# 进入reids目录 配置在f中 更加方便
f
# :/loadmodule redisbloom.so是刚才具体的pwd位置 cv一下
loadmodule /xxx/redis/redis-5.0.8/RedisBloom-2.2.2/redisbloom.so
# 保存退出
wq
# 重新启动redis-server 我是在redis中 操作的 若不在请写出具体位置 不然会报错
f
# 连接容器中的 redis 服务 若是无密码 redis-cli即可
redis-cli -a 密码
# 进入可以使用BF.ADD命令算成功
缓存击穿就是某个热点key突然失效,然后大量的请求读缓存没有读取到,从而导致高并发访问数据库,引起数据库压力剧增。这种现象就叫做缓存击穿。
案例网址
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0"xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><parent><artifactId>redis</artifactId><groupId>com.hmx</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>redis_20210511</artifactId><properties><mavenpiler.source>8</mavenpiler.source><mavenpiler.target>8</mavenpiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version><hutool.version>5.5.8</hutool.version><druid.version>1.1.18</druid.version><mapper.version>4.1.5</mapper.version><pagehelper.version>5.1.4</pagehelper.version><mysql.version>8.0.25</mysql.version><swagger2.version>3.0.0</swagger2.version><mybatis.spring.version>2.1.1</mybatis.spring.version><junit.version>4.13.2</junit.version></properties><dependencies><!--guava Google 开源的 Guava 中自带的布隆过滤器--><dependency><groupId&le.guava</groupId><artifactId>guava</artifactId><version>23.0</version></dependency><!--加密和解密--><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.10</version></dependency><!-- redisson --><dependency><groupId&disson</groupId><artifactId>redisson</artifactId><version>3.13.4</version></dependency><!--使用 Spring Boot 的 Actuator 的 Starter,它提供了生产就绪的特性来帮助你监控和管理你的应用程序--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency><!--SpringBoot与redis整合--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--SpringCache Spring框架的缓存支持--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!--SpringCache连接池依赖包--><dependency><groupId>org.apachemons</groupId><artifactId>commons-pool2</artifactId></dependency><!--jedis--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.6.1</version></dependency><!--springboot集成druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><!--mybatis和springboot整合--><dependency><groupId&batis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis.spring.version}</version></dependency><!--springboot整合RabbitMQ--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!--通用基础配置junit/devtools/test/log4j/lombok/hutool--><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.7</version></dependency><!--junit--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope><version>${junit.version}</version></dependency><!--热部署--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--通用Mapper tk单独使用,自己带着版本号--><dependency><groupId&batis</groupId><artifactId>mapper</artifactId><version>${mapper.version}</version></dependency><!--mybatis自带版本号--><dependency><groupId&batis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId><version>1.0.2</version></dependency><!--数据库连接--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><resources><resource><directory>${basedir}/src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>${basedir}/src/main/resources</directory></resource></resources><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.5.2</version></plugin></plugins></build></project>
# 项目启动端口
server.port=5555# 项目名
spring.application.name=redis0511# ======================Logging 日志相关的配置======================
# 系统默认,全局root配置的日志形式,可以注释掉
=warn
# 开发人员自己设置的包结构,对那个package进行什么级别的日志监控
logging.dis=info
# 开发人员自定义日志路径和日志名称
logging.file.name=E:/mylog/logs/redis1026.log
#%d{HH:mm:ss.SSS} - 日志输出时间
#%thread - 输出日志的进程名字,这在web应用以及异步任务处理中很有用
#%-5level - 日志级别,并且使用5个字符串靠左对齐
#%Logger - 日志输出者的名字
#%msg - 日志消息
#%n - 平台的换行符
#sole=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %Logger- %msg%n
sole=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %Logger- %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %Logger- %msg%n# =====================druid相关配置=======================
pe=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-namesql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://yourip/redis?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.st-while-idle=false# ====================redis相关配置======================
# Redis数据库索引(默认为0)
dis.database=0
# Redis服务器地址
dis.host=yourip
# Redis服务器连接端口
dis.port=6379
# Redis服务器连接密码(默认为空)
dis.password=
# 连接池最大连接数(使用负值表示没有限制) 默认-1,记得加入单位ms,不然idea报红色
dis.lettuce.pool.max-active=9
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1,记得加入单位ms,不然idea报红色
dis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接 默认8
dis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认0
dis.lettuce.pool.min-idle=0# =========mybatis相关配置===============
mybatis.mapper-locations=classpath:com/hmx/redis/mapper/*.xml
pe-aliases-package=ity# ==========swagger================
abled=true# ================rabbitmq相关配置===============
#spring.rabbitmq.host=127.0.0.1
#spring.rabbitmq.port=5672
#spring.rabbitmq.username=guest
#spring.rabbitmq.password=guest
#spring.rabbitmq.virtual-host=/# ================redis 布隆过滤器相关配置====================
#redis.bloom.url=192.168.111.147
#redis.bloom.post=6379
#redis.bloom.init-capacity=10000
#-rate=0.01
配置RedisConfig
@Configuration
@Slf4j
public class RedisConfig {@Beanpublic RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(lettuceConnectionFactory);//设置key序列化方式stringredisTemplate.setKeySerializer(new StringRedisSerializer());//设置value的序列化方式为jsonredisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}}
entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "聚划算活动producet信息")
public class Product {private Long id;/*** 产品名称*/private String name;/*** 产品价格*/private Integer price;/*** 产品详情*/private String detail;}
常量:
public class Constants {public static final String JHS_KEY="jhs";public static final String JHS_KEY_A="jhs:a";public static final String JHS_KEY_B="jhs:b";public static final String REBLOOM="rebloom:uid:";}
采用定时器将参与聚划算活动的特价商品新增进入redis中
@Service
@Slf4j
public class JHSABTaskService {@Autowiredprivate RedisTemplate redisTemplate;//@PostConstruct ,平时注释,用时打开public void initJHSAB(){log.info("启动AB定时器计划任务淘宝聚划算功能模拟.........."w());new Thread(() -> {//模拟定时器,定时把数据库的特价商品,刷新到redis中while (true){//模拟从数据库读取100件特价商品,用于加载到聚划算的页面中List<Product> list=this.products();//先更新B缓存disTemplate.delete(Constants.JHS_KEY_B);disTemplate.opsForList().leftPushAll(Constants.JHS_KEY_B,list);pire(Constants.JHS_KEY_B,20L,TimeUnit.DAYS);//间隔一分钟 执行一遍try { TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }log.info("runJhs定时刷新..............");}},"t1").start();}/*** 模拟从数据库读取100件特价商品,用于加载到聚划算的页面中*/public List<Product> products() {List<Product> list=new ArrayList<>();for (int i = 1; i <=20; i++) {Random rand = new Random();int id= Int(10000);Product obj=new Product((long) id,"product"+i,i,"detail");list.add(obj);}return list;}
}
controller
@RestController
@Slf4j
@Api(value = "聚划算商品列表接口")
public class JHSProductController {@Autowiredprivate RedisTemplate redisTemplate;/*** 分页查询:在高并发的情况下,只能走redis查询,走db的话必定会把db打垮* localhost:5555/swagger-ui.html#/jhs-product-controller/findUsingGET*/@RequestMapping(value = "/product/find",method = RequestMethod.GET)@ApiOperation("按照分页和每页显示容量,点击查看")public List<Product> find(int page, int size) {List<Product> list=null;long start = (page - 1) * size;long end = start + size - 1;try {//采用redis list数据结构的lrange命令实现分页查询list = disTemplate.opsForList().range(Constants.JHS_KEY_B, start, end);if (CollectionUtils.isEmpty(list)) {//TODO 走DB查询}log.info("查询结果:{}", list);} catch (Exception ex) {//这里的异常,一般是redis瘫痪 ,或 redis网络("exception:", ex);//TODO 走DB查询}return list;}}
Service
@Service
@Slf4j
public class JHSABTaskService {@Autowiredprivate RedisTemplate redisTemplate;@PostConstruct//平时注释,用时打开public void initJHSAB(){log.info("启动AB定时器计划任务淘宝聚划算功能模拟.........."w());new Thread(() -> {//模拟定时器,定时把数据库的特价商品,刷新到redis中while (true){//模拟从数据库读取20件特价商品,用于加载到聚划算的页面中List<Product> list=this.products();//先更新B缓存disTemplate.delete(Constants.JHS_KEY_B);disTemplate.opsForList().leftPushAll(Constants.JHS_KEY_B,list);pire(Constants.JHS_KEY_B,20L,TimeUnit.DAYS);//再更新A缓存disTemplate.delete(Constants.JHS_KEY_A);disTemplate.opsForList().leftPushAll(Constants.JHS_KEY_A,list);pire(Constants.JHS_KEY_A,15L,TimeUnit.DAYS);//间隔一分钟 执行一遍try { TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }log.info("runJhs定时刷新..............");}},"t1").start();}/*** 模拟从数据库读取100件特价商品,用于加载到聚划算的页面中*/public List<Product> products() {List<Product> list=new ArrayList<>();for (int i = 1; i <=20; i++) {Random rand = new Random();int id= Int(10000);Product obj=new Product((long) id,"product"+i,i,"detail");list.add(obj);}return list;}
}
Controller
@RestController
@Slf4j
@Api(value = "聚划算商品列表接口AB")
public class JHSABProductController {@Autowiredprivate RedisTemplate redisTemplate;@RequestMapping(value = "/pruduct/findab",method = RequestMethod.GET)@ApiOperation("按照分页和每页显示容量,点击查看AB")public List<Product> findAB(int page, int size) {List<Product> list=null;long start = (page - 1) * size;long end = start + size - 1;try {//采用redis list数据结构的lrange命令实现分页查询list = disTemplate.opsForList().range(Constants.JHS_KEY_A, start, end);if (CollectionUtils.isEmpty(list)) {log.info("=========A缓存已经失效了,记得人工修补,B缓存自动延续5天");//用户先查询缓存A(上面的代码),如果缓存A查询不到(例如,更新缓存的时候删除了),再查询缓存disTemplate.opsForList().range(Constants.JHS_KEY_B, start, end);}log.info("查询结果:{}", list);} catch (Exception ex) {//这里的异常,一般是redis瘫痪 ,或 redis网络("exception:", ex);//TODO 走DB查询}return list;}}
本文发布于:2024-01-29 05:37:12,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170647783313072.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |