说到布隆过滤器不得不提到,redis,

redis作为现在主流的nosql数据库,备受瞩目;它的丰富的value类型,以及它的偏向计算向数据移动属性减少IO的成本问题。备受开发人员的青睐。通常我们使用redis作为数据缓存来使用,但是作为缓存redis会有一些问题,就是缓存穿透问题击穿雪崩一致性双写。本次主要讲解的就是穿透问题

首先我们先思考一下为什么会产生穿透的问题。

假设我们有一些数据,存储在了MySQL中,但是由于用户量的庞大我们需要在在用户访问数据的时候需要在redis中进行一个过滤、拦截,reids中存在则放行,不存在则直接拒绝;从而使用户不会过多的去操作数据库,减轻数据库的压力。但是此时就会有一个问题:

  • 我们如何保证redis在用户携带数据过来的时候进行一个判断呢,此时就需要写一个算法来将用户的数据进行一个拆解,计算来比对redis中已经存在的数据。到这里,我们理论上解决了数据的过滤问题。
  • 那么还有一个问题就是redis存储MySQL数据的时候如何存储呢,是将数据全部存储在redis中吗?如果是的话那么redis基于内存的一种nosql数据库,根本不可能存储那么多的数据量的啊?此时我们就需要利用redisbitmap的类型的特性。来进行数据的存储。

所谓的bitmap就是使用1Bit位来标记元素对应的value,而key就是该元素,想一下1Bytes是8个Bit,那么1个KB就是8192Bit,1M的话就是8388608Bit,可想而知,如果利用reids的bitmap处理大数据量的数据是不成问题的

缓存穿透的解决思路

基于以上的思路:整体的解决方案就是这样子的

  • 首先我们需要利用算法在项目启动的时候将需要缓存的数据加载到redis的bitmap中
  • 然后再写一个算法在用户访问的使用将数据进行拆解,比对redis的bitmap是否存在该条数据。存在放行,防止直接返回。

图解:

上述方案也可能存在一个漏掉的问题,误打误撞穿过去了,这种情况也不是不存在的。但是我们可以在穿透过去之后,在redis中加一个key,为这个error做一个标记,防止下一次再次穿透过去

注意

概率解决问题不可能百分之百解决问题>1% (No Silver Bullet)

  1. 你有什么
  2. 有的向bitmap标记
  3. 请求有可能被误标记
  4. 但是 一定概率减少数据放行 穿透
  5. 成本低

总结一句话:redis告诉你不存在的那么一定不存在,百分之百;但是redis告诉你有的,却不一定百分之百存在

大致的解决思路已经理清,接下来整理一下解决方案吧:

缓存穿透的解决方案

解决方案大概有三种:

首先我们先来实现第一种:(客户端实现bloom算法,自己承载bitmap)

