负载均衡和一致性哈希

负载均衡和一致性哈希 反向代理 reverse proxy 是指以代理服务器来接收由客户端发送来的请求,并通过一定的策略将其转变发给实际处理请求的后端服务器;主要应用于负载均衡、动态缓存、安全认证、内网穿透、SSL 加密等;而负载均衡 load balancing 是指在多个 slot(槽,一般是某种计算资源)中分配负载,以优化资源利用率和避免单点故障问题的方法,是高可用性分布式系统的必备中间件;常用的开源 load balancer 有 nginx,LVS,Haproxy 等;负载均衡可以视为反向代理的一种应用,负载均衡的方法大致可以分为传统负载均衡算法和哈希算法两种,本文简单地总结了这些算法的原理。 1 传统负载均衡算法 随机 random:将 key 随机分配到某一个 slot 上,根据概率论可知,吞吐量越大,随机算法的效果越好; 加权随机 weighted random:为每一个 slot 分配一个权重,在随机的时候考虑权重的影响;可以通过在所有 slot 的权重总和中随机出一个数字 k,找到 k 所在的 slot 位置来实现; 轮询 round robin:按顺序依次将 key 分配给每一个 slot; 加权轮询 weighted round robin:为每一个 slot 分配一个权重,在按序分配时为权重更高的 slot 分配更多的 key; 平滑加权轮询 smooth weighted round robin:一种能够均匀地分散调度序列的加权轮询方法,分为以下几个步骤: 选出当前权重最高的 slot,将 key 分配给它; 将选出的 slot 的权重数值减去其初始权重; 将所有 slot 的权重数值都加上它们的原始权重; 重复以上步骤; 最少连接数 least connections:将 key 分配给当前具有最少连接数量的 slot; 2 Mod-N 哈希 在有些场景下,传统负载均衡算法无法满足我们的需求,例如:...

October 25, 2020 · 4 min

C++ 并发入门:以 LeetCode 1114 为例

C++ 并发入门:以 LeetCode 1114 为例 题目 直接做题:1114 按序打印 解法 1. std::mutex 如果你对 c++ 11 略为熟悉的话,应该能够想到用 std::mutex 来解这道题,在函数构造时(主线程)对 std::mutex 进行 lock,然后在各个线程调用的函数中依次对 std::mutex 对象进行 unlock: class Foo { mutex mtx1, mtx2; public: Foo() { mtx1.lock(), mtx2.lock(); } void first(function<void()> printFirst) { printFirst(); mtx1.unlock(); } void second(function<void()> printSecond) { mtx1.lock(); printSecond(); mtx1.unlock(); mtx2.unlock(); } void third(function<void()> printThird) { mtx2.lock(); printThird(); mtx2.unlock(); } }; Mutex 即 mutual exclusion,是用来防止多个线程同时访问共享资源对象的机制,在同一时间只有一个线程可以拥有一个 mutex 对象,其他线程调用 std::mutex::lock 函数时会阻塞直到其获取锁资源。 这段代码能够 ac,但实际上这种使用 mutex 的方法是错误的,因为根据 c++ 标准,在一个线程尝试对一个 mutex 对象进行 unlock 操作时,mutex 对象的所有权必须在这个线程上;也就是说,应该由同一个线程来对一个 mutex 对象进行 lock 和 unlock 操作,否则会产生未定义行为。题目中提到了 first, second, third 三个函数分别是由三个不同的线程来调用的,但我们是在 Foo 对象构造时(可以是在 create 这几个线程的主线程中,也可以是在三个线程中的任意一个)对两个 mutex 对象进行 lock 操作的,因此,调用 first 和 second 函数的两个线程中至少有一个在尝试获取其他线程所拥有的 mutex 对象的所有权。...

September 30, 2020 · 3 min

Effective C++ notes

