1.基础
什么是API网关?
API网关是一个服务器,它充当API前端和后端服务之间的中介,用于接收API请求、执行各种必要的处理,并将请求路由到适当的微服务。它可以处理不同的跨切面任务,如请求路由、负载均衡、身份验证、授权、安全策略执行、流量监控、日志记录、缓存、请求和响应转换等。
API网关在微服务架构中的作用是什么?
请求路由:当一个客户端请求到达API网关时,网关负责将该请求路由到适当的微服务。
负载均衡:API网关可以分发进入的请求流量,确保微服务实例之间的负载均衡。
身份验证和授权:网关可以验证请求的来源,确保它们来自合法的用户,并授权他们访问特定的服务或资源。
请求和响应转换:API网关可以修改请求和响应,以满足特定的前端或后端需求。
安全策略执行:例如,防止DDoS攻击、SQL注入等。
流量监控和日志记录:API网关可以记录关于请求和响应的详细信息,以进行分析和监控。
缓存:为了提高性能,API网关可以缓存后端服务的响应。
服务发现:在动态的微服务环境中,API网关可以自动发现新的服务实例并将流量路由到它们。
网关的服务发现通常不是自己独立实现的,而是依赖于现有的服务发现机制或注册中心,如Zookeeper、Consul、Etcd、Eureka等。这些注册中心提供了一种方式,使得服务实例在启动时可以注册自己,并在关闭时注销,同时还可以提供健康检查功能。
以下是网关服务发现的一般工作原理:
服务注册:当一个新的服务实例启动时,它会在注册中心注册自己,提供一些元数据,如服务的IP地址、端口、版本、健康检查端点等。
健康检查:注册中心会定期对注册的服务实例进行健康检查。如果某个服务实例不再健康或无法访问,注册中心会将其标记为不可用。
服务发现:当API网关需要路由一个请求到某个服务时,它会查询注册中心,获取该服务的所有健康实例的列表。然后,网关可以使用某种负载均衡策略(如轮询、最少连接等)来选择一个服务实例,并将请求路由到该实例。
动态更新:如果服务实例的状态发生变化(例如,新的实例被添加或现有的实例失败),注册中心会通知所有监听这些变化的客户端(如API网关)。这样,网关可以实时更新其路由决策,确保请求总是被路由到健康的服务实例。
负载均衡:在得到健康的服务实例列表后,网关可以使用负载均衡算法来决定将请求路由到哪个实例。
故障转移:如果选定的服务实例突然变得不可用,网关可以重新选择另一个健康的实例,并重新路由请求。
借助注册中心和服务发现机制,API网关可以确保高可用性和弹性,即使在动态的、经常变化的微服务环境中也是如此。
网关和代理之间的主要区别是什么?
功能性:代理通常是一个中介,它在客户端和服务器之间转发HTTP请求,而API网关更为复杂,提供了上述的多种功能,如请求路由、负载均衡、身份验证等。 用途:代理主要用于转发请求,可能会进行内容过滤或提供匿名访问。API网关则专为API设计,处理API的特定需求。 智能性:API网关通常比简单的代理更“智能”,因为它知道如何路由请求、如何与多个微服务交互,以及如何应用各种策略。 位置:代理可以位于客户端或服务器的任何位置,而API网关通常位于API的前端,作为所有请求的入口点。
网关故障
高可用性部署:部署多个网关实例,通常跨越多个物理位置或云区域。这样,如果一个实例或一个区域发生故障,其他实例仍然可以处理请求。
负载均衡器:在网关前面使用负载均衡器(如Nginx、HAProxy或云提供商的负载均衡器)。负载均衡器可以检测网关实例的健康状况,并将流量路由到健康的实例。
自动恢复:使用容器编排工具(如Kubernetes)可以确保当网关实例发生故障时,自动启动新的实例来替换它。
故障转移:预先配置故障转移策略,以便在主要网关或区域发生故障时,流量可以被重定向到备用的网关或区域。
监控和警报:持续监控网关的健康状况和性能指标,并在检测到问题时立即发出警报,以便运维团队可以迅速采取行动。
限流和熔断:使用限流和熔断机制来防止系统过载。如果一个服务开始响应缓慢或失败,熔断器可以“断开”该服务,防止进一步的请求,直到服务恢复正常。
备份和灾难恢复:定期备份网关的配置和数据,并确保有一个灾难恢复计划,以便在发生大规模故障时可以迅速恢复服务。
测试:定期进行故障注入和混沌工程实验,模拟网关故障,以确保上述策略和措施的有效性,并训练团队应对真实故障。
使用API网关进行流量控制或限流
流量控制或限流是API网关中的关键功能,用于防止系统过载并确保资源的公平使用。以下是实现流量控制的常见方法:
令牌桶算法:每个请求都需要从桶中获取一个令牌。桶以固定的速率添加令牌,如果桶为空,请求将被拒绝。这种方法允许突发流量,但长时间的流量不会超过配置的速率。
漏桶算法:请求进入一个“漏桶”,然后以固定的速率流出。如果桶满了,新的请求将被拒绝。
固定窗口限流:将时间分为固定的窗口,并限制每个窗口中的请求次数。这种方法简单,但可能在窗口边界时导致流量突增。
滑动日志限流:记录每个请求的时间戳,并在任何时候都计算最近的时间窗口内的请求总数。
分布式限流:对于分布式系统,可以使用中央存储(如Redis)来跟踪全局请求速率。
客户端限流:基于客户端IP或API密钥进行限流,确保单个客户端不会占用过多资源。
处理跨域请求(CORS):
跨域资源共享(CORS)是一种安全机制,允许web页面请求来自不同域的资源。API网关可以处理CORS请求,提供集中的跨域策略管理。
预检请求:当浏览器检测到跨域请求可能对服务器数据产生副作用时(例如POST请求),它会首先发送一个预检请求(HTTP OPTIONS方法)。API网关可以捕获这些请求并返回允许的HTTP方法和其他CORS相关的头信息。
简单请求:对于某些类型的跨域GET请求和POST请求,浏览器不会发送预检请求。在这种情况下,API网关只需在响应中添加适当的CORS头。
设置CORS头:API网关可以配置为自动添加以下CORS相关的HTTP头:
Access-Control-Allow-Origin: 指定哪些域可以访问资源。 Access-Control-Allow-Methods: 指定允许的HTTP方法。 Access-Control-Allow-Headers: 指定允许的HTTP头。 Access-Control-Allow-Credentials: 指定是否允许浏览器包含凭据。 Access-Control-Max-Age: 指定预检请求的结果可以缓存多长时间。
使用API网关进行身份验证和授权:
身份验证:API网关可以集中处理所有入站请求的身份验证。常见的方法包括: API密钥:每个请求都需要提供一个预先共享的密钥。 JWT (JSON Web Tokens):客户端发送一个签名的token,网关验证该token的签名并从中提取用户信息。 OAuth:使用第三方身份提供者进行身份验证,并为客户端提供一个token,该token随后用于访问API。 授权:一旦用户被身份验证,API网关可以检查用户的角色或权限,以确定他们是否有权访问请求的资源。
网关如何帮助在微服务之间进行请求路由:
服务发现:API网关可以与服务发现组件(如Consul、Eureka或Zookeeper)集成,以动态地知道每个服务的位置。 负载均衡:网关可以根据某种策略(如轮询、最少连接或响应时间)将请求分发到多个服务实例。 版本管理:如果有多个版本的服务,API网关可以根据请求的头信息、路径或其他属性将流量路由到正确的服务版本。 蓝绿部署或金丝雀发布:API网关可以将一部分流量路由到新版本的服务,以进行测试。
使用网关来集中处理日志和监控:
日志聚合:API网关可以捕获所有进入和离开的请求和响应日志,并将它们发送到集中的日志系统,如ELK堆栈(Elasticsearch、Logstash、Kibana)或Graylog。 性能指标:API网关可以收集关于请求速率、响应时间和服务健康状况的指标,并将这些数据推送到监控解决方案,如Prometheus、Graphite或Datadog。
日志聚合:API网关可以捕获所有进入和离开的请求和响应日志,并将它们发送到集中的日志系统,如ELK堆栈(Elasticsearch、Logstash、Kibana)或Graylog。 性能指标:API网关可以收集关于请求速率、响应时间和服务健康状况的指标,并将这些数据推送到监控解决方案,如Prometheus、Graphite或Datadog。 错误跟踪:API网关可以捕获从服务返回的任何错误或异常,并将详细信息发送到错误跟踪系统,如Sentry或Rollbar。 分布式追踪:对于复杂的微服务交互,API网关可以使用像Jaeger或Zipkin这样的工具来支持分布式追踪,以便更好地理解请求如何在系统中流动。
网关可以解析响应时间从以下几个方面:
请求开始时间:当API网关开始处理一个入站请求时,它会记录当前的时间戳。
请求结束时间:当API网关从下游服务接收到响应并准备将其转发回客户端时,它会再次记录当前的时间戳。
计算响应时间:网关可以通过简单地从请求结束时间中减去请求开始时间来计算响应时间。这给出了从客户端发送请求到网关,再从网关发送请求到目标服务,然后再从目标服务返回响应到网关,最后从网关返回响应到客户端的总时间。
中间件/拦截器:许多现代的API网关或反向代理软件(如Nginx, Kong, Traefik等)提供中间件或拦截器功能,这些功能可以自动记录这些时间戳并计算响应时间。
日志和监控:一旦计算出响应时间,网关可以将其记录在访问日志中,并/或将其作为性能指标发送到监控系统。
考虑网络延迟:值得注意的是,这种方法计算的响应时间包括了网络延迟,特别是从API网关到目标服务的延迟。如果要单独测量服务的处理时间,需要在服务端进行测量。
网关从哪里可以解析到响应时间呢?
一个中间件实现计算响应时间的基本原理是在请求的生命周期的开始和结束时记录时间戳,然后计算这两个时间戳之间的差异。以下是一个简化的步骤,描述如何在中间件中实现响应时间的计算:
记录请求开始时间: 当请求到达中间件时,立即记录当前的时间戳。这可以使用高精度的时间函数来完成,以确保准确性。
继续请求处理: 中间件将请求传递给下一个处理程序或中间件。在Web框架中,这通常是通过调用某种“next”或“continue”函数来完成的。
记录请求结束时间: 当下一个处理程序或中间件完成处理并返回响应时,再次记录当前的时间戳。
计算响应时间: 使用结束时间减去开始时间来计算响应时间。
记录或使用响应时间: 得到的响应时间可以记录在日志中、添加到响应头中、发送到监控系统或用于其他目的。
以下是一个简单的示例,使用伪代码展示如何在中间件中实现响应时间的计算:
function responseTimeMiddleware(request, response, next) {
// 1. 记录请求开始时间
let startTime = getCurrentTimestamp()
// 2. 继续请求处理
next()
// 3. 记录请求结束时间
let endTime = getCurrentTimestamp()
// 4. 计算响应时间
let duration = endTime - startTime
// 5. 记录或使用响应时间
log("Response time:", duration)
response.setHeader("X-Response-Time", duration)
}
这只是一个基本的示例,实际的实现可能会更复杂,特别是在异步环境中。但基本的原理是相同的:在请求的开始和结束时记录时间,然后计算差异。
2.Java 和Go
Go和Java在并发方面都提供了强大的工具和特性,但它们的方法和哲学是不同的。以下是两者在并发方面的主要区别:
并发原语: Java: Java提供了多种并发原语,如synchronized关键字、wait()、notify()和notifyAll()方法,以及ReentrantLock、Condition、Semaphore等高级并发库。 Go: Go有其自己的并发原语,包括goroutines(轻量级线程)和channels(用于goroutines之间的通信)。
并发模型: Java: Java使用的主要是线程模型。Java线程在操作系统层面映射为本地线程。 Go: Go使用了CSP(Communicating Sequential Processes)模型。在Go中,会启动数以万计的goroutines,但它们不直接映射为操作系统的线程。Go的运行时会在少量的OS线程上调度这些goroutines。
内存模型: Java: Java有一个明确定义的内存模型,它定义了多线程程序中变量的可见性规则。 Go: Go也有其内存模型,但与Java的内存模型相比,它更简洁、直接。
锁和同步: Java: Java提供了各种锁和同步工具,如ReentrantLock、ReadWriteLock、CountDownLatch、CyclicBarrier等。
Go: Go更倾向于使用channels来实现同步,但也提供了像sync.Mutex和sync.WaitGroup这样的同步工具。
并发库: Java: Java有一个强大的并发库java.util.concurrent,提供了许多高级工具和数据结构。 Go: Go标准库中有sync和sync/atomic包,以及一些其他工具,但相对来说它的并发工具库更为简洁。
异常处理: Java: Java使用try-catch块来处理并发中的异常。 Go: Go使用内建的panic和recover机制,但这在并发编程中并不是主流做法。Go更鼓励显式地返回错误并处理它们。
性能和优化: Java: JVM进行了大量的优化,包括JIT编译、垃圾收集和线程优化。但Java线程有时可能因为JVM和操作系统之间的交互而产生开销。 Go: Go运行时直接管理goroutines的调度,这通常意味着更少的上下文切换和更高效的执行。 两者都有自己的优势。Java的并发工具库更加成熟和全面,但可能需要更多的资源。Go提供了一个简单、直接和高效的方式来处理并发,但可能需要开发者对CSP模型有更深入的理解。
Go的运行时会在少量的OS线程上调度这些goroutines。这里的少量的OS线程和Java线程在操作系统层面映射的本地线程有什么区别?
实现:
Go goroutines: 当在Go中启动一个goroutine时,它不是直接映射到一个操作系统线程上的。相反,Go运行时维护了一些真实的操作系统线程,并在这些线程上调度多个goroutines运行。这是通过Go的M:N调度模型实现的,其中M代表真实的操作系统线程,N代表goroutines。 Java线程: 当在Java中创建一个线程时,它通常直接映射到一个操作系统线程。这是一个1:1模型。
开销和效率: Go goroutines: Goroutines设计得非常轻量。它们有更小的栈(通常从2KB开始,但可以动态增长),并且创建、销毁的成本都很低。由于在少量的OS线程上进行调度,goroutines的上下文切换成本也通常比传统的线程低。 Java线程: 由于Java线程是直接映射到操作系统线程的,它们的开销通常比goroutines大。线程的栈空间、创建和销毁的成本都比较高。此外,频繁的上下文切换可能会导致性能下降。
可伸缩性: Go goroutines: 由于goroutines的轻量性,可以在一个程序中创建数以万计、甚至百万计的goroutines,而不会导致系统资源耗尽。 Java线程: 创建大量的Java线程可能会迅速耗尽系统资源,尤其是内存。因此,Java程序通常依赖于线程池来重复使用线程,以避免创建和销毁的开销。
控制和灵活性: Go: Go的运行时对线程的管理提供了很大的控制权,例如可以设置使用的最大OS线程数。 Java: Java提供了对线程的低级访问,但很多细节(如线程调度)是由操作系统和JVM控制的。 总的来说,Go的并发模型主要关注轻量级、高效和可伸缩性,而Java的并发模型则依赖于操作系统的线程模型,可能需要更多的资源管理和优化。
3.SQL 请求经过中间件的过程
词法分析: 操作: 输入的SQL字符串首先会被分解成一个令牌序列。 结果: 例如,SELECT, name, FROM, employees,等等。 如何被使用: 这些令牌会被语法分析器用来构建一个抽象语法树(AST)。
语法分析: 操作: 使用预定义的语法规则(通常定义在.y文件中)解析令牌序列。 结果: 一个代表查询结构的AST。 如何被使用: AST会被用于后续的查询优化、分析和执行。
语义分析: 操作: 检查查询的语义是否正确。例如,确保name和department列确实存在于employees表中。 结果: 验证了的AST。 如何被使用: 只有在验证了查询的语义之后,才会进行进一步的处理。
查询优化: 操作: 重新组织或简化查询以提高执行效率。这可能涉及改变连接的顺序、选择不同的索引等。 结果: 一个优化了的AST。 如何被使用: 这个优化了的AST会被用于生成执行计划。
执行计划生成: 操作: 根据优化了的AST,确定如何实际执行查询。这包括决定使用哪个索引、如何进行表连接等。 结果: 一个执行计划。 如何被使用: 执行引擎使用这个执行计划来实际执行查询。
查询执行: 操作: 中间件执行生成的计划,可能涉及在一个或多个数据库实例上执行部分查询。 结果: 从数据库实例检索到的数据。 如何被使用: 如果中间件服务多个数据库实例(例如,在分片环境中),它可能需要合并各个实例的结果,并返回给客户端。
结果传回客户端: 操作: 一旦查询执行完毕并且所有的结果都被收集,这些结果就会被格式化并发送回客户端。 结果: 最终的查询结果。 如何被使用: 客户端收到结果后进行相应的处理,例如展示给用户或者进一步的处理。 在整个过程中,从最初的SQL字符串到最终的查询结果,每一步都构建或使用前一步的输出来完成其工作。
4.Parser的工作原理
工作流程:
通过SQL字符串输入。 词法分析器(通常由工具如Lex生成)将字符串转换为一个令牌序列。 语法分析器读取这些令牌,并参考parser.y中定义的规则尝试匹配这些令牌。 当找到匹配的规则时,执行与之关联的动作,通常是构建AST的部分。 最终,完整的AST被构建并返回给调用者。
令牌序列怎么理解?
词法分析,通常称为扫描或词法化,是将输入字符串分解为一系列称为”令牌”(tokens)的过程。这些令牌是源代码中的单个元素或实体。
令牌(token)的概念可以被理解为源代码中的“词”或“符号”。例如,在编程和查询语言中,关键字、变量、操作符、标识符、字面量和其他语法元素都可以被视为令牌。
让我们通过一个简单的SQL查询来理解令牌序列的概念:
SELECT name FROM users WHERE age > 25;
对于上述查询,一个词法分析器可能会生成以下的令牌序列:
SELECT (关键字)
name (标识符)
FROM (关键字)
users (标识符)
WHERE (关键字)
age (标识符)
> (操作符)
25 (数值字面量)
; (终止符)
这个令牌序列可以被视为原始SQL查询的“断词”版本。每个令牌都包含了关于其类型和位置的信息,这些信息在后续的语法分析阶段非常有用。
在编译器和解释器的上下文中,词法分析是第一步,它简化了后续的语法分析阶段,因为处理令牌序列通常比直接处理原始输入字符串更容易。
SQL流转的过程
当一个数据库中间件接收到上述的SQL查询时,它会经过一系列的步骤来处理这个查询。以下是这个处理过程的逐步描述:
词法分析:
操作: 输入的SQL字符串首先会被分解成一个令牌序列。 结果: 例如,SELECT, name, FROM, employees,等等。 如何被使用: 这些令牌会被语法分析器用来构建一个抽象语法树(AST)。
语法分析: 操作: 使用预定义的语法规则(通常定义在.y文件中)解析令牌序列。 结果: 一个代表查询结构的AST。 如何被使用: AST会被用于后续的查询优化、分析和执行。
语义分析: 操作: 检查查询的语义是否正确。例如,确保name和department列确实存在于employees表中。 结果: 验证了的AST。 如何被使用: 只有在验证了查询的语义之后,才会进行进一步的处理。
查询优化: 操作: 重新组织或简化查询以提高执行效率。这可能涉及改变连接的顺序、选择不同的索引等。 结果: 一个优化了的AST。 如何被使用: 这个优化了的AST会被用于生成执行计划。
执行计划生成: 操作: 根据优化了的AST,确定如何实际执行查询。这包括决定使用哪个索引、如何进行表连接等。 结果: 一个执行计划。 如何被使用: 执行引擎使用这个执行计划来实际执行查询。
查询执行: 操作: 中间件执行生成的计划,可能涉及在一个或多个数据库实例上执行部分查询。 结果: 从数据库实例检索到的数据。 如何被使用: 如果中间件服务多个数据库实例(例如,在分片环境中),它可能需要合并各个实例的结果,并返回给客户端。 结果传回客户端:
操作: 一旦查询执行完毕并且所有的结果都被收集,这些结果就会被格式化并发送回客户端。 结果: 最终的查询结果。 如何被使用: 客户端收到结果后进行相应的处理,例如展示给用户或者进一步的处理。 在整个过程中,从最初的SQL字符串到最终的查询结果,每一步都构建或使用前一步的输出来完成其工作。
Kubernetes的网络策略
Kubernetes的网络策略允许控制Pod之间的通信。以下是Kubernetes网络策略的主要组件和概念:
PodSelector: 选择器定义了策略应用于哪些Pod。如果省略,策略将应用于所有Pod。
PolicyTypes: 定义策略应用于Pod的哪些流量类型。可以是”Ingress”、”Egress”或两者都有。
Ingress: 控制进入Pod的流量。
从特定的源地址或Pod选择器访问 从特定的端口访问 从特定的命名空间访问 Egress: 控制从Pod发出的流量。
访问特定的目的地地址、Pod选择器或命名空间 访问特定的端口
IPBlock: 允许指定CIDR范围的IP地址作为源或目的地。
Ports: 定义允许的端口和协议。
默认策略: 如果没有定义任何网络策略,大多数网络解决方案默认允许所有入站和出站流量。
隔离: 当至少有一个选择Pod的网络策略存在时,该Pod被视为”隔离”的。否则,它是”非隔离”的,并且所有入站和出站流量都被允许。
隔离特定Pod的所有入站流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
允许来自特定命名空间的流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-namespace
spec:
podSelector: {}
ingress:
- from:
- namespaceSelector:
matchLabels:
project: myproject
允许来自特定Pod的流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-pod
spec:
podSelector:
matchLabels:
app: myapp
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
只允许来自特定 CIDR 范围的 IP 访问:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-cidr
spec:
podSelector:
matchLabels:
app: myapp
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
限制 Pod 到特定端口的出站访问:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-on-port
spec:
podSelector:
matchLabels:
app: myapp
egress:
- ports:
- protocol: TCP
port: 6379
policyTypes:
- Egress
5.如何保证数据库中间件的高可用性?
保证数据库中间件的高可用性是确保整个系统稳定性的关键部分。以下是一些常用的策略和方法来确保数据库中间件的高可用性:
冗余部署:
- 使用主-备或多副本的架构来部署中间件。当主节点出现故障时,备用节点可以立即接管,确保服务的连续性。
负载均衡:
- 使用负载均衡器(如HAProxy、Nginx等)来分发流量,确保单点故障不会导致整个系统的瓶颈或故障。
健康检查和故障转移:
- 定期检查中间件的健康状态。当检测到节点故障时,自动将流量切换到健康的节点。
数据同步和复制:
- 保证所有中间件节点之间的数据同步,这样当一个节点失败时,其他节点可以无缝地接管。
分布式设计:
- 使用分布式系统设计原则,如CAP定理,来确保在网络分区或其他故障时,系统仍然可用。
数据持久化:
- 定期备份中间件的配置和状态,确保在故障发生后可以快速恢复。
限流和熔断:
- 使用限流和熔断机制来防止系统过载,确保在高流量情况下系统的稳定性。
监控和告警:
- 实施全面的监控策略,监控中间件的性能指标、错误率等。当出现异常时,立即发送告警,以便及时介入处理。
灾难恢复计划:
- 制定详细的灾难恢复计划,并定期进行演练,确保在真正的故障发生时,团队知道如何快速恢复服务。
持续更新和维护:
- 定期更新中间件软件,修复已知的漏洞和问题,确保系统的安全性和稳定性。
扩展性:
- 设计中间件以支持水平扩展,这样在需要处理更多的流量时,可以简单地添加更多的节点,而不是对现有节点进行升级。
通过上述策略和方法,可以大大提高数据库中间件的高可用性,确保在各种故障情况下,系统仍然可以正常运行。
6.HA 负载均衡器(如HAProxy、Nginx等)如何分发流量
准备工作:
首先,确保的Docker正在运行。
创建两个简单的Web服务器:
我们将使用Docker运行两个简单的HTTP服务器。这些服务器将作为我们的后端服务,由HAProxy进行负载均衡。
docker run -d -p 8081:80 --name web1 nginx
docker run -d -p 8082:80 --name web2 nginx
创建HAProxy配置文件
在的本地机器上,创建一个名为haproxy.cfg的文件,并添加以下内容
global
daemon
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http_front
bind *:8080
default_backend http_back
backend http_back
balance roundrobin
server web1 host.docker.internal:8081 check
server web2 host.docker.internal:8082 check
这个配置文件定义了一个前端http_front,监听在8080端口,并将流量转发到名为http_back的后端。后端使用roundrobin策略进行负载均衡,并定义了两个服务器web1和web2
使用Docker运行HAProxy:
docker run -d -p 8080:8080 -v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro --name haproxy haproxy:latest
这将启动HAProxy容器,监听在8080端口,并使用我们刚刚创建的配置文件。
测试负载是否均衡
curl http://localhost:8080
docker logs web1
docker logs web2
多次运行上述命令,会看到请求在两个服务器之间进行负载均衡。
清理 完成测试后,可以停止并删除所有容器:
docker stop web1 web2 haproxy
docker rm web1 web2 haproxy