apache traffic server架构

SheConolly 9年前

来自: http://blog.csdn.net/ljy1988123/article/details/50386458



功能:

         Trafficserver的主要功能是缓存,当然你也可以用它来做纯粹的反向代理(像通常用nginx那样)。通常切入一个庞大的系统的最好方式是看如何使用,使用traffic server的主要入口有两个:配置文件和插件。所有使用者都需要配置文件,高级使用者则需要插件。

traffic支持大规模的集群处理,不同于nginx的单点(需要ospf均衡链路来做冗余),所有的配置文件可以做到改动一个通知全部。程序根据功能划分为不同的几个子程序,有服务运行时使用的程序,也有管理使用的。详细见下文。

配置文件:

l  缓存Cache.config

n  上游拉取数据拥塞控制:congestion.config

n  缓存分割与上游分配:hosting.config

n  划分不同种类的缓存类型(与hosting.config配合可以实现不同种的数据缓存安排)

n  定义上游的peer:Icp.config

n  定义可以使用cache的白名单:ip_allow.config

n  缓存可以定义多级,定义级别的配置:parent.config

n  缓存持久化:storage.config

l  Log配置

n  将不同上游的log放到不同的log文件中:log_hosts.config

n  定义不同的log格式:logs_xml.config

n   

l  插件管理plugins.config

l  主程序可调整参数:records.config

l  代理:

n  请求和响应的url修改配置:remap.config

l  域名解析:splitdns.config

l  安全:配置多个ssl证书:ssl_multicert.config

插件系统:

         标准的面向过程做插件的过程。一个HTTP有个处理流程,包括request头部处理(你可以改url),dns查询(你可以决定去哪个后台获取数据)、从后台或缓存拉取数据、返回内容等。只要是http请求,这个流程就是固定的。因此插件系统就是在这些流程上注册回调函数。这里的回调函数还不是直接调用,还会传递一个event事件参数,用于表示在当前的钩子上发生的事情,使plugin可以更好的处理。

         除了被调用,trafficserver还要提供调用方法。这里提供的调用方式可不是一般意义上的函数调用,而是类似远程过程调用。插件通过将自己要被执行的代码(action)发送给server(就连发送都是要指明ip地址和端口的),然后通过查询server返回的接口来获得action执行的状态。这里的action就是traffic server里面的协程概念,整个过程类似golang的go func(){}()关键字操作。

         除了这种远程调用,很多函数插件也是可以直接调用的。

协程:

         Trafficserver的超高并发自然需要协程的概念(ng也是)。Traffic server自己实现的协程叫做continuation,结构体用TSCont表示。

         一个TSCont代表一个异步执行的代码块,这个代码块拥有自己的执行状态,并且可以被

         协程是用户空间管理的线程,也就是说调度算法是在用户空间的程序中实现的。可以保存程序执行的状态,可以在某时刻拉出来执行。多个协程在一个操作系统的线程上执行,或者是M个协程在N个线程上执行。如此带来的好处是可以任性的阻塞,不必担心资源的浪费问题。所以协程本质上也是一种应对阻塞调用的方式。其他的重要思想还有异步。貌似操作系统更倾向于异步,而不是倾向于协程。

         Trafficserver底层大量基于异步,但向上提供的并发却大量基于协程的概念。

插件类型:

内容变换

         内容变换是修改request的内容或者response的内容。由于内容是变长的,所以traffic server定义了vconnection(结构体TSVConn)和vio。Vconnection代表从一个buffer到另一个buffer的连接,经过这个连接的数据可以根据连接指定的变化方法变化。这也就是内容变换的本质。本质上TSVConn是一个continuation,所以也具备协continuation具备的数据通知能力。

         而VIO是VConnection两端。一个input一个output,由于可以多个vconnection串行,所以一个vconnection的output vio就可以另一个vconnection的input。Vconnection的本质是变换,VIO的本质是内存buffer。

