数据结构和一致性哈希复习

This commit is contained in:
Estom
2021-09-09 15:28:48 +08:00
parent 825343abe6
commit 60c84ac5c3
13 changed files with 154 additions and 24 deletions

View File

@@ -0,0 +1,41 @@
## 1 alloc
alloc是向栈申请内存因此无需释放。
* 功能返回一个指向n个连续字符存储单元的指针。利用afree§释放已经分配的存储空间以便以后使用。
* 函数原型: char *alloc(int n);
## 2 malloc(memeory allocation,动态内存分配)
malloc分配的内存是位于堆中的并且没有初始化内存的内容因此基本上malloc之后调用函数memset来初始化这部分的内存空间。
* **功能** 用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存,且分配的大小就是程序要求的大小。
* 函数原型: void *malloc(unsigned int size)
### 工作机制
* malloc函数的实质体现在他有一个将可用的内存块连接为一个常常的链表的所谓空闲链表。调用malloc函数是他沿着连接表寻找一个大到足以满足用户请求所需要的内存块。然后将该内存块一分为二一块的大小与用户请求的大小相等另一块的大小就是剩下的字节。接下来将分配给用户的那块内存传给用户并将剩下的那块如果有的话返回到连接表上。调用free函数时他将用户释放的内存块连接到空闲链表上。到最后空闲链会被切成很多的小内存片段如果这时用户申请一个大的内存片段那么空闲链上可能没有满足用户要求的片段了。于是malloc函数请求延时并开始在空闲链上检查各个内存片段对他们进行整理将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块malloc函数会返回NULL指针因此在调用malloc动态申请内存块时一定要进行返回值的判断。
* linux Libc6采用的机制是在free的时候试图整合相邻的碎片使其合并称为一个较大的free空间。
注意总结:
1. 使用malloc一定要判断返回值是否为NULL。
2. 使用malloc要用memset初始化。
## 3 calloc
calloc则将初始化这部分的内存设置为0。
* 函数原型void *calloc(unsigned int num,unsigned int size);
* 功能在内存的动态存储区中分配n个长度为size的连续空间函数返回一个指向分配起始地址的指针如果分配不成功返回NULL。num对象个数size对象占据的内存字节数相较于malloc函数calloc函数会自动将内存初始化为0。而malloc不做初始化分配到空间中的数据是随机数据。
## 4 realloc
realloc对申请的内存进行大小的调整申请的内存最终需要通过函数free来释放。
* 函数原型extern void *realloc(void *mem_address,unsigned int newsize);
* 功能先判断当前的指针是够有足够的连续空间如果有扩大mem_address指向的地址并且将mem_address返回如果空间不够先按照newsize指定的大小分配空间将原有数据从头到尾拷贝到新分配的内存区域而后释放原来mem_address所指内存区域(注意L原来指针是自动释放不需要使用free),同时返回新分配的内存区域的首地址。
### 注意事项
1. 头文件:#include <stdlib.h> 有些编译器需要 #include <malloc.h>
2. 原先的地址如果放弃使用是会被自动释放的。不要手动的free否则会造成内存泄露。
3. newsize小于原先的大小的时候可能会造成数据丢失。
4. mem_address一定要是malloccalloc或realloc申请得到指针。
5. 如果newsize大小为0那么释放mem_address指向的内存并返回NULL。这里需要注意的是只对指针本身进行释放例如对二维指针**a对a调用realloc时只会释放一维使用时谨防内存泄漏。
6. 如果没有足够可用的内存用来完成重新分配扩大原来的内存块或者分配新的内存块则返回NULL。而原来的内存块保持不变。
7. 传递给realloc的指针可以为空效果等同于malloc。

View File