方案一
public class test {
//位图的长度
public static final int NUM_SLOTS = 1024 * 1024 * 8;
//哈希函数的个数
public static final int NUM_HASH = 8;
//初始化位图
private static BigInteger bits = new BigInteger("0"); private static void addElement(String string) {
//增加元素将对应位图上的位置为1
//使用哈希函数计算哈希值:循环八次
for(int i = 0; i < NUM_HASH; i++){
int bit = hash(string, i);
if(!bits.testBit(bit)){
//BigInteger对象运行的必须是另外的BigInteger对象
//左移将对应位图上的位置为1
bits = bits.or(new BigInteger("1").shiftLeft(bit));
}
}
} private static int hash(String message, int index) {
//这里也可以使用其他的哈希函数来计算哈希值,不影响最终的结果
//使用md5得到加密后的字符串相当于哈希函数计算出hashCode的过程
message += index;
try {
MessageDigest md5 = MessageDigest.getInstance("md5");
byte bytes[] = message.getBytes();
md5.update(bytes);
byte bits[] = md5.digest();
BigInteger bi = new BigInteger(bits);
return Math.abs(bi.intValue()) % NUM_SLOTS;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return -1;
} private static boolean check(String string) {
//使用与填充位图的方法一致检查对应位上是否为一
for(int i = 0; i < NUM_HASH; i++){
int index = hash(string, i);
if(!bits.testBit(index)){
return false;
}
}
return true;
}
}
方案二
@Component
@Slf4j
public class RedisBloomUtil { @Autowired
private JedisPool jp; @PostConstruct
public void initJedis(){
jedisPool = jp;
} private static JedisPool jedisPool; private static Jedis jedis = null; /**
* 要存储的数据量
*/
private static long n = 1000000L; /**
* 容忍的错误率
*/
private static double fpp = 0.01F; /**
* bit数组的长度
*/
private static long numBits = optNumOfBits(n,fpp); /**
* hash函数的个数
*/
private static int hashNum = optNumOfHashFunction(n,numBits); /**
* 获取redis bitmap 中的数量
* @return
*/
public long getCount(){
jedis = jedisPool.getResource();
Pipeline pipeline = jedis.pipelined();
Response<Long> newsInfo = pipeline.bitcount(RedisConfig.newsCacheKey);
pipeline.sync();
Long count = newsInfo.get();
pipeline.close();
return count;
} /**
* 判断keys 是否存在集合 where中
* @param where
* @param key
* @return
*/
public static boolean isExist(String where,String key){
jedis = jedisPool.getResource();
long[] indexs = getIndex(key);
boolean flag;
// 这里同样采用管道的方式来降低过滤器运行当中访问redis的次数 降低redis并发量
Pipeline pipeline = jedis.pipelined();
try {
for (long index:indexs) {
pipeline.getbit(where,index);
}
flag = !pipeline.syncAndReturnAll().contains(false);
} finally {
pipeline.close();
}
// 不存在则放进redis的 bitmap 中
// if (!flag){
// putRedis(where,key);
// }
return flag;
} /**
* 将key存储在redis bitmap 中
* @param where
* @param key
*/
public static void putRedis(String where,String key){
jedis = jedisPool.getResource();
long[] indexs = getIndex(key);
// 这里使用redis管道来降低过滤器运行当中访问redis的次数 降低redis并发量
Pipeline pipeline = jedis.pipelined();
try {
for (long index: indexs) {
pipeline.setbit(where,index,true);
}
pipeline.sync();
// 这里可以将数据存储到mysql中
} finally {
pipeline.close();
}
Long ttl = jedis.ttl(where);
// 设置 key的过期时间 30天
if (ttl == -1 || ttl == -2){
jedis.expire(where,2592000);
}
} /**
* 根据key获取 bitmap 下表
* @param key
* @return
*/
public static long[] getIndex(String key){
long hash1 = hashOpt(key);
long hash2 = hash1 >>>16;
long[] result = new long[hashNum];
for (int i = 0; i < hashNum; i++) {
long combinedHash = hash1 + i * hash2;
if (combinedHash<0){
combinedHash = ~combinedHash;
}
result[i] = combinedHash % numBits;
}
return result;
} /**
* 获取一个hash值方法来自 guava
* @param key
* @return
*/
public static long hashOpt(String key){
Charset charset = Charset.forName("UTF-8");
return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
} /**
* 计算bit数组的长度
* @param n
* @param fpp
* @return
*/
private static long optNumOfBits(Long n,double fpp){
if (fpp == 0){
fpp = Double.MAX_VALUE;
}
return (long) (-n * Math.log(fpp) / (Math.log(2) *Math.log(2)) );
} /**
* 计算hash函数的个数
* @param n
* @param numBits
* @return
*/
private static int optNumOfHashFunction(long n, long numBits){
return Math.max(1,(int) Math.round((double) numBits/n * Math.log(2)));
} }

关于方案二需要的依赖:

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>

好了,时间有限 。关于方案三暂时不做说明了。redis本身现在也支持bloom过滤器。如果有时间我在编写关于方案三吧。

感兴趣的小伙伴可以微信搜索码上遇见你获取更多精彩内容。

关于布隆过滤器,手写你真的知其原理吗?让我来带你手写redis布隆过滤器。的更多相关文章

  1. SpringBoot(18)---通过Lua脚本批量插入数据到Redis布隆过滤器

    通过Lua脚本批量插入数据到布隆过滤器 有关布隆过滤器的原理之前写过一篇博客: 算法(3)---布隆过滤器原理 在实际开发过程中经常会做的一步操作,就是判断当前的key是否存在. 那这篇博客主要分为三 ...

  2. Redis 布隆过滤器

    1.布隆过滤器 内容参考:https://www.jianshu.com/p/2104d11ee0a2 1.数据结构 布隆过滤器是一个BIT数组,本质上是一个数据,所以可以根据下标快速找数据 2.哈希 ...

  3. 硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战

