热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

简单的流量控制系统

流量控制概述在一个后台系统中,流量控制属

流量控制概述

在一个后台系统中,流量控制属于基础组件的功能,其实,在很久之前的通讯时代,流量控制就已经非常成熟了,在路由器交换机上面几乎都有全面的流量控制的解决方案,像QoS这类流量整形的方案,都已经是在网络模型的各个层来进行流量的控制和分发了,可以按照通道,按照端口,IP,MAC,业务类型等各个维度对流量进行整形和控制,比如让语音类的这种高优先级的流量优先通过,而视频聊天这种丢了几帧数据其实没什么影响的低优先级流量慢点通过。

对于流量控制,在中后台系统中,一般分成两个类型吧,一种是对连接数进行控制,保证一个机器有可控的连接数,一种是对真实流量的控制,保证机器能通过的流量有多少。

流量控制和缓存一样,实际上是对后端的服务起到一个保护的作用,不至于把后端的服务击穿,不同的地方在于缓存保护的主要是读操作,如果用缓存来保护写操作的话,也是一个异步的过程,像下面这个图一样。

而流量控制主要保护的就是写操作了,保证后端的服务别被写请求给击穿,目前我们之所以很少见到流量控制的服务了,主要因为大家优化方向已经朝其他两个方向上来做了。

  • 一是优化了后端的服务,把后端服务变成了一个可以动态扩展的集群,现在都是说要弹性扩展嘛,所以把优化放到后端服务器上去了,让后端的服务器能够承载更多的写流量,流量控制的东西相比就少了,而且从用户体验上来说,后端服务器能够承载更大的流量也可以保证数据的实时性更好,不会产生数据的延迟,所以扩展后端的集群规模成了一个优化方向。就像下图这样,后端的DB变成了一个集群来保证写规模的扩大。

  • 另外一个优化方向就是引入了消息队列这个东西,实际上消息队列就是一个升级版本的流量控制系统,虽然它没有用到流量控制的这些个算法,但是它达到的目前和流控其实差不太多,效果还更好,所以现在一旦出现后端扛不住写的情况,都在中间加上一个消息队列来解决,一是解决了写入流量的可控,二是还把系统给解耦了,一举两得。

正因为上面的两个原因,现在关注流量控制的人变少了。但有时候,如果后端的服务不能抗住写的压力,并且也没有足够的资源去部署一个消息队列的话(因为消息队列的部署是需要单独的服务器的,还是有成本上的考虑),那么做一个简单的流控系统也基本能满足要求。

在本文中,基于连接的流量控制就不是我们讨论的范围了,那个比较简单一点。

流控设计

我们所说的流量控制,大家比较了解的一般分成两种算法,一种是漏桶算法,一种是令牌桶算法,我们这里并不去深究这两种算法的区别,这个可以在网上很容易找到两种算法的定义和算法描述,这两种流控策略都是来源于路由器的IP层流量控制的算法,我们从另外一个角度来看看流控,我们只借用这些算法的思想,从需求开始,自己一步一步设计一个流控系统。

流控需求

首先,拿到一个流量控制的需求,需求是入口流量是5MB/s,但是峰值流量是100MB/s,出口的流量要控制在50MB/s以内,数据还不能丢弃,如何来实现这个系统。

初步设计

第一感觉应该就是下图这个样子,中间有一个内存的FIFO队列,写入方不停的往这个队列里面写入数据,而另一端不停的读取这个FIFO,然后把流量分发到后端上去,这样就完成了数据不能丢弃这个需求,很像前面的那个消息队列,但是慢着,要是前端的写入流量一直保持在峰值的话,那么这内存也爆了,所以除了内存的FIFO以外,还需要一个文件的FIFO来保证在一直是峰值的情况下保证数据的不丢失,你要是问要是硬盘满了怎么办,那我只能呵呵了,当然,也不是没有解决办法,把服务设计成多机模式嘛,这不在本文的讨论范围内。

总之,按照上图的设计方法,基本可以满足数据不丢的情况了,对于FIFO的实现方式,可以有很多种,一种是自己开链表,两个指针一头一尾,一边写一边读,如果两边都是多线程的话,锁的设计需要特别注意,尽量减少锁的消耗。还有如果是使用想golang这样的带channel的语言,那么直接丢到channel里面也行,不过这样就是内存不太可控,如果某一个时间段上的数据包都特别大的话,容易造成整体内存的飙升,看具体场景和硬件资源吧,这里就不在赘述了,那么,接下来就考虑流控了。

流控控制器

既然需要流量控制,那么就是发送端在发送数据的时候得知道我现在这个数据能不能发,能发的话可以全发出去还是只能发一部分,最简单的办法就是有个整体的流量控制器,每次发送端发送数据的时候都去询问一下这里流量控制器,现在有多少配额,我能用多少,结构图如下图所示,发送端去询问流量控制器,然后拿到一个发送的配额,按照这个配额进行发送。

现在FIFO队列也有了,流量控制器也有了,但是最关键的就是流量控制器如何工作的呢?接下来就要设计这个流量控制器了。

流控算法

对于流控算法的设计,因为是配额制的,所以我们首先得有一个配额的产生机制,比如需求里面说的50MB/s,那就是每秒可以产生50MB的配额,这个简单,你把配额看成一个池子,每秒往池子里加50MB就行了,一旦池子满了,就不加了嘛,这里说的是每秒50MB,实际上加的时候可以按照毫秒来,比如每10毫秒往池子里面加0.5MB,这个用一个线程循环的线程就可以完成。

Quota=0
while(1){ sleep(10MS) ; Quota =0.5;if(Quota>=50){continue;} }

如果是golang的话,也可以把这一部分交给channel来做,写满了就阻塞在channel上了,就像下面这样:

