Redis

一、Redis相关操作

1.docker安装redis

1
2
3
docker pull redis
docker run -d --name redis -p 6379:6379 redis
docker exec -it redis redis-cli

2.redis配置

1
2
3
4
进入解压的Redis目录,将redis.conf复制到安装文件的目录下
cp redis.conf /usr/local/redis
启动自定义配置的Redis
/usr/local/redis/bin/redis-server /usr/local/redis/redis.conf

3.配置详解

1
2
3
4
5
6
7
8
9
10
11
12
13
daemonize : 默认为no,修改为yes启用守护线程
port :设定端口号,默认为6379
bind :绑定IP地址
databases :数据库数量,默认16
save <second> <changes> :指定多少时间、有多少次更新操作,就将数据同步到数据文件
#redis默认配置有三个条件,满足一个即进行持久化
save 900 1 #900s有1个更改
save 300 10 #300s有10个更改
save 60 10000 #60s有10000更改
dbfilename :指定本地数据库的文件名,默认为dump.rdb
dir :指定本地数据库的存放目录,默认为./当前文件夹
requirepass :设置密码,默认关闭
redis -cli -h host -p port -a password

4.redis常用命令

redis五种数据类型:string、hash、list、set、zset

  • DEL key
  • Dump key:序列化给定key,返回被序列化的值
  • EXIST key:检查KEY是否存在
  • EXPIRE key second:为key设定过期时间
  • TTL key:返回key剩余时间
  • PERSIST key:移除key的过期时间,持久保存
  • KEY pattern:查询所有符号给定模式的key
  • RENAME key:修改key的名称
  • MOVE key db:移动key到指定数据库中
  • TYPE key:返回key所存储的值的类型
1
2
3
4
5
EXPIRE key second的使用场景:
1、限时的优惠活动
2、网站数据缓存
3、手机验证码
4、限制网站访客频率

key的命名建议

  1. key不要太长,尽量不要超过1024字节。不仅消耗内存,也会降低查找的效率
  2. key不要太短,太短可读性会降低
  3. 在一个项目中,key最好使用统一的命名模式,如user:123:password
  4. key区分大小写

string

string类型是二进制安全的,redis的string可以包含任何数据,如图像、序列化对象。一个键最多能存储512MB。二进制安全是指,在传输数据的时候,能保证二进制数据的信息安全,也就是不会被篡改、破译;如果被攻击,能够及时检测出来。

常用命令

  • setkey_name value:命令不区分大小写,但是key_name区分大小写
  • SETNX key value:当key不存在时设置key的值。(SET if Not eXists)
  • get key_name
  • GETRANGE key start end:获取key中字符串的子字符串,从start开始,end结束
  • **MGET key1 [key2 …]**:获取多个key
  • GETSET KEY_NAME VALUE:设定key的值,并返回key的旧值。当key不存在,返回nil
  • STRLEN key:返回key所存储的字符串的长度
  • INCR KEY_NAME :INCR命令key中存储的值+1,如果不存在key,则key中的值话先被初始化为0再加1
  • INCRBY KEY_NAME 增量
  • DECR KEY_NAME:key中的值自减一
  • DECRBY KEY_NAME
  • append key_name value:字符串拼接,追加至末尾,if不存在,为其赋值

string的应用场景

1、String通常用于保存单个字符串或JSON字符串数据

2、因为String是二进制安全的,所以可以把保密要求高的图片文件内容作为字符串来存储

3、计数器:常规Key-Value缓存应用,如微博数、粉丝数。INCR本身就具有原子性特性,所以不会有线程安全问题 。

hash

Redis hash是一个string类型的field和value的映射表,hash特别适用于存储对象。每个hash可以存储232-1键值对。可以看成KEY和VALUE的MAP容器。相比于JSON,hash占用很少的内存空间。

常用命令

  • HSET key_name field value:为指定的key设定field和value
  • hmset key field value[field1,value1]
  • hget key field
  • hmget key field[field1]
  • hgetall key:返回hash表中所有字段和值
  • hkeys key:获取hash表所有字段
  • hlen key:获取hash表中的字段数量
  • -hdel key field [field1]:删除一个或多个hash表的字段

hash的应用场景

hash的应用场景,通常用来存储一个用户信息的对象数据

1、相比于存储对象的string类型的json串,json串修改单个属性需要将整个值取出来。而hash不需要

2、相比于多个key-value存储对象,hash节省了很多内存空间

3、如果hash的属性值被删除完,那么hash的key也会被redis删除

list

类似于java中的LinkedList