    在Redis 缓存击穿(失效).缓存穿透.缓存雪崩怎么解决?中我们说到可以使用布隆过滤器避免「缓存穿透」. 码哥,布隆过滤器还能在哪些场景使用呀? 比如我们使用「码哥跳动」开发的「明日头条」APP 看 ...

  4. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  5. C基础 带你手写 redis sds

    前言 - Simple Dynamic Strings  antirez 想统一 Redis,Disque,Hiredis 项目中 SDS 代码, 因此构建了这个项目 https://github.c ...

  6. C基础 带你手写 redis adlist 双向链表

    引言 - 导航栏目 有些朋友可能对 redis 充满着数不尽的求知欲, 也许是 redis 属于工作, 交流(面试)的大头戏, 不得不 ... 而自己当下对于 redis 只是停留在会用层面, 细节层 ...

  7. 黑马vue---40、结合Node手写JSONP服务器剖析JSONP原理

    黑马vue---40.结合Node手写JSONP服务器剖析JSONP原理 一.总结 一句话总结: 服务端可以返回js代码给script标签,那么标签会执行它,并且可带json字符串作为参数,这样就成功 ...

  8. Redis详解(十三)------ Redis布隆过滤器

    本篇博客我们主要介绍如何用Redis实现布隆过滤器,但是在介绍布隆过滤器之前,我们首先介绍一下,为啥要使用布隆过滤器. 1.布隆过滤器使用场景 比如有如下几个需求: ①.原本有10亿个号码,现在又来了 ...

  9. CI中的控制器中要用model中的方法,是统一写在构造器方法中,还是在每一个方法中分别写

    Q: CI中的控制器中要用model中的方法,是统一写在构造器方法中,还是在每一个方法中分别写 A: 建议统一写,CI框架会自动识别已经加载过的类,所以不用担心重复加载的问题 class C_User ...

随机推荐

  1. ArcGIS图层添加字段出现:“定义了过多字段”

    首先,我图层数据格式为mdb,也就是Access数据库 Access一个表最大支持255个字段,可是我的才添加第一个字段就出现"定义了过多字段"的错误 打开ArcMap添加字段也是 ...

  2. python某个module使用了相对引用,同时其__name__又是__main__导致的错误

    主要讲解 某个module中使用了相对引用,同时这个module的 __name__ 属性 又是 __main__ 会报错的问题 1.问题复现 文件结构很简单: |--------package | ...

  3. tomcat Debug 启动

    eclipse有web工程,将打包为war包(export -> War file) 将打包好的war 包放在tomcat 的webapps下面: 配置debug: 参考来自 http://bl ...

  4. Struts2之处理请求参数

    时间:2017-1-11 11:05 --Struts2中获取请求参数(重点)1.Struts2是一个MVC框架,那么分别表示什么?    View:JSP    Model:Action    Co ...

  5. webpack编译后的代码如何在浏览器执行

    浏览器是无法直接使用模块之间的commonjs或es6,webpack在打包时做了什么处理,才能让浏览器能够执行呢,往下看吧. 使用commonjs语法 先看下写的代码, app.js minus.j ...

  6. MySQL读写IO的操作过程解析

    数据库作为存储系统,所有业务访问数据的操作都会转化为底层数据库系统的IO行为(缓存系统也可以当做是key-value的数据库),本文主要介绍访问MySQL数据库的IO流程以及IO相关的参数. 一.My ...

  7. Tolist案例(父子传参实现增删改)

    1.Tolist案例(父子传参实现增删改) 目录结构 实现效果: App.jsx class App extends Component { // 状态在哪里, 操作状态的方法就在哪里 state = ...

  8. Kotlin之内联回调函数

    let 定义: let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择:let函数另一个作用就是可以避免写一些判断null的操作. 翻译: ...

  9. JS中原型与原型链

    一. 普通对象与函数对象 JavaScript 中,万物皆对象!但对象也是有区别的.分为普通对象和函数对象,Object .Function等 是 JS 自带的函数对象.下面举例说明. var o1 ...

  10. Microsoft Remote Desktop 通过 .rdp 文件登录

    最近在淘宝上买了「市场洞察」子账号,说是子账号,其实是需要登录到他们的 Windows 服务器上才能用的.并且子账号也是 5-6 个人共用的,且不说远程服务器很老又有延迟,经常是我想添加一个监控店铺或 ...