介绍
什么是Redis
1.非关系型的键值对数据库,可以根据键以O(1)的时间复杂度取出或插入关联值
2.Redis的数据是存在内存的
3.键值对中的键的类型可以是字符串,整形,浮点型等,且键值唯一
4.键值对中的值的类型可以是String,hash,set,zset,list等
5.Redis内置了复制,磁盘持久化,lua脚本,事务,ssl,客户端代理等功能
6.通过Redis哨兵和分区提供高可用性
介绍数据库到Redis
介绍Redis
安装redis
IO模型(BIO->NIO->select->epoll)
零拷贝
Redis数据结构
1.简单动态字符串SDS:
结构:
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
};
Redis只会使用C字符串作为字面量,大多数情况下,使用SDS作为字符串表示
比起C字符串,SDS的优点:
1).常数获取字符串的长度
2).杜绝缓冲区溢出:当需要对SDS进行修改的时,会先检查SDS的空间是否满足修改需求,如果不满足,先扩展至所需的大小,然后执行修改操作
3).减少修改字符串时带来的内存重分配次数:
1.空间预分配:
- 如果对SDS进行修改后,使用长度len小于1MB,那么也会分配未使用空间free的长度为len
- 如果对SDS进行修改后,使用长度len大于等于1MB,那么会分配未使用空间free的长度为len+1MB
2.惰性空间释放:不释放多余的空间,当真正需要释放的时候,SDS提供的ADI也可以进行释放
4).二进制安全:
- 以处理二进制的方式处理buf数组里面的数据,
- buf数组中实际存放的是一系列二进制数据
- 使用len属性来判断字符串是否结束
- 不仅可以保存文本数据,还可以保存二进制数据
5).兼容部分c字符串函数
2.链表:
1).链表被广泛用于Redis的各种功能,如列表建,发布与订阅,慢查询,监视器等
2).双端队列,每个链表节点由listNode结构来表示
3).每个链表使用list来表示,带有头head尾tail节点指针,及链表长度len信息
4).通过为链表设置不同类型的特定函数,Redis便可以保存各种不同的数据
Redis五大对象
1.String对象
底层数据结构
整型(int),embstr编码的简单动态字符串,简单动态字符串(sds)
应用场景
字符串常用操作:
set key value //存入字符串键值对
mset key value [key value ...] //批量存储字符串键值对
setnx key value //存入一个不存在的字符串键值对
get key //获取一个字符串键值
mget key [key...] //批量获取字符串键值
del key [key..] //删除一个键
expire key seconds //设置一个键的过期时间(秒)
原子加减:
incr key //将key中存储的数字加一
decr key //将key中存储的数字减一
incrby key increment //将key中存储的值加上increment
decrby key decrement //将key中存储的值减去decrement
单值缓存:
set key value
get key
对象缓存:
set user:1 value(json格式数据)
mset user:1:name ypf user:1:balance 1880
mget user:1:name user:1:balance
分布式锁:
setnx product:1001 true //返回1代表获取锁成功
setnx product:1001 false //返回0代表获取锁失败
...执行业务操作
del product:1001 //执行完业务释放锁
set product:1001 true ex 10 nx //防止程序意外终止导致死锁
计数器
incr:article:readcount:{文章id}
get:article:readcount:{文章id}
web集群session共享:
spring session + Redis 实现session共享
分布式系统全局序列号:
incrby orderid 100 //redis批量生成序列号提升性能
2.hash对象:
底层数据结构
哈希表(Hashtable),压缩表(ZipList)
应用场景
- hash常用操作:
hset key field value //存储一个哈希表key的键值
hsetnx key field value //存储一个不存在的哈希表key的键值
hmset key field value [field value...] //在一个哈希表key中存储多个field键值
hget key field //获取哈希表key对应的field键值
hmget key field [field...] //批量删除哈希表key中多个field键值
hdel key field [field...] //删除哈希表key中的field键值
hlen key //返回哈希表key中field的数量
hgetall key //返回哈希表key中所有的键值
hincrby key field increment //为哈希表key中field键的值加上增量increment
- 对象缓存:
hmset user {userId}:name ypf {userId}:balance 1880
hmset user 1:name ypf 1:balance 1880
hmget user 1:name 1:balance
- 电商购物车
1).以用户id为key
2).商品id为field
3).商品数量为value
- 购物车操作
1).添加商品->hset cart:1001 10088 1
2).增加数量->hincrby cart:1001 10088 1
3).商品总数->hlen cart:1001
4).删除商品->hdel cart:1001 10088
5).获取购物车所有商品->hgetall cart:1001
- 优点
1).同类数据归类整合存储,方便数据管理
2).相比string操作消耗内存与CPU更小
3).相比string存储更节省空间
- 缺点
1).过期功能不能使用在field上,只能用在key上
2).Redis集群架构下不适合大规模使用
负载均衡问题:多个key落在同一个节点上,造成数据倾斜
3.list对象:
底层数据结构
链表(LinkedList),压缩表(ZipList)
应用场景
- list常用操作
lpush key value [value...] //将一个或多个value插入到key列表的表头(最左边)
rpush key value [value...] //将一个或多个value插入到key列表的表尾(最右边)
lpop key //移除并返回key列表的头元素
rpop key //移除并返回key列表的尾元素
lrange key start stop //返回列表key中指定区间内的元素,区间以偏移量start和stop指定
blpop key [key...] timeout //从key列表表头弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
brpop key [key...] timeout //从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
- 常用数据结构
Stack(栈)=lpush+lpop->fifo
Queue(队列)=lpush+rpop
Blocking MQ(阻塞队列)=lpush+brpop
- 微博消息和微信公众号消息
李老师关注了MacTalk,备胎说车等大V
1).MacTalk发微博,消息的id为10018
lpush msg:{李老师-id} 10018
2).备胎说车发微博,消息id为10086
lpush msg{李老师-id} 10086
3).查看最新微博消息
lrange msg:{李老师-id} 0 4
4.set对象
底层数据结构
整型集合(intset),哈希表(Hashtable)
应用场景
- set常用操作
sadd key member [member...] //往集合key中存入元素,元素存在则忽略,若key不存在则新建
srem key member [member...] //从集合key中删除元素
smembers key //获取集合key中所有元素
scard key //获取集合key的元素个数
sismember key member //判断member元素是否存在于集合key中
srandmember key [count] //从集合key中选出count个元素,元素不从key中删除
spop key [count] //从集合key中选出count个元素,元素从key中删除
- set运算操作,微博微信关注模型,电商商品筛选
sinter key [key...] //交集运算
sInterStore destination key [key...] //将交集结果存入新集合destinct中
sUnion key [key...] //并集运算
sUnionStore destination key [key...] //将并集结果存入新集合destinct中
sDiff key [key...] 差集运算
sDiffStore destination key [key...] //将差集结果存入新集合destination中
- 微信抽奖小程序
1).点击参与抽奖加入集合
sadd key {userId}
2).查看参与抽奖所有用户
sMembers key
3).抽取count名中奖者
sRandMember key [count] / sPop key [count]
- 微信微博点赞,收藏,标签
1).点赞
sAdd like:{消息id} {用户id}
2).取消点赞
sRem like:{消息id} {用户id}
3).检查用户是否点过赞
sisMember like:{消息id} {用户id}
4).获取点赞的用户列表
sMembers like:{消息id}
5).获取点赞用户数
scard like:{消息id}
5.zSet对象
底层数据结构
压缩表(ZipList),跳表(skipList)
应用场景
- zSet常用操作
zAdd key score member [[score member]...] /往有序集合key中加入带分值元素
zRem key member [member...] //从有序集合中删除元素
zScore key member //返回有序集合key中元素member的分值
zIncrBy key increment member //为有序集合key中元素member的分值加上increment
zCard key //返回有序集合key中元素个数
zRange key start stop [withScores] //正序获取有序集合key从start下标到stop下标的元素
zRevRange key start stop [withScores] //倒序获取有序集合key从start下标到stop下标的元素
- zSet集合操作
zUnionStore destKey numKeys key [key...] //并集计算
zInterStore destKey numkeys key [key...] //交集计算
- zSet集合操作实现排行榜
1).点击新闻
zIncrBy hotNews:20190819 1 守护香港
2).展示当日排行前十
zRevRange hotNews:20190819 0 9 withScores
3).七日搜索榜单计算
zUnionStore hotNews:20190813-20190819 7
hotNews:20190813 hotNews:20190814... hotNews:20190819
4).展示七日排行前十
zRevRange hotNews:20190813-20190819 0 9 withScores
查看常用命令: help @hash
Redis 管道(Pipelining)发布订阅(Pub/Sub) 事务
请求/响应协议和RTT
Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。
客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
服务端处理命令,并将结果返回给客户端。
客户端和服务器通过网络进行连接。这个连接可以很快(loopback接口)或很慢(建立了一个多次跳转的网络连接)。无论网络延如何延时,数据包总是能从客户端到达服务器,并从服务器返回数据回复客户端。
这个时间被称之为 RTT (Round Trip Time - 往返时间). 当客户端需要在一个批处理中执行多次请求时很容易看到这是如何影响性能的
Redis 管道(Pipelining)
一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(pipelining)
重要说明: 使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,
例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。
发布订阅(Pub/Sub)
常用命令
PUBLISH channel message
将信息 message 发送到指定的频道 channel
返回值 integer-reply: 收到消息的客户端数量。
SUBSCRIBE channel [channel ...]
订阅给指定频道的信息。
一旦客户端进入订阅状态,客户端就只可接受订阅相关的命令SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE和PUNSUBSCRIBE除了这些命令,其他命令一律失效。
事务 (transactions)
MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
EXEC 命令负责触发并执行事务中的所有命令:
- 如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
- 另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。
用法
MULTI 命令用于开启一个事务,它总是返回
OK
。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。另一方面, 通过调用 DISCARD , 客户端可以清空事务队列, 并放弃执行事务。
> MULTI OK > INCR foo QUEUED > INCR bar QUEUED > EXEC 1) (integer) 1 2) (integer) 1
EXEC 命令的回复是一个数组, 数组中的每个元素都是执行事务中的命令所产生的回复。 其中, 回复元素的先后顺序和命令发送的先后顺序一致。
当客户端处于事务状态时, 所有传入的命令都会返回一个内容为
QUEUED
的状态回复(status reply), 这些被入队的命令将在 EXEC 命令被调用时执行。
事务中的错误
使用事务时可能会遇上以下两种错误:
- 事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用
maxmemory
设置了最大内存限制的话)。- 命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回
QUEUED
,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。不过,从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。
在 Redis 2.6.5 以前, Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。
至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。
即使事务中有某条/某些命令执行失败了, 事务队列中的其他命令仍然会继续执行 —— Redis 不会停止执行事务中的命令。
为什么 Redis 不支持回滚(roll back)
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。
使用 check-and-set 操作实现乐观锁
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。
如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了
mykey
的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。
WATCH
WATCH 使得 EXEC 命令需要有条件地执行: 事务只能在所有被监视键都没有被修改的前提下执行, 如果这个前提不能满足的话,事务就不会被执行。
WATCH 命令可以被调用多次。 对键的监视从 WATCH 执行之后开始生效, 直到调用 EXEC 为止。
用户还可以在单个 WATCH 命令中监视任意多个键, 就像这样:
redis> WATCH key1 key2 key3 OK
当 EXEC 被调用时, 不管事务是否成功执行, 对所有键的监视都会被取消。
另外, 当客户端断开连接时, 该客户端对键的监视也会被取消。
使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。
布隆过滤器
过期淘汰策略
回收策略
当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置。
- noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
- allkeys-random: 回收随机的键使得新添加的数据有空间存放。
- volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
Redis如何淘汰过期的keys
Redis keys过期有两种方式:被动和主动方式。
当一些客户端尝试访问它时,key会被发现并主动的过期。
当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。
具体就是Redis每秒10次做的事情:
- 测试随机的20个keys进行相关过期检测。
- 删除所有已经过期的keys。
- 如果有多于25%的keys过期,重复步奏1.
这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。
持久化
RDB
AOF
集群
三个一致性
主从复制(解决单点故障和压力问题)
解决容量问题(拆分)
业务可拆分,根据业务拆分
业务不可拆分(sharding分片)
问题(2类)
每个redis实例可能存有部分数据,客户需要和每个redis实例进行连接,IO成本
解决办法
面试常问:击穿,穿透,雪崩,分布式锁,API (jedis,luttce,springboot:low/high level)
分布式锁
- bug版本分布式锁
@RestController
public class IndexController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/decrProduct")
public String decrProduct() {
String lockKey = "product_001";
String clientId = UUID.randomUUID().toString();
try {
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId);
stringRedisTemplate.expire(lockKey, 30, TimeUnit.SECONDS);
if (!result) {
return "error_code";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get(lockKey));
if (stock > 0) {
......
} else {
......
}
}finally {
stringRedisTemplate.delete(lockKey);
}
return "end";
}
}
- 实现分布式锁的思路:
一个线程拿到锁加锁成功后,开始执行业务代码,同时开启一个分线程,搞一个定时任务,每隔一段时间检查一下这个锁是否还在,如果还在,重新设置这把锁的超时时间
redisson
- 用法:
1.引入相关的redisson包
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.6.5</version> </dependency>
2.加入Redisson配置的客户端
@Bean public Redisson redisson() { //单机版 Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0); return (Redisson) Redisson.create(config); }
3.核心操作
RLock redissonLock = redisson.getLock(lockKey);//获取锁 redissonLock.lock();//加锁 redissonLock.unlock();//释放锁
锁的超时时间默认为30秒,后台线程每隔10秒检查一下是否还持有锁
- 原理图:
bug场景
主从模式下,第一个线程获取到锁,从节点还没有同步到数据,主节点挂了,另外一个线程又会加锁成功
解决办法
:zookeeper实现,第一个线程修改了数据,等主节点半数以上的节点都同步到值以后,才会告诉客户端写成功了;如果主节点挂了,zookeeper内部选举机制,会将拿到数据的那一个节点选举为主节点
并发场景下,可以采用分段的思想,将相同的商品分段到不同的Redis节点上去,以提高并发
Redis集群
哨兵模式
![](https://img-blog.csdnimg.cn/20200813152202953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMjA0NTUw,size_16,color_FFFFFF,t_70)
哨兵监控多个Redis节点,当发现主节点挂了,就会在从节点中选出一个作为主节点,性能及高可用等发面表现一般,主从切换的时候回出现瞬断的情况
高可用集群模式
是一个由多个主从节点群组成的分布式服务群,具有复制,高可用和分片特性.不需要哨兵也可以完成节点移除和故障转移的功能,需要将每个节点设置成集群模式,这种集群没有中心点,可以水平扩展,性能和高可用优于哨兵模式
原理:client发型一个请求过来,服务端JedisCluster(负载均衡)组件将写请求的操作方放在第一个集群上,下次再发送一个读的请求,请求会落在第二个集群上,Redis内部会将请求重定位到第一个集群上,多个集群的数据是分开的,瞬断可能性非常低
redis高可用集群的搭建
Redis安装
下载地址:https://redis.io/download
Redis核心
计算向数据移动
Redis除了有kv,每种value有本地方法,如果是list本地方法为lindex(x),给出一个下标,Redis找到这个下标给你返回
客户端只是给你发了一条指令,这条指令就是lindex(2),这个指令发出去之后,数据是在server端,计算的算法也是在server端,方法是Redis自己实现的,指定的计算向数据这边移动,计算没有移动,移动了计算的触发
取回的数据为指定的数据,其他的数据不会伴随着IO的返回,特别省IO,在并发情况下,吞吐能力很强
Redis的IO模型
串行化:Redis工作线程为单线程
Redis持久化
单机的问题
Redis分布式锁及一致性
Redis的过半机制(防止脑裂和对外提供服务的数量)
Redis分片技术
缓存击穿,缓存穿透,缓存雪崩,布隆过滤器
1.如何使用缓存?
//提高性能,保护数据库
public R selectOrderById(Integer id) {
//查询缓存
Object redisObj = valueOperations.get(String.valueOf(id));
//缓存命中
if (redisObj != null) {
//正常返回数据
return new R().setCode(200).setData(redisObj).setMsg("OK");
}
try {
Order order = orderMapping.selectOrderById(id);
if (order != null) {
valueOperations.set(String.valueOf(id), order, 10, TimeUnit.MINUTES);//加入缓存
return new R().setCode(200).setData(order).setMsg("OK");
}
}finally {
}
return new R().setCode(500).setData(new NullValueResultDO()).setMsg("查询无果");
}
2.缓存穿透
指查询一条数据库没有,缓存中也没有的数据,解决办法有两种,缓存空对象和布隆过滤器
缓存空对象: 代码简单,效果不太好,解决的问题是多次查询一条根本不存在的数据,
//提高性能,保护数据库
public R selectOrderById(Integer id) {
//查询缓存
Object redisObj = valueOperations.get(String.valueOf(id));
//缓存命中
if (redisObj != null) {
if (redisObj instanceof NullValueResultDO) {
return new R().setCode(500).setData(new NullValueResultDO()).setMsg("查询无果");
}
//正常返回数据
return new R().setCode(200).setData(redisObj).setMsg("OK");
}
try {
Order order = orderMapping.selectOrderById(id);
if (order != null) {
valueOperations.set(String.valueOf(id), order, 10, TimeUnit.MINUTES);//加入缓存
return new R().setCode(200).setData(order).setMsg("OK");
} else {
valueOperations.set(String.valueOf(id), new NullValueResultDO(), 10, TimeUnit.MINUTES);//加入缓存
}
}finally {
}
return new R().setCode(500).setData(new NullValueResultDO()).setMsg("查询无果");
}
问题: 换一个key依然会查询数据库,缓存中会有大量的空对象
布隆过滤器: 代码维护复杂,效果很好,占用体积小
没有get方法,只有put方法和mightContain方法,是一个特殊的集合对象,布隆过滤器这个判断对象是否存在的方法会有误判
使用
1.引入jar包
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.0.1-jre</version> </dependency>
2.测试
public class TestBloomFilter { private static int size = 1000000; private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.1); public static void main(String[] args) { for (int i = 1; i <= size; i++) { bloomFilter.put(i); } List<Integer> list = new ArrayList<>(10000); for (int i = size + 10000; i < size + 20000; i++) { if (bloomFilter.mightContain(i)) { list.add(i); } } System.out.println("误判的数量:" + list.size()); } }
原理:底层实现为bit
多个哈希函数对键进行哈希,得到三个位置,对应位置改为1,判断是否可能存在的方法也是同样的多个哈希函数,看对应位置是否都为1