常用命令

  • lpush key value1 [value2]
  • rpush key value1 [value2]
  • lpushx key value:从左侧插入值,如果list不存在,则不操作
  • rpushx key value:从右侧插入值,如果list不存在,则不操作
  • llen key:获取列表长度
  • lindex key index:获取指定索引的元素
  • lrange key start stop:获取列表指定范围的元素
  • lpop key :从左侧移除第一个元素
  • prop key:移除列表最后一个元素
  • blpop key [key1] timeout:移除并获取列表第一个元素,如果列表没有元素会阻塞列表到等待超时或发现可弹出元素为止
  • brpop key [key1] timeout:移除并获取列表最后一个元素,如果列表没有元素会阻塞列表到等待超时或发现可弹出元素为止
  • ltrim key start stop :对列表进行修改,让列表只保留指定区间的元素,不在指定区间的元素就会被删除
  • lset key index value :指定索引的值
  • linsert key before|after world value:在列表元素前或则后插入元素

应用场景

1、对数据大的集合数据删减 列表显示、关注列表、粉丝列表、留言评价…分页、热点新闻等

2、任务队列 list通常用来实现一个消息队列,而且可以确保先后顺序,不必像MySQL那样通过order by来排序

补充

rpoplpush list1 list2 移除list1最后一个元素,并将该元素添加到list2并返回此元素。用此命令可以实现订单下单流程、用户系统登录注册短信等。

set

唯一、无序

常用命令

  • sadd key value1[value2]:向集合添加成员
  • scard key:返回集合成员数
  • smembers key:返回集合中所有成员
  • sismember key member:判断memeber元素是否是集合key成员的成员
  • srandmember key [count]:返回集合中一个或多个随机数
  • srem key member1 [member2]:移除集合中一个或多个成员
  • spop key:移除并返回集合中的一个随机元素
  • smove source destination member:将member元素从source集合移动到destination集合
  • sdiff key1 [key2]:返回所有集合的差集
  • sdiffstore destination key1[key2]:返回给定所有集合的差集并存储在destination中

应用场景

对两个集合间的数据【计算】进行交集、并集、差集运算

1、以非常方便的实现如共同关注、共同喜好、二度好友等功能。对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存储到一个新的集合中。

2、利用唯一性,可以统计访问网站的所有独立 IP

zset

有序且不重复、每个元素都会关联一个double类型的分数,redis通过分数进行从小到大的排序。分数可以重复

常用命令

  • ZADD key score1 memeber1
  • ZCARD key :获取集合中的元素数量
  • ZCOUNT key min max 计算在有序集合中指定区间分数的成员数
  • ZCOUNT key min max 计算在有序集合中指定区间分数的成员数
  • ZRANK key member:返回有序集合指定成员的索引
  • ZREVRANGE key start stop :返回有序集中指定区间内的成员,通过索引,分数从高到底
  • ZREM key member [member …] 移除有序集合中的一个或多个成员
  • ZREMRANGEBYRANK key start stop 移除有序集合中给定的排名区间的所有成员(第一名是0)(低到高排序)
  • ZREMRANGEBYSCORE key min max 移除有序集合中给定的分数区间的所有成员

应用场景

常用于排行榜

1、推特可以以发表时间作为score存储

2、存储成绩

3、做带权重的队列,让重要的任务先执行

二、SpringBoot整合Redis

基于内存进行存储,支持key-value的存储形式,底层是用C语言写的。

基于key-value形式的数据字典,结构非常简单,没有数据表的概念,直接用键值对的形式完成数据的管理,支持5种数据类型:

  • 字符串
  • 列表
  • 集合
  • 有序集合
  • 哈希
off
1
2
3
4
5
6
7
8
//Redis启动批处理程序
title redis-server
set ENV_HOME="C:\redis"
C:
color 0F
cd %ENV_HOME%
redis-server redis.windows.conf
exit

Spring Boot 整合 Redis

实际上使用Spring Data Redis操作Redis,

1、创建maven工程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.hhzhu</groupId>
<artifactId>redis_practice</artifactId>
<version>1.0-SNAPSHOT</version>

<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.5.RELEASE</version>

</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>


</project>

2、创建实体类,实现序列化接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.hhzhu.pojo;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
* Description:
*
* @author ZhuHh
* @data Create on 2020/3/15
*/
@Data
//实现序列化接口,否则无法存入redis
public class Student implements Serializable {
private Integer id;
private String name;
private Double score;
private Date birthday;
}

3、创建控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.hhzhu.controller;

import com.hhzhu.pojo.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
* Description:
*
* @author ZhuHh
* @data Create on 2020/3/15
*/
@RestController
public class StudentHandler {

@Autowired
private RedisTemplate redisTemplate;

@PostMapping("/set")
//Request将json数据转换成java对象
public void set(@RequestBody Student student){
redisTemplate.opsForValue().set("student",student);

}
}

