• 技术文章

    mysql索引数据结构及原理

    转自:http://www.cnblogs.com/tgycoder/p/5410057.html 数据结构及算法基础 索引的本质 MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是一种数据结构。 数据库查询是数据库的主要功能之一,最基本的查询算法是顺序查找(linear search)时间复杂度为O(n),显然在数据量很大时效率很低。优化的查找算法如二分查找(binary search)、二叉树查找(binary tree search)等,虽然查找效率提高了。但是各自对检索的数据都有要求:二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织)。所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构。这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构就是索引。 看一个例子: 图1 图1展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。 虽然这是一个货真价实的索引,但是实际的数据库系统几乎没有使用二叉查找树或其进化品种红黑树(red-black tree)实现的,原因会在下文介绍。 B-Tree和B+Tree 关于B树和B+树请参考关于B树的一些总结,这篇文章介绍的比较详细,同时容易理解。 目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree作为索引结构,在本文的下一节会结合存储器原理及计算机存取原理讨论为什么B-Tree和B+Tree在被如此广泛用于索引,这一节先单纯从数据结构角度描述它们。 B-Tree 为了描述B-Tree,首先定义一条数据记录为一个二元组[key, data],key为记录的键值,对于不同数据记录,key是互不相同的;data为数据记录除key外的数据。那么B-Tree是满足下列条件的数据结构: d>=2,即B-Tree的度; h为B-Tree的高; 每个非叶子结点由n-1个key和n个指针组成,其中d<=n<=2d; 每个叶子结点至少包含一个key和两个指针,最多包含2d-1个key和2d个指针,叶结点的指针均为NULL; 所有叶结点都在同一层,深度等于树高h; key和指针相互间隔,结点两端是指针; 一个结点中的key从左至右非递减排列; 如果某个指针在结点node最左边且不为null,则其指向结点的所有key小于v(key1),其中v(key1)为node的第一个key的值。 如果某个指针在结点node最右边且不为null,则其指向结点的所有key大于v(keym),其中v(keym)为node的最后一个key的值。 如果某个指针在结点node的左右相邻key分别是keyi和keyi+1且不为null,则其指向结点的所有key小于v(keyi+1)且大于v(keyi)。 图2是一个d=2的B-Tree示意图。 图2 由于B-Tree的特性,在B-Tree中按key检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到null指针,前者查找成功,后者查找失败。B-Tree上查找算法的伪代码如下: BTree_Search(node, key) { if(node == null) return null; foreach(node.key) { if(node.key[i] == key) return node.data[i]; if(node.key[i] > key) return BTree_Search(point[i]->node); } return BTree_Search(point[i+1]->node); } data = BTree_Search(root, my_key); 关于B-Tree有一系列有趣的性质,例如一个度为d的B-Tree,设其索引N个key,则其树高h的上限为logd((N+1)/2),检索一个key,其查找结点个数的渐进复杂度为O(logdN)。从这点可以看出,B-Tree是一个非常有效率的索引数据结构。 B+Tree B-Tree有许多变种,其中最常见的是B+Tree,例如MySQL就普遍使用B+Tree实现其索引结构。 与B-Tree相比,B+Tree有以下不同点: 每个结点的指针上限为2d而不是2d+1。 内结点不存储data,只存储key;叶子结点不存储指针。 图3是一个简单的B+Tree示意。 图3 由于并不是所有节点都具有相同的域,因此B+Tree中叶结点和内结点一般大小不同。这点与B-Tree不同,虽然B-Tree中不同节点存放的key和指针可能数量不一致,但是每个结点的域和上限是一致的,所以在实现中B-Tree往往对每个结点申请同等大小的空间。 一般来说,B+Tree比B-Tree更适合实现外存储索引结构,具体原因与外存储器原理及计算机存取原理有关,将在下面讨论。 带有顺序访问指针的B+Tree 一般在数据库系统或文件系统中使用的B+Tree结构都在经典B+Tree的基础上进行了优化,增加了顺序访问指针。 图4 如图4所示,在B+Tree的每个叶子结点增加一个指向相邻叶子结点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,例如图4中如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着结点和指针顺序遍历就可以一次性访问到所有数据结点,极大提到了区间查询效率。 这一节对B-Tree和B+Tree进行了一个简单的介绍,下一节结合存储器存取原理介绍为什么目前B+Tree是数据库系统实现索引的首选数据结构。 为什么使用B-Tree(B+Tree) 上文说过,红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用B-/+Tree作为索引结构,这一节将结合计算机组成原理相关知识讨论B-/+Tree作为索引的理论基础。 一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。下面先介绍内存和磁盘存取原理,然后再结合这些原理分析B-/+Tree作为索引的效率。 主存存取原理 目前计算机使用的主存基本都是随机读写存储器(RAM),现代RAM的结构和存取原理比较复杂,这里本文抛却具体差别,抽象出一个十分简单的存取模型来说明RAM的工作原理。 图5 从抽象角度看,主存是一系列的存储单元组成的矩阵,每个存储单元存储固定大小的数据。每个存储单元有唯一的地址,现代主存的编址规则比较复杂,这里将其简化成一个二维地址:通过一个行地址和一个列地址可以唯一定位到一个存储单元。图5展示了一个4 x 4的主存模型。 主存的存取过程如下: 当系统需要读取主存时,则将地址信号放到地址总线上传给主存,主存读到地址信号后,解析信号并定位到指定存储单元,然后将此存储单元数据放到数据总线上,供其它部件读取。 写主存的过程类似,系统将要写入单元地址和数据分别放在地址总线和数据总线上,主存读取两个总线的内容,做相应的写操作。 这里可以看出,主存存取的时间仅与存取次数呈线性关系,因为不存在机械操作,两次存取的数据的“距离”不会对时间有任何影响,例如,先取A0再取A1和先取A0再取D3的时间消耗是一样的。 磁盘存取原理 上文说过,索引一般以文件形式存储在磁盘上,索引检索需要磁盘I/O操作。与主存不同,磁盘I/O存在机械运动耗费,因此磁盘I/O的时间消耗是巨大的。 图6是磁盘的整体结构示意图。 图6 一个磁盘由大小相同且同轴的圆形盘片组成,磁盘可以转动(各个磁盘必须同步转动)。在磁盘的一侧有磁头支架,磁头支架固定了一组磁头,每个磁头负责存取一个磁盘的内容。磁头不能转动,但是可以沿磁盘半径方向运动(实际是斜切向运动),每个磁头同一时刻也必须是同轴的,即从正上方向下看,所有磁头任何时候都是重叠的(不过目前已经有多磁头独立技术,可不受此限制)。 图7是磁盘结构的示意图。 图7 盘片被划分成一系列同心环,圆心是盘片中心,每个同心环叫做一个磁道,所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段,每个段叫做一个扇区,每个扇区是磁盘的最小存储单元。为了简单起见,我们下面假设磁盘只有一个盘片和一个磁头。 当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间。 局部性原理与磁盘预读 由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理: 当一个数据被用到时,其附近的数据也通常会马上被使用。 程序运行期间所需要的数据通常比较集中。 由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。 预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。 B-/+Tree索引的性能分析 从使用磁盘I/O次数评价索引结构的优劣性:根据B-Tree的定义,可知检索一次最多需要访问h个结点。数据库系统的设计者巧妙的利用了磁盘预读原理,将一个结点的大小设为等于一个页面,这样每个结点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧: 每次新建结点时,直接申请一个页面的空间,这样可以保证一个结点的大小等于一个页面,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。 B-Tree中一次检索最多需要h-1次I/O(根结点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出读d是非常大的数字,通常超过100,因此h非常小。 综上所述,用B-Tree作为索引结构效率是非常高的。 而红黑树结构,h明显要深得多。由于逻辑上很近的结点(父子结点)物理上可能离得很远,无法利用局部性原理。所以即使红黑树的I/O渐进复杂度也为O(h),但是查找效率明显比B-Tree差得多。…

  • 技术文章

    flume搭建日志收集系统

    闲话不说直接上配置文件及过程中注意的问题: 现有两个主机其中一个A做agent1,agent2   B用来collect 日志,并且agent1 agent2统一收集到一个文件里。 flume 配置相关及使用方法网上太多了,关注及学会以下三个组件的使用方法,就基本搞定 Source:用来消费传递到该组件的Event ##即收集日志的方式,可以文件,命令…… Channel:中转Event的一个临时存储,保存有Source组件传递过来的Event Sink:从Channel中读取并移除Event,将Event传递到Flow Pipeline中的下一个修复处理服务,或者保存到文件 一,配置agent 我官网下载的源码版本为: apache-flume-1.7.0-bin.tar.gz agent1配置文件flume-agent.conf.properties 如下: [well] agent1.sources = source1 agent1.sinks = sink1 agent1.channels = channel1   # Describe/configure source1 agent1.sources.source1.type = exec agent1.sources.source1.command = tail -f /data/logs/access-admin.log agent1.sources.source1.channels = channel1 # Describe sink1 # agent1.sinks.sink1.type = file_roll # agent1.sinks.sink1.sink.directory=/home/dev/bankie/data #agent1.sinks.sink1.sink.rollInterval=0 # agent1.sinks.sink1.channel = channel1 agent1.sinks.sink1.type = avro agent1.sinks.sink1.hostname = 10.1.0.8 #B服务的IP agent1.sinks.sink1.port = 44444 # Use a channel which buffers events in memory agent1.channels.channel1.type = memory agent1.channels.channel1.capacity = 50000 agent1.channels.channel1.transactionCapacity = 10000   [/well] agent2配置文件flume-agent2.conf 如下: [well] agent2.sources = source1 agent2.sinks = sink1 agent2.channels = channel1   # Describe/configure source1 agent2.sources.source1.type = exec agent2.sources.source1.command = tail -f /data/logs/access-web.log…

  • 技术文章

    nginx缓存使用及原理

    这几天正好为公共账号写几篇技术文章,就整理一下nginx下的缓存,做web注重性能,webcache使用的好,会事半功倍。 今天重点分析一下web服务器的缓存 一、nginx分为两类缓存,第一类,控制客户端的本地缓存,第二类就是直接缓存地web服务器的文件缓存。 1,expires 指令设置静态文件缓存即使用客户端本地缓存。expires可以控制 http 应答中的“Expires ”和“ Cache-Control ”的头标(起到控制页面缓存的作用),所以在使用浏览器访问的时候,经常可以看到一个状态值304返回. 最常见的静态文件的缓存,配置方式如下: [well] location~ .*\.(gif|jpg|png|htm|html|css|js|flv|ico|swf)(.*) { expires 6h; } [/well] 语法: expires [time|epoch|max|off] 默认值: expires off 作用域: http, server, location 设置expires这个参数即可表示需要客户端缓存时间.如果不想让代理或浏览器缓存,加no-cache参数或private参数: # expires 1d; add_header Cache-Control no-cache; add_header Cache-Control private; 这样浏览器F5刷新时,返回的依然是200,而不是304. 分析一下原理,expires使用了特定的时间,在第一次get请求时服务端会在header里返回Last-Modified响应头来确定最后一次修改时间,如果图所示。   当客户端第二次访问的时候,request会带有If-Modified-Since响应头来确定缓存时间是否过期,如下图,如果还在缓存时效内,客户端就不会下载缓存内容,服务会返回304,同理时间过了就会重新下载。 总结一下就是客户端使用If-Modified-Since头,和原始服务器中的Last-Modified制作比较,所以实现浏览器缓存并判断是否过期。 简单的说,Last-Modified 与If-Modified-Since 都是用于记录页面最后修改时间的 HTTP 头信息,只是Last-Modified 是由服务器往客户端发送的 HTTP 头,而 If-Modified-Since则是由客户端往服务器发送的头,可以看到,再次请求本地存在的 cache 页面时,客户端会通过 If-Modified-Since头将先前服务器端发过来的 Last-Modified最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则返回304告诉客户端其本地cache的页面是最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器的负担。 2,nginx的proxy模块 可以实现类似于Squid的缓存功能,HTTP的缓存与文件系统或者块设备的缓存还有所不同,文件系统或者块设备的缓存可以使用预取方法做优化,提前预取出将要被访问的部分,但是HTTP的缓存却无法预知文件的访问情形。 可以在nginx里面实现动态页面的静态化工作。 具体的配置方法如下: 在http域内添加如下参数 [well] proxy_temp_path /var/tmp/nginx/proxy; proxy_cache_path /var/tmp/nginx/cache levels=1:2 keys_zone=cache_one:20m inactive=1d max_size=5g; server { listen 80; server_name localhost; root /var/www; location ~ \.(jpg|png|jpeg|gif|css|js)$ { proxy_cache cache_one; proxy_cache_valid 200 304 12h; proxy_cache_key $host$uri$is_args$args; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://127.0.0.1:8181; proxy_set_header X-Forwarded_For $proxy_add_x_forwarded_for; expires 1d; } } [/well] 语法解析: 说明 proxy_temp_path :…

  • 技术文章

    php正则表达式

    经常使用,但是有些基本属性还的查找。今天记录一下,特别是模式匹配及修饰符这部分。网上内容也不多。 (转自:http://www.cnblogs.com/-run/articles/2371078.html)   正则表达式定义 正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。 列目录时, dir *.txt或ls *.txt中的*.txt就不是一个正则表达式,因为这里*与正则式的*的含义是不同的。 正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。 3.1 普通字符 由所有那些未显式指定为元字符的打印和非打印字符组成。这包括所有的大写和小写字母字符,所有数字,所有标点符号以及一些符号。 3.2 非打印字符   字符 含义 \cx 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。 \f 匹配一个换页符。等价于 \x0c 和 \cL。 \n 匹配一个换行符。等价于 \x0a 和 \cJ。 \r 匹配一个回车符。等价于 \x0d 和 \cM。 \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 \S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 \t 匹配一个制表符。等价于 \x09 和 \cI。 \v 匹配一个垂直制表符。等价于 \x0b 和 \cK。 3.3 特殊字符 所谓特殊字符,就是一些有特殊含义的字符,如上面说的”*.txt”中的*,简单的说就是表示任何字符串的意思。如果要查找文件名中有*的文件,则需要对*进行转义,即在其前加一个\。ls \*.txt。正则表达式有以下特殊字符。   特别字符 说明 $ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 \$。 ( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。 * 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。 + 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。 . 匹配除换行符 \n之外的任何单字符。要匹配 .,请使用…

  • 技术文章

    Golang channels 教程

    转自:http://www.oschina.net/translate/golang-channels-tutorial   Go语言内置了书写并发程序的工具。将go声明放到一个需调用的函数之前,在相同地址空间调用运行这个函数,这样该函数执行时便会作为一个独立的并发线程。这种线程在Go语言中称作goroutine。在这里我要提一下,并发并不总是意味着并行。Goroutines是指在硬件允许情况下创建能够并行执行程序的架构。这是这个主题的一次讨论:并发不是并行。 让我们从一个例子开始: ? 1 2 3 4 5 func main() {      // Start a goroutine and execute println concurrently      go println("goroutine message")      println("main function message") }     这段程序将输出main function messageand 或者goroutine message。我说“ 或者”是因为催生的goroutine有一些特点。当你运行一个goroutine时,调用的代码(在我们的例子里它是main函数)不等待goroutine完成,而是继续往下运行。在调用完println后main函数结束了它的执行,在Go语言里这意味着这个程序及所有催生的goroutines停止执行。但是,在这个发生之前,goroutine可能已经完成了其代码的执行并输出了goroutine message字符。 你明白这些后必须有方法来避免这种情况。这就是Go语言中channels的作用。 Channels 基础知识 Channels用来同步并发执行的函数并提供它们某种传值交流的机制。Channels的一些特性:通过channel传递的元素类型、容器(或缓冲区)和传递的方向由“<-”操作符指定。你可以使用内置函数 make分配一个channel: ? 1 2 3 4 5 i := make(chan int)       // by default the capacity is 0 s := make(chan string, 3) // non-zero capacity r := make(<-chan bool)          // can only read from w := make(chan<- []os.FileInfo) // can only write to Channels是一个第一类值(一个对象在运行期间被创建,可以当做一个参数被传递,从子函数返回或者可以被赋给一个变量。)可以像其他值那样在任何地方使用:作为一个结构元素,函数参数、函数返回值甚至另一个channel的类型: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 // a channel which: //  - you can only write to //  - holds another channel as its value…

  • 技术文章

    翻墙路由器的原理与实现

    转自:http://drops.wooyun.org/papers/10177?123&from=timeline&isappinstalled=0   编辑备注:因无法联系到原作者以及文章原文声明放弃著作权以及领接权,因此进行转载,希望作者能够联系邮箱 drops@wooyun.org 0x00 开篇 GFW具有重大的社会意义。无论是正面的社会意义,还是负面的意义。无论你是讨厌,还是憎恨。它都在那里。在可以预见的将来,墙还会继续存在。我们要学会如何与其共存。我是一个死搞技术的,就是打算搞技术到死的那种人。当我读到“西厢计划”的博客上的这么一段话时,我被深深的触动了。不是为了什么政治目的,不是为了什么远大理想,仅仅做为一个死搞技术的人显摆自己的价值,我也必须做些什么。博客上的原话是这么写的: 作为个搞技术的人,我们要干点疯狂的事。如果我们不动手,我们就要被比我们差的远的坏技术人员欺负。这太丢人了。眼前就是,GFW这个东西,之前是我们不抱团,让它猖狂了。现在咱们得凑一起,想出来一个办法让它郁闷一下,不能老被欺负吧。要不,等到未来,后代会嘲笑我们这些没用的家伙,就象我们说别人“你怎么不反抗?” gfwrev.blogspot.com 我把翻墙看成一场我们与GFW之间的博弈,是一个不断对抗升级的动态过程。目前整体的博弈态势来讲是GFW占了绝对的上风。我们花费了大量的金钱(买VPS买VPN),花费大量时间(学习各种翻墙技术),而GFW只需要简单发几个包,配几个路由规则就可以让你的心血都白费。 GFW并不需要检查所有的上下行流量中是不是有不和谐的内容,很多时候只需要检查连接的前几个包就可以判断出是否要阻断这个连接。为了规避这种检查,我们就需要把所有的流量都通过第三方代理,还要忍受不稳定,速度慢等各种各样的问题。花费的是大量的研究的时间,切换线路的时间,找出是什么导致不能用的时间,当然还有服务器的租用费用和带宽费用。我的感觉是,这就像太极里的四两拨千斤。GFW只需要付出很小的成本,就迫使了我们去付出很大的反封锁成本,而且这种成本好像是越来越高了。 这场博弈的不公平之处在于,GFW拥有国家的资源和专业的团队。而我们做为个体,愿意花费在翻墙上的时间与金钱是非常有限的。在竞争激烈的北上广深,每天辛苦忙碌的白领们。翻墙无非是为了方便自己的工作而已。不可能在每天上下班从拥挤的地铁中挤出来之后再去花费已经少得可怜的业余时间去学习自己不是翻墙根本不需要知道的名词到底是什么意思。于是乎,我们得过且过。不用Google也不会死,对不对。SSH加浏览器设置,搞一搞也就差不多能用就行啦。但是得过且过也越来越不好过了。从最开始的HTTP代理,到后来的SOCKS代理,到最近的OpenVPN,一个个阵亡。普通人可以使用的方式越来越少。博弈的天平远远不是平衡的,而是一边倒。 GFW用技术的手段达到了四两拨千斤的作用。难道技术上就没有办法用四两拨千斤的方法重新扭转这一边倒的局面吗? 办法肯定是有的。我能想到的趋势是两个。第一个趋势是用更复杂的技术,但是提供更简单的使用方式。简单的HTTP代理,SOCKS明文代理早已阵亡。接下来的斗争需要更复杂的工具。无论是ShadowSocks还是GoAgent都在向这个方向发展。技术越复杂,意味着普通人要学习要配置的成本就越高。每个人按照文档,在自己的PC上配置ABC的方式已经不能满足下一阶段的斗争需要了。我们需要提升手里的武器,站在一个更高的平台上。 传统的配置方式的共同特点是终端配置。你需要在你的PC浏览器上,各种应用软件里,手机上,平板电脑上做各种各样的配置。这样的终端配置的方式在过去是很方便的。别人提供一个代理,你在浏览器里一设置就好用了。但是在连OpenVPN都被封了的今天,这种终端配置的方式就大大限制了我们的选择。缺点是多方面的: 翻墙的方式受到终端支持的限制。特别是手机和平板电脑,不ROOT不越狱的话,选择就非常有限了。 终端种类繁多,挂一漏万。提供翻墙的工具的人不可能有精力来测试支持所有种类的终端。 如果家里有多个笔记本,还有手机等便携设备使用起来就很不方便。躺在床上要刷Twitter的时候,才发现手机的里的OpenVPN帐号已经被封了,新的那个只配置在了电脑里。 最主流的终端是Windows的PC机。但是在Windows上控制底层网络的运作非常不方便。给翻墙工具的作者设置了一个更高的门槛。 终端一般处于家庭路由器的后面。大多数直穿的穿墙方式都很难在这种网络环境下工作正常。 把翻墙工具做到路由器上就可以达到实现更复杂的翻墙技术,同时提供极其简单的使用体验。但是路由器的缺陷也是非常明显的。传统的路由器刷OpenWRT等可以定制的第三方系统有如下缺点: 便携不方便,路由器大部分没有电池,也不方便放在包里 相比在电脑上装一个软件试试好不好使,额外购买专门用来翻墙的路由器未免试用成本也太高了。如果没有人愿意尝试,更加不会有人来使用。 路由器安装软件不方便。笔者花了大量时间研究OpenWRT的USB刷机方式。虽然技术上有所突破,但是仍然感觉不适合普通人操作。 硬件受限。路由器的CPU都很慢。内存非常小。如果不是用C来编写应用,速度会非常慢。极大地抬高了开发成本。流行的翻墙工具GoAgent和shadowsocks的最初版本都是Python的。 有没有既可以获得路由器的好处,又克服了其缺点的解决方案呢?答案是肯定的。手机做为路由器就可以。目前fqrouter已经推出了Android版本,把手机变成了翻墙路由器。一方面,完成了平台的跃升,从终端翻墙变为了路由器翻墙。另外一方面,因为手机的便携,无需额外设备,安装软件简单,而且硬件强大完胜了常规意义上的路由器。使用手机做为路由器之后: 翻墙方式不再受到终端的限制。只要能接入路由器,就可以翻墙。 提供翻墙工具的人不需要测试所有的终端是不是支持。 多种终端可以同时共享一个路由器。无需重复配置。 路由器基于的Linux操作系统给翻墙工具的作者提供了极大的便利,新的工具可以更容易地被实现出来。 提供了一定的直穿的可能性。 iPhone, Windows Phone等设备不需要越狱,也可以通过翻墙路由器享受到shadowsocks等更高级的翻墙工具。 运行在Android上的翻墙工具fqrouter已经在Google Play上架了:https://play.google.com/store/apps/details?id=fq.router2 这是趋势一,平台的提升。第二个趋势是去中心化。我相信未来的趋势肯定不是什么境外敌对势力出于不可告人的目的给我们提供翻墙方式。未来的趋势是各自为战的,公开贩卖的各种翻墙服务会被封杀殆尽。我们要确保的底线是,做为个人,在拥有一台国外服务器,然后有一定技术能力的情况下,能够稳定无忧的翻墙。 在我们能够保证独善其身的前提下,才有可能怎么去达则兼善天下。才有可能以各自为圆心,把服务以P2P的方式扩散给亲朋好友使用。即便是能够有这样的互助网络建立起来,也肯定是一种去中心化的,开源的实现。只有遍地开花,才能避免被连根拔起。 前面谈到路由器刷第三方固件对于个人来说不是理想的翻墙路由器的实现方式。但是固定部署的路由器却是理想的P2P节点。P2P的一个简化版本是APN,也就是把代理放在国内,然后iPhone等可以简单地使用HTTP未加密方式使用代理。这种部署方式就比较适合刷在固定部署的路由器上。个人可以在自己家里的路由器上部署了代理,然后无论走到哪里都可以通过家里的路由器代理上网。使用路由器固定部署P2P节点的好处是P2P网络可以有更多的稳定接入点。这些刷了OpenWRT等第三方系统安装了P2P节点程序的路由器不会是普通人玩得转的。其意义更多是有技术实力的志愿者,提供自己的家庭路由器,以换得其他方面的方便。 实现一个P2P的网络的难点有三个: 代理服务器的容量有限。传统的代理服务器是无法负载很多人同时用1080P看youtube的,因为带宽不够。不要说免费的P2P网络,就是很多付费的代理服务,也无法满足容量要求。 中心服务器被封IP。TOR做为著名的P2P网络,其主要问题就是要接入其网络需要连接一个中心服务器。这些服务器的IP数量是有限的。GFW会尽一切力量找到这些IP,然后封IP。 P2P意味着索取与奉献。人人都想这索取,为什么会有人奉献?如果没有一个等价交换做为社区的基础,这个社区是无法长久的。 目前仍然没有理想的P2P翻墙方式出现。但是这是fqrouter的努力方向。 中心化的翻墙方式,特别是商业贩卖的翻墙服务注定难逃被捕杀殆尽的命运。具有光明未来的翻墙方式必然是去中心化的,松散的,自组织的P2P的。 0x01 全面学习GFW GFW会是一个长期的存在。要学会与之共存,必须先了解GFW是什么。做为局外人,学习GFW有六个角度。渐进的来看分别是: 首先我们学习到的是WHAT和WHEN。比如说,你经常听到人的议论是“昨天”,“github”被封了。其中的昨天就是WHEN,github就是WHAT。这是学习GFW的最天然,最朴素的角度。在这个方面做得非常极致的是一个叫做greatfire的网站。这个网站长期监控成千上万个网站和关键词。通过长期监控,不但可以掌握WHAT被封锁了,还可以知道WHEN被封的,WHEN被解封的。 接下来的角度是WHO。比如说,“方校长”这个人名就经常和GFW同时出现。但是如果仅仅是掌握一个两个人名,然后像某位同志那样天天在twitter上骂一遍那样,除了把这个人名骂成名人之外,没有什么特别的积极意义。我更看好这篇文章“通过分析论文挖掘防火长城(GFW)的技术人员”的思路。通过网络上的公开信息,掌握GFW的哪些方面与哪些人有关系,这些合作者之间又有什么联系。除了大家猜测的将来可以鞭尸之外,对现在也是有积极的意义的。比如关注这些人的研究动态和思想发展,可以猜测GFW的下一步发展方向。比如阅读过去发表的论文,可以了解GFW的技术演进历史,可以从历史中找到一些技术或者管理体制上的缺陷。 再接下来就是WHY了。github被封之后就常听人说,github这样的技术网站你封它干啥?是什么原因促成了一个网站的被封与解封的?我们做为局外人,真正的原因当然是无从得知的。但是我们可以猜测。基于猜测,可以把不同网站被封,与网络上的舆情时间做关联和分类。我们知道,方校长对于网路舆情监控是有很深入研究的。有一篇论文(Whiskey, Weed, and Wukan on the World Wide Web: On Measuring Censors’ Resources and Motivations)专门讨论监管者的动机的。观测触发被封的事件与实际被封之间的时间关系,也可以推测出一些有趣的现象。比如有人报告,OpenVPN触发的封端口和封IP这样的事情一般都发生在中国的白天。也就是说,GFW背后不光是机器,有一些组件是血肉构成的。 剩下的两个角度就是对如何翻墙穿墙最有价值的两个角度了:HOW和WHERE。HOW是非常好理解的,就是在服务器和客户端两边抓包,看看一个正常的网络通信,GFW做为中间人,分别给两端在什么时候发了什么包或者过滤掉了什么包。而这些GFW做的动作,无论是过滤还是发伪包又是如何干扰客户端与服务器之间的正常通信的。WHERE是在知道了HOW之后的进一步发展,不但要了解客户端与服务器这两端的情况,更要了解GFW是挂在两端中间的哪一级路由器上做干扰的。在了解到GFW的关联路由器的IP的基础上,可以根据不同的干扰行为,不同的运营商归属做分组,进一步了解GFW的整体部署情况。 整体上来说,对GFW的研究都是从WHAT和WHEN开始,让偏人文的就去研究WHO和WHY,像我们这样偏工程的就会去研究HOW和WHERE。以上就是全面了解GFW的主体脉络。接下来,我们就要以HOW和WHERE这两个角度去看一看GFW的原理。 0x03 GFW的原理 要与GFW对抗不能仅仅停留在什么不能访问了,什么可以访问之类的表面现象上。知道youtube不能访问了,对于翻墙来说并无帮助。但是知道GFW是如何让我们不能访问youtube的,则对下一步的翻墙方案的选择和实施具有重大意义。所以在讨论如何翻之前,先要深入原理了解GFW是如何封的。 总的来说,GFW是一个分布式的入侵检测系统,并不是一个严格意义上的防火墙。不是说每个出入国境的IP包都需要先经过GFW的首可。做为一个入侵检测系统,GFW把你每一次访问facebook都看做一次入侵,然后在检测到入侵之后采取应对措施,也就是常见的连接重置。整个过程一般话来说就是: 检测有两种方式。一种是人工检测,一种是机器检测。你去国新办网站举报,就是参与了人工检测。在人工检测到不和谐的网站之后,就会采取一些应对方式来防止国内的网民访问该网站。对于这类的封锁,规避检测就不是技术问题了,只能从GFW采取的应对方式上采取反制措施。另外一类检测是机器检测,其检测过程又可以再进一步细分: 重建 重建是指GFW从网络上监听过往的IP包,然后分析其中的TCP协议,最后重建出一个完整的字节流。分析是在这个重建的字节流上分析具体的应用协议,比如HTTP协议。然后在应用协议中查找是不是有不和谐的内容,然后决定采用何种应对方式。 所以,GFW机器检测的第一步就是重建出一个字节流。那么GFW是如何拿到原始的IP包的呢?真正的GFW部署方式,外人根本无从得知。据猜测,GFW是部署在国家的出口路由器的旁路上,用“分光”的方式把IP包复制一份到另外一根光纤上,从而拿到所有进出国境的IP包。下图引在gfwrev.blogspot.com: 但是Google在北京有自己的机房。所以聪明的网友就使用Google的北京机房提供的GAE服务,用Goagent软件达到高速翻墙的目的。但是有网友证实(https://twitter.com/chengr28/status/260970749190365184),即便是北京的机房也会被骨干网丢包。事实上Google在北京的谷翔机房有一个独立的AS(BGP的概念)。这个AS与谷歌总部有一条IPV6的直连线路,所以通过这个机房可以用IPV6不受墙的限制出去。但是这个AS无论是连接国内还是国外都是要经过GFW的。所以机房在北京也不能保证国内访问不被墙。GFW通过配置骨干网的BGP路由规则,是可以让国内的机房也经过它的。另外一个例子是当我们访问被封的网站触发连接重置的时候,往往收到两个RST包,但是TTL不同。还有一个例子是对于被封的IP,访问的IP包还没有到达国际出口就已经被丢弃。所以GFW应该在其他地方也部署有设备,据推测是在省级骨干路由的位置。 对于GFW到底在哪这个话题,最近又有国外友人表达了兴趣(https://github.com/mothran/mongol)。笔者在前人的基础上写了一个更完备的探测工具https://github.com/fqrouter/qiang。其原理是基于一个IP协议的特性叫TTL。TTL是Time to Live的简写。IP包在没经过一次路由的时候,路由器都会把IP包的TTL减去1。如果TTL到零了,路由器就不会再把IP包发给下一级路由。然后我们知道GFW会在监听到不和谐的IP包之后发回RST包来重置TCP连接。那么通过设置不同的TTL就可以知道从你的电脑,到GFW之间经过了几个路由器。比如说TTL设置成9不触发RST,但是10就触发RST,那么到GFW就是经过了10个路由器。另外一个IP协议的特性是当TTL耗尽的时候,路由器应该发回一个TTL EXCEEDED的ICMP包,并把自己的IP地址设置成SRC(来源)。结合这两点,就可以探测出IP包是到了IP地址为什么的路由器之后才被GFW检测到。有了IP地址之后,再结合IP地址地理位置的数据库就可以知道其地理位置。据说,得出的位置大概是这样的: 但是这里检测出来的IP到底是GFW的还是骨干路由器的?更有可能的是骨干路由器的IP。GFW做为一个设备用“分光”的方式挂在主干路由器旁边做入侵检测。无论如何,GFW通过某种神奇的方式,可以拿到你和国外服务器之间来往的所有的IP包,这点是肯定的。更严谨的理论研究有:Internet Censorship in China: Where Does the Filtering Occur? GFW在拥有了这些IP包之后,要做一个艰难的决定,那就是到底要不要让你和服务器之间的通信继续下去。GFW不能太过于激进,毕竟全国性的不能访问国外的网站是违反GFW自身存在价值的。GFW就需要在理解了IP包背后代表的含义之后,再来决定是不是可以安全的阻断你和国外服务器之间的连接。这种理解就要建立了前面说的“重建”这一步的基础上。大概用图表达一下重建是在怎么一回事: 重建需要做的事情就是把IP包1中的GET /inde和IP包2中的x.html H和IP包3中的TTP/1.1拼到一起变成GET /index.html HTTP/1.1。拼出来的数据可能是纯文本的,也可能是二进制加密的协议内容。具体是什么是你和服务器之间约定好的。GFW做为窃听者需要猜测才知道你们俩之间的交谈内容。对于HTTP协议就非常容易猜测了,因为HTTP的协议是标准化的,而且是未加密的。所以GFW可以在重建之后很容易的知道,你使用了HTTP协议,访问的是什么网站。 重建这样的字节流有一个难点是如何处理巨大的流量?这个问题在这篇博客(http://gfwrev.blogspot.tw/2010/02/gfw.html)中已经讲得很明白了。其原理与网站的负载均衡器一样。对于给定的来源和目标,使用一个HASH算法取得一个节点值,然后把所有符合这个来源和目标的流量都往这个节点发。所以在一个节点上就可以重建一个TCP会话的单向字节流。 最后为了讨论完整,再提两点。虽然GFW的重建发生在旁路上是基于分光来实现的,但并不代表整个GFW的所有设备都在旁路。后面会提到有一些GFW应对形式必须是把一些GFW的设备部署在了主干路由上,比如对Google的HTTPS的间歇性丢包,也就是GFW是要参与部分IP的路由工作的。另外一点是,重建是单向的TCP流,也就是GFW根本不在乎双向的对话内容,它只根据监听到的一个方向的内容然后做判断。但是监听本身是双向的,也就是无论是从国内发到国外,还是从国外发到国内,都会被重建然后加以分析。所以一个TCP连接对于GFW来说会被重建成两个字节流。具体的证据会在后面谈如何直穿GFW中详细讲解。 分析 分析是GFW在重建出字节流之后要做的第二步。对于重建来说,GFW主要处理IP协议,以及上一层的TCP和UDP协议就可以了。但是对于分析来说,GFW就需要理解各种各样的应用层的稀奇古怪的协议了。甚至,我们也可以自己发明新的协议。 总的来说,GFW做协议分析有两个相似,但是不同的目的。第一个目的是防止不和谐内容的传播,比如说使用Google搜索了“不该”搜索的关键字。第二个目的是防止使用翻墙工具绕过GFW的审查。下面列举一些已知的GFW能够处理的协议。 对于GFW具体是怎么达到目的一,也就是防止不和谐内容传播的就牵涉到对HTTP协议和DNS协议等几个协议的明文审查。大体的做法是这样的。 像HTTP这样的协议会有非常明显的特征供检测,所以第一步就没什么好说的了。当GFW发现了包是HTTP的包之后就会按照HTTP的协议规则拆包。这个拆包过程是GFW按照它对于协议的理解来做的。比如说,从HTTP的GET请求中取得请求的URL。然后GFW拿到这个请求的URL去与关键字做匹配,比如查找Twitter是否在请求的URL中。为什么有拆包这个过程?首先,拆包之后可以更精确的打击,防止误杀。另外可能预先做拆包,比全文匹配更节省资源。其次,xiaoxia和liruqi同学的jjproxy的核心就是基于GFW的一个HTTP拆包的漏洞,当然这个bug已经被修复了。其原理就是GFW在拆解HTTP包的时候没有处理有多出来的\r\n这样的情况,但是你访问的google.com却可以正确处理额外的\r\n的情况。从这个例子中可以证明,GFW还是先去理解协议,然后才做关键字匹配的。关键字匹配应该就是使用了一些高效的正则表达式算法,没有什么可以讨论的。 HTTP代理和SOCKS代理,这两种明文的代理都可以被GFW识别。之前笔者认为GFW可以在识别到HTTP代理和SOCKS代理之后,再拆解其内部的HTTP协议的正文。也就是做两次拆包。但是分析发现,HTTP代理的关键字列表和HTTP的关键字列表是不一样的,所以笔者现在认为HTTP代理协议和SOCKS代理协议是当作单独的协议来处理的,并不是拆出载荷的HTTP请求再进行分析的。 目前已知的GFW会做的协议分析如下: DNS 查询 GFW可以分析53端口的UDP协议的DNS查询。如果查询的域名匹配关键字则会被DNS劫持。可以肯定的是,这个匹配过程使用的是类似正则的机制,而不仅仅是一个黑名单,因为子域名实在太多了。证据是:2012年11月9日下午3点半开始,防火长城对Google的泛域名 .google.com 进行了大面积的污染,所有以 .google.com 结尾的域名均遭到污染而解析错误不能正常访问,其中甚至包括不存在的域名(来源http://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D%E5%8A%AB%E6%8C%81)…

  • 技术文章

    分布式存储

    分布存储方案 一.BitCask 这篇文章很好(http://blog.csdn.net/qq910894904/article/details/37756377) 1.存储的介质与读写 谈存储,那么理解存储的介质的特性显然很重要,书中谈了很多硬件结构,但最重要的结论,都浓缩在存储介质对比这张表中了。 磁盘介质对比 类别 每秒读写(IOPS)次数 每GB价格(元) 随机读取 随机写入 内存 千万级 150 友好 友好 SSD盘 35000 20 友好 写入放大问题 SAS磁盘 180 3 磁盘寻道 磁盘寻道 SATA磁盘 90 0.5 磁盘寻道 磁盘寻道 从表中可以看出,内存的随机读写能力最强,远超SSD盘和磁盘。但是我们都知道,内存无法持久化。现在许多公司在性能要求高的地方都使用了SSD盘,相对SAS和SATA磁盘,随机读取速度有了很大的提升。但是对于随机写入,存在写入放大问题。 写入放大问题与SSD盘的特性有关,SSD盘不能随机写入,只能整块整块的写入。最简单的例子,比如要写入一个4KB的数据,最坏的情况就是,一个块里已经没有干净空间了,但是有无效数据可以擦除,所以主控就把所有的数据读出来,擦除块,再加上这个4KB新数据写回去,这个操作带来的写入放大就是: 实际写4K的数据,造成了整个块(512KB)的写入操作,那就是128倍放大。此外,SSD盘的寿命也有写入次数相关。 如果使用SSD来作为存储引擎的存储介质,最好从设计上减少或避免随机写入,使用顺序写入取而代之。   2.Bitcask存储模型介绍 存储系统的基本功能包括:增、删、读、改。其中读取操作有分为顺序读取和随机读取。   总体来说,大部分应用使用读的功能最多,解决读的性能是存储系统的重要命题。一般来说。快速查找的思想基本源自二分查找法和哈希查询。例如关系数据库中常用的B+存储模型就是使用二分查找的思想,当然,实际实现比二分查找复杂很多。B+存储模型支持顺序扫描。另外一类则是基于哈希思想的键值模型,这类模型不支持顺序扫描,仅支持随机读取。 今天要讨论的Bitcask模型是一种日志型键值模型。所谓日志型,是指它不直接支持随机写入,而是像日志一样支持追加操作。Bitcask模型将随机写入转化为顺序写入。有两个好处: 提高随机写入的吞吐量,因为写操作不需要查找,直接追加即可 如果使用SSD作为存储介质,能够更好的利用新硬件的特性 Bitcask中存在3种文件,包括数据文件,索引文件和线索文件(hint file,姑且就叫线索文件吧)。数据文件存储于磁盘上,包含了原始的数据的键值信息;索引文件存在于内存,用于记录记录的位置信息,启动Bitcask时,它会将所有数据的位置信息全部读入一个内存中的哈希表,也就是索引文件;线索文件(hint file)并不是Bitcask的必需文件,它的存在是为了提供启动时构建索引文件的速度。 2.1 日志型的数据文件 Bitcask的数据文件组织如下图:任意时刻,系统中只有一个数据文件支持写入,称为active data file。其余的数据文件都是只读文件,称为older data file。       文件中的数据结构非常简单,是一条一条的数据写入操作,每一条数据的结构如下:   上面数据项分别为:后面几项的crc校验值,时间戳,key,value,key的大小,value的大小。 数据文件中就是连续一条条上面格式的数据,如下图:   2.2 索引哈希表 索引哈希表记录了全部记录的主键和位置信息,索引哈希表的值包含了:记录文件的编号,value长度,value的在文件中的位置和时间戳。Bitcask的总体数据结构如下图:     2.3 线索文件(hint file) Bitcask启动时要重建索引哈希表,如果数据量特别大,则会导致启动很慢。而线索文件(hint file)则是用来加速启动时重建哈希表的速度。线索文件(hint file)的记录与数据文件的格式基本相同,唯一不同的是数据文件记录数据的值,而线索文件(hint file)则是记录数据的位置。   这样在启动的时候就可以不用读数据文件,而是读取线索文件(hint file),一行行重建即可,大大加快了哈希表的重建速度。 3. Bitcask功能介绍 上节提到,存储系统的基本功能包括:增、删、读、改。那么Bitcask中如何实现的呢? 如何增加记录? 用户写入的记录直接追加到活动文件,因此活动文件会越来越大,当到达一定大小时,Bitcask会冻结活动文件,新建一个活动文件用于写入,而之前的活动文件则变为了older data file。写入记录的同时还要在索引哈希表中添加索引记录。 如何删除记录? Bitcask不直接删除记录,而是新增一条相同key的记录,把value值设置一个删除的标记。原有记录依然存在于数据文件中,然后更新索引哈希表。 如何修改记录? Bitcask不支持随机写入。因为对于存储系统的基本功能中的增和改,实际上都是一样的,都是直接写入活动数据文件。同时修改索引哈希表中对应记录的值。(这个时候,实际上数据文件中同一个key值对应了多条记录,根据时间戳记录来判断,以最新的数据为准。) 如何读取记录? 读取时,首先从索引哈希表中定位到记录在磁盘中位置,然后通过IO读取出对应的记录。 合并(Marge)操作 Bitcask这种只增不减地不断写入,必然会是数据文件不断的膨胀。而其中有许多是被标记删除和修改后留下的无用记录。合并操作就是为了剔除这部分数据,减小数据文件大小。 merge操作,通过定期将所有older data file中的数据扫描一遍并生成新的data file(没有包括active data file 是因为它还在不停写入)。如果同一个Key有多条记录,则只保留最新的一条。从而去掉数据文件中的冗余数据。而且进行合并(Marge)操作时,还可以顺带生成线索文件(hint file)。合并(Marge)操作通常会在数据库较闲的时候进行,比如凌晨一两点等。 二、关系连数据结构的设计 1,key-list 2,key-table 3,redis 有序集 4, netMq,httpsqs,activeMq 各人使用过redis有序集,使用起来方便,但是造价高是个问题,在排名设计文章时,详细说明过,其他数据结构详细补充   三、ZooKeeper 名字服务做分布式服务器群组 记录

  • 技术文章

    php json_decode返回NULL 特别是使用file_get_contents

    今天遇到了json_decode无法解析json的情况,很是头痛,查来查去原来 编码服务器可以正常解析,但是解码服务无法解析,等到把整个json字串 放到页面上查看元素突然发现json字串头部多了字串,考虑是BOM问题,然后在编码服务打印字串长度6617到了解码服务打印长度成了6620 还要多一点,因些断定是BOM引起的json_decode无法解析。 if(preg_match(‘/^\xEF\xBB\xBF/’,$receive_part)) { $receive_part= substr($receive_part,3); } 去掉BOM 解析正常,原来形成应该是编码服务使用header(‘Content-Type:application/json; charset=utf-8’);转UTF-8时形成的BOM 。

  • 技术文章

    测试hhvm 与phpfpm性能

    总是说hhvm性能很高亲测试数据如下:   从cpu\mem\io 三个方面 服务器: cpu 8核 3.30GHz mem 16G disk 500G 一、time 测试: 网上的例子CUP test.php <?php function fib($n) {     if ($n <= 2)         return 1;     else         return fib($n-1) + fib($n-2); } $n = 36; printf("fib(%d) = %d\n", $n, fib($n, 2)); ?> root#time hhvm test.php real 0m0.407s user 0m0.336s sys 0m0.068s   root #time php test.php real 0m1.046s user 0m0.952s sys 0m0.092s   内存及IO 也相差类似   二、ab测试 ab -c1000 -n500 http://xxx.xxx.com/test.php 分别在hhvm php-fpm下 php-fpm:数据: Server Port: 80 Document Path: /test.php Document Length: 0 bytes Concurrency Level: 500 Time taken for tests: 40.838 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 159000 bytes HTML transferred: 0 bytes Requests per second: 24.49 [#/sec] (mean) Time per request: 20419.000 [ms] (mean) Time per request: 40.838…

  • 技术文章

    mysql 配置优化

    感觉这篇文章比较靠谱 收藏,经常会说优化 ,基本从三个方面,配置优化、设计优化、使用优化。这个文章从配置 方面来说不错 转自:http://www.itokit.com/2013/0412/74892.html 定运行了一段时间后运行,根据服务器的“状态”进行优化。 mysql> show global status; 可以列出MySQL服务器运行各种状态值,我个人较喜欢的用法是show status like ‘查询值%’; 一、慢查询 mysql> show variables like ‘%slow%’; +——————+——-+ | Variable_name | Value | +——————+——-+ | log_slow_queries | ON | | slow_launch_time | 2     | +——————+——-+ mysql> show global status like ‘%slow%’; +———————+——-+ | Variable_name    | Value | +———————+——-+ | Slow_launch_threads | 0     | | Slow_queries        | 4148 | +———————+——-+ 打开慢查询日志可能会对系统性能有一点点影响,如果你的MySQL是主-从结构,可以考虑打开其中一台从服务器的慢查询日志,这样既可以监控慢查询,对系统性能影响又小,另mysql有自带的命令mysqldumpslow可进行查询,例下列命令可以查出访问次数最多的20个sql语句mysqldumpslow -s c -t 20 host-slow.log 二、连接数 经常会遇见”MySQL: ERROR 1040: Too manyconnections”的情况,一种是访问量确实很高,MySQL服务器抗不住,这个时候就要考虑增加从服务器分散读压力,另外一种情况是MySQL配置文件中max_connections值过小: mysql> show variables like ‘max_connections’; +—————–+——-+ | Variable_name | Value | +—————–+——-+ | max_connections | 256 | +—————–+——-+ 这台MySQL服务器最大连接数是256,然后查询一下服务器响应的最大连接数: mysql> show global status like ‘Max_used_connections’; +———————-+——-+ | Variable_name        | Value | +———————-+——-+ | Max_used_connections | 245…

Free Web Hosting