其他协议插件

         这个就比较底层了。一般的插件都是服务于http协议的,你也可以直接跳过http协议支持别的协议,或者是支持http之上的其他协议。课件traffic server对其网络基础结构的信心。

插件提高:

         每个插件都必须包含voidTSPluginInit(int argc, const char *argv[])函数,熟悉C的很容易理解,固定的名字和参数对应着固定的符号表符号,当插件被加载的时候,主程序可以直接按照这个符号表去执行就好了,这就是入口。

1.        向主程序注册插件:TSPluginRegister。可以不注册,主要是为了兼容性

2.        向某个全局钩子位置添加钩子回调:TSHttpHookAdd

a)        注册的钩子可以是全局的也可能是trasaction、session相关的。如果是transaction相关的,通过TSHttpTxn txnp = (TSHttpTxn)edata;获得transaction的指针。使用TSHttpTxnHookAdd函数添加transactionhook。

b)        如果是session相关的,使用TSHttpSsnHookAdd进行注册。Plugin中获得session的方法变为TSHttpSsn ssion = (TSHttpSsn)edata;

 

插件允许发起网络连接,使用TSNetConnect()发起只连接traffic server的http连接,TSHttpConnect()发起向任意地址的http连接。

cache系统:

         使用ats无非就是反向代理和缓存两种,其中缓存是ats最重要的功能。要想理解ats的cache架构,理解几个关键字和概念就好了。

         还有就是ats的cache可以组成集群,有两种方式:一种是配置共享的,一种是统一缓存的。配置共享的只是保证各个节点的配置是一样的,各个节点的cache还是各自缓存(重复是肯定有的),而统一缓存则是互相协作的,在多个节点之间排序缓存的,在本机找不到会自动去隔壁拉数据。

         ats在使用磁盘的时候不使用文件概念,所以可以直接使用裸盘(如果你使用文件,也只有一个大文件),ats会自己安排对磁盘的使用方式和数据的组织。

概念:

         代理:Http请求并不一定要全部从server获取,可以在靠近用户的机房缓存。尤其是图片视频等资源,这个缓存过程叫做代理。

         新鲜度:代理必须要保证缓存的数据是当前最新的,如何与上游的服务器确认是一个专门的话题。HTTP协议本身有提供一些头部来控制缓存新鲜度。例如max-age、last-modified、expires、date等。ats根据这些头部和用户的配置计算一个对象的新鲜度,决定是否要,什么时候去server段拉取(快要过期的时候去拉取叫fuzz Revalidation)。

         缓存控制:可以控制有的不缓存、当上游服务器出现拥塞的时候停止拉取、同一个url缓存不同的版本。

         缓存层级:cache本身也是可以分层的,就像CPU的一级缓存、二级缓存的概念。在ats中这个概念叫做parent,每个cache可以有sibling也可以有parent,cache在本地查不到,或者在本地的集群查不到,就可以去parent去查,parent查不到再回源到后台server查询。这些缓存之间还可以使用ICP协议(缓存控制协议)查查询parent或者sibling中的缓存状态,以便更新自己的缓存。

关键字:

l  裸盘:ats的cache支持硬件存储,不但支持文件系统的文件,还支持裸盘。裸盘支持在linux中有被移除的可能,因为直接访问磁盘可以通过O_DIRECT替代。裸盘其实就是不使用文件系统的磁盘,由于没有文件系统自然也没有文件的概念。在内核中是直接走sd驱动,电梯层,到scsi到磁盘的,不需要走文件系统层和缓存层。O_DIRECT也是一样。

l  cache span:一块连续的物理存储空间,一般是一个磁盘。

l  cache volumn:一个逻辑和业务上的存储空间,可以横跨多个cache span。这就像lvm横跨多个物理磁盘划分的逻辑分区。

l  cache strip:位于cache span(volumn)上的一条一条的存储带。数据都是组织在cache strip中。