4、创建配置文件appliacation.yml

1
2
3
4
5
spring:
redis:
database: 0
host: localhost
port: 6379

5、创建启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.hhzhu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* Description:
*
* @author ZhuHh
* @data Create on 2020/3/15
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}

6、CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.hhzhu.controller;

import com.hhzhu.pojo.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

/**
* Description:
*
* @author ZhuHh
* @data Create on 2020/3/15
*/
@RestController
public class StudentHandler {

@Autowired
private RedisTemplate redisTemplate;

@PostMapping("/set")
//Request将json数据转换成java对象
public void set(@RequestBody Student student){
redisTemplate.opsForValue().set("student",student);

}

@GetMapping("/get/{key}")
public Student get(@PathVariable("key") String key){
return (Student) redisTemplate.opsForValue().get(key);
}

@DeleteMapping("/delete/{key}")
public boolean delete(@PathVariable("key") String key){
redisTemplate.delete(key);
return redisTemplate.hasKey(key);
}
}

Redis 5种数据类型

  • 字符串

    1
    2
    3
    4
    5
    6
    7
    @GetMapping("/string")
    public String stringTest(){
    redisTemplate.opsForValue().set("str","Hello world");
    //System.out.println(redisTemplate.opsForValue().get("str"));
    String str = (String) redisTemplate.opsForValue().get("str");
    return str;
    }
  • 列表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @GetMapping("list")
    public List<String> listTest(){
    ListOperations<String,String> listOperations = redisTemplate.opsForList();
    listOperations.leftPush("list","hello");
    listOperations.leftPush("list","world");
    listOperations.rightPush("list","java");

    List<String> list = listOperations.range("list",0,2);
    return list;
    }
  • 集合(set)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @GetMapping("/set")
    public Set<String> setTest(){
    SetOperations<String,String> setOperations = redisTemplate.opsForSet();
    setOperations.add("set","Hello");
    setOperations.add("set","Hello");
    setOperations.add("set","world");
    setOperations.add("set","world");
    setOperations.add("set","java");
    setOperations.add("set","java");

    Set<String> set = setOperations.members("set");
    return set;
    }
  • 有序集合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @GetMapping("/zset")
    public Set<String> zsetTest(){
    ZSetOperations<String,String> zSetOperations = redisTemplate.opsForZSet();
    zSetOperations.add("zset","Hello",1);
    zSetOperations.add("zset","world",2);
    zSetOperations.add("zset","java",3);

    Set<String> set = zSetOperations.range("zset",0,2);
    return set;
    }
  • 哈希

    Hash:key value

    HashOperations:key hashkey value

    key是每一组数据的ID,hashkey和value是一组完整的HashMap数据,通过key来区分不同的HashMap未命名文件 _1_.png

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    HashMap hashMap = new HashMap();
    hashMap.put(key1,value1);
    HashMap hashMap = new HashMap();
    hashMap.put(key2,value2);
    HashMap hashMap = new HashMap();
    hashMap.put(key3,value3);

    HashOperations<String,String,String> hashOperation = redisTemplate.opsForHash();
    hashOperation.put(hashMap1,key1,value1);
    hashOperation.put(hashMap2,key2,value2);
    hashOperation.put(hashMap3,key3,value3);
    1
    2
    3
    4
    5
    6
    7
    @GetMapping("/hash")
    public void hashTest(){
    HashOperations<String,String,String> hashOperations = redisTemplate.opsForHash();
    hashOperations.put("key","hashKey","Hello");
    System.out.println(hashOperations.get("key","hashKey"));

    }

三、Redis常见问题

简单介绍

使用C语言开发的k-v数据库,数据存储在内存,读写速度很快,一般用于缓存方向,也可以用来做分布式锁,消息队列。提供了五中数据类型String,hash,list,set,zset。支持事务,持久化,lua脚本,多重集群方案。

分布式缓存常见的技术选型

Memcached和Redis。

前者是分布式缓存刚兴起,后来随着Redis发展,都是用redis了。分布式缓存主要解决的问题是,单击缓存的容量收到服务器限制且无法保存通用的消息。因为本地缓存只在当前服务有效。

redis和Memcached的区别和共同点

共同点:

  • 基于内存
  • 过期策略
  • 高性能