QuotaChannel:=make([]int,100)
for{
QuotaChannel<-5
time.Sleep(time.Millisecond*10)
}

配额产生搞定了,那么配额的消耗就是这个的反操作嘛,代码就不写了,但是如果是像前面那样使用一个变量的话,那么读写都要加锁,而用golang的channel的话,就不用加锁了,看上去后面的效率更高,但实际上差不多,因为golang的channel在实现上也是加了锁的,而且锁的粒度还比较大,所以用channel并没有什么效率上的提升。

整个流控算法实现完以后,就是下图这样样子了。

上面实现了一个简单的流控算法,我们没有去深究令牌桶和漏桶算法的具体实现方式,只是按照我们自己的思想去设计了一个流控算法,实际上和令牌桶的思想基本是一致的,大家可以去具体的看看令牌桶,再在算法上做一些优化。

流控模型的层级

一般的流控设计是跑在TCP层上的,就是能限制TCP层的流量,这样有一个好处就是当你要发送的一个数据包大于50MB的时候,也可以使用这个流控模型,因为在TCP的连接上是可以分包进行发送的,可以拆成多次进行发送,这没有什么问题。

如果你的应用是跑在HTTP协议上的,现在很多语言都集成了HTTP的包,直接调用POST请求的API就可以发送数据了,这时候如果出现大于50MB的数据包,就无法进行拆包发送了,出现这种情况,就需要根据实际的业务要求来进行修改和优化了。

如果只是偶发情况并且后端服务也可以忍受的话,那就忍了吧,如果不是偶发情况或者后端服务完全不能忍,那你就别用语言自带的HTTP包了,发送的时候自己建立TCP连接自己发送吧,这种改造也不是很复杂。

关于流控的其他

这篇文章说到的是一个流控的模型,借鉴了令牌桶和漏桶的一些算法,但不要以为流控就是这样的,令牌桶的模型,只是路由器中对IP报文进行流控的一种方式,在路由器上,还有TCP滑动窗口的流控方式,底层还有MAC层的流控方式等等。

实际上在TCP层以上是没法做到比较准确的流控的,因为一些协议的开销,TCP自动重传的开销,这个流控器都监测不到,根本就没法准确的流控,只能说是做做简单的流量限制,就拿本文说的例子来说,需求说是需要50MB/s的速度,这是一秒的速度,准确来讲需求方是希望50MB的数据在一秒钟以内均匀的发送出去,而我们的这个流控模型根本无法做到这一点,我们来了一个数据,如果发现池子够大,那么50MB的数据直接就发走了,只是下一次发送的时候我们还需要等0.5秒而已,而均匀的端到端的流控,一般采用的都是TCP的滑动窗口调整来实现,这已经超过本文要描述的了,如果大家感兴趣可以深入去研究一下路由器的流控方式,包括滑动窗口流控啊,MAC层流控【802.1Qbb】啊。

通讯领域还是有很多很牛逼的算法的,只是现在通讯领域有些没落了,互联网起来了,做个高可用,分布式就很牛叉一样,实际上和通讯领域的许多东西比起来就是渣渣啊。就如同本文,写了这么多,可能在路由器芯片的功能介绍文档上,就是一行,呵呵。


如果你觉得不错,欢迎转发给更多人看到,也欢迎关注我的公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行


推荐阅读
  • 本文介绍了 Go 语言中的高性能、可扩展、轻量级 Web 框架 Echo。Echo 框架简单易用,仅需几行代码即可启动一个高性能 HTTP 服务。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • vue引入echarts地图的四种方式
    一、vue中引入echart1、安装echarts:npminstallecharts--save2、在main.js文件中引入echarts实例:  Vue.prototype.$echartsecharts3、在需要用到echart图形的vue文件中引入:   importechartsfrom&amp;quot;echarts&amp;quot;;4、如果用到map(地图),还 ... [详细]
  • 本文详细介绍如何使用Netzob工具逆向未知通信协议,涵盖从基本安装到高级模糊测试的全过程。通过实例演示,帮助读者掌握Netzob的核心功能。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 包含phppdoerrorcode的词条 ... [详细]
  • Spring Boot 中配置全局文件上传路径并实现文件上传功能
    本文介绍如何在 Spring Boot 项目中配置全局文件上传路径,并通过读取配置项实现文件上传功能。通过这种方式,可以更好地管理和维护文件路径。 ... [详细]
  • 本文详细介绍了如何使用OpenSSL自建CA证书的步骤,包括准备工作、生成CA证书、生成服务器待签证书以及证书签名等过程。 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • 本文详细介绍如何在忘记MySQL服务器密码的情况下进行密码重置,包括具体的步骤和注意事项。 ... [详细]
  • 使用HTML和JavaScript实现视频截图功能
    本文介绍了如何利用HTML和JavaScript实现从远程MP4、本地摄像头及本地上传的MP4文件中截取视频帧,并展示了具体的实现步骤和示例代码。 ... [详细]
  • 本文介绍了如何查看PHP网站及其源码的方法,包括环境搭建、本地测试、源码查看和在线查找等步骤。 ... [详细]
  • 小程序的授权和登陆
    小程序的授权和登陆 ... [详细]
  • Nacos 0.3 数据持久化详解与实践
    本文详细介绍了如何将 Nacos 0.3 的数据持久化到 MySQL 数据库,并提供了具体的步骤和注意事项。 ... [详细]
  • 深入解析Django CBV模型的源码运行机制
    本文详细探讨了Django CBV(Class-Based Views)模型的源码运行流程,通过具体的示例代码和详细的解释,帮助读者更好地理解和应用这一强大的功能。 ... [详细]
author-avatar
狂风
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有