构建api gateway之 负载均衡
什么是负载均衡
负载均衡,英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行
以下为几种负载均衡策略介绍
1.随机(Random)
大家很多时候说到随机的负载均衡都会想到 Round Robin, 其实 Round Robin并非随机,
Random 这种是真正意义上随机,根据随机算法随意分配请求到服务器。
优点:
- 有了负载能力
缺点:
- 受随机算法影响,并不能均衡各个服务器的负载,
- 也不能根据服务器的负载情况进行自我调节 所以基本很少有如此单纯的真随机策略了
2.轮循(Round Robin)
如上述,其实轮询是平均策略,并非随机策略,
它的具体策略内容如下:
负载均衡负责者有一份 服务器列表,
它会将其做排序,形成固定的 1 到 N 的顺序列表排队,
每次请求都会队列依次选择一位没有轮到的服务器同志接受 请求任务,
当整个队列都接受过任务后,就会从头开始新一轮的任务排队。
而大家为什么很多时候又说它是随机策略呢?
主要是对请求的client 来说, 这一次和下一次请求的服务器并不一定是同一个服务器,所以像是随机。
优点:
- 负载非常平均
缺点:
- 不能根据服务器差异(比如性能)调配负载情况
- 由于是顺序 1 开始, 如果一开始就是瞬间大并发流量大的情况,第一台存在被击垮的风险
3.最少连接 (Least Connection)
从名字我们就能很轻松明白了,
它的策略非常简单: 就是每次取连接计数最小的那个服务器使用
优点:
- 能根据连接数变化动态平衡资源情况
- 长连接多的场景(比如 ftp),资源调配很合理
缺点:
- 但在服务器资源差异情况下,连接数并不能平衡这种差异
- 动态增删服务器列表的场景,请求都会命中新加入的服务器,大流量易击垮这台服务器 (一般会通过 缓启动策略减低对应负载,降低风险)
4.Hash
其他的负载均衡策略都适合于无状态服务,
只有 Hash 是专门解决有状态服务的负载均衡问题的。
它的具体策略就以其中简单的做法作为说明:
比如 ip 或者 url hash, 会用 ip 或者 url 的string 根据 hash 算法 算出固定的整型数值,
然后用该整型数值 根据 服务器数量 取模运算 得出对应哪一台机器,
从而形成 粘机 的效果
优点:
- 解决了有状态服务无法负载均衡的问题
缺点:
- 服务器下线,可能导致 部分粘机的访问仍然访问失效的机器 (一般会通过health check 识别下线,然后重新hash 粘机)
- 如采用有重新 hash 粘机的算法策略,需要业务方处理上规避其带来的影响,比如不能将数据只放在粘机的服务器上
5. EWMA
印象中好像该方式最早见于 Finagle(Twitter的客户端RPC库) 中。
理论上来说服务器 在cpu 算力不足,网卡负荷过大,端口不足等等各种情况下,响应的时间都会存在明显变长的情况
那么响应的延迟变化就可以一定程度上用来评价服务器的负载以及服务器自身情况,
EWMA 的思想就是衡量请求延迟变化来动态优化负载均衡效果。
简单来说,EWMA就是 保持每个服务器请求的往返时间的移动平均值,以未完成请求的数量加权,并将流量分配给成本函数最小的服务器。
一般来说,还会使用P2C策略结合 EWMA使用,以避免同一时间集中命中同一台服务器。 (P2C 就是随机选取两台服务器,比较他们俩的EWMA值,取最小的那一个)
linkerd 做过一个负载均衡的测验,其结果 (当然并不一定代表实际效果)
优点:
- 算法能更好根据变化情况动态调整负载情况
缺点:
- 业务场景本身就是高延迟的情况,比如长轮询,该算法就不能衡量出服务器的负载情况
权重的特殊说明
严格来说,权重很少作为单独的负载均衡策略,
一般都是与上述各种负载均衡策略进行组合。
权重的目的主要是解决 我们在已知或者能预估出服务器的负载能力的情况下, 我们如何更好的预设资源的分配。
所以现在一般这些负载均衡算法都会提供 权重参数以便大家预设负载比例,
甚至一些还尝试用机器学习等手段动态调整权重参数等,以便更快调整资源负载情况
轮循(Round Robin) 简单实现
篇幅关系,这里不解释每一个怎么实现了,只介绍 轮循(Round Robin)
以下内容更新到 openresty-dev-1.rockspec
-- 依赖包
dependencies = {
"lua-resty-balancer >= 0.04",
}
然后执行
luarocks install openresty-dev-1.rockspec --tree=deps --only-deps --local
具体demo 代码如下:
worker_processes 1; #nginx worker 数量
error_log logs/error.log; #指定错误日志文件路径
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr [$time_local] $status $request_time $upstream_status $upstream_addr $upstream_response_time';
access_log logs/access.log main buffer=16384 flush=3; #access_log 文件配置
lua_package_path "$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/?/init.lua;$prefix/?.lua;$prefix/?/init.lua;;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;";
lua_package_cpath "$prefix/deps/lib64/lua/5.1/?.so;$prefix/deps/lib/lua/5.1/?.so;;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;";
# 开启 lua code 缓存
lua_code_cache on;
upstream nature_upstream {
server 127.0.0.1:6699; #upstream 配置为 hello world 服务
# 一样的balancer
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local upstream = ngx.ctx.api_ctx.upstream
local ok, err = balancer.set_current_peer(upstream.host, upstream.port)
if not ok then
ngx.log(ngx.ERR, "failed to set the current peer: ", err)
return ngx.exit(ngx.ERROR)
end
}
}
init_by_lua_block {
-- 初始化 lb
local roundrobin = require("resty.roundrobin")
local nodes = {k1 = {host = '127.0.0.1', port = 6698}, k2 = {host = '127.0.0.1', port = 6699}}
local ns = {}
for k, v in pairs(nodes) do
-- 初始化 weight
ns[k] = 1
end
local picker = roundrobin:new(ns)
-- 初始化路由
local radix = require("resty.radixtree")
local r = radix.new({
{paths = {'/aa/d'}, metadata = picker},
})
-- 匹配路由
router_match = function()
local p, err = r:match(ngx.var.uri, {})
if err then
log.error(err)
end
-- 执行 roundrobin lb 选择
local k, err = p:find()
if not k then
return nil, err
end
return nodes[k]
end
}
server {
#监听端口,若你的8699端口已经被占用,则需要修改
listen 8699 reuseport;
location / {
# 在access阶段匹配路由
access_by_lua_block {
local upstream = router_match()
if upstream then
ngx.ctx.api_ctx = { upstream = upstream }
else
ngx.exit(404)
end
}
proxy_http_version 1.1;
proxy_pass http://nature_upstream; #转发到 upstream
}
}
#为了大家方便理解和测试,我们引入一个hello world 服务
server {
#监听端口,若你的6699端口已经被占用,则需要修改
listen 6699;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("HelloWorld")
}
}
}
}
启动服务并测试
$ openresty -p ~/openresty-test -c openresty.conf #启动
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第一次
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
</body>
</html>
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第二次
HelloWorld
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第三次
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
</body>
</html>
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第四次
HelloWorld
可以看到 一次失败一次成功轮着来,证明 lb 起效
所有这里介绍的lb实现都可以参考 nature 中的例子
目录
构建api gateway之 负载均衡的更多相关文章
- RabbitMQ(四):使用Docker构建RabbitMQ高可用负载均衡集群
本文使用Docker搭建RabbitMQ集群,然后使用HAProxy做负载均衡,最后使用KeepAlived实现集群高可用,从而搭建起来一个完成了RabbitMQ高可用负载均衡集群.受限于自身条件,本 ...
- API网关spring cloud gateway和负载均衡框架ribbon实战
通常我们如果有一个服务,会部署到多台服务器上,这些微服务如果都暴露给客户,是非常难以管理的,我们系统需要有一个唯一的出口,API网关是一个服务,是系统的唯一出口.API网关封装了系统内部的微服务,为客 ...
- [项目构建 十三]babasport Nginx负载均衡的详细配置及使用案例详解.
在这里再次说明下, 这个项目是从网上 找到的一套学习资料, 自己在 空闲时间学习了这些东西. 这里面的code当然会有很多不完善的地方, 但是确实也能学到很多新东西.感谢看过这一些列博文和评论的小伙伴 ...
- Azure上七层负载均衡APP Gateway
Azure的SLB和ILB是最常用的4层负载均衡工具.但有些场景是7层的负载均衡,SLB和ILB就无能为力了. Azure上已经推出了APP Gateway的服务,就是7层负载均衡的负载均衡器. 如上 ...
- 【SpringCloud微服务实战学习系列】客户端负载均衡Spring Cloud Ribbon
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现.通过Spring Cloud的封装,可以让我们轻松地将面向服务的RES模板 ...
- Spring Cloud Ribbon负载均衡
目录 一.简介 二.客户端负载均衡 三.RestTemplate详解 GET请求 POST请求 PUT请求 DELETE请求 一.简介 Spring Cloud Ribbon是一个基于HTTP 和 ...
- 负载均衡之Ocelot
Ocelot 负载均衡: 背景知识,ocelot是基于 webapi 的网关框架,要使用ocelot来做路由转发和负载均衡,需要创建一个webapi,然后以这个webapi来做gateway. ...
- Spring Cloud微服务开发笔记5——Ribbon负载均衡策略规则定制
上一篇文章单独介绍了Ribbon框架的使用,及其如何实现客户端对服务访问的负载均衡,但只是单独从Ribbon框架实现,没有涉及spring cloud.本文着力介绍Ribbon的负载均衡机制,下一篇文 ...
- apache + tomcat 负载均衡分布式集群配置
Tomcat集群配置学习篇-----分布式应用 现目前基于javaWeb开发的应用系统已经比比皆是,尤其是电子商务网站,要想网站发展壮大,那么必然就得能够承受住庞大的网站访问量:大家知道如果服务器访问 ...
- 详解API Gateway流控实现,揭开ROMA平台高性能秒级流控的技术细节
摘要:ROMA平台的核心系统ROMA Connect源自华为流程IT的集成平台,在华为内部有超过15年的企业业务集成经验. 本文分享自华为云社区<ROMA集成关键技术(1)-API流控技术详解& ...
随机推荐
- JS学习笔记 (三) 对象进阶
1.JS对象 1.1 JS对象特征 1.JS对象是基本数据数据类型之一,是一种复合值,可以看成若干属性的集合. 属性是名值对的形式(key:value) 属性名是字符串,因此可以把对象看成是字符串到值 ...
- [AGC057D] Sum Avoidance
Link 本篇题解大部分内容来自这篇文章 首先题意翻译: 给定一个正整数 \(S\) ,称一个正整数集合 \(A\) 是好的,当且仅当它满足以下条件: \(A\) 中元素在 \((0,S)\) 之间 ...
- Docker | 常用命令——排错很有帮助
众所周知,docker 排查问题相较而言是困难的.因此,熟知一些常用命令对我们快速的排查定位问题是非常有帮助的.下面让我们一起来学习一下吧 1.显示docker的系统信息 docker info [r ...
- 解决“fast-forward, aborting”问题
1. 现象 对某一个远程仓库 git pull 过程中,报错如下: # zl @ srv123 in ~/git/radxa/kernel [14:09:54] $ git pull remote: ...
- Feign 实现 GET 方法传递 POJO
Feign 实现 GET 方法传递 POJO 作者:Grey 原文地址: 博客园:Feign 实现 GET 方法传递 POJO CSDN:Feign 实现 GET 方法传递 POJO 需求 Sprin ...
- Java注解与原理分析
目录 一.注解基础 二.注解原理 三.常用注解 1.JDK注解 2.Lombok注解 四.自定义注解 1.同步控制 2.类型引擎 五.参考源码 使用的太多,被忽略的理所当然: 一.注解基础 注解即标注 ...
- SpringBoot 06: springboot中使用redis
配置SpringBoot 创建SpringBoot项目时勾选Redis起步依赖 <dependency> <groupId>org.springframework.boot&l ...
- Dockerfile 跨阶段多阶段使用 ARG 命令
若要想 Dockerfile 的 ARG 命令可以跨多个阶段使用,需要有以下几步: 先在文件最前面使用 ARG 命令定义 然后在每个阶段分别引用 ARG 命令使用 一个示范的 Dockerfile 文 ...
- PGL图学习之图神经网络GraphSAGE、GIN图采样算法[系列七]
0. PGL图学习之图神经网络GraphSAGE.GIN图采样算法[系列七] 本项目链接:https://aistudio.baidu.com/aistudio/projectdetail/50619 ...
- 重学c#系列——委托和匿名函数[二十五]
前言 简单介绍一下什么是委托. 正文 以前也写过委托,这次算是重新归档,和新的补充吧. https://www.cnblogs.com/aoximin/p/13940125.html 有些人说委托是函 ...