区别:

  • redis数据类型更丰富-五种,后者只是简单的k-v
  • redis数据持久化,重启的时候可以再次加载使用。后者单纯放在内存。
  • redis灾难恢复机制。因为可以持久化
  • redis在内存用完之后,可以将数据放磁盘,后者直接报异常。
  • M没有原生的集群模式。redis原生支持cluster模式
  • M多线程非阻塞IO复用的网络模型;R单线程多路IO复用模型(R6.0之后引入多线程)
  • R支持发布订阅模型,Lua脚本,事务。M不支持。
  • M过期数据只用了惰性删除,R使用了惰性删除和定期删除。

缓存数据的处理流程

  • 数据在缓存直接返回
  • 不在缓存查数据库
  • 数据库存在更新缓存
  • 数据库不在返回空

为什么用R缓存

高性能:

直接访问数据库会比较慢,因为要从次硬盘读取。对于某些不常改变的高频数据,可以放在缓存中,访问时直接存缓存读取,这样比较快。需要保证数据一致性,当数据库的数据改变时,缓存的数据也要同步更新。

高并发:

MYSQL的QPS在1W左右(4core 8G),使用R之后可以达到10W-30W+。

QPS:服务器每秒可以执行的查询次数。

R单线程模型

Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据 套接字目前执行的任务来为套接字关联不同的事件处理器。

当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。

可以看出,文本事件处理器主要是4个部分:

  • 多个socket(客户端连接)
  • IO多路复用(支持多个客户端连接的关键)
  • 文本事件分派器(将socket关联到相应的时间处理器)
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

image-20210325164739624

R为什么不使用多线程

单线程模型,但是在4.0之后就加入了对多线程的支持。但是多线程主要是针对一些大键值对的删除操作的命令。

大体来说R还是单线程处理,为何不使用多线程?

  • 单线程编程容易且易维护
  • 性能瓶颈不在CPU,在于内存和网络
  • 多线程存在死锁,线程上下文切换问题,甚至影响性能。

为什么6.0引入多线程。

主要是为了提高网络IO性能瓶颈。

虽然6.0引入多线程,但是只在网路数据的读写这类耗时操作使用,执行命令仍然是单线程。默认是禁用的。

开多线程之后,还需要设置线程数,否则不生效。

1
io-threads 4 #官网建议4核的机器建议设置为2或3个线程,8核的建议设置为6个线程

缓存数据设置过期时间有啥用

内存有限。

业务场景需要。(token,短信验证码)

R如何判断数据是否过期呢?

过期字典来保存过期时间。过期字典的k指向数据库的某个key,保存了数据库键的过期时间(毫秒精度的unix时间戳)

过期数据的删除策略

  • 惰性删除:取出key的时候过期检查。CPU友好,但是可能太多过期K没删除
  • 定期删除:每隔一段时间抽取K删除过期K。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。

一种是内存友好,一种CPU友好,所以R是定期+惰性。

仅仅通过K的过期时间有问题,因为可能存在定期删除和惰性漏掉的问题,这样导致大量过期K堆积在内存。–内存淘汰机制。

内存淘汰机制

  • volatile-lru:从已设置过期时间的数据集挑选最少使用的数据淘汰
  • V-ttl:从已设置过期时间的数据集挑选将要过期的数据淘汰。
  • V-random:字面意思
  • allkeys-lru:当内存不足以容纳写入新数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  • a-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-evition:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

4.0版本以后增加了两种:

  • v-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  • a-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

持久化机制

快照和只追加文件

  • 快照持久化

    通过创建快照的方式获得存储在内存里面的数据的某个时间节点的副本。创建快照后可以进行备份,或者复制到其他服务器从而常创建具有相同数据的服务器副本(主从结构)。

    快照是默认的持久化方式

  • AOF持久化

    实时性更好,成为主流的方案,默认不开启,通过appendonly yes开启。

    开启后,每次执行一条更改Redis的命令,redis就会将命写入AOF文件。默认的文件时appendonly.aof。

    在redis存在三种不同的AOF持久化方式,分别是:

    • appendsync always # 每次更细都写AOF
    • appendsync everysec # 每秒钟同步一次
    • appendsync no # OS决定什么时候同步

    兼顾数据和写入性能,选择everysec。对性能几乎没影响。

Redis事务

通过MULTI,EXEC,DISCARD,WATCH命令实现事务。使用 MULTI命令后可以输入多个命令。Redis不会立即执行这些命令,而是将它们放到队列,当调用了EXEC命令将执行所有命令。

R的事务和数据库的事务不太一样,事务四大特性:

  • 原子性:动作要么全部完成,要么不起作用。
  • 一致性:事务提交后,数据保持一致,多个事务对同一个数据库读取的结果是相同的。
  • 隔离性:并发访问数据库,一个用户的事务不被打扰,并发事务之间数据库是独立的。
  • 持久性:一个事物提交后,对数据的改变是持久的,及时数据库发生故障也不能有任何影响。

