Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6166787
修改语句不通的问题
Nov 20, 2019
a33ef93
Merge pull request #560 from Franub/master
Snailclimb Nov 21, 2019
933db61
Merge pull request #561 from zongzhang/master
Snailclimb Nov 21, 2019
d37a25e
a good commit message
0xPoe Nov 21, 2019
66f0e2c
Merge pull request #565 from Rustin-Liu/git-commit
Snailclimb Nov 22, 2019
9449c4f
Update java-learning-path-and-methods.md
Snailclimb Nov 22, 2019
9921bbd
重载和重写概念完善
Snailclimb Nov 22, 2019
a72a93c
Update Java基础知识.md
Snailclimb Nov 22, 2019
66c2feb
Create 最重要的JVM参数指南.md
Snailclimb Nov 22, 2019
3d6e429
Update Java内存区域.md
Snailclimb Nov 22, 2019
d2bc0a7
Create GC调优参数.md
Snailclimb Nov 22, 2019
571c02b
Update README.md
Snailclimb Nov 22, 2019
33cae60
Update README.md
Snailclimb Nov 24, 2019
622e124
修改错误图片链接
Snailclimb Nov 25, 2019
3e9332e
Update JavaConcurrencyAdvancedCommonInterviewQuestions.md
Snailclimb Nov 25, 2019
bcfc371
fix-569
LiWenGu Nov 26, 2019
a5c53a4
Merge pull request #570 from LiWenGu/hotfix/569
Snailclimb Nov 26, 2019
d6ad33c
Update AQS.md
Snailclimb Nov 27, 2019
6e286c6
Update 美团面试常见问题总结.md
Snailclimb Nov 27, 2019
7c67a9f
github 上 Star 数量最多的 10 个项目
Snailclimb Nov 27, 2019
4867d66
Redis集群以及应用场景文档的补充
LiWenGu Nov 28, 2019
2aac53b
Redis集群以及应用场景文档的补充
LiWenGu Nov 28, 2019
ce3cf23
Redis集群以及应用场景文档的补充
LiWenGu Nov 28, 2019
c8c3793
Redis集群以及应用场景文档的补充
LiWenGu Nov 28, 2019
e6b888a
Redis集群以及应用场景文档的补充
LiWenGu Nov 28, 2019
d82f399
Update github-star-ranking.md
Snailclimb Nov 29, 2019
c458ea0
Update Java内存区域.md
Snailclimb Nov 29, 2019
6fba0af
Delete springboot-questions.md
Snailclimb Nov 29, 2019
f4e02f9
Merge pull request #572 from LiWenGu/hotfix/redis_cluster_1
Snailclimb Nov 29, 2019
cc82e27
Update redis集群以及应用场景.md
Snailclimb Nov 29, 2019
700d2ec
完善消息队列!
Snailclimb Nov 30, 2019
8a757d2
Merge branch 'master' of https://github.com/Snailclimb/JavaGuide
Snailclimb Nov 30, 2019
80a2521
完善 Redis 内容
Snailclimb Nov 30, 2019
5bda607
布隆过滤器
Snailclimb Nov 30, 2019
8c85b74
布隆过滤器内容完善
Snailclimb Nov 30, 2019
7dbe2b8
Create 【真实面试经历】我所经历的阿里一二面总结(附详解).md
Snailclimb Nov 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</a >
</p>

推荐使用 https://snailclimb.github.io/JavaGuide/ 在线阅读(访问速度慢的话,请使用 https://snailclimb.gitee.io/javaguide ),在线阅读内容本仓库同步一致。这种方式阅读的优势在于:有侧边栏阅读体验更好,Gitee pages 的访问速度相对来说也比较快
推荐使用 https://snailclimb.gitee.io/javaguide 在线阅读,在线阅读内容本仓库同步一致。这种方式阅读的优势在于:阅读体验会更好

## 目录

Expand Down Expand Up @@ -103,6 +103,8 @@
* [四 类文件结构](docs/java/jvm/类文件结构.md)
* **[五 类加载过程](docs/java/jvm/类加载过程.md)**
* [六 类加载器](docs/java/jvm/类加载器.md)
* **[【待完成】八 最重要的 JVM 参数指南(翻译完善了一半)](docs/java/jvm/最重要的JVM参数指南.md)**
* [九 JVM 配置常用参数和常用 GC 调优策略](docs/java/jvm/GC调优参数.md)

### I/O

Expand Down Expand Up @@ -298,8 +300,9 @@

- [Github 上热门的 Spring Boot 项目实战推荐](docs/data/spring-boot-practical-projects.md)