l  cache ID、cache key:cache key唯一的标示一个缓存对象,一般以url表示,cache ID则是从cache key计算得来的128位的MD5值。

l  directory:在cache strip中的数据是由directory组织的。一个cache strip中有多个directory,每个directory中有多个条目。每一个directory都对应一个cache,由cache id索引。但directory只是cache的一个索引,通过directory可以找到cache在磁盘中的信息和实体。而所有的directory都是加载到内存的,所以如果一次cache查询结果是miss,就不需要磁盘(通过url计算得到cache id,但是查询内存发现该cache id没有对应directory),就可以返回。所以directory的存在让miss过程加速,但是如果找到了directory,每一个cache查询都需要接下来读取磁盘。值得注意的是,内存中只有directory,不包含实体,并且directory的大小是固定的,磁盘大下也是固定的,只要程序启动就会尽可能多的创建最多的directory,所以程序运行的过程中ats的内存需求是不会增加的(因为可以支持的缓存数是固定的,每条缓存在内存中的记录大小也是固定的)。

l  segment、bucket: 并不是strip下面就是并排的一片directory,这些directory也是组织的。4个directory是一个bucket,多个bucket是一个segment。在每个strip的头部都有一个空闲列表,里面是每个segment的directory空闲列表,也就是说又多少个segment在strip的头部就有多少个列表。事实上,cache ID定位的并不是directory,而是bucket,strip的free list也不包含每个bucket的第一个directory,而是顺序的包含第4个、第3个、第2个。如此,来了一个cache object的cache ID(128位),就可以定位到某个bucket,然后查看该bucket的第一个directory是不是used,如果used说明整个bucket都满了(只有后面3个都用完了才会用第1个),这个cache object就添加失败了。否则就会顺序的从4/3/2/1开始使用。所以,综上可以看出,bucket实际上是一个哈希桶,用来出来哈希函数的碰撞情况,只给出了4个,说明只能处理4个cache ID一致的情况。所以segment和bucket这两种组织结构的引入,是为了解决管理问题。

l  content:我们知道directory只是元数据,是要常驻内存的,存储了cache的索引。所以可以根据directory判断一个cache是否存在。如果发现了对应的directory,就得去取directory对应的cache的真实内容,这个内容就是放在content里的,位置由directory指明。directory的数目是动态计算出来的,总大小除以平均一个对象的大小就可以获得,平均一个对象的大小可以通过proxy.config.cache.min_average_object_size进行设置,从而控制directory的数目。content的大小是动态的,也是有限的,所以当content满的时候会自动从开头开始覆盖。但是并不会更新directory。直到下一次读取到directory的时候才会发现内容不存在,从而更新directory。这里可以带来的一个问题是,通过查看directory的统计值得到的结果是不准确的,并且一旦跑满数据量一直满的。

l  fragment:由于ats的并行性,不可能一下子存储太多的连续数据。所以大文件必然要分片(否则并发的来很多大文件缓存请求将无法应对)。我们知道directory里面会指出数据再content中的位置,这里指出的只是该cache的第一个fragment,在这个fragment的头部又很多信息,包括其他的fragment到哪里去找,还包括其他的同名版本的存储directory(例如同一个url的png、jpg版本)

l  SPDY:用户与同一个IP的http通信,无论是不是同一个网站,都复用一个tcp连接。这在大部分情况下是没用的,但是在用户使用的代理的时候就用户大了。因为用户的所有http请求都是发到代理去的,使用这个协议可以一整天都只使用一个tcp连接跑http,每个网战的http流只是tcp流里面的一个stream。这对提供代理效率和减轻代理和客户端负担有很大的提高。

集群

         多个cache可以配置为集群,完整的集群模式包含配置文件统一,和节点数据的交互。集群中的每个节点的配置文件是一样的,所以配置文件中不要出现本机的IP。在配置为集群模式后(这个需要每台机器单独配置),对任何一个节点的配置修改会被自动的同步到其他节点。同步配置使用多播,交换数据使用单播。