Redis不支持回滚,不满足原子性。而且不满足持久性。为啥呢?因为他们认为没必要回滚,这样更简单且性能更好。Redis开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。

R的事务可以理解为:提供了一种将多个命令请求打包的功能,然后再按照顺序执行所有命令,并且不会被中途打断。

缓存穿透

  • 是啥?

    大量请求的Key不在缓存,直接到数据库。黑客故意制造缓存中不存在的Key请求,导致大量请求落到数据库。

  • 如何解决?

    最基本的是做好参数校验,不合法的参数直接返回客户端,例如ID不小于0,邮箱格式等。

    • 缓存无效key

      缓存和数据库都查不到,就缓存这个无效key,适用于key变化不频繁的情况。会导致缓存大量无效key,可以尽量将key的过期时间设置短一点,例如1min。

    • 布隆过滤器

      通过它可以方便的判断一个给定数据是否存在于海量数据中。具体的做法是:把所有可能的请求的值放在过滤器,请求过来,先判断请求的值是否在过滤器,不存在的话,返回参数信息错误。存在的话,再去走缓存的流程。

      过滤器时候存在,小概率误判;说不存在,一定不存在。

      • 元素加入过滤器

        1.使用过滤器中的哈希函数对元素值计算,得到哈希值。(几个哈希函数得到几个哈希值)

        2.根据得到的哈希值,数组中对应的下标标记为1

      • 判断是否存在

        1.对给定元素计算哈希值。

        2.得到值之后判断数组中的每个元素位置是否都为1,是,说明在过滤器,存在一个值不是1,说明不在。

      有一种情况:不同字符串可能哈希出来的位置相同。(哈希冲突,最原始的问题)

布隆过滤器

就是一个很长的数组,在存入数据的过程中,先通过三次(也可能是更多次)的哈希计算,然后把这些hash的值都标记为1,在查询的时候,先通过三次hash值,然后去查数组,有一个不是1,则数据一定不存在。但是过滤器很难实现删除操作,本质原因是没有解决哈希冲突。

有点是插入和删除很快。保密性好,因为只存0和1.

缺点就是不好删除;容易出现误判,原因是哈希冲突。问题就是如何去计量避免误判呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class test{
private static int size = 1000000;
//期望的误判率
private static double fpp = 0.01;
//布隆过滤器
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(),size,fpp);
private static int total = 1000000;
psvm{
//插入10万样本数据
for(int i = 0;i<total;i++){
bloomFilter.put(i);
}

//用另外十万测试数据,测试误判率
int count = 0;
for(int i = total;i<total+100000;i++){
if(bloomFilter.mightContain(i)){
count++;
sout(i+"误判了");
}
}
sout("总的误判数是:"+count);
}
}

但是误判率不能设置成无线小,这会拖慢过滤器的计算速度。

误判率的底层原理:

给出更多的哈希函数和数组空间,使用多个哈希函数算出来的哈希位置也不一样,多对应的二进制数据也就越多,这样就可以减少重复的概率。

使用布隆过滤器解决redis缓存穿透的问题:

将数据库的数据全部存放在布隆过滤器上,先查过滤器。没有返回,有的哈,从redis查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RedisBloomFilter{
psvm{
Config config = new Config();
config.useSingleServer.setSddress("redis://127.0.0.1:6379");
config.useSingleServer().setPassword("1234");
//构造过滤器
RedissonClient redisson = Redisson.create(config);
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
//初始化过滤器,预计元素是1000000L,误差率是3%
bloomFilter.tryInit(10000L,0.03);
//将号码10086插入到过滤器
bloomFilter.put("10086");

//判断号码是否在过滤器
sout(bloomFilter.contains("123456"));
}
}

缓存雪崩

缓存在同一时间大面积失效,后面的请求落在数据库,造成短时间内数据库承受大量请求。(缓存模块宕机)

还有一种是有一些被大量访问的数据在某一时刻失效,导致请求落在数据库。

解决办法:

  • 缓存服务宕机
    • redis集群
    • 限流,避免同时处理大量请求。
  • 热点缓存失效:
    • 设置不同的失效时间比如随机设置缓存的失效时间。
    • 缓存永不失效。

如何保证缓存和数据库的一致性

旁路缓存模式

遇到写请求是这样的,更新DB,直接删除缓存。

数据库更新成功缓存删除失败。两个方案:

  • 缓存失效时间变短(不推荐):我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
  • 增加cache更新重试机制(常用):如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将 缓存中对应的 key 删除即可。

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

请我喝杯咖啡吧~

支付宝
微信