@@ -13,19 +13,21 @@
* [x] 标准库
* [x] 面向对象
* [x] 设计模式
* [ ]编程
* [ ]编程
* [ ] 网络编程
* [ ] GO
* [x] 重新整理go知识在原先的基础上进行了扩充
* [ ] 基础知识。
* [ ] go语言的优势。
* [ ] 协程并发编程等,相关内容的了解。
* [ ] 数据结构
* [ ] 算法
* [x] 数据结构
* [x] 算法
### 知识复习——基础
* [ ] 操作系统
* [ ] 基础知识
* [ ] 关于同步异步机制的理解。总结各种语言、库、中间件、设计模式、数据库、操作系统的同步异步方式。
* [ ] Linux底层原理和常见函数。为什么说一切皆文件
* [x] 操作系统
* [x] 基础知识
* [x] 关于同步异步机制的理解。总结各种语言、库、中间件、设计模式、数据库、操作系统的同步异步方式。
* [x] Linux底层原理和常见函数。为什么说一切皆文件
* [ ] 面试内容复习
* [x] 数据库
* [x] 基础知识
* [x] MySQL
@@ -35,19 +37,18 @@
* [x] 消息队列定义
* [x] kafka消息队列
* [x] redis实现消息队列
* [ ] 计算机网络
* [ ] 应用层
* [ ] 网络
* [ ] 通信
* [ ] 链路
* [ ] 物理
* [ ] 面试内容复习
* [x] 计算机网络
* [x] 应用
* [x] 网络
* [x] 通信
* [x] 链路
* [x] 物理层
* [ ] 面试内容复习
### 知识复习——框架
* [ ] C++/GO框架
* [ ] 微服务框架
* [ ] docker/k8s/kite
* [ ] Django MVC框架
* [ ] Go kitex微服务框架
### 编程实战
* [ ] C++/Go网络编程(总结自己的网络编程经验。自己的grpc和kite/thrift框架的应用经验。关于异步通信的一些实现和总结。)
* [ ] web开发总结自己的web开发经验
* [ ] Linux系统编程
### 力扣刷题
* [ ] 算法编程(力扣刷题)

View File

@@ -172,7 +172,7 @@ malloc大于128k的内存使用mmap分配内存在堆和栈之间找一块
### malloc的实现方案
1. malloc 函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。
2. 调用 malloc函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内存块。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的那块(如果有的话)返回到连接表上。
2. 调用 malloc()函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内存块。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的那块(如果有的话)返回到连接表上。
3. 调用 free 函数时,它将用户释放的内存块连接到空闲链表上。
4. 到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的片段了。于是malloc函数请求延时并开始在空闲链表上检查各内存片段对它们进行内存整理将相邻的小空闲块合并成较大的内存块。

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,88 @@
# 一致性哈希算法
## 一致性Hash算法背景
一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的设计目标是为了解决因特网中的热点(Hot spot)问题初衷和CARP十分类似。一致性哈希修正了CARP使用的简单哈希算法带来的问题使得DHT可以在P2P环境中真正得到应用。
但现在一致性hash算法在分布式系统中也得到了广泛应用研究过memcached缓存数据库的人都知道memcached服务器端本身不提供分布式cache的一致性而是由客户端来提供具体在计算一致性hash时采用如下步骤
首先求出memcached服务器节点的哈希值并将其配置到0232的圆continuum上。
然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
然后从数据映射到的位置开始顺时针查找将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器就会保存到第一台memcached服务器上。
![](image/2021-09-09-15-21-22.png)
从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率但Consistent Hashing中只有在园continuum上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响如下图所示
![](image/2021-09-09-15-22-11.png)
## 2 一致性Hash性质
考虑到分布式系统每个节点都有可能失效并且新的节点很可能动态的增加进来如何保证当系统的节点数目发生变化时仍然能够对外提供良好的服务这是值得考虑的尤其实在设计分布式缓存系统时如果某台服务器失效对于整个系统来说如果不采用合适的算法来保证一致性那么缓存于系统中的所有数据都可能会失效即由于系统节点数目变少客户端在请求某一对象时需要重新计算其hash值通常与系统中的节点数目有关由于hash值已经改变所以很可能找不到保存该对象的服务器节点因此一致性hash就显得至关重要良好的分布式cahce系统中的一致性hash算法应该满足以下几个方面
### 平衡性(Balance)
平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。
### 单调性(Monotonicity)
单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中又有新的缓冲区加入到系统中那么哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲区中去而不会被映射到旧的缓冲集合中的其他缓冲区。简单的哈希算法往往不能满足单调性的要求如最简单的线性哈希x = (ax + b) mod (P)在上式中P表示全部缓冲的大小。不难看出当缓冲大小发生变化时(从P1到P2)原来所有的哈希结果均会发生变化从而不满足单调性的要求。哈希结果的变化意味着当缓冲空间发生变化时所有的映射关系需要在系统内全部更新。而在P2P系统内缓冲的变化等价于Peer加入或退出系统这一情况在P2P系统中会频繁发生因此会带来极大计算和传输负荷。单调性就是要求哈希算法能够应对这种情况。
### 分散性(Spread)
在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。
### 负载(Load)
负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。
### 平滑性(Smoothness)
平滑性是指缓存服务器的数目平滑改变和缓存对象的平滑改变是一致的。
## 3 原理
### 基本概念
一致性哈希算法Consistent Hashing最早在论文《Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web》中被提出。简单来说一致性哈希将整个哈希值空间组织成一个虚拟的圆环如假设某哈希函数H的值空间为0-2^32-1即哈希值是一个32位无符号整形整个哈希空间环如下
![](image/2021-09-09-15-23-14.png)
整个空间按顺时针方向组织。0和232-1在零点中方向重合。
下一步将各个服务器使用Hash进行一个哈希具体可以选择服务器的ip或主机名作为关键字进行哈希这样每台机器就能确定其在哈希环上的位置这里假设将上文中四台服务器使用ip地址哈希后在环空间的位置如下
![](image/2021-09-09-15-24-05.png)
接下来使用如下算法定位数据访问到相应服务器将数据key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置从此位置沿环顺时针“行走”第一台遇到的服务器就是其应该定位到的服务器。
例如我们有Object A、Object B、Object C、Object D四个数据对象经过哈希计算后在环空间上的位置如下
![](image/2021-09-09-15-24-47.png)
根据一致性哈希算法数据A会被定为到Node A上B被定为到Node B上C被定为到Node C上D被定为到Node D上。
### 容错性与扩展性
下面分析一致性哈希算法的容错性和可扩展性。现假设Node C不幸宕机可以看到此时对象A、B、D不会受到影响只有C对象被重定位到Node D。一般的在一致性哈希算法中如果一台服务器不可用则受影响的数据仅仅是此服务器到其环空间中前一台服务器即沿着逆时针方向行走遇到的第一台服务器之间数据其它不会受到影响。
下面考虑另外一种情况如果在系统中增加一台服务器Node X如下图所示
![](image/2021-09-09-15-24-57.png)
此时对象Object A、B、D不受影响只有对象C需要重定位到新的Node X 。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。
综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
### 数据倾斜问题
另外,一致性哈希算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。例如系统中只有两台服务器,其环分布如下,
![](image/2021-09-09-15-25-26.png)
此时必然造成大量数据集中到Node A上而只有极少量会定位到Node B上。为了解决这种数据倾斜问题一致性哈希算法引入了虚拟节点机制即对每一个服务节点计算多个哈希每个计算结果位置都放置一个此服务节点称为虚拟节点。具体做法可以在服务器ip或主机名的后面增加编号来实现。例如上面的情况可以为每台服务器计算三个虚拟节点于是可以分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,于是形成六个虚拟节点:
![](image/2021-09-09-15-26-10.png)
同时数据定位算法不变只是多了一步虚拟节点到实际节点的映射例如定位到“Node A#1”、“Node A#2”、“Node A#3”三个虚拟节点的数据均定位到Node A上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中通常将虚拟节点数设置为32甚至更大因此即使很少的服务节点也能做到相对均匀的数据分布。

