分享IT技术,分享生活感悟,热爱摄影,热爱航天。
Zip是一种非常常见的压缩格式,其可以将多个文件打包并压缩为一个zip文件,同时Android所使用的apk包就是一个zip格式的压缩文件。这里我们尝试在不解压缩zip文件的情况下对zip包中的一部分内容进行修改。
Zip文件是将多个文件按照顺序进行排列,每个文件包括文件头和文件内容,其结构如下图所示
其中每个文件头部分包括签名(固定值0x04034b50),解包需要的Zip版本,通用标志位,压缩方法,文件最后修改时间,文件最后修改日期,CRC-32校验值,压缩后的文件大小,压缩前的文件大小,文件名长度,扩展域长度,文件名,扩展域。其中获取一些文件头部分的代码如下所示
<?php $file = fopen($argv[1], 'r'); //signature fseek($file, 0); $data = fread($file, 4); //Compressed size fseek($file, 18); $data = fread($file, 4); $size1 = unpack('i', $data); //Uncompressed size fseek($file, 22); $data = fread($file, 4); $size2 = unpack('i', $data); //File last modification time fseek($file, 10); $data = fread($file, 2); //File last modification date fseek($file, 12); $data = fread($file, 2); //File name length fseek($file, 26); $data = fread($file, 2); $len1 = unpack('s', $data); //File name fseek($file, 30); $data = fread($file, $len1[1]);
Zip文件中文件头对应的头部分中的最后修改时间和日期采用了MS-DOS时间日期格式进行保存,其使用了4个字节的各个位段保存了年月日和时分秒这6个数值。
其中前两个字节保存时间,解析为一个16位整数,其中0~4位为秒数除以2,5~10位为分钟,11~15位为小时
后两个字节保存日期,解析为一个16位整数,其中0~4位为日,5~8位为月,9~15位年份减去1980
其解析的代码如下
<?php //... //File last modification time fseek($file, 10); $data = fread($file, 2); $v = unpack('s', $data); $h = ($v[1] & (0b11111 << 11)) >> 11; $i = ($v[1] & (0b111111 << 5)) >> 5; $s = ($v[1] & (0b11111)) * 2; //File last modification date fseek($file, 12); $data = fread($file, 2); $v = unpack('S', $data); $y = (($v[1] & (0b1111111) << 9) >> 9) + 1980; $m = ($v[1] & (0b1111) << 5) >> 5; $d = ($v[1] & (0b11111));
其编码的代码如下
<?php $h = date('G'); $i = date('i'); $s = date('s'); $v1 = pack('s', ($h << 11) + ($i << 5) + $s); $y = date('Y') - 1980; $m = date('n'); $d = date('j'); $v2 = pack('s', ($y << 9) + ($m << 5) + $d); $data = $v1 . $v2;
在Nginx向用户发送Zip文件时,使用Lua的body filter修改Zip文件中第一个文件的最后修改时间为当前时间,其中需要使用到Lua的struct模块
location ~ \.zip$ { #记录当前chunked号 set $c "0"; header_filter_by_lua_block { ngx.header.content_length = nil; } body_filter_by_lua_block { --替换第一个文件的最后修改时间 if ngx.var.c == "0" then --加载struct模块 local struct = require("struct"); --获取当前时间 local t = os.date("*t"); --生成MS-DOS格式的时间日期 local v2 = struct.pack("I2", (t["year"] - 1980) * 2^9 + t["month"] * 2^5 + t["day"]); local v1 = struct.pack("I2", t["hour"] * 2^11 + t["min"] * 2^5 + math.floor(t["sec"] / 2)); ngx.arg[1] = string.sub(ngx.arg[1], 1, 10) .. v1 .. v2 .. string.sub(ngx.arg[1], 15, -1); end ngx.var.c = ngx.var.c + 1; } }
如此就能够动态的修改Zip包中的一些内容而不需要每次都重新打包,能够较大程度提高Web服务器的效率。
以上的配置中取消了Content-Length头,会导致不能够支持断点续传,为了能够继续支持断点续传需要对请求中的Range头进行简要的分析,并做出对应的处理,同时替换文件尾部的一段随机注释字符串,处理后的配置如下
#将加载Lua模块放在Nginx初始化阶段 init_by_lua_block { struct = require("struct"); } server { #.... location ~ \.zip$ { set $c "0"; body_filter_by_lua_block { --替换第一个文件的最后修改时间 if ngx.var.c == "0" then --设置Range的默认起止位置 local s1 = 0; local s2 = ngx.header.content_length; if ngx.var.http_range then --分析Range头 local m = ngx.re.match(ngx.var.http_range, "bytes=(\\d+)-(\\d+)?"); --如果匹配成功则更新起止位置 if m then s1 = tonumber(m[1]); if m[2] then s2 = tonumber(m[2]); end end end --需要更新的时间值在当前的范围内时,则进行修改 if s1 < 10 and s2 > 15 then local t = os.date("*t"); local v2 = struct.pack("I2", (t["year"] - 1980) * 2^9 + t["month"] * 2^5 + t["day"]); local v1 = struct.pack("I2", t["hour"] * 2^11 + t["min"] * 2^5 + math.floor(t["sec"] / 2)); --调整修改的位置 ngx.arg[1] = string.sub(ngx.arg[1], 1, 10 - s1) .. v1 .. v2 .. string.sub(ngx.arg[1], 15 - s1, -1); end end --替换文件尾部的一段随机注释字符串(需要源文件尾部有一段32字节长度的注释字符) if ngx.arg[2] == true then if ngx.header.content_range then local m = ngx.re.match(ngx.header.content_range, "bytes (\\d+)-(\\d+)/(\\d+)"); if m then local m2 = tonumber(m[2]); local m1 = m2 - string.len(ngx.arg[1]) + 1; local m3 = tonumber(m[3]); if m2 >= m3 - 32 then local s = ngx.md5(math.random()); if m1 >= m3 - 32 then ngx.arg[1] = string.sub(s, 1, m2 - m1 + 1); else ngx.arg[1] = string.sub(ngx.arg[1], 1, -1 - (33 + m2 - m3)) .. string.sub(s, 1, m2 - m3); end end end else ngx.arg[1] = string.sub(ngx.arg[1], 1, -33) .. ngx.md5(math.random()); end end ngx.var.c = ngx.var.c + 1; } } }
Zabbix作为一个分布式的监控系统,相比Nagios其较大的优点在于设置和数据保存在数据库中,便于维护和分析,同时自身或是通过grafana还可以实现监控项目曲线图的绘制。可以完成以前使用Cacti+Nagios来实现的系统监控任务,相关的监控主机配置只需要保存一份即可,带来了很大的便利。
Zabbix和Nagios的监控方式较为类似,其包括Zabbix-server和Zabbix-agent,对应Nagios中的Nagios和Nrpe,但Zabbix可以支持被监控的主机主动向Zabbix-server提交需要监控项目的相关数据,如此可以很大程度减少Zabbix-server所在的服务器在数据采集时的工作量,以之前的经验在Cacti+Nagios采集数据的时间点,监控服务器的负载会非常高,同时大量读写RRD文件也会成为系统之后的瓶颈,同时监控一两百台服务器就已经力不从心了。
对于一个监控系统都存在的一个问题就是如何来监控自己,如果自身发生故障会导致全部监控实效,并且也无法发送报警消息。这就需要实现Zabbix系统的高可用,至少在监控系统出现故障时能够及时发送报警消息。一个比较简单的想法就是通过其他的脚本来监控Zabbix的状态,但这并不是一个很好的做法,相当于又做了一套新的监控系统,而且同样存在如何自监控的问题。
首先分析一下Zabbix的系统组成,其包括负责处理监控数据的zabbix-server进程,另外还包括一个用来做Zabbix相关配置的Web前端服务和一个用来保存配置和监控数据的MySQL数据库,在被监控的主机上还存在一个用来执行监控逻辑和发送监控信息的zabbix-agentd进程。
对于一个监控项目,监控的流程为zabbix-server向zabbix-agentd发送要获取的监控数据,如果为被动监控,则zabbix-server通过调用zabbix-agentd中提供的相关监控函数获取到需要的监控项目数据并保存到数据库,如果为主动监控,则zabbix-server通知zabbix-agentd需要的数据项目和提交的时间间隔,然后由zabbix-agentd按照要求主动提交监控数据。
其中需要做到高可用的几个过程分别为,zabbix-server读取数据库,zabbix-agentd向zabbix-server提交数据。这里使用的方法是让zabbix-server通过本机安装的Haproxy来连接数据库,同时zabbix-agentd则通过本机的Haproxy来向zabbix-server提交数据。整个系统的结构设计入图所示
使用两台服务器来安装Zabbix监控服务,如图中左右两个虚线方框中所示,其中包括配置使用的Web前端,Zabbix-server,Haproxy,MySQL,Haproxy中配置MySQL的后端分别指向两台服务器上的MySQL,两个MySQL服务通过主从复制保持数据的一致性,Haproxy中设置Zabbix-server的后端分别指向两台服务器上的Zabbix-server,另外需要监控的主机上安装Haproxy设置Zabbix-server后端为上述的两个Zabbix-server,其Zabbix-agent通过这个Haproxy来向Zabbix-server提交数据。
如此任何一个Zabbix-server服务器出现问题,都可以通过Haproxy切换到另外一个Zabbix-server服务器上,实现监控服务的高可用。同时如此配置后建议监控项目都使用主动模式,因为如果配置为Zabbix-server通过zabbix-agentd获取,则两个Zabbix-server会采集两次数据,写入时可能会产生一些冲突。
Haproxy的配置,在Haproxy的配置文件haproxy.cfg中增加以下配置
#两个MySQL后端,这里设置其中的从库为backup,防止两个库同时写入导致数据的冲突 listen mysql bind 127.0.0.1:33061 mode tcp server z1 192.168.1.101:3306 server z2 192.168.1.102:3306 backup #Zabbix-server后端 listen zabbix bind 127.0.0.1:10061 mode tcp server z1 192.168.1.101:10051 server z2 192.168.1.102:10051 backup
Zabbix-agent的配置,在配置中将连接的Zabbix-server指向本机的Haproxy
#修改ServerActive ServerActive=127.0.0.1:10061
另外为了方便Zabbix-agent配置的管理,建议将Hostname参数单独写到一个文件中(这个参数是Zabbix配置中配置的对应该主机的名字),如/etc/zabbix/zabbix_agentd.d/hostname.conf,这样除了这一个文件以外,全部需要监控的主机的Zabbix-agent配置都是相同的。
最后将Web前端的配置也进行修改如下,即使连接的过程都通过Haproxy来实现
<?php // Zabbix GUI configuration file. global $DB; $DB['TYPE'] = 'MYSQL'; $DB['SERVER'] = '127.0.0.1'; $DB['PORT'] = '33061'; $DB['DATABASE'] = 'zabbix'; $DB['USER'] = 'zabbix'; $DB['PASSWORD'] = 'zabbix'; // Schema name. Used for IBM DB2 and PostgreSQL. $DB['SCHEMA'] = ''; $ZBX_SERVER = '127.0.0.1'; $ZBX_SERVER_PORT = '10061'; $ZBX_SERVER_NAME = ''; $IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;