Effective C++ 笔记 0 导言 1 构造函数 default 构造函数:可被调用而不带任何实参的构造函数,这样的构造函数要么没有参数,要么每个参数都带有默认值,例如 class Bar { public: // explicit Bar(); // 是 default 构造函数 // explicit Bar(int x = 0) // 不是 default 构造函数 explicit Bar(int x = 0, bool b = true); // 是 default 构造函数 private: int x; bool b; }; explicit 关键字:阻止执行隐式类型转换,其优点是禁止了编译器执行非预期的类型转换,例如 void Foo(Bar obj); // Foo 函数的参数是一个类型为 Bar 的对象 Bar obj_1; // 构造一个 Bar 类型的对象 Foo (obj_1); // 没问题,传递一个 Bar 类型的对象给 Foo 函数 Foo (Bar()); // 没问题,构造一个 Bar 类型的对象,并传递给 Foo 函数 Foo (2); // 如果 Bar 的构造函数没有被声明为 explicit,那么会调用 Bar 的构造函数构造一个成员变量 x = 2 的对象,也就是说发生了隐式类型转换;如果其构造函数被声明为 explicit,那么就不会构造出 Bar 类型的对象 copy 构造函数:用同类型的对象初始化新的对象,它定义了一个对象如何 pass by reference。...

September 24, 2020 · 6 min

C++ 单例模式的模板实现

C++ 单例模式的模板实现 单例模式是一种创建型的设计模式(creational design patterns),使用单例模式进行设计的类在程序中只拥有一个实例(single instance),这个类称为单例类,它会提供一个全局的访问入口(global access point),关于单例模式的讨论可以参考Singleton revisited;基于这两个特点,单例模式可以有以下几种实现: Meyer’s Singleton Scott Meyers 在 Effective C++ 的 Item 4: Make sure that objects are initialized before they’re used 里面提出了一种利用 C++ 的 static 关键字来实现的单例模式,这种实现非常简洁高效,它的特点是: 仅当程序第一次执行到 GetInstance 函数时,执行 instance 对象的初始化; 在 C++ 11 之后,被 static 修饰的变量可以保证是线程安全的; template<typename T> class Singleton { public: static T& GetInstance() { static T instance; return instance; } Singleton(T&&) = delete; Singleton(const T&) = delete; void operator= (const T&) = delete; protected: Singleton() = default; virtual ~Singleton() = default; }; 通过禁用单例类的 copy constructor,move constructor 和 operator= 可以防止类的唯一实例被拷贝或移动;不暴露单例类的 constructor 和 destructor 可以保证单例类不会通过其他途径被实例化,同时将两者定义为 protected 可以让其被子类继承并使用。...

September 10, 2020 · 2 min

并行计算入门

并行计算入门 1 概述 1.1 并行计算 高性能计算(High Performance Computing)是计算机科学中的一个领域,其目的可以概括为优化性能,它包括了缓存技术、数据结构和算法、IO 优化、指令重组(instruction reorganization)、编译器优化等; 并行计算(Parallel Computing)是高性能计算下的一个细分领域,其主要思想是将复杂问题分解成若干个部分,将每一个部分交给独立的处理器(计算资源)进行计算,以提高效率;针对不同的问题,并行计算需要专用的并行架构,架构既可以是专门设计的,含有多个处理器的单一硬件或超级计算机,也可以是以某种方式互连的若干台的独立计算机构成的集群;并没有一个统一的并行计算架构适用于每一个问题,如果使用了错误的架构,并行计算甚至会导致性能下降。 1.2 硬件架构 中央处理器(Central Processing Unit)的主要功能是解释计算机指令,它由控制单元(Control Unit)、算术逻辑单元(Arithmetic Logic Unit)、乱序控制单元(Out-of-Order Control Unit)、分支预测器(Branch Predictor)、数据缓存(Data Cache)等部件组成;CPU 被设计为可以快速地处理各种通用计算任务并最小化延迟,但在并发性(时钟频率)方面受到限制; 图形处理器(Graphics Processing Unit, GPU)是英伟达(NVIDIA)在 1999 年 8 月发布 NVIDIA GeForce 256 时提出的概念;现代 GPU 的模型设计可以概括为几个关键点: GPU 的设计目的是最大化吞吐量(Throughput) 能够将程序中数据可并行的部分从 CPU 转移到 GPU 能够使用尽可能多的线程进行并行计算 GPU 拥有的内核数量相较于 CPU 多得多,可以有数千个同时运行的内核执行大规模并行计算,因此在早期专门应用于图形数据的处理,但随着近十几年的发展,其强大的并行处理能力也使其可以处理非图形数据,尤其在深度学习领域非常受欢迎; 在制造工艺的限制下,芯片的密度和最大面积都是有限的(摩尔定律),因此芯片设计实际上是功能和元件数量的权衡;出于对通用性的要求,CPU 的芯片设计必须使用较多种类的原件以增加其功能,同时放弃部分具有复杂功能的元件数量,而 GPU 的芯片设计则是通过移除部分具有复杂功能的元件来换取更多的空间,并集成更多的基本功能元件; GPU 设备由多个流多处理器(Streaming Multiprocessor)的处理器集群(Processor Cluster)组成。每个流多处理器都关联一个控制单元 和 L1 Cache,这样的设计使得一个芯片可以同时支持上百个指令流的并行执行;通常一个流多处理器在与全局 GDDR-5 内存交换数据之前都会利用与之关联 L1 Cache 和 L2 Cache 来减少数据传输的延迟;而又因为 GPU 通常拥有足够大的计算量,使得其不需要与 CPU 一样非常频繁地从内存中获取数据,因此 GPU 的缓存层一般是小于 CPU 的。...

August 12, 2020 · 12 min