Skip to content

Commit edd7932

Browse files
authored
Merge pull request Snailclimb#1 from Snailclimb/master
merge my forks
2 parents e5a7e74 + 7dbe2b8 commit edd7932

File tree

17 files changed

+1543
-583
lines changed

17 files changed

+1543
-583
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
</a >
2323
</p>
2424

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

2727
## 目录
2828

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

107109
### I/O
108110

@@ -298,8 +300,9 @@
298300

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

301-
### Github 历史榜单
303+
### Github
302304

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

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

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

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

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
最近,当我在做一个项目的时候需要过滤掉重复的 URL ,为了完成这个任务,我学到了一种称为 Bloom Filter (布隆过滤器)的东西,然后我学会了它并写下了这个博客。
2+
3+
下面我们将分为几个方面来介绍布隆过滤器:
4+
5+
1. 什么是布隆过滤器?
6+
2. 布隆过滤器的原理介绍。
7+
3. 布隆过滤器使用场景。
8+
4. 通过 Java 编程手动实现布隆过滤器。
9+
5. 利用Google开源的Guava中自带的布隆过滤器。
10+
6. Redis 中的布隆过滤器。
11+
7. 总结。
12+
13+
### 1.什么是布隆过滤器?
14+
15+
首先,我们需要了解布隆过滤器的概念。
16+
17+
布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于1970年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map 、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。
18+
19+
![布隆过滤器示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/布隆过滤器-bit数组.png)
20+
21+
位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000 / 8 = 125000 B = 15625 byte ≈ 15.3kb 的空间。
22+
23+
总结:**一个名叫 Bloom 的人提出了一种来检索元素是否在给定大集合中的数据结构,这种数据结构是高效且性能很好的,但缺点是具有一定的错误识别率和删除难度。并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大。**
24+
25+
### 2.布隆过滤器的原理介绍
26+
27+
**当一个元素加入布隆过滤器中的时候,会进行如下操作:**
28+
29+
1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
30+
2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。
31+
32+
**当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:**
33+
34+
1. 对给定元素再次进行相同的哈希计算;
35+
2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
36+
37+
举个简单的例子:
38+
39+
40+
41+
![布隆过滤器hash计算](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/布隆过滤器-hash运算.png)
42+
43+
如图所示,当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为0)。当第二次存储相同字符串时,因为先前的对应位置已设置为1,所以很容易知道此值已经存在(去重非常方便)。
44+
45+
如果我们需要判断某个字符串是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
46+
47+
**不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。**
48+
49+
综上,我们可以得出:**布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
50+
51+
### 3.布隆过滤器使用场景
52+
53+
1. 判断给定数据是否存在:比如判断一个数字是否在于包含大量数字的数字集中(数字集很大,5亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
54+
2. 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。
55+
56+
### 4.通过 Java 编程手动实现布隆过滤器
57+
58+
我们上面已经说了布隆过滤器的原理,知道了布隆过滤器的原理之后就可以自己手动实现一个了。
59+
60+
如果你想要手动实现一个的话,你需要:
61+
62+
1. 一个合适大小的位数组保存数据
63+
2. 几个不同的哈希函数
64+
3. 添加元素到位数组(布隆过滤器)的方法实现
65+
4. 判断给定元素是否存在于位数组(布隆过滤器)的方法实现。
66+
67+
下面给出一个我觉得写的还算不错的代码(参考网上已有代码改进得到,对于所有类型对象皆适用):
68+
69+
```java
70+
import java.util.BitSet;
71+
72+
public class MyBloomFilter {
73+
74+
/**
75+
* 位数组的大小
76+
*/
77+
private static final int DEFAULT_SIZE = 2 << 24;
78+
/**
79+
* 通过这个数组可以创建 6 个不同的哈希函数
80+
*/
81+
private static final int[] SEEDS = new int[]{3, 13, 46, 71, 91, 134};
82+
83+
/**
84+
* 位数组。数组中的元素只能是 0 或者 1
85+
*/
86+
private BitSet bits = new BitSet(DEFAULT_SIZE);
87+
88+
/**
89+
* 存放包含 hash 函数的类的数组
90+
*/
91+
private SimpleHash[] func = new SimpleHash[SEEDS.length];
92+
93+
/**
94+
* 初始化多个包含 hash 函数的类的数组,每个类中的 hash 函数都不一样
95+
*/
96+
public MyBloomFilter() {
97+
// 初始化多个不同的 Hash 函数
98+
for (int i = 0; i < SEEDS.length; i++) {
99+
func[i] = new SimpleHash(DEFAULT_SIZE, SEEDS[i]);
100+
}
101+
}
102+
103+
/**
104+
* 添加元素到位数组
105+
*/
106+
public void add(Object value) {
107+
for (SimpleHash f : func) {
108+
bits.set(f.hash(value), true);
109+
}
110+
}
111+
112+
/**
113+
* 判断指定元素是否存在于位数组
114+
*/
115+
public boolean contains(Object value) {
116+
boolean ret = true;
117+
for (SimpleHash f : func) {
118+
ret = ret && bits.get(f.hash(value));
119+
}
120+
return ret;
121+
}
122+
123+
/**
124+
* 静态内部类。用于 hash 操作!
125+
*/
126+
public static class SimpleHash {
127+
128+
private int cap;
129+
private int seed;
130+
131+
public SimpleHash(int cap, int seed) {
132+
this.cap = cap;
133+
this.seed = seed;
134+
}
135+
136+
/**
137+
* 计算 hash 值
138+
*/
139+
public int hash(Object value) {
140+
int h;
141+
return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
142+
}
143+
144+
}
145+
}
146+
```
147+
148+
测试:
149+
150+
```java
151+
String value1 = "https://javaguide.cn/";
152+
String value2 = "https://github.com/Snailclimb";
153+
MyBloomFilter filter = new MyBloomFilter();
154+
System.out.println(filter.contains(value1));
155+
System.out.println(filter.contains(value2));
156+
filter.add(value1);
157+
filter.add(value2);
158+
System.out.println(filter.contains(value1));
159+
System.out.println(filter.contains(value2));
160+
```
161+
162+
Output:
163+
164+
```
165+
false
166+
false
167+
true
168+
true
169+
```
170+
171+
测试:
172+
173+
```java
174+
Integer value1 = 13423;
175+
Integer value2 = 22131;
176+
MyBloomFilter filter = new MyBloomFilter();
177+
System.out.println(filter.contains(value1));
178+
System.out.println(filter.contains(value2));
179+
filter.add(value1);
180+
filter.add(value2);
181+
System.out.println(filter.contains(value1));
182+
System.out.println(filter.contains(value2));
183+
```
184+
185+
Output:
186+
187+
```java
188+
false
189+
false
190+
true
191+
true
192+
```
193+
194+
### 5.利用Google开源的 Guava中自带的布隆过滤器
195+
196+
自己实现的目的主要是为了让自己搞懂布隆过滤器的原理,Guava 中布隆过滤器的实现算是比较权威的,所以实际项目中我们不需要手动实现一个布隆过滤器。
197+
198+
首先我们需要在项目中引入 Guava 的依赖:
199+
200+
```java
201+
<dependency>
202+
<groupId>com.google.guava</groupId>
203+
<artifactId>guava</artifactId>
204+
<version>28.0-jre</version>
205+
</dependency>
206+
```
207+
208+
实际使用如下:
209+
210+
我们创建了一个最多存放 最多 1500个整数的布隆过滤器,并且我们可以容忍误判的概率为百分之(0.01)
211+
212+
```java
213+
// 创建布隆过滤器对象
214+
BloomFilter<Integer> filter = BloomFilter.create(
215+
Funnels.integerFunnel(),
216+
1500,
217+
0.01);
218+
// 判断指定元素是否存在
219+
System.out.println(filter.mightContain(1));
220+
System.out.println(filter.mightContain(2));
221+
// 将元素添加进布隆过滤器
222+
filter.put(1);
223+
filter.put(2);
224+
System.out.println(filter.mightContain(1));
225+
System.out.println(filter.mightContain(2));
226+
```
227+
228+
在我们的示例中,当`mightContain()` 方法返回*true*时,我们可以99%确定该元素在过滤器中,当过滤器返回*false*时,我们可以100%确定该元素不存在于过滤器中。
229+
230+
### 6.Redis 中的布隆过滤器
231+
232+
- https://juejin.im/post/5bc7446e5188255c791b3360
233+
234+
### 8.其他推荐阅读
235+
236+
1. 详解布隆过滤器的原理,使用场景和注意事项:https://zhuanlan.zhihu.com/p/43263751
237+
2.

docs/database/Redis/Redis.md

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -257,28 +257,62 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。
257257
258258
### 缓存雪崩和缓存穿透问题解决方案
259259

260-
**缓存雪崩**
260+
#### **缓存雪崩**
261+
262+
**什么是缓存雪崩?**
261263

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

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

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

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

276+
#### **缓存穿透**
272277

273-
**缓存穿透**
278+
**什么是缓存穿透?**
274279

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

277-
解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
282+
一般MySQL 默认的最大连接数在 150 左右,这个可以通过 `show variables like '%max_connections%'; `命令来查看。最大连接数一个还只是一个指标,cpu,内存,磁盘,网络等无力条件都是其运行指标,这些指标都会限制其并发能力!所以,一般 3000 个并发请求就能打死大部分数据库了。
283+
284+
**有哪些解决办法?**
285+
286+
最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
287+
288+
**1)缓存无效 key** : 如果缓存和数据库都查不到某个 key 的数据就写一个到 redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086`。这种方式可以解决请求的 key 变化不频繁的情况,如何黑客恶意攻击,每次构建的不同的请求key,会导致 redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
289+
290+
另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值`
291+
292+
如果用 Java 代码展示的话,差不多是下面这样的:
293+
294+
```java
295+
public Object getObjectInclNullById(Integer id) {
296+
// 从缓存中获取数据
297+
Object cacheValue = cache.get(id);
298+
// 缓存为空
299+
if (cacheValue != null) {
300+
// 从数据库中获取
301+
Object storageValue = storage.get(key);
302+
// 缓存空对象
303+
cache.set(key, storageValue);
304+
// 如果存储数据为空,需要设置一个过期时间(300秒)
305+
if (storageValue == null) {
306+
// 必须设置过期时间,否则有被攻击的风险
307+
cache.expire(key, 60 * 5);
308+
}
309+
return storageValue;
310+
}
311+
return cacheValue;
312+
}
313+
```
278314

279-
参考:
280315

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

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

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

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

345+
### 参考
346+
347+
- 《Redis开发与运维》
348+
- Redis 命令总结:http://redisdoc.com/string/set.html
349+
311350
## 公众号
312351

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

0 commit comments

Comments
 (0)