![在这里插入图片描述](https://img-blog.csdnimg.cn/1986bca946aa4cf8b1c60edf3e36c00d.png)
2. 负载场景
经过哈希算法hash(ip:port)%N
,同一客户的请求都会被映射到相同的服务器上,这有效解决了会话共享的问题,这时候就不需要加入redis缓存服务器去记录客户的登录状态,避免引用外部模块过多导致系统不稳定的问题。
但这种方法也有问题,比如当某个服务器宕机了(或增加服务器),这时候N的值就发生改变,导致相同客户的请求就不一定还会转发到以前那台服务器上,这会发生身份认证的问题。
我们想要的是同一 ip:port
的请求,永远被映射到同一台server上处理
2. 缓存场景
客户通常发请求查询信息的时候,是现在缓存服务器上查找,查找不到再到DB查找,然后把数据从DB转移到缓存服务器。
- 若此时有一台服务器宕机了,同理在0号缓存服务器中的信息再次被查询时,这个请求很有可能会被转发到1号服务器,此时无法在1号缓存服务器中找到信息,就会去DB中查找,此时大量的磁盘IO会严重影响server的响应速度。更严重来讲,一台服务器的负载太高,可能会影响整个系统的运转,甚至崩溃。
- 若此时增加了一台服务器,N发生改变。举个极端的例子,此时所有的请求都被转发到新的服务器中,新的服务器无法查询到相应信息,同理会产生大量的磁盘IO,甚至服务器崩溃。
我们的理想情况是:
某个server挂了,不影响其他server的正常运转,不会请求急剧增加
server增加了,不影响原来server的请求,只会把后续的请求映射到新的server上
解决方法:一致性哈希算法
3. 一致性哈希算法
#include<iostream>
#include<set>
#include<list>
#include<map>
#include<string>
#include"md5.h"
using namespace std;
//一致性哈希环的取值类型
using uint = unsigned int;
//前置声明物理节点主机类型
class PhysicalHost;
//虚拟节点
class VirtualHost {
public:
VirtualHost(string ip, PhysicalHost* phy_host_ptr)
: ip_(ip)
, phy_host_ptr_(phy_host_ptr)
{
md5_ = getMD5(ip_.c_str());
}
// 虚拟节点存放到set的时候,需要排序,默认less,需要提供operator<
bool operator<(const VirtualHost& vir_host) const {
// 根据md5值排序
return md5_ < vir_host.md5_;
}
// 删除哈希环上的虚拟节点时,需要查找,重载operator==
bool operator==(const VirtualHost& vir_host) const {
return ip_ == vir_host.ip_;
}
const uint get_md5() const {
return md5_;
}
const PhysicalHost* get_phy_host() const {
return phy_host_ptr_;
}
private:
string ip_; // 虚拟节点记录的ip信息
uint md5_; // 根据物理节点的ip计算的ip值得到的MD5,这是32位加密串运算得到的uint
PhysicalHost* phy_host_ptr_; // 指向实际的物理节点
};
//物理节点
class PhysicalHost {
public:
// 物理节点的ip,创建虚拟节点的个数
PhysicalHost(string ip, int v_number)
: ip_(ip)
{
for (int i = 0; i < v_number; i++) {
// 虚拟节点需要记录ip以及对应的物理节点
virtual_hosts_.push_back(VirtualHost(ip_ + "#" + ::to_string(i), this));
}
}
const string get_ip() const{
return ip_;
}
const list<VirtualHost>& get_virtual_hosts() const {
return virtual_hosts_;
}
private:
string ip_;//物理机器的ip地址
list<VirtualHost> virtual_hosts_; // 双向循环链表,存储虚拟节点的链表
};
//一致性哈希
class ConsistentHash {
public:
// 添加物理主机的虚拟节点到一致性哈希环
void add_host(PhysicalHost& phy_host) {
auto vir_list = phy_host.get_virtual_hosts();
for (auto vir_host : vir_list) {
hash_circle_.insert(vir_host);
}
}
// 删除哈希环物理节点所有的虚拟节点
void del_host(PhysicalHost& phy_host) {
//获取物理主机所有的虚拟节点列表
auto vir_list = phy_host.get_virtual_hosts();
for (auto vir_host : vir_list) {
// 红黑树查找,O(log2n)
auto iter = hash_circle_.find(vir_host);
if (iter != hash_circle_.end()) {
hash_circle_.erase(iter);
}
}
}
// 根据客户的ip,计算其对应的虚拟主机,然后根据虚拟主机返回真是的物理主机的ip
string get_client_host(string client_ip) const{
uint client_md5 = getMD5(client_ip.c_str());
// 找第一个比客户ip的md5大的虚拟主机
for (VirtualHost vir_host : hash_circle_) {
if (vir_host.get_md5() > client_md5) {
return vir_host.get_phy_host()->get_ip();
}
}
// 客户的ip得到的md5过大,无法找到更大的md5,那直接分配第一个虚拟节点
return hash_circle_.begin()->get_phy_host()->get_ip();
}
private:
// 由于需要顺时针查找,需要排序,所以一致性哈希算法底层用到红黑树
set<VirtualHost> hash_circle_;
};
void show_consistent_hash(const ConsistentHash& hash_circle) {
list<string> client_ip_list{
"192.168.1.100",
"192.168.1.101",
"192.168.1.102",
"192.168.1.103",
"192.168.1.104",
"192.168.1.105",
"192.168.1.106",
"192.168.1.107",
"192.168.1.108",
"192.168.1.109",
"192.168.1.110",
"192.168.1.111",
"192.168.1.112",
"192.168.1.113",
};
// 物理服务器ip 所服务的客户端ip
map<string, list<string>> ip_map;
for (string client_ip : client_ip_list) {
// 根据客户端的ip,计算哈希环上的虚拟主机,从而拿到对应物理主机的ip
string phy_host_ip = hash_circle.get_client_host(client_ip);
ip_map[phy_host_ip].push_back(client_ip);
}
for (auto pair : ip_map) {
cout << "server ip :" << pair.first << endl;
cout << "该服务器服务的客户端有" << pair.second.size() << "个" << endl;
cout << "client ip :" << endl;
for (string client_ip : pair.second) {
cout << client_ip << endl;
}
cout << "-----------------------------" << endl;
}
}
int main() {
PhysicalHost phy_host1("10.117.121.66", 200);
PhysicalHost phy_host2("10.117.121.67", 200);
PhysicalHost phy_host3("10.117.121.68", 200);
ConsistentHash hash_circle;
hash_circle.add_host(phy_host1);
hash_circle.add_host(phy_host2);
hash_circle.add_host(phy_host3);
show_consistent_hash(hash_circle);
hash_circle.del_host(phy_host2);
cout << "*********主机2宕机*********" << endl;
show_consistent_hash(hash_circle);
return 0;
}