源代码与架构:

核心代码在iocore里面,iocore里面按照大功能分为了如下的几个模块。

         这就要从trafficserver的架构说起了。这几个目录几乎就是traffic server的关键字:异步(aio)、缓存(cache)、集群支持(cluster)、域名解析(dns)、事件系统(eventsysytem)、上游配置(hostdb)、网络(net)。

网络与nginx的对比:http://www.cnblogs.com/liushaodong/archive/2013/02/26/2933535.html

         除了提供核心程序功能,程序需要入口,入口一般是一个启动server的主程序和若干的管理程序,管理程序都在cmd目录下,每一个目录是一个管理程序:

         主程序位于proxy目录下的Main.cc(.cc后缀的是C++文件的glibc后缀表示)。

常用命令

         主程序名称是traffic_server,

        

traffic_manager:为traffic_ctl提供服务

traffic_cop:独立的监控程序,监控traffic_server和traffic_manager的职责和内存交换空间使用情况,发现异常重启进程。

traffic_crashlog:由traffic_server进程启动,在traffic_server崩溃的时候打印一个崩溃报告到log目录。

traffic_ctl:在线配置一些traffic_server可以配置的参数

traffic_logcat:将trafficserver的二进制log文件转变成可读的ASCII log

traffic_logstats:trafficserver的log分析工具

traffic_via:可以配置proxy.config.http.insert_request_via_str、proxy.config.http.insert_response_via_str两个参数使得所有的数据的http头部都携带VIA信息(表示cache状态,可以看出是从哪里拿来的),wget这个文件就会在http头部看到这个信息,而这个信息是被traffic server编码过的,使用traffic_via命令可以将这个信息解码就可以看到缓存的获取路径。

traffic_sac:standalonecollator。日志收集器,用在traffic server集群中。可以用来收集各个节点的日志集中到本机进行处理。一个节点可以不安装traffic server,只安装sac可以发挥更大的日志能力。

tspush:不需要用户请求可以直接使用这个命令将内容投递到traffic server的cache中,使用这个命令需要打开proxy.config.http.push_method_enabled 选项

tsxs:插件编译程序。用来编译和安装插件。

traffic_top:一个方便的查看当前trafficserver内部状态的程序。要编译这个必须要有libncurses5-dev库,否则会静悄悄不安装。

架构

         Trafficserver虽然大部分情况跑在linux上,但是却是跨平台的。一般的操作系统都提供了网络访问的方式,但是并没有提供大量并发网络访问的方式(或许日后操作系统的API可以直接提供这个功能),所以处理大量并发需要程序自己来(有的编程语言内部封装了这部分逻辑,例如golang,就省了程序的事了)。目前处理这个问题的最常用办法就是上层抽象协程。

         除了网络访问,操作系统一般也不提供特别易用的事件系统、dns系统、缓存系统、集群系统等接口。然而这些都是traffic server核心功能所要依赖的底层服务。Traffic server的应对办法是将这些服务封装,主要逻辑全部基于封装的服务,而不是操作系统的API(这里就不得不说操作系统和glibc的API不与时俱进了。。。还得别人还得自己搞)。

上层设计

         http://dev.fastweb.com.cn/wordpress/?p=28

代码分析:

/proxy目录主程序:

         用这个命名主程序的目录就知道这个程序的核心意义了。

         可以看到生成的主程序的二进制文件也在这个文件夹。

HTTP事务流程:

         必须要理解的是trafficserver是个程序,主要的业务是无数的transaction,每个transaction都是一个用户的http连接处理,不仅包含了用户的tcp连接,还包含了traffic server与后端的通信和本地操作等。一个transaction只是用户一个tcp连接上执行的一次事务,还有一个session概念,是一个client和server之间tcp概念上的连接。一个session可以包括很多个transaction。对用户来说,一个request和response是一个transaction。