1 商品详情页生成
普通秒杀商品,直接对接普通秒杀流程,如果是热点秒杀商品,则对接热点秒杀商品流程。
静态页生成,我们采用freemarker来实现,因此我们要先创建模板,并且准备模板数据,然后填充模板即可。
数据模型:
我们创建一个普通方法,用于根据ID查询商品详情, 并生成静态页,代码如下:
模板:
模板的静态页在资料\详情页面-静态页面\detail.html
,把它打开,然后填充指定数据即可。
基础数据填充:
规格分类填充:
倒计时代码:
我们需要引入vue和axios,然后将下面倒计时代码拷贝到页面,即可实现倒计时:
<!-- 引入组件库 --> <script src="vue.js"></script> <script type="text/javascript" src="axios.js"></script><!-- axios交互--> <script> var app = new Vue({ el:'#app', data:{ message:'', hours:'', minutes:'', seconds:'' }, methods:{ /*** * 时间运算 * @param starttimes:秒杀开始时间 * @param endtimes:秒杀结束时间 */ timeCalculate:function (starttimes,endtimes) { //获取当前时间 let nowtimes = new Date().getTime(); if(nowtimes>endtimes){ this.message='活动已结束!'; this.isbegin=0; return; } //时间差 let nums = 0; //前缀,记录倒计时描述 let prefix ='距离结束:'; //判断是距离开始/距离结束 if(starttimes<=nowtimes){ //距离结束,运算求出nums nums = endtimes-nowtimes; this.isbegin=1; }else{ //距离开始 prefix='距离开始:'; //运算求出nums nums = starttimes-nowtimes; this.isbegin=0; } //nums定时递减 let clock = window.setInterval(function () { //时间递减 nums=nums-1000; //消息拼接 prefix+app.timedown(nums); //nums<0 if(nums<=0){ //结束定时任务 window.clearInterval(clock); //刷新时间,重新调用该方法 app.timeCalculate(starttimes,endtimes); } },1000); }, //将毫秒转换成天时分秒 timedown:function(num) { var oneSecond = 1000; var oneMinute=oneSecond*60; var oneHour=oneMinute*60 var oneDay=oneHour*24; //天数 //var days =Math.floor(num/oneDay); //小时 //this.hours =Math.floor((num%oneDay)/oneHour); this.hours =Math.floor(num/oneHour); //分钟 this.minutes=Math.floor((num%oneHour)/oneMinute); //秒 this.seconds=Math.floor((num%oneMinute)/oneSecond); //拼接时间格式 //var str = days+'天'+hours+'时'+minutes+'分'+seconds+'秒'; //return str; }, //计算时间 loadTime:function () { //秒杀倒计时时间 let tm1 = new Date("${sku.seckillBegin?string('yyyy-MM-dd HH:mm:ss')}").getTime(); let tm2 = new Date("${sku.seckillEnd?string('yyyy-MM-dd HH:mm:ss')}").getTime(); this.timeCalculate(tm1,tm2); }, //下单 addOrder:function () { //获取令牌 var token = localStorage.getItem("token"); if(token!=null && token!=''){ //将令牌传给后台 /lua/order/add var instance = axios.create({ }); instance.defaults.headers.common['Authorization'] = 'Bearer '+token; //发送请求 instance.post(`http://data-seckill-java.test.net/api/order/add/${sku.id}`).then(function (response) { console.log(response) //跳转到个人中心 location.href='http://data-seckill-java.test.net/#/user'; }) }else{ //跳转登录 location.href='http://data-seckill-java.test.net/#/login'; } } }, created:function () { this.loadTime(); } }); </script>
倒计时时间显示:
效果如下:
2 热点商品状态通知
2.1 后端实现
抢单先经过nginx,nignx会创建热点商品抢单排队队列,order此时会监听消息,监听消息后,会进行下单,下单完成或者失败,都会向message发送消息,此时客户端也就是商品详情页和message建立了长连接,正好可以获取消息。
2.2 前端对接
前端采用vue+websocket实现,当抢单的商品为热点商品的时候,需要建立websocket链接,一旦监听到消息表明抢单有结果了,可以根据结果选择跳转到订单列表页,或者输出抢单结果。
//下单 addOrder:function () { //获取令牌 localStorage.setItem("token","eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4MzRlZmE3Mi0zODRmLTQ0YzMtYjE0Ny02ZTRiMGNjNWE4NDkiLCJpYXQiOjE1OTA2MzY3NDgsImlzcyI6IjgzNGVmYTcyLTM4NGYtNDRjMy1iMTQ3LTZlNGIwY2M1YTg0OSIsInN1YiI6IjgzNGVmYTcyLTM4NGYtNDRjMy1iMTQ3LTZlNGIwY2M1YTg0OSIsInBob25lIjoiMTM2NzAwODEzNzYiLCJuYW1lIjoi5rKI5Z2k5p6XIiwidXNlcm5hbWUiOiJpdGhlaW1hIiwiZXhwIjoxNTkxNDM2NzQ4fQ.IvOURAz7gpNqz22sIffGkDLvQglFx2-vQWI_wq-FgVY") var token = localStorage.getItem("token"); if(token!=null && token!=''){ //将令牌传给后台 /lua/order/add var instance = axios.create({ }); instance.defaults.headers.common['Authorization'] = 'Bearer '+token; //发送请求 instance.post(`http://data-seckill-java.test.net/lua/order/add?id=${sku.id}`).then(function (response) { console.log(response) //跳转到个人中心 //location.href='http://data-seckill-java.test.net/#/user'; if(response.data.code==202){ //如果在排队,就建立链接 app.initWebSocket(response.data.username); } }) }else{ //跳转登录 //location.href='http://data-seckill-java.test.net/#/login'; console.log("未登录!") } }, //初始化创建websocket链接 initWebSocket(uname){ //实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接 this.socket = new WebSocket("ws://localhost:28082/ws/"+uname); //打开事件 this.socket.onopen = function() { console.log("Socket 已打开"); }; //接收消息 this.socket.onmessage=this.loadMessage; }, //后端消息接收 loadMessage(e){ console.log(e.data) }
3 Sentinel限流
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
3.1 Sentinel介绍
Sentinel 具有以下特征:
1.丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促销流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。 2.完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。 3.广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。 4.完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
Sentinel 目前已经针对 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等进行了适配,用户只需引入相应依赖并进行简单配置即可非常方便地享受 Sentinel 的高可用流量防护能力。Sentinel 还为 Service Mesh 提供了集群流量防护的能力。未来 Sentinel 还会对更多常用框架进行适配。
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel 的关注点在于:
- 多样化的流量控制
- 熔断降级
- 系统负载保护
- 实时监控和控制台
Sentinel和Hystrix对比:
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间、异常比率、异常数 | 基于异常比率 |
实时指标实现 | 滑动窗口(LeapArray) | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
调用链路信息 | 支持同步调用 | 不支持 |
限流 | 基于 QPS / 并发数,支持基于调用关系的限流 | 有限支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 较为简单 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
学习参考地址:Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub
3.2 Sentinel控制台安装
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群)、规则管理和推送的功能。https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
Sentinel 控制台包含如下功能:
- 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
- 监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
- 规则管理和推送:统一管理推送规则。
- 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
1)jar包运行方式安装
Sentinel控制台下载地址:Releases · alibaba/Sentinel · GitHub
jar包下载后,直接启动即可,启动命令如下:
java -Dserver.port=8858 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.2.jar
控制台访问:http://localhost:8858 效果如下:
登录的账号密码都是sentinel。
2)Docker安装
docker pull bladex/sentinel-dashboard docker run --name sentinel -d -p 8858:8858 -d bladex/sentinel-dashboard
访问http://192.168.211.137:8858/ 登录后,效果如下:
3.3 Sentinel案例
我们基于SpringCloud工程集成Sentinel,实现限流操作。为了节省时间,我们可以直接把写好的半成品案例导入到IDEA中来,将资料\sentinel
中的sentinel工程导入进来,分别有一个生产者一个消费者和一个微服务网关。
生产者:provider
消费者:consumer
微服务网关:gateway
3.3.1 基于Feign的服务降级
因为我们项目中服务之间调用使用的是feign,因此这里只讲解基于feign的服务降级实现。
1)引入依赖包:
在消费者中引入sentinel的包。
<!-- spring cloud alibaba sentinel 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
2)Sentinel配置
Sentinel 适配了 Feign 组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依赖外还需要 开启Sentinel对feign的支持,并且配置控制台信息,我们可以在bootstrap.yml中配置,配置如下:
spring: cloud: #sentinel sentinel: transport: port: 8719 dashboard: 192.168.211.137:8858 #Sentinel对feign的支持 feign: sentinel: enabled: true
3)配置服务降级方法
服务降级的方法就是原来springcloud中的服务降级配置,这里的配置完全是feign的配置,所以不详细讲解了。
创建服务降级处理工厂对象com.test.feign.fallback.GoodsFeignFallback
,代码如下:
@Component public class GoodsFeignFallback implements FallbackFactory<GoodsFeign> { @Override public GoodsFeign create(Throwable throwable) { return new GoodsFeign() { @Override public String one(Integer max) { return "服务降级"; } }; } }
在feign上添加fallbackFactory指向服务降级的工厂类对象,代码如下:
@FeignClient(value = "goods",fallbackFactory = GoodsFeignFallback.class) public interface GoodsFeign { /*** * 获取一件商品 */ @GetMapping(value = "/goods/one/{max}") String one(@PathVariable(value = "max")Integer max); }
3.3.2 Sentinel控制台使用
我们接着启动生产者和消费者,并访问我们的控制台:http://192.168.211.137:8858/#/dashboard/home,效果如下:
指定方法流量实时监控:
选择 实时监控,再输入被访问的方法名字,就会出现指定方法访问的流量报表,如下图:
簇点链路
这块主要是单个节点中所有资源以及实时的调用数据,如下图:
3.3.2.1 流量控制
流控:
在上图中,针对每个资源都有3个操作按钮,其中流控主要用于做流量控制操作,其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
讲解:
resource:资源名,即限流规则的作用对象 count: 限流阈值 grade: 限流阈值类型(QPS 或并发线程数) limitApp: 流控针对的调用来源,若为 default 则不区分调用来源 strategy: 调用关系限流策略 controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
流控效果:
快速失败: 当QPS超过任何规则的阈值后,新的请求就会立即拒绝,拒绝方式为抛出FlowException . 这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。 Warm Up: 当系统长期处理低水平的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值的上限,给系统一个预热的时间,避免冷系统被压垮。 排队等待: 匀速排队严格控制请求通过的时间间隔,也即是让请求以均匀的速度通过,对应的是漏桶算法。
3.3.2.2 降级规则
RT:
异常比例:
异常数量:
3.3.2.3 热点规则
热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。
参数说明:
3.3.2.4 黑白名单
参数说明:
| resource | 资源名,即限流规则的作用对象 | - |
| limitApp | 对应的黑名单/白名单,不同 origin 用 ,
分隔,如 appA,appB
| default,代表不区分调用来源 |
| strategy | 限制模式,AUTHORITY_WHITE
为白名单模式,AUTHORITY_BLACK
为黑名单模式,默认为白名单模式 | AUTHORITY_WHITE |
3.4 微服务网关Sentinel限流
在秒杀项目中,我们需要集成Sentinel进行限流操作,有些商品目前属于非热点商品,但也有可能存在部分商品会在中途突然增加抢购热度成为热点,成为热点有可能会导致瞬间的流量膨胀,这时候我们可以采用Sentinel做限流操作,以实现对后台微服务的保护。后台秒杀我们在微服务网关进行限流操作。
1)引入依赖
微服务网关集成Sentinel限流,需要引入如下依赖包:
<!--Sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency> <!-- spring cloud alibaba sentinel 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
2)配置控制台信息
在bootstrap.yml中配置endpoint以及控制台信息,配置如下:
spring: cloud: #sentinel sentinel: transport: port: 8719 dashboard: 192.168.211.137:8858 #endpoint management: endpoints: web: exposure: include: '*'
现在所有的集成就成功了,我们启动微服务网关,看看Sentinel控制台也会显示微服务网关的节点信息,并且可以在控制台配置限流策略。
4 nginx限流
一般情况下,首页的并发量是比较大的,即使 有了多级缓存,当用户不停的刷新页面的时候,也是没有必要的,另外如果有恶意的请求 大量达到,也会对系统造成影响。
而限流就是保护措施之一。
4.1 生活中限流对比
- 水坝泄洪,通过闸口限制洪水流量(控制流量速度)。
- 办理银行业务:所有人先领号,各窗口叫号处理。每个窗口处理速度根据客户具体业务而定,所有人排队等待叫号即可。若快下班时,告知客户明日再来(拒绝流量)
- 火车站排队买票安检,通过排队 的方式依次放入。(缓存带处理任务)
4.2 nginx的限流
nginx提供两种限流的方式:
- 一是控制速率
- 二是控制并发连接数
4.2.1 控制速率
控制速率的方式之一就是采用漏桶算法。
(1)漏桶算法实现控制速率限流
漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:
(2)nginx的配置
配置示意图如下:
修改/usr/local/openresty/nginx/conf/nginx.conf:
#限流设置 binary_remote_addr:根据请求IP进行限流 contentRateLimit:缓存空间名称 10m:缓存空间 #rate=2r/s:每秒钟允许有2个请求被处理 limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=2r/s; #静态页 location /web/items/ { #限流 limit_req zone=contentRateLimit; content_by_lua_file /usr/local/openresty/nginx/lua/items-access.lua; }
配置说明:
binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。 zone:定义共享内存区来存储访问信息, contentRateLimit:10m 表示一个大小为10M,名字为contentRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。 rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求.我们这里设置成2 方便测试。
测试:
重新加载配置文件
cd /usr/local/openresty/nginx/sbin ./nginx -s reload
访问页面:http://192.168.211.137/web/items/S1235433012716498944.html
,连续刷新会直接报错。
(3)处理突发流量
上面例子限制 2r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。
例如,如下配置表示:
上图代码如下:
server { listen 80; server_name data-changgou-java.test.net; location /update_content { content_by_lua_file /root/lua/update_content.lua; } location /read_content { limit_req zone=contentRateLimit burst=4; content_by_lua_file /root/lua/read_content.lua; } }
burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数,当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。
此处,**burst=4 **,若同时有4个请求到达,Nginx 会处理第一个请求,剩余3个请求将放入队列,然后每隔500ms从队列中获取一个请求进行处理。若请求数大于4,将拒绝处理多余的请求,直接返回503.
不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。
因此,burst 往往结合 nodelay 一起使用。
例如:如下配置:
如上表示:
平均每秒允许不超过2个请求,突发不超过4个请求,并且处理突发4个请求的时候,没有延迟,等到完成之后,按照正常的速率处理。
如上两种配置结合就达到了速率稳定,但突然流量也能正常处理的效果。
4.2.2 控制并发量
ngx_http_limit_conn_module 提供了限制连接数的能力。主要是利用limit_conn_zone和limit_conn两个指令。
利用连接数限制 某一个用户的ip连接的数量来控制流量。
注意:并非所有连接都被计算在内 只有当服务器正在处理请求并且已经读取了整个请求头时,才会计算有效连接。此处忽略测试。
配置语法:
Syntax: limit_conn zone number; Default: —; Context: http, server, location;
(1)配置限制固定连接数
如下,配置如下:
上图配置如下:
#根据IP地址来限制,存储内存大小10M limit_conn_zone $binary_remote_addr zone=addr:1m; #测试 location /user/ { limit_conn addr 2; proxy_pass http://172.16.0.235:18083; }
表示:
limit_conn_zone $binary_remote_addr zone=addr:10m; 表示限制根据用户的IP地址来显示,设置存储地址为的内存大小10M limit_conn addr 2; 表示 同一个地址只允许连接2次。
测试:
此时开3个线程,测试的时候会发生异常,开2个就不会有异常
(2)限制每个客户端IP与服务器的连接数,同时限制与虚拟服务器的连接总数
如下配置:
代码如下图:
limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn_zone $server_name zone=perserver:10m; #测试 location /user/ { #limit_conn addr 2; limit_conn perip 10;#单个客户端ip与服务器的连接数. limit_conn perserver 100; #限制与服务器的总连接数 proxy_pass http://172.16.0.235:18083; }
4.3 秒杀Nginx限流
秒杀抢单,需要在抢单的入口进行限流,限流根据每个IP和总的并发量进行配置,配置如下:
5 Lvs+Nginx集群配置
5.1 Lvs介绍
LVS(Linux Virtual Server)即Linux虚拟服务器,是由章文嵩博士主导的开源负载均衡项目,目前LVS已经被集成到Linux内核模块中。该项目在Linux内核中实现了基于IP的数据请求负载均衡调度方案,其体系结构如图1所示,终端互联网用户从外部访问公司的外部负载均衡服务器,终端用户的Web请求会发送给LVS调度器,调度器根据自己预设的算法决定将该请求发送给后端的某台Web服务器,比如,轮询算法可以将外部的请求平均分发给后端的所有服务器,终端用户访问LVS调度器虽然会被转发到后端真实的服务器,但如果真实服务器连接的是相同的存储,提供的服务也是相同的服务,最终用户不管是访问哪台真实服务器,得到的服务内容都是一样的,整个集群对用户而言都是透明的。
工作中采用Lvs集群模式:
5.2 Lvs工作解析模式介绍
NAT模式:即网络地址转换
访问步骤:
1.用户通过互联网DNS服务器解析到公司负载均衡设备上面的外网地址,相对于真实服务器而言,LVS外网IP又称VIP(Virtual IP Address),用户通过访问VIP,即可连接后端的真实服务器(Real Server),而这一切对用户而言都是透明的,用户以为自己访问的就是真实服务器,但他并不知道自己访问的VIP仅仅是一个调度器,也不清楚后端的真实服务器到底在哪里、有多少真实服务器。 2.用户将请求发送至124.126.147.168,此时LVS将根据预设的算法选择后端的一台真实服务器(192.168.0.1~192.168.0.3),将数据请求包转发给真实服务器,并且在转发之前LVS会修改数据包中的目标地址以及目标端口,目标地址与目标端口将被修改为选出的真实服务器IP地址以及相应的端口。 3.真实的服务器将响应数据包返回给LVS调度器,调度器在得到响应的数据包后会将源地址和源端口修改为VIP及调度器相应的端口,修改完成后,由调度器将响应数据包发送回终端用户,另外,由于LVS调度器有一个连接Hash表,该表中会记录连接请求及转发信息,当同一个连接的下一个数据包发送给调度器时,从该Hash表中可以直接找到之前的连接记录,并根据记录信息选出相同的真实服务器及端口信息。
TUN负载均衡模式(IP隧道):
在LVS(NAT)模式的集群环境中,由于所有的数据请求及响应的数据包都需要经过LVS调度器转发,如果后端服务器的数量大于10台,则调度器就会成为整个集群环境的瓶颈。我们知道,数据请求包往往远小于响应数据包的大小。因为响应数据包中包含有客户需要的具体数据,所以LVS(TUN)的思路就是将请求与响应数据分离,让调度器仅处理数据请求,而让真实服务器响应数据包直接返回给客户端。VS/TUN工作模式拓扑结构如图3所示。其中,IP隧道(IP tunning)是一种数据包封装技术,它可以将原始数据包封装并添加新的包头(内容包括新的源地址及端口、目标地址及端口),从而实现将一个目标为调度器的VIP地址的数据包封装,通过隧道转发给后端的真实服务器(Real Server),通过将客户端发往调度器的原始数据包封装,并在其基础上添加新的数据包头(修改目标地址为调度器选择出来的真实服务器的IP地址及对应端口),LVS(TUN)模式要求真实服务器可以直接与外部网络连接,真实服务器在收到请求数据包后直接给客户端主机响应数据。
DR负载均衡模式(推荐:直接路由模式):
在LVS(TUN)模式下,由于需要在LVS调度器与真实服务器之间创建隧道连接,这同样会增加服务器的负担。与LVS(TUN)类似,DR模式也叫直接路由模式,其体系结构如图4所示,该模式中LVS依然仅承担数据的入站请求以及根据算法选出合理的真实服务器,最终由后端真实服务器负责将响应数据包发送返回给客户端。与隧道模式不同的是,直接路由模式(DR模式)要求调度器与后端服务器必须在同一个局域网内,VIP地址需要在调度器与后端所有的服务器间共享,因为最终的真实服务器给客户端回应数据包时需要设置源IP为VIP地址,目标IP为客户端IP,这样客户端访问的是调度器的VIP地址,回应的源地址也依然是该VIP地址(真实服务器上的VIP),客户端是感觉不到后端服务器存在的。由于多台计算机都设置了同样一个VIP地址,所以在直接路由模式中要求调度器的VIP地址是对外可见的,客户端需要将请求数据包发送到调度器主机,而所有的真实服务器的VIP地址必须配置在Non-ARP的网络设备上,也就是该网络设备并不会向外广播自己的MAC及对应的IP地址,真实服务器的VIP对外界是不可见的,但真实服务器却可以接受目标地址VIP的网络请求,并在回应数据包时将源地址设置为该VIP地址。调度器根据算法在选出真实服务器后,在不修改数据报文的情况下,将数据帧的MAC地址修改为选出的真实服务器的MAC地址,通过交换机将该数据帧发给真实服务器。整个过程中,真实服务器的VIP不需要对外界可见。
5.3 Lvs-DR配置
综合上面分析,我们可以得出结论,DR模式性能效率比较高,安全性很高,因此一般公司都推荐使用DR模式。我们这里也配置DR模式实现Lvs+Nginx集群。
3台机器:
192.168.211.136 192.168.211.137 192.168.211.138
VIP:
192.168.211.130
5.3.1 Vip配置
关闭网络配置管理器(每台机器都要做)
systemctl stop NetworkManager systemctl disable NetworkManager
配置虚拟IP(VIP)
在/etc/sysconfig/network-scripts
创建文件ifcfg-ens33:1
,内容如下:
BOOTPROTO=static DEVICE=ens33:1 ONBOOT=yes IPADDR=192.168.211.130 NETMASK=255.255.255.0
重启网络服务:
service network restart
我们可以看到在原来的网卡上面添加了一个虚拟IP 130:
同时需要对192.168.211.137
、192.168.211.138
构建虚拟机IP,但只是用于返回数据,而不能被用户访问到,这时候需要操作ifcfg-lo
。
IPADDR=127.0.0.1,这里127.0.0.1属于本地回环地址,不属于任何一个有类别地址类。它代表设备的本地虚拟接口,所以默认被看作是永远不会宕掉的接口。
NETMASK=255.255.255.255
192.168.211.137
:
将ifcfg-lo
拷贝一份ifcfg-lo:1
,并修改ifcfg-lo:1
配置,内容如下:
刷新lo:
ifup lo
查看IP可以发现lo下多了130ip:
192.168.211.138
:
操作同上。
5.3.2 LVS集群管理工具安装
ipvsadm用于对lvs集群进行管理,需要手动安装。
安装命令:
yum install ipvsadm
版本查看:
ipvsadm -Ln
效果如下:
5.3.3 ARP配置(地址解析协议)
arp_ignore和arp_announce参数都和ARP协议相关,主要用于控制系统返回arp响应和发送arp请求时的动作。这两个参数很重要,特别是在LVS的DR场景下,它们的配置直接影响到DR转发是否正常。
arp-ignore:arp_ignore参数的作用是控制系统在收到外部的arp请求时,是否要返回arp响应(0~8,2-8用的很少)
0,只要本机配置了IP,就能响应请求。 1,请求的目标地址到达对应的网络接口,才能响应对应请求
arp-announce:ARP通告行为:
0,本机上任何网络接口都向外通告(也就是任何请求都对用户进行响应),所有网卡都能接收通告 1,尽可能避免本网卡与不匹配的目标进行通告 2,只在本网卡进行通告(推荐)
配置文件:/etc/sysctl.conf
,将如下文件拷贝进去:
net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.default.arp_ignore = 1 net.ipv4.conf.lo.arp_ignore = 1 net.ipv4.conf.all.arp_announce = 2 net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.lo.arp_announce = 2
刷新配置:
sysctl -p
添加路由:
route add -host 192.168.211.130 dev lo:1
添加了一个host地址,目的是用于接收数据报文,接收到了数据报文后会交给lo:1处理。(防止关机失效,需要将上述命令添加到/etc/rc.local中)
添加完host后,可以查看一下:route -n
,效果如下:
此时如果无法识别route,需要安装相关工具yum install net-tools
。
上述配置我们同样要在192.168.211.138
中配置。
5.3.4 集群配置
ipvsadm命令讲解:
ipvsadm -A:用于创建集群 ipvsadm -E:用于修改集群 ipvsadm -D:用于删除集群 ipvsadm -C:用于清除集群数据 ipvsadm -R:用于重置集群配置规则 ipvsadm -S:用于保存修改的集群规则 ipvsadm -a:用于添加一个rs节点 ipvsadm -e:用于修改一个rs节点 ipvsadm -d:用于删除一个rs节点
添加集群TCP服务地址:(外部请求由该配置指定的VIP处理)
ipvsadm -A -t 192.168.211.130:80 -s rr
参数说明:
-A:添加集群配置 -t:TCP请求地址(VIP) -s:负载均衡算法
负载均衡算法:
算法 | 说明 |
---|---|
rr | 轮询算法,它将请求依次分配给不同的rs节点,也就是RS节点中均摊分配。这种算法简单,但只适合于RS节点处理性能差不多的情况 |
wrr | 加权轮训调度,它将依据不同RS的权值分配任务。权值较高的RS将优先获得任务,并且分配到的连接数将比权值低的RS更多。相同权值的RS得到相同数目的连接数。 |
Wlc | 加权最小连接数调度,假设各台RS的全职依次为Wi,当前tcp连接数依次为Ti,依次去Ti/Wi为最小的RS作为下一个分配的RS |
Dh | 目的地址哈希调度(destination hashing)以目的地址为关键字查找一个静态hash表来获得需要的RS |
SH | 源地址哈希调度(source hashing)以源地址为关键字查找一个静态hash表来获得需要的RS |
Lc | 最小连接数调度(least-connection),IPVS表存储了所有活动的连接。LB会比较将连接请求发送到当前连接最少的RS. |
Lblc | 基于地址的最小连接数调度(locality-based least-connection):将来自同一个目的地址的请求分配给同一台RS,此时这台服务器是尚未满负荷的。否则就将这个请求分配给连接数最小的RS,并以它作为下一次分配的首先考虑。 |
配置rs(2个)节点:
ipvsadm -a -t 192.168.211.130:80 -r 192.168.211.137:80 -g ipvsadm -a -t 192.168.211.130:80 -r 192.168.211.138:80 -g
参数说明:
-a:给集群添加一个节点 -t:指定VIP地址 -r:指定real server地址 -g:表示LVS的模式为dr模式
添加了节点后,我们通过ipvsadm -Ln查看,可以看到多了2个节点。
此时访问VIP:192.168.211.130
的时候,发现一直是138 rs,原因是因为lvs这里有一个用户请求持久化操作,会将用户请求的数据持久化,下次请求的时候,会从持久化数据中取出来,如果有持久化数据,就按持久化数据中进行访问,没有则轮询,因此我们需要配置持久化时间。
此时我们来查看集群列表,会多了持久化时间:
ipvsadm -E -t 192.168.211.130:80 -s rr -p 5
设置tcp
ipvsadm --set 2 2 2
效果如下:
访问192.168.211.130
,在输入ipvsadm -Lnc
,效果如下: