Apache Traffic Server插件开发手记(一)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Apache Traffic Server插件开发手记(一)相关的知识,希望对你有一定的参考价值。
参考技术A 在Main.cc中添加,这样在开发插件的时候就能以root的方式运行traffic_server,拥有绑定1-1023端口的权限traffic server文件目录
功能:
Trafficserver的主要功能是缓存,当然你也可以用它来做纯粹的反向代理(像通常用nginx那样)。通常切入一个庞大的系统的最好方式是看如何使用,使用traffic server的主要入口有两个:配置文件和插件。所有使用者都需要配置文件,高级使用者则需要插件。
traffic支持大规模的集群处理,不同于nginx的单点(需要ospf均衡链路来做冗余),所有的配置文件可以做到改动一个通知全部。程序根据功能划分为不同的几个子程序,有服务运行时使用的程序,也有管理使用的。详细见下文。
[root@controller trafficserver]# tree -L 3 bin/ etc/ var/ bin/ ├── traffic_cop ├── traffic_crashlog ├── traffic_ctl ├── traffic_layout ├── traffic_line ├── traffic_logcat ├── traffic_logstats ├── traffic_manager ├── traffic_sac ├── trafficserver ├── traffic_server ├── traffic_top ├── traffic_via ├── tspush ├── tstop -> traffic_top └── tsxs etc/ └── trafficserver ├── body_factory │ └── default ├── cache.config ├── cache.config_1 ├── cluster.config ├── cluster.config_1 ├── congestion.config ├── congestion.config_1 ├── hosting.config ├── hosting.config_1 ├── icp.config ├── icp.config_1 ├── ip_allow.config ├── ip_allow.config_1 ├── log_hosts.config ├── log_hosts.config_1 ├── logs_xml.config ├── logs_xml.config_1 ├── parent.config ├── parent.config_1 ├── plugin.config ├── plugin.config_1 ├── prefetch.config ├── prefetch.config_1 ├── proxy.pac ├── proxy.pac_1 ├── records.config ├── records.config_1 ├── remap.config ├── remap.config_1 ├── remap.config_2 ├── snapshots ├── socks.config ├── socks.config_1 ├── splitdns.config ├── splitdns.config_1 ├── ssl_multicert.config ├── ssl_multicert.config_1 ├── stats.config.xml ├── stats.config.xml_1 ├── storage.config ├── storage.config_1 ├── storage.config_2 ├── trafficserver-release ├── update.config ├── update.config_1 ├── vaddrs.config ├── vaddrs.config_1 ├── volume.config └── volume.config_1 var/ ├── log │ └── trafficserver │ ├── access.log_controller.19691231.19h00m00s-20171201.00h00m04s.old │ ├── access.log_controller.20171201.00h35m04s-20171201.02h00m02s.old │ ├── diags.log │ ├── error.log │ ├── manager.log │ ├── squid.blog │ ├── squid.blog_controller.19691231.19h00m00s-20171201.00h00m04s.old │ ├── traffic.out │ ├── traffic_server.stderr │ └── traffic_server.stdout └── trafficserver ├── cache.db ├── cop.lock ├── eventapi.sock ├── host.db ├── hostdb.config ├── manager.lock ├── mgmtapi.sock ├── processerver.sock ├── records.snap ├── server.lock └── stats.snap
配置文件:
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
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事务流程:
必须要理解的是trafficserver是个程序,主要的业务是无数的transaction,每个transaction都是一个用户的http连接处理,不仅包含了用户的tcp连接,还包含了traffic server与后端的通信和本地操作等。一个transaction只是用户一个tcp连接上执行的一次事务,还有一个session概念,是一个client和server之间tcp概念上的连接。一个session可以包括很多个transaction。对用户来说,一个request和response是一个transaction。
以上是关于Apache Traffic Server插件开发手记(一)的主要内容,如果未能解决你的问题,请参考以下文章
安装ATS(apache traffic server)正向代理
CDN 解决方案 Traffic Control 升级为 Apache 顶级项目 | 软件推介