### Github 历史榜单
### Github

- [Github 上 Star 数最多的 10 个项目,看完之后很意外!](docs/tools/github/github-star-ranking.md)
- [Java 项目月榜单](docs/github-trending/JavaGithubTrending.md)

***
Expand Down Expand Up @@ -336,7 +339,7 @@ Markdown 格式参考:[Github Markdown格式](https://guides.github.com/featur

1. 笔记内容大多是手敲,所以难免会有笔误,你可以帮我找错别字。
2. 很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。
3. 现有的知识点难免存在不完善或者错误,所以你可以对已有知识点的修改/补充。
3. 现有的知识点难免存在不完善或者错误,所以你可以对已有知识点进行修改/补充。

### 为什么要做这个开源文档?

Expand Down
237 changes: 237 additions & 0 deletions docs/dataStructures-algorithms/data-structure/bloom-filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
最近,当我在做一个项目的时候需要过滤掉重复的 URL ,为了完成这个任务,我学到了一种称为 Bloom Filter (布隆过滤器)的东西,然后我学会了它并写下了这个博客。

下面我们将分为几个方面来介绍布隆过滤器:

1. 什么是布隆过滤器?
2. 布隆过滤器的原理介绍。
3. 布隆过滤器使用场景。
4. 通过 Java 编程手动实现布隆过滤器。
5. 利用Google开源的Guava中自带的布隆过滤器。
6. Redis 中的布隆过滤器。
7. 总结。

### 1.什么是布隆过滤器?

首先,我们需要了解布隆过滤器的概念。

布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于1970年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map 、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。

![布隆过滤器示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/布隆过滤器-bit数组.png)

位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000 / 8 = 125000 B = 15625 byte ≈ 15.3kb 的空间。

总结:**一个名叫 Bloom 的人提出了一种来检索元素是否在给定大集合中的数据结构,这种数据结构是高效且性能很好的,但缺点是具有一定的错误识别率和删除难度。并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大。**

### 2.布隆过滤器的原理介绍

**当一个元素加入布隆过滤器中的时候,会进行如下操作:**

1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。

**当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:**

1. 对给定元素再次进行相同的哈希计算;
2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

举个简单的例子:



![布隆过滤器hash计算](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/布隆过滤器-hash运算.png)

如图所示,当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为0)。当第二次存储相同字符串时,因为先前的对应位置已设置为1,所以很容易知道此值已经存在(去重非常方便)。

如果我们需要判断某个字符串是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

**不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。**

综上,我们可以得出:**布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**

### 3.布隆过滤器使用场景

1. 判断给定数据是否存在:比如判断一个数字是否在于包含大量数字的数字集中(数字集很大,5亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
2. 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。

### 4.通过 Java 编程手动实现布隆过滤器

我们上面已经说了布隆过滤器的原理,知道了布隆过滤器的原理之后就可以自己手动实现一个了。

如果你想要手动实现一个的话,你需要:

1. 一个合适大小的位数组保存数据
2. 几个不同的哈希函数
3. 添加元素到位数组(布隆过滤器)的方法实现
4. 判断给定元素是否存在于位数组(布隆过滤器)的方法实现。

下面给出一个我觉得写的还算不错的代码(参考网上已有代码改进得到,对于所有类型对象皆适用):

```java
import java.util.BitSet;

public class MyBloomFilter {

/**
* 位数组的大小
*/
private static final int DEFAULT_SIZE = 2 << 24;
/**
* 通过这个数组可以创建 6 个不同的哈希函数
*/
private static final int[] SEEDS = new int[]{3, 13, 46, 71, 91, 134};

/**
* 位数组。数组中的元素只能是 0 或者 1
*/
private BitSet bits = new BitSet(DEFAULT_SIZE);

/**
* 存放包含 hash 函数的类的数组
*/
private SimpleHash[] func = new SimpleHash[SEEDS.length];

/**
* 初始化多个包含 hash 函数的类的数组,每个类中的 hash 函数都不一样
*/
public MyBloomFilter() {
// 初始化多个不同的 Hash 函数
for (int i = 0; i < SEEDS.length; i++) {
func[i] = new SimpleHash(DEFAULT_SIZE, SEEDS[i]);
}
}

/**
* 添加元素到位数组
*/
public void add(Object value) {
for (SimpleHash f : func) {
bits.set(f.hash(value), true);
}
}

/**
* 判断指定元素是否存在于位数组
*/
public boolean contains(Object value) {
boolean ret = true;
for (SimpleHash f : func) {
ret = ret && bits.get(f.hash(value));
}
return ret;
}

/**
* 静态内部类。用于 hash 操作!
*/
public static class SimpleHash {

private int cap;
private int seed;

public SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}

/**
* 计算 hash 值
*/
public int hash(Object value) {
int h;
return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
}

}
}
```

测试:

```java
String value1 = "https://javaguide.cn/";
String value2 = "https://github.com/Snailclimb";
MyBloomFilter filter = new MyBloomFilter();
System.out.println(filter.contains(value1));
System.out.println(filter.contains(value2));
filter.add(value1);
filter.add(value2);
System.out.println(filter.contains(value1));
System.out.println(filter.contains(value2));
```

Output:

```
false
false
true
true
```

测试:

```java
Integer value1 = 13423;
Integer value2 = 22131;
MyBloomFilter filter = new MyBloomFilter();
System.out.println(filter.contains(value1));
System.out.println(filter.contains(value2));
filter.add(value1);
filter.add(value2);
System.out.println(filter.contains(value1));
System.out.println(filter.contains(value2));
```

Output:

```java
false
false
true
true
```

### 5.利用Google开源的 Guava中自带的布隆过滤器

自己实现的目的主要是为了让自己搞懂布隆过滤器的原理,Guava 中布隆过滤器的实现算是比较权威的,所以实际项目中我们不需要手动实现一个布隆过滤器。

首先我们需要在项目中引入 Guava 的依赖:

```java
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
```

实际使用如下:

我们创建了一个最多存放 最多 1500个整数的布隆过滤器,并且我们可以容忍误判的概率为百分之(0.01)

```java
// 创建布隆过滤器对象
BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
1500,
0.01);
// 判断指定元素是否存在
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));
```

在我们的示例中,当`mightContain()` 方法返回*true*时,我们可以99%确定该元素在过滤器中,当过滤器返回*false*时,我们可以100%确定该元素不存在于过滤器中。

### 6.Redis 中的布隆过滤器

- https://juejin.im/post/5bc7446e5188255c791b3360

### 8.其他推荐阅读

1. 详解布隆过滤器的原理,使用场景和注意事项:https://zhuanlan.zhihu.com/p/43263751
2.
53 changes: 46 additions & 7 deletions docs/database/Redis/Redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,28 +257,62 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。

### 缓存雪崩和缓存穿透问题解决方案

**缓存雪崩**
#### **缓存雪崩**

**什么是缓存雪崩?**

简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决办法(中华石杉老师在他的视频中提到过,视频地址在最后一个问题中有提到):
**有哪些解决办法?**

(中华石杉老师在他的视频中提到过,视频地址在最后一个问题中有提到):

- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存

![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-25/6078367.jpg)

#### **缓存穿透**

**缓存穿透**
**什么是缓存穿透?**

简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库

解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
一般MySQL 默认的最大连接数在 150 左右,这个可以通过 `show variables like '%max_connections%'; `命令来查看。最大连接数一个还只是一个指标,cpu,内存,磁盘,网络等无力条件都是其运行指标,这些指标都会限制其并发能力!所以,一般 3000 个并发请求就能打死大部分数据库了。

**有哪些解决办法?**

最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。

**1)缓存无效 key** : 如果缓存和数据库都查不到某个 key 的数据就写一个到 redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086`。这种方式可以解决请求的 key 变化不频繁的情况,如何黑客恶意攻击,每次构建的不同的请求key,会导致 redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。

另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值`。

如果用 Java 代码展示的话,差不多是下面这样的:

```java
public Object getObjectInclNullById(Integer id) {
// 从缓存中获取数据
Object cacheValue = cache.get(id);
// 缓存为空
if (cacheValue != null) {
// 从数据库中获取
Object storageValue = storage.get(key);
// 缓存空对象
cache.set(key, storageValue);
// 如果存储数据为空,需要设置一个过期时间(300秒)
if (storageValue == null) {
// 必须设置过期时间,否则有被攻击的风险
cache.expire(key, 60 * 5);
}
return storageValue;
}
return cacheValue;
}
```

参考:

- [https://blog.csdn.net/zeb_perfect/article/details/54135506](https://blog.csdn.net/zeb_perfect/article/details/54135506)

### 如何解决 Redis 的并发竞争 Key 问题

Expand Down Expand Up @@ -308,6 +342,11 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。

**参考:** Java工程师面试突击第1季(可能是史上最好的Java面试突击课程)-中华石杉老师!公众号后台回复关键字“1”即可获取该视频内容。

### 参考

- 《Redis开发与运维》
- Redis 命令总结:http://redisdoc.com/string/set.html

## 公众号

如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
Expand Down
Loading