View File

@@ -142,7 +142,7 @@ int largestRectangleArea(vector<int>& heights) {
### 策略选择
* 思路:使用暴力解法求出所有区间,再求出区间的最小值相乘跟新数据,并不是一种很好的算法,所以经过上面俩题的磨  炼,此时我们应该使用一个单调递减栈
* 思路:使用暴力解法求出所有区间,再求出区间的最小值相乘跟新数据,并不是一种很好的算法
### 算法设计
@@ -228,13 +228,13 @@ int GetMaxSequence(vector<int>& v)
### 算法设计
* 因此,我们可以使用单调栈作为维护 22 的数据结构,并给出下面的算法:
* 因此,我们可以使用单调栈作为维护 2 的数据结构,并给出下面的算法:
* 我们用单调栈维护所有可以作为 22 的候选元素。初始时,单调栈中只有唯一的元素 \textit{a}[n-1]a[n1]。我们还需要使用一个变量 \textit{max\_k}max_k 记录所有可以真正作为 22 的元素的最大值;
* 我们用单调栈维护所有可以作为 2 的候选元素。初始时,单调栈中只有唯一的元素 $\textit{a}[n-1]$。我们还需要使用一个变量 $\textit{max\_k}$max_ 记录所有可以真正作为 2 的元素的最大值;
* 随后我们从 n-2n2 开始从右到左枚举元素 a[i]a[i]
* 随后我们从 n-2开始从右到左枚举元素 a[i]
* 首先我们判断 a[i]a[i] 是否可以作为 11。如果 a[i] < \textit{max\_k}a[i]<max_k,那么它就可以作为 11我们就找到了一组满足 132132 模式的三元组;
* 首先我们判断 a[i]是否可以作为 1。如果 $a[i] < \textit{max\_k}$,那么它就可以作为 1我们就找到了一组满足 132模式的三元组
* 随后我们判断 a[i]a[i] 是否可以作为 33以此找出哪些可以真正作为 22 的元素。我们将 a[i]a[i] 不断地与单调栈栈顶的元素进行比较,如果 a[i]a[i] 较大,那么栈顶元素可以真正作为 22将其弹出并更新 \textit{max\_k}max_k