SegmentFault 靠谱崔小拽最新的文章
2019-05-12T21:02:46+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
字符编码那些事儿
https://segmentfault.com/a/1190000019154637
2019-05-12T21:02:46+08:00
2019-05-12T21:02:46+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
2
<blockquote>身为一名要冲出国门的国际化码农?,字符编码是必备课题。小拽本文依次介绍下<strong>字节,ASCII,GB2312,GBK,GB18030,UNICODE,UTF8,UTF16,ICU</strong> 等到底是什么鬼?最后理论结合实际,研究下网站中经常出现的“<strong>锟斤拷,��,烫烫烫烫烫,屯屯屯屯屯屯</strong>”是什么神兵利器O(∩_∩)O?</blockquote>
<h2>一、二进制和字节</h2>
<p>大概一百多年前,贝尔实验室制造了世界上的第一个晶体管,晶体管具有开合(0和1)的状态,这种01状态的变化被称为<code>二进制位(bit)</code>。</p>
<p>过了几年,英特尔把八个可以开合的晶体管组合,做为一个基本的记录单元,这个单元被称作<code>字节(byte)</code>,所以<strong>一个byte由8个bit构成,可以表达256种状态</strong>。</p>
<p>又过几年,祖师爷冯诺依曼设计了一台可以存储和处理(冯诺依曼体系)字节变动的机器<strong>ENIAC</strong>,后来这个机器被称作<code>计算机</code>。</p>
<h2>二、标准ASCII</h2>
<p>计算机运行是二进制,如何用二进制位来标识人类语言,就需要和计算机有一套约定关系。例如约定,0100 1111代表O,0100 1011代表K,那么存储为01001111 01001011的两个字节就代表OK,这套约定关系被称作<code>字符编码</code>。</p>
<p>冯祖师爷是的德国人,二战去了美国设计了第一台计算机。起初,只有美国人能用计算机,山姆大叔就根据英语习惯设计了一套映射约定</p>
<ul>
<li>0-31 标识控制字符,例如换行[LF],删除[DEL],确认[ACK]等</li>
<li>32-47 标识符号例如!@#$等</li>
<li>48-57 标识0-9是个阿拉伯数字</li>
<li>65-122 标识大小写字母</li>
</ul>
<p>大家都按着这个约定来,交流表达起来没啥问题,呵呵,都挺好。于是这个方案就一致通过了,山姆大叔也给这个约定起了个名字<code>ASCII</code>编码(<strong>American Standard Code for Information Interchange,美国信息互换标准代码</strong>)。当时世界上所有的计算机都用同样的ASCII方案来保存英文字符。</p>
<blockquote>计算机一个标准字节8bit本身可以标识256个符号,但<strong>标准的ASCII的最高位去掉用做奇偶校验,用剩余7位标识128个符号</strong>,如下图</blockquote>
<p><a href="https://link.segmentfault.com/?enc=Rc0zlciCJGNle8nhYjot0g%3D%3D.ZZgc%2FvPTgqRvlhmT3OQtgdi0tSdI42erbNbzx4FGWUkGUlhsR3YS6n3FQaPj49BjkgIMO9b2bDHRRIiCLm9E6Q%3D%3D" rel="nofollow"><img src="/img/remote/1460000019154640" alt="ASCII" title="ASCII"></a></p>
<h2>三、ASCII 扩展字符集</h2>
<p>随着计算机的发展,欧洲人开始逐步接触计算机了。</p>
<p><strong>英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的</strong>。比如在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,<strong>利用字节中闲置的最高位编入新的符号</strong>。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,<strong>可以表示最多256个符号</strong>。</p>
<p>IBM牵头扩充了ASCII编码128-256位的标识字符,主要是一些欧洲的常用符号,这部分扩展映射被称为<code>ASCII扩展字符集</code></p>
<h2>四、GB2312</h2>
<p>雄关漫道真如铁,而今迈步从头越,美帝国主义万万没有想到,二战后,大量第三世界的人民站起来了,逐步开始使用计算机。但<strong>问题是256个字符已经没啥可利用的字节状态来表示汉字了,更何况中华文明有6000多个常用汉字需要保存</strong>呢。</p>
<p>但是这难不倒智慧的中国人民,面对帝国主义的压迫,我们毫不客气的做了两件事情,并在1980年发表了这个声明</p>
<ul>
<li>互相尊重:尊重标准ASCII 规范中0-127位表示的标准字符。</li>
<li>平等互利:<strong>ASCII的128-256位,我们用来标识中文,由于中文太多,我们要使用两个字节来表示一个中文^_^</strong>
</li>
</ul>
<p>中国人民觉的这个声明还不错,毕竟当时计算机的使用范围也不大,基本满足需求,于是就把这种汉字方案叫做 <code>GB2312编码</code>。<strong>GB2312 是对 ASCII 的中文扩展</strong>。</p>
<pre><code>非专业人士可以忽略: GB2312如何组合,能表示多少个?
GB2312中用两个字节来标识一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,简单计算
0xA1:10*16 + 1 = 161
0xF7:15*16 + 7 = 247 => 247-161 = 86
0xFE:15*16 + 14= 254 => 254-161 = 93
因此GB2312可以标识约86*93=7998 个汉字
实时上 GB2312 标准共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个。
同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。
几乎覆盖了大陆常用的99.75%汉字</code></pre>
<p>这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的<code>全角字符</code>,而原来在127号以下的那些就叫<code>半角字符</code>了。</p>
<h2>五、GBK</h2>
<p>满足了基础和常用的汉字需求后,但<strong>依然会有很多人的生僻字名字打不出来,屌丝还好,但是一旦牵涉伟人名字打不出来那就坑爹了</strong>!改改改,抓紧改!</p>
<p>GB2312的编码,使用两个字符,每个都<strong>只用了后128位,不合理呀,干脆我们把低字节127号之后的内码我们也用了,不浪费</strong>。</p>
<p>说干就干,于是扩展之后的编码方案被称为<code>GBK</code>标准(不知道K是不是扩展的缩写K^_^),<strong>GBK包括了GB2312的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号</strong>。</p>
<pre><code>非专业人士可以忽略: GBK如何组合,能表示多少个?
第一个字节的值从 0x81 到 0xFE保持不变,第二个字节的值扩展从 0x40 到 0xFE
0xFE-0x81:254 - 8*16+1 =125
0xFE-0x40:254 - 4*16 =190
因此,GBK约标识了 125*190 = 23750
GBK 共收入 21886 个汉字和图形符号,包括:GB2312 中的全部汉字、非汉字符号;BIG5中的全部汉字;ISO10646 相应的国家标准GB13000 中的其它 CJK 汉字
以上合计 20902 个汉字; 其它汉字、部首、符号,共计 984 个。</code></pre>
<h2>六、GB18030</h2>
<p>中华民族大团结,<strong>后来少数民族也要用电脑了,于是我们需要再次扩展,又加了几千个新的少数民族的字</strong>,GBK扩成了<code>GB18030</code>。从此之后,中华民族的文化就可以完美的在计算机时代中传承了。</p>
<pre><code>非专业人士忽略:
这一系列汉字编码的标准通为 `DBCS`(Double Byte Charecter Set 双字节字符集)。
在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,
必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了</code></pre>
<p>从ASCII到GB2312,再到GBK,而后GB18030,中华民族终于完成了全量中文字符的编码,简单总结下</p>
<ul>
<li>第一阶段:中国人民通过对 ASCII 编码的中文扩充改造,产生了GB2312 编码,可以表示6000多个常用汉字。</li>
<li>第二阶段:汉字实在是太多了,包括繁体和各种字符,于是产生了 GBK 编码,它包括了 GB2312 中的编码,同时扩充了很多。</li>
<li>第三阶段:中国是个多民族国家,各个民族几乎都有自己独立的语言系统,为了表示那些字符,继续把 GBK 编码扩充为 GB18030 编码,完成全量中文字符编码。</li>
</ul>
<h2>六、UNICODE</h2>
<p>之后的世界,百花齐放,百家争鸣,<strong>各国纷纷制造自己的编码规范,同时互相不去理解对方规范,即使同一种语言也区别巨大</strong>,例如台湾地区中文采用<code>big5</code>的繁体编码,名字也牛逼大了,叫<code>大五码</code>。</p>
<p>各自为政引来了大量的问题,<strong>各个语言互不兼容</strong>,此时,一堆大佬看不下去了,勇敢的站了出来,着手解决这个问题,他们成立了一个类似于TC的组织,叫做<code>ISO</code>(International Organization for Standardization 国际标准化组织)。</p>
<p>他们采用的方法很简单:<strong>废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码</strong>!他们打算叫它"Universal Multiple-Octet Coded Character Set",简称 <code>UCS</code>, 俗称 "<code>UNICODE</code>"。这就是<strong>Unicode,就像它的名字都表示的,这是一种所有符号的编码</strong>!</p>
<p>UNICODE统一了各国,成为了实事上的大一统的编码规范。这种编码非常大,大到可以容纳世界上任何一个文字和标志。所以只要电脑上有 UNICODE 这种编码系统,无论是全球哪种文字,只需要保存文件的时候,保存成 UNICODE 编码就可以被其他电脑正常解释。</p>
<pre><code>非专业人士忽略:unicode 编码
unicode开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。
于是ISO就直接规定:
1:必须用两个字节,也就是16位来统一表示所有的字符
2:对于ASCII里的那些“半角”字符,unicode包持其原编码不变,只是将其长度由原来的8位扩展为16位,
3:其他文化和语言的字符则全部重新统一编码。
由于”半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。</code></pre>
<h2>七、UTF,UTF8,UTF16</h2>
<p>UNICODE很好的解决了不同语言统一编码的问题,但同样也不完美,有两个主要问题,</p>
<ul>
<li>
<strong>字符识别</strong>:如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?</li>
<li>
<strong>存储浪费</strong>:我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么<strong>每个英文字母前都必然有二到三个字节是0,这对于存储空间来说是极大的浪费,文本文件的大小会因此大出二三倍</strong>。</li>
</ul>
<p>此时<code>UTF</code>(unicode transfer format)标准出现了,顾名思义,是<strong>UNICODE在传输和存储过程中的格式化标准</strong>,其中使用最广的是utf8和utf16</p>
<p><code>UTF-16</code>相对好理解,就是<strong>任何字符对应的数字都用两个字节来保存!我们通常对Unicode的理解就是把Unicode与UTF-16等同</strong>了。但是很显然如果都是英文字母这做有点浪费,明明用一个字节能表示一个字符为啥整两个啊。</p>
<p><code>UTF-8</code>最大的一个特点,就是它是一种<strong>变长的编码方式</strong>。它可以<strong>使用1~4个字节表示一个符号,根据不同的符号而变化字节长度</strong>,当字符在ASCII码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码做为它的一部分,注意的是unicode一个中文字符占2个字节,而UTF-8一个中文字符占3个字节)。从unicode到utf-8并不是直接的对应,而是要过一些算法和规则来转换。</p>
<pre><code>非专业人士直接忽略:unicode 如何转换成utf-8
以小拽的"拽"字为例
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
—————————————————————–
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
中文一般三个字节,前置标识位
举个栗子,中文:拽 unicode是25341
25341 十进制 unicode
0x62fd 十六进制
0110 0010 1111 1101 二进制
### 套上模板
0110 001011 111101 二进制 25341
1110xxxx 10xxxxxx 10xxxxxx 模板第三行
11100110 10001011 10111101 utf8 二进制
e 6 8 b b d utf8 十六进制【一切为了节省】
最终utf8拽对应的就是0xe68bdb</code></pre>
<p>UTF-8就是在互联网上使用最广的一种unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。</p>
<p>简单对比下GB系列,UTF8,UTF16</p>
<ul>
<li>UTF16:不推荐使用utf16,因为<strong>utf16最初能表示的字符数有6万多,看起来很多,但是实际上目前 Unicode5.0 收录的字符已经达到99024个字符,其实不够,有可能出现乱码</strong>。</li>
<li>UTF8:国际化编码首推UTF8,兼容全量,<strong>唯一的问题是空间略有浪费</strong>!</li>
<li>GB系列:<strong>GB系列都是双字节字符集,相对节省空间,如果只是国内使用GB18030完全可以兼容所有</strong>。</li>
</ul>
<h2>八、“锟斤拷��” 是什么</h2>
<p>通过上面介绍,可以看出来,各个编码规则是不一样的,目前互联网浏览器默认传输和解析方式是UTF8,但是部分老的网页采用GB系列,就会出现传输过程UTF8解析不了,展示GB错乱问题。</p>
<p>UNIDCODE规定:<strong>当unicode遇到解释失败的字时,会尝试用 「U+FFFD」 来代替,「U+FFFD」乃是 unicode 的一个占位符, 显示为 �</strong></p>
<p>而utf8识别为异常的传输字符后,传到页面转为双字节展示的GB会怎么样呢?</p>
<pre><code>➜ xiaozhuai ✗ python
Python 2.7.10 (default, Aug 17 2018, 19:45:58)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> s = (u'\uFFFD'.encode('utf8')*2)
>>> print(s.decode('gbk'))
锟斤拷</code></pre>
<p>也就产生了,传说中的"锟斤拷"神器!</p>
<blockquote>另外还有几个神器:"烫烫烫烫烫,屯屯屯屯屯屯"<br>“烫” 主要出没于 windows 平台下,ms 的 vc++ 编译器中, 当你在栈内开辟新内存时, vc 会使用 0xcc 来初始化填充, 很多个 0xcc 连起来就成了 烫烫烫烫烫 同理在堆内开辟新内存时, 会用 0xcd 填充,这便是 屯屯屯屯屯屯<br>不管是 “锟斤拷” 还是 “烫” 都要求最后是用GB码输出。</blockquote>
<h2>九、ICU</h2>
<p>在unicode的统治下,世界各国的基本编码不会出现乱码等异常。但当中华民族逐步强大,准备冲出中国统一世界的时候,<strong>发现各国的货币,时间,数字等表示灰常不统一</strong>,例如数字1234.5,英文表示1,234.5,葡语表示确是1.234,5,很是苦恼。</p>
<p>此时IBM站了出来,叫上google,apple等小伙伴,<strong>遵循"IBM公共许可证",开源了一套基于unicode的国际化组件</strong><code>ICU</code>(International Component for Unicode<br>)。<strong>根据各地的风俗和语言习惯,实现对数字、货币、时间、日期、和消息的格式化、解析,对字符串进行大小写转换、整理、搜索和排序等功能</strong>,ICU4C提供了强大的BIDI算法,对阿拉伯语等BIDI语言提供了完善的支持。</p>
<p><strong>ICU成为了目前国际化组件的实事标准,底层依赖UNICODE和CLDR</strong>,官方提供了C/C++和JAVA的SDK,ICU4C和ICU4J,同时,各个语言在此基础上开发了各个语言的版本,例如php的intl组件。</p>
<h2>十、实事标准</h2>
<p>字符编码的从产生,发展,到国际化一步一步走来,逐步形成了下列实事标准</p>
<ul>
<li>字符集:UNICODE</li>
<li>字节编码:UTF8</li>
<li>国际化:ICU</li>
</ul>
<blockquote>需要注意的是,mysql的utf8并不完全兼容标准的utf8编码,后续推出了utf8mb4完全兼容,所以推荐采用utf8mb4</blockquote>
<p>参考网站:</p>
<ul>
<li>BIDI:<a href="https://link.segmentfault.com/?enc=VcdSxUbY%2BuM5AOPwPFIe6g%3D%3D.iAf9Dtg0ndzCG6DYqko3i0QZLaZM%2FWqRSbeClxE8SVck2nAB8LswNNvxxCJWLkm49wtIaQRxXj%2FYYKMiDfk3PJg5A7cHkrbeCM17Sv5ow9w%3D" rel="nofollow">https://www.ibm.com/developer...</a>
</li>
<li>CLDR:<a href="https://link.segmentfault.com/?enc=huYjN8Is75%2FdKsbEv%2FLzYg%3D%3D.sfDAbyjpkReX17J40XXYvfQ5aSeZozmQCwQJBdpAPoo%3D" rel="nofollow">http://cldr.unicode.org/</a>
</li>
<li>ICU:<a href="https://link.segmentfault.com/?enc=IvazvxNoAjVpp6jPzi1FHQ%3D%3D.zSkaLx%2BmLMM8fePZR5AR3HgPcwnyACK85cCb%2FkNvTxdr%2FJUvE4tBcPlT8Q7mcGI6" rel="nofollow">http://site.icu-project.org/d...</a>
</li>
<li>UNICODE:<a href="https://link.segmentfault.com/?enc=SJGuMR7DyVGo570BpCDifA%3D%3D.G51WE1zTOjUH5jAYcHpwLx7Ze%2Fp%2F3xXQoKvgyOy3gjFHf2dqFxXVrN9dEn8vj8R%2B3JTk1oORNiF5Yp3DEV8f6fSjciDwm3Mz4HMYB0qeebE%3D" rel="nofollow">http://www.unicode.org/standa...</a>
</li>
</ul>
<p><img src="/img/bVbsw9R?w=384&h=372" alt="clipboard.png" title="clipboard.png"></p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=xnu94JMIkATsCMNqefhEsA%3D%3D.JeXUw1lXJOpI4jsP44FMnswdopZ11owqxUAWjBNshsmSDOIH2t7DfbfAZbPWRvznu5MiE56G9moEzqZ%2BFmAkng%3D%3D" rel="nofollow">字符编码那些事儿</a> | <a href="https://link.segmentfault.com/?enc=Ozy9RPGjO1htzbbtlrk6eQ%3D%3D.nYQoZYmXFIbr9c1I8Uz1TVe68GLuQMCEb6iRlMnfVpI%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
数据归档那些事儿
https://segmentfault.com/a/1190000018758509
2019-04-03T21:54:35+08:00
2019-04-03T21:54:35+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
3
<blockquote>在<a href="https://link.segmentfault.com/?enc=mtd%2FlL%2Fb49BHZL%2FVAleyMw%3D%3D.5Kt9mHvsTCiXfCSQdfnDQdcWymvaGI9eHRCBYqZHV%2Bq1w%2FMeDXJY2OuOYGS7pilsupaT7lT4zGJKbJOYF6BkHXCBHZp2KFFu3UtIRYzHAtMPWnFYtvF7lXkkX24XVC%2F4oCswuuQlbhcS4sazpzIiLn0li5%2B9zRRf1SaEJhpCnIo38zj1oY5I7r%2FORxHEjs1J3Nbjy8xuY4KKaYgTiMNiut82s%2BJV6HG8QCbpVWGtsRY%3D" rel="nofollow">热点账户问题和常用解决方案【中】</a>这篇文章中提到,解决热点读性能的一个非常通用方式是数据归档。本篇小拽总结下在操作数据归档过程中遇到的一些问题和经验!</blockquote>
<h2>一、数据归档</h2>
<p>所谓数据归档就是把部分<code>低频访问的历史数据</code>从线上库迁移到归档库的过程。在设计数据归档方案的时候通常需要思考三个问题</p>
<ul>
<li>归档前:如何进行存储选型</li>
<li>归档中:如何保证迁移准确</li>
<li>归档后:如何处理数据完整性破坏所引起的问题</li>
</ul>
<p>下面也着重从这三部分来聊聊</p>
<h2>二、存储选型</h2>
<p><strong>存储选型</strong>是归档前要做的最重要的一件事情,目前市面上的存储方式多如牛毛,如何选择能够支撑当前业务环境的存储选型,就非常重要!</p>
<h3>2.1 归档的数据特点</h3>
<p>既然是要选型数据归档的存储,首先来需要梳理下<strong>归档数据</strong>的特点</p>
<ul>
<li>读性能:归档数据对读性能没啥要求,能够读出来就可以</li>
<li>写性能:尽可能好的批量写入性能,能够批量1w+达标</li>
<li>压缩比:尽可能的节省空间,采用高压缩比的存储引擎</li>
<li>分布式:最好能够分布式,考虑到目前单片都40T了,非分也可</li>
<li>数据量级:上限尽可能高,考虑到实际情况,10TB+目前达标</li>
<li>一致性保证:归档是兜底,尽可能高的保证数据不会出现异常丢失</li>
</ul>
<h3>2.2 通用选型因素</h3>
<p>除了考虑归档数据的特点,还要考虑一些通用因素,例如</p>
<ul>
<li>公司是否运维支持:大厂这个因素很重要,如果运维支持背书,最好不过!</li>
<li>开源活跃程度:活跃度太低不能选</li>
<li>普遍使用场景:跳出存储给的通用场景的不能选</li>
</ul>
<h3>2.3 备选存储的特性</h3>
<p>也初步总结和梳理了下可能用到的集中存储的特性</p>
<p><a href="https://link.segmentfault.com/?enc=5HomUvO5hzwKXBRoXXdgRg%3D%3D.t07pZDXNOTZo%2BDUTQtNDWF9wAoYypekcqi%2Bab8MXGMrTHEDcu4WXmmviIeee302aKXerSxFFdDFrWzk9%2Ff9Nyw%3D%3D" rel="nofollow"><img src="/img/remote/1460000018758512?w=1694&h=1254" alt="archive_1" title="archive_1"></a></p>
<p>结合归档数据的特点和不同存储的优势,最终选用了</p>
<ul>
<li>rocksDB:作为存储归档数据引擎,性能和数据压缩比都不错,最主要是公司DBA愿意支持</li>
<li>ES:作为在线查询,公司运维支持</li>
<li>HIVE:作为财务数仓核心数据和全量数据中心,哈哈,为下一篇财务数据中台做铺垫^_^</li>
<li>fusion:作为幂等健破坏后的幂等健KV池</li>
</ul>
<h2>三、一致性保证</h2>
<p><code>归档过程存在会删除线上数据,是个非常高危的操作</code>,所以操作过程中和操作之后都需要特别注意数据一致性的保证。</p>
<p>对于操作过程的一致性保证相对简单,过程通常两步<br>step1 插入确认:查询线上库->插入归档库->查询归档库->确认插入<br>step2 删除确认:删除线上库->查询线上库->确认删除<br>注意:过程中尽可能的保证读取和写入的时间,删除会锁库,大批量读会抢网络和IO,防止对线上业务造成压力,尽可能调优批量数据,推荐条目在200-1000条一次,数据量在5M-100M一次</p>
<h2>四、归档后问题</h2>
<p><code>数据归档后,必然破坏了数据的完整性</code>,会造成下面几个问题,,需要提前考虑</p>
<h3>4.1 读数据穿透问题</h3>
<p>低频历史数据归档后,造成线上数据缺失,查询数据穿透和范围关系查询损失都会存在。因此,数据归档后,对于读操作有两种处理方式</p>
<ul>
<li>归档数据不读:最简单,但是对于某些场景可能确实不太合适。</li>
<li>读proxy兼容:通过读proxy,穿透性的选择各种存储截止。</li>
</ul>
<p>针对读数据,还有一种比较特殊的情况,就是跨区间范围关系聚合,这样就<code>需要有一份完整数据</code>来满足极端需求,目前财务系统对于这类需求统一走离线财务数仓来解决!</p>
<h3>4.2 写幂等破坏问题</h3>
<p>对于写数据最大的问题就是幂等健被破坏,<code>归档了数据后,rds写入唯一健破坏,在极端情况下,可能会造成duplicate</code>。考虑到问题的出现概率和实现成本,初期可以忽略,采用人工干预的方式,归档最终要写入全量,写不进去就是duplicate了;后面可以采用前置幂等健组来挡,做到最终一致!</p>
<h3>4.3 数据一致性问题</h3>
<p>无论是数仓数据还是归档数据,作为财务数据,一旦提供资金服务,那么就必须保证强一致性,财务目前采用离线分天统计数据的金额和数量,来保证宏观上的一致性。这里面也有需要小坑,例如数据飘移,时间gap等,关于财务数仓中遇到的坑和解决方案,后续专项讨论</p>
<h2>五、最终归档方案</h2>
<p>分析了归档前选型,归档中数据转移,归档后数据完整性问题,初步的归档方案如下图</p>
<p><a href="https://link.segmentfault.com/?enc=s41ZusuDh%2B55lnoD1VL9DQ%3D%3D.rhLdVd%2FP8d2LfiAomUAjW5Ln3auLlxUD%2FGcgHwizqmFEc1v%2BJpIDU7%2FLcTfvmZDfz14Wq39IRVaXJkCpPXDA9g%3D%3D" rel="nofollow"><img src="/img/remote/1460000018758513?w=1644&h=902" alt="archive_2" title="archive_2"></a></p>
<p>简单梳理下核心流程</p>
<ul>
<li>写数据流:写数据写入online rds[备注:目前没有前置幂等拦截,后面择机完善],写入后通过binlog准实时写入es,提供线上读服务;通过binlog小时级入hive,作为分析数据和全量数据存储;通过天级归档脚本,将历史数据导入rocksdb归档。同时,如果有日切,也会天级进行数据日切和新表创建。</li>
<li>读数据流:读数据过proxy,非归档期间数据直接读取es,归档数据和es没有的数据都会穿透到rocksdb。</li>
<li>监控流:天级监控hive,es,rocksdb,三个不同来源的数据条目和总金额,保证一致性。</li>
</ul>
<h2>六、总结和不足</h2>
<p>本篇主要总结了小拽在数据归档过程中,如何选型,如何归档以及在归档数据后引起的问题如何处理。</p>
<p>通过数据归档,更清楚的划定了不同存储介质的功能边界,是进行数据中台搭建,赋能业务的前置准备!</p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=%2BJhhpGfGhxylB6ob44rxQQ%3D%3D.DCQ%2BWXwRCU2%2FB%2FZIuBk5bFttuIkkcuf2jedd7%2BMLF9%2FGKF3Qbl%2BZz%2FhUxbVwN1YsTCvW2Yk67Brrw8ELApV15P70LzuUL4pe2nNynDBaToJgcnvRxKMlAjhUYWuahWL4OFLHUtXOdjEQNlDNRlOdHQ%3D%3D" rel="nofollow">数据归档那些事儿</a> | <a href="https://link.segmentfault.com/?enc=Kc94mMYB670xl8spDNwfzA%3D%3D.s5D5C6SHcu9J0eRpVl2KK9kbgn7iYZleM%2BtYCWruaBg%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
热点账户问题和常用解决方案【中】
https://segmentfault.com/a/1190000018733109
2019-04-01T22:37:16+08:00
2019-04-01T22:37:16+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
2
<blockquote>话接上回,上篇阐述了什么是热点账户,基本财务账户如何设计,幂等健和链式设计!本篇将针对热点账户在实践中引发的问题,梳理和拆解业务流,分析问题点,提出七种常用解决方案。</blockquote>
<h2>一、性能问题初现</h2>
<p>上线初期数据量较小,运行正常!<br>一次大促后,账户流水的总数目接近亿级别,初现性能问题:<code>系统整体的qps也就10+,但热点账户写入失败率偏高,并且随数据量增加失败率逐步升高;整个账户系统全靠上游有redo标识位不断重试,才能保证最终写入成功!</code></p>
<p>哈哈,作为一名拥有三年工作经验的老码农,面对问题,要做的第一件事,就是<code>静</code>,抽根烟静静,准备开搞!</p>
<h2>二、数据流拆解</h2>
<p>拿到问题,抽根烟静一下之后,分析问题需要三步:<strong>梳理数据流,拆解过程,定位问题点</strong>。先对财务账户更新的数据流进行拆解</p>
<p><a href="https://link.segmentfault.com/?enc=J0%2BKAci%2FmveR5VK9FYEuKw%3D%3D.JDptsaIfPCUMuy6s2SJxbNqaWBAcUJxVLMRH6CnxyagX2plbnmsAWCjJbNk18tdJsGXTmpXCy%2F4oXPhnmGGsUbhsJ5C7nAS0DP0M0bF%2Bo%2B4%3D" rel="nofollow"><img src="/img/remote/1460000018733112?w=1244&h=446" alt="finance_hot_account_3" title="finance_hot_account_3"></a></p>
<p>链式锁后的基本账户操作过程,分为如下五阶段</p>
<ul>
<li>请求阶段:账户操作请求。</li>
<li>查询阶段:查询当前账户信息。主要获取当前链,资金数据等!</li>
<li>计算阶段:对链和操作的资金进行计算,判定资金操作合规,同时保证幂等和并发!</li>
<li>写入阶段:事务同步写入流水和余额</li>
<li>响应阶段:告知上游回调</li>
</ul>
<h2>三、链路分析</h2>
<p>梳理数据流后,接下来分析每个阶段可能引发的问题。按照优先级,<code>先分析业务问题区域(读取阶段,计算阶段,写入阶段)</code>,通常问题会出现在业务阶段;之后,<code>再分析框架问题区域(请求阶段和回调阶段)</code>,该区域出现问题的情况偏小,但是一旦出现问题,就是比较有意思^_^!</p>
<h3>3.1 业务问题区域分析</h3>
<p>读取阶段,计算阶段,写入阶段三个阶段是具体的业务操作,从并发和耗时两个角度来分析下可能的问题点</p>
<h4>3.2.1 耗时分析</h4>
<p>耗时分为三块</p>
<ul>
<li>查询耗时:RDS拥有亿级别数据量,<code>查询未中primary,但命中索引,业务数据体并未完全在索引中,因此访问数据走index match;数据主键聚簇,唯一健索引查询获取数据,page极难命中cache,也不会命中磁盘电梯算法优化!结合实际情况,查询耗时在10-100ms级别</code>
</li>
<li>写入耗时:insert 包含了自增,理论上在数据落盘是追加写,即使uniq_key去创建索引的情况下,耗时在ms级</li>
<li>过程耗时:长连接情况下,init conn时间基本可以忽略,但是读写两次往返数据库的链路时间还是需要考虑,整体预估在1-2ms之间</li>
</ul>
<p>从整体上看,预估该阶段的耗时在10-100+ms,从实际失败率来看也基本一致!</p>
<h4>3.2.2 并发分析</h4>
<ul>
<li>天级QPS:当时分析天级几十万单,天级QPS不到10,不高!</li>
<li>瞬间QPS:每个订单拆解到资金流后,会同时操作多次热点账户,瞬间qps相对很高,理论qps就可能达到语言上限,由于上游链路限流1024,按照10级别操作拆分,理论上满池QPS在万级别。考虑实际单量,瞬间QPS=单量(10)*拆解量(10),实际的满额预估QPS可能到100+ !</li>
</ul>
<p>按照上面分析,在<code>瞬时QPS达到10+的情况下,热点账户整体延时在10-100+ms,由于DB在写入uniq_key保证链点唯一,所以出现并发写入失败也在情理之中</code>;并且随着数据量的提升,读取延时增加,写入失败率会继续增加。</p>
<h3>3.2 框架问题区域</h3>
<p>请求阶段做为入口,一般也分为三个小阶段</p>
<ul>
<li>webserver接收请求</li>
<li>框架加载和路由</li>
<li>基础校验</li>
</ul>
<p>请求阶段核心耗时一般存在于框架加载和路由,高并发场景webserver和upstream之间的调用也是一个可能出问题点!当时财务系统,采用欢总封装的go-thrift,并且其他模块并未出现请求阶段问题,所以并未对这个阶段的latency和并发做一个衡量,重点分析了业务模块!</p>
<h2>四、解决方案</h2>
<h3>4.1 读取和写入阶段优化</h3>
<p>通过上面分析,目前问题的痛点是<code>并发读取热点账户数据高延时引发写入失败</code>,提升读性能成为了关键</p>
<blockquote>读性能提升有两个基本思路:<strong>读的时效快和读的次数少</strong>
</blockquote>
<p>针对上面两个基本思路,结合财务账户情况提出了五种提升读性能的解决方案</p>
<ul>
<li>
<strong>【读快】持久化last record</strong>:不从全量数据里面读,抽离子账户的最新信息,持久化到单独的表中或者内存中,降低整体数据量,提升了读性能。<code>缺点是要保证持久化信息的准确性,引入事务写。</code>
</li>
<li>
<strong>【读快】纵向切分-时间分库分表</strong>:按照时间进行纵向切分,降低查询范围内的数据量,提升读性能。<code>缺点是跨时间读不友好,开发量也不小</code>
</li>
<li>
<strong>【读快】纵向切分-归档</strong>:历史数据归档是<code>实现相对简单,跨时间读也比较友好,随着数据量的提升,也是必须要做</code>,之后会详细介绍归档方案和选型。</li>
<li>
<strong>【读快】横向切分-业务分库分表</strong>:按照账户类型或者城市分库分表,可以优化读写数据量,同时,跨表读负担也会较小。但对于热点账户或者热点城市,依然聚簇,效果不是很明显。同时,再次对热点账户进行横向分库分表也是极度不推荐,引入的极高的读写成本均。</li>
<li>
<strong>【读少】阶段快照</strong>:一定量或者一定时间内的数据,持久化一次。优势是极大的降低读写次数;缺点是需要复杂的逻辑来保证undo操作和数据一致性!</li>
</ul>
<p>五种解决方案各有千秋,作为一个初期的财务系统推荐采用持久化last record和数据归档来保证写入读性能和整体读的数据量。如果系统发展到了中期,推荐按照时间分库分表。如果发展到了双11或者春晚某些极端场景,牺牲掉部分准确性,采用阶段快照也是可以的。</p>
<h3>4.2 计算阶段优化</h3>
<p>存在计算阶段造成的最大影响也就是引起了两次数据传输,通常是不可避免的,但是如果真的是要进行提升有一种方案通用方案</p>
<ul><li>
<strong>DB计算</strong>:<code>通过存储计算,转嫁计算成本给DB,减少一次链路请求。</code>但不太推荐,复杂的sql往往有坑,insert computer from select 还会造成大面积的数据隔离,很容易引起死锁。</li></ul>
<h3>4.3 请求和回调阶段优化</h3>
<p>请求阶段一般有三种形式:<strong>同步调用,异步调用和伪同步调用</strong>!<br>前两种调用非常常见:同步爆池的情况,一般采用限流来降压,采用漏桶,令牌桶等等策略;异步调用通常采用消息队列来削峰填谷;这里重点阐述对于支付和财务系统在请求阶段经常采用的伪同步的方式</p>
<p>伪同步流量较早出现在innodb,leveldb等存储引擎为了利用追加写提升写入性能,采用类WAL日志来持久化数据。通常伪同步方案采用三件套:<code>WAL日志+校验位+广播消息</code>来完成一次完整的请求!流程图一般如下</p>
<p><a href="https://link.segmentfault.com/?enc=CcUNyHPVUTpGIbCzAov4sQ%3D%3D.3KKNtBNBxEwseSXErmB4ysPwTyIGJMPCMhL19Y9RRA%2BcK2QgLz2Ml9uYoaoMLgqAp6vNm1FgO2DIC%2B4154fqbfrdRaCG200E%2FOuxInvL6OA%3D" rel="nofollow"><img src="/img/remote/1460000018733113" alt="finance_hot_account_4" title="finance_hot_account_4"></a></p>
<ul>
<li>请求阶段:同步请求调用,核心要素追加写入wal日志,变更校验位,完成同步调用!此处追加写保证了快速写入,校验位来保证数据的最终写入成功。图中1,2</li>
<li>异步阶段:<code>通过读取wal日志的核心数据,进行复杂事务处理,如果成功进入下一阶段;如果失败,没问题,通过外部trigger来触发redo操作!如果多次redo依然失败,那么通过undo来回滚数据</code>。</li>
<li>回调阶段:如果成功,更改校验位,同时发布成功广播消息,关注结果和时效性的模块,可以获取最终成功的标识!如果undo回滚数据,则发布失败广播消息,告知结果失败!</li>
</ul>
<p>在伪同步的模式下指标衡量:</p>
<ul>
<li>QPS:伪同步模式,采用WAL核心要素追加写,所以写性能可以极大提升,进而满额QPS相对直接同步调用也大量提升</li>
<li>时效性:伪同步并非完全同步,所以结果需要监听回调。对于结果强一致的请求,必须监听回调,确保一致,时效性降低;对于弱一致可以认为同步回调即成功,时效性提升。</li>
<li>失败率:操作知识核心要素追加写入,真正的操作通过异步保证,整体成功率提升!</li>
</ul>
<p>对于资金处理过程,大量采用<strong>伪同步的请求方式来保证快速写入和最终一致性</strong>。</p>
<h3>4.4 解决方案总结</h3>
<p>总的来说,归结了七种优化方式(哈哈,上篇写的八种优化,当时总结的,现在愣是想不到还有啥了^_^)。其中请求和回调的伪同步方式,是在架构层面优化,这个在多数的财务系统和财务系统的内部数据流中都会用到;而读写和计算阶段的优化,可以跟进实际业务情况进行选型处理。</p>
<h2>五、事故复盘</h2>
<p>面对各种优化方案,需要结合实际情况做出取舍,有的是长期方案,有的是快速方案,<code>但一定需要想清楚了再开搞</code>,过程中有一个对小拽之后影响很大的事故,引以为戒。</p>
<p>翻车过程:当时觉的读->计算->写这个过程,<code>两次读DB操作,下沉计算过程到DB后,通过DB计算,可以减少一次数据库请求</code>。于是<code>合并了一个大SQL,也就是所谓的insert ( field computer from select)</code>,觉的写了个狂赚酷炫吊炸天的SQL,一上线,库锁死了!幸好有前置的redo flag,全量redo数据恢复,要不然估计直接祭天了!</p>
<p>对于这个复杂大SQL事故,小拽总结了三个方面</p>
<pre><code>莫炫技:没啥好说的,解决问题的成就感要远大于炫技!
简单设计:简单的设计通常意味着可依赖;复杂的设计要尽可能的拆解,想清楚,队友都理解不了的设计,那就别上线了,可能真的需要再思考拆解下
尊重线上:核心服务基本上线流程一定要遵守,测试,监控和回滚方案缺一不可</code></pre>
<h2>六、小结</h2>
<p>本篇主要针对热点账户问题提出了七种常用的解决方案,下篇将继续引申探索下,各种解决方案在不规则高并发场景,例如双十一,微博热点事件中如何套用</p>
<p>预知后事如何,下回再聊!</p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=lCvcUReDJXvhN25fmQlR1Q%3D%3D.reBbPw3wFKZAfEYAucrYmXOWjrP6wdxsWGQUT0JBcYewmrEDd7SocWvYx%2BgnEZAhpiLVdMZPY7OGBLnbo8MDEadoMPfad2T%2BmlREj3pccVcZkGT2gdKtKPSHO5fSsRgwMV4HFxV9bZh1Pk2rvEnbqD3AQ74yHFYEBLvSJ3RIWUQwP1qwx5YTpEVGXPnSmDGP30Pe8XbRUoYH0V%2BEViJaClde0TGwOrhbxzvZbiztOQI%3D" rel="nofollow">热点账户问题和常用解决方案【中】</a> | <a href="https://link.segmentfault.com/?enc=xN9zhsBLXgdLxPXfwoWzTA%3D%3D.wewZeHKA3PFBWFY%2FZ%2BN%2BeUUJTAT5xdCvpAt3bBI4UV4%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
热点账户问题和常用解决方案【上】
https://segmentfault.com/a/1190000018144862
2019-02-13T22:50:50+08:00
2019-02-13T22:50:50+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
2
<blockquote>
<strong>热点账户问题</strong>由来已久,一直是账户系统设计中的一个难点和瓶颈!<br>小拽将通过上中下三篇文章,分别介绍下热点账户的产生,解决方案和延伸应用!<br>本篇主要介绍下什么是热点账户?通用财务账户系统如何设计?以及其中的幂等健和链式设计等</blockquote>
<h2>一、热点账户问题</h2>
<h3>1.1 什么是热点账户</h3>
<p><strong>热点账户</strong>:顾名思义,热点账户就是会被高频操作的账户!相较于普通的账户,热点账户数量不多,但操作频率极高!</p>
<p>热点账户从产生来源可分两大类:</p>
<ul>
<li>
<strong>富二代型</strong>:从产生之初就是热点账户,非常稳定。例如<strong>财务中公司的账户</strong>,每一笔资金操作都要经过公司出金账户,自然而然操作就会灰常频繁,此类账户还包括:<strong>大V账户</strong>,<strong>大KA账户</strong>等等,此类账户所引起的问题是本文重点要解决的</li>
<li>
<strong>暴发户型</strong>:本身是普通账户,由于热点问题变为热点帐户。例如<strong>微博出轨女猪脚账户</strong>,<strong>诺贝尔奖获得者</strong>等等,由于热点事件造成的短时间内访问暴增!此类热点账户防不胜防,超出本文的攻击范围,暂不讨论。</li>
</ul>
<h3>1.2 热点账户问题</h3>
<p>热点账户一旦产生便伴随着<strong>高并发,流量分布不均匀,高一致性</strong>等等问题。在实际场景中是热点账户必然存在,常常成为用户系统的瓶颈!<br>同时,热点账户问题也是高并发问题的延展,由于热点的不规则性,如何在高并发情况下,削峰填谷,弹性抗压也是很有挑战性的一个方向!</p>
<h3>1.3 热点账户通用解决方案的价值</h3>
<p>热点账户除了是账户体系的一个通用问题,在高并发,流量分布不均匀,异常峰值等其他问题上,也有一定的通用性。例如<strong>微博热点问题,支付宝双11弹性变更,高频抢购问题</strong>等等。期望通过学习热点账户的八种解决方案,能够举一反三,应用于不同场景!</p>
<h2>二、如何设计一个财务账户</h2>
<p>在解决热点账户问题之前,先来看下如何设计一个简单的财务账户,来保障资金记账的安全!</p>
<h3>2.1 业务场景分析</h3>
<p>从业务上看,财务账户需要<strong>准确记录用户的资金变动过程和结果</strong>!因此设计一个简单财务账户至少要能包括两个部分:<strong>账户余额</strong>和<strong>账户流水</strong></p>
<p>便于理解,来张传统的账本,看下什么是流水,什么是余额<br><a href="https://link.segmentfault.com/?enc=D%2BLIWNbvdJ%2B2cX6O5ydRHA%3D%3D.EARTtRGc9%2FOHhsHDhFsIU8BztjujPtqaukwl7sOimoBLvKT2UaLICEveNKBUan0sbqg%2F9er9MYltrDIBtZmRFx9H62fxmBtCa3Ad2Zbha%2Fo%3D" rel="nofollow"><img src="http://cuihuan.net/wp_content/new/finance/finance_hot_account_1.png" alt="finance_hot_account_1" title="finance_hot_account_1"></a></p>
<p><strong>账户流水</strong>:账户流水也就是通俗意义上的帐或者账单!针对某个账户,每一笔资金的变更都需要记录下来,并且保障<strong>准确,不可更改</strong>!同时如图所示,流水中需要包含单据产生的原因,来源,变更额等等</p>
<p><strong>账户余额</strong>:账户余额记录用户某个场景账户的当前资金额度!在复杂的业务场景中往往需要拆分出不同的子账户和账户模型。例如,未结算子账户,可提现子账户,冻结子账户,授信账户等等。</p>
<p>从业务场景上一个账户系统核心需要准确记录余额和流水,同时,必须保障记录的<strong>准确,完备,不可变更</strong>!</p>
<h3>2.2 技术层面拆解</h3>
<h4>2.2.1 基本表方案</h4>
<p>通过业务场景初步分析,基本的账户系统,需要三张基本表</p>
<pre><code>账户基本信息:账户信息表
子账户余额信息:账户余额表
账户流水信息:账户流水表
三张表基本关系
账户信息表 1:N 账户余额表
账户余额表 1:N 账户流水表
## 具体账户和用户的关联可以参考三户模型 </code></pre>
<h4>2.2.2 表字段设计</h4>
<p>从技术层面看,设计具体表细节关键要解决以下几个问题</p>
<ol>
<li>防重:幂等健设计</li>
<li>防改:链式设计</li>
<li>防错:销账设计</li>
</ol>
<p>先上结果,简单的,能够满足上述需求的设计可以参考innodb mvcc,核心表字段如下</p>
<p><a href="https://link.segmentfault.com/?enc=QZi3Wayzdv%2B2g3OoL2adXg%3D%3D.fz2Seba%2Bqytq3uFiovSoIgNLS%2Btyf98RLNbZVKeb7p%2FRfqgCU4DAE4%2B%2B6oMMEFGh%2B%2BRKF1EehwXsiqG376%2F%2B2HzMMPL4qXVAyCSojBqG7yU%3D" rel="nofollow"><img src="http://cuihuan.net/wp_content/new/finance/finance_hot_account_2.png" alt="finance_hot_account_2" title="finance_hot_account_2"></a></p>
<h4>2.2.3 表字段解读</h4>
<h5>2.2.3.1 幂等健设计</h5>
<p>通过三个属性<strong>资金凭证号+版本号+rollback</strong>三个字段作为uniq key来保证幂等!</p>
<p><strong>资金凭证号</strong>:来自业务方,业务方发起资金操作的唯一财务凭证,必须可追溯上游凭证和对账!<br><strong>版本号</strong>:每次获取DB最新流水n后,版本号n+1插入,保障在并发情况下,每个子账户只有唯一一个版本号:n+1条记录能够插入成功!<br><strong>rollback</strong>:回滚标识,保证每条记录<strong>能且只能销账一次</strong>!</p>
<p>对于幂等建设计此处有三条小技巧</p>
<ol>
<li>
<strong>上游产生</strong>:每一个幂等健如果可能的话,尽可能的上游产生,这样可以最大限度的避免自产生幂等健的重复问题。如果确实不能上游产生,例如订单ID,提现单ID,那么也尽可能的分阶段产生,例如提现时,先生成提现单ID,真正提现操作的时候,一定是带着提现单ID和信息来的,防止重复造成资损!</li>
<li>
<strong>业务关联</strong>:幂等健的产生可以用ice生成,但是,最好能够和业务关联,因为通过业务强关联的幂等健可以无限回溯来容灾!比如,a用户的b订单进行c操作,uniq_key = a_b_c的话,也就是在任何情况下,无论多少次回溯,重试也只会有一个唯一的a_b_c,而ice生成则可能造成自回溯的时候插入多条!</li>
<li>
<strong>写库保证</strong>:这条原则是高一致高并发的基本原则!因为读取a,校验a,然后插入,必然会存在读写之间a变了,或者主从延时a已经变了,读了历史a。因此,幂等一定要通过写库保证或者最底层保证</li>
</ol>
<h5>2.2.3.2 链式设计</h5>
<p>链式设计是保证操作精准不可篡改的非常有效手段!<br>通过资金的<strong>before info,after info,版本号</strong>三个要素来保证一条资金记录一旦插入成功,前后置信息固化!</p>
<p>链式设计的情况下单条修改是不可能的,多条修改需要在保证条目不变的情况下重组资金,但是,整体资金不可变</p>
<p>解决多条修改的一般方案:分布式存储,选举来判定最终正确的链,来确认是否某条链发生了过程修改,这种设计有一个很时髦的名字:<strong>区块链</strong>!而每条流水的核心信息加密后也有了一个更加时髦的名字:<strong>比特币</strong>!</p>
<h5>2.2.3.3 销账设计</h5>
<p>销账设计在账户系统中是一直存在的,现实财务系统可以<strong>红销蓝抵</strong>,线上财务系统加了链式之后,基本上就只能采用<strong>蓝抵</strong><br>通过增加rollback字段,并且严格限制0|1,保证一条账务流水只能被抵销一次!</p>
<p>具体三张表详细字段,需要脱敏,就不贴了,参考上面,其中索引,字段大小,联合索引等设计根据自身业务场景兼容即可!</p>
<h2>小结:欲知后事如何,且听下回分解</h2>
<p>本部分简单介绍了什么是热点账户和账户的基本设计,涵盖幂等健设计,链式设计等等!<br>下一篇重点分析下热点账户在链式设计下的问题,产生原因和八种基本解决方案</p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=kautSICFAajbyApObn4wOw%3D%3D.81Z0ktmzUuOU%2BL%2Bbk2rPRQ6Z7PZJOu0AwEhnqARvUxiGTFhyRIp2BJoyUnCZR%2FHKVJThJelnZOxw2gycsZvdmUVdzAcWr1WIa6Wro%2F%2Bj414gsZzbEMIUWJfBhZ587IC2SvLFpYwd2lptW4UccyTmcoD22W%2FfkneOsZNd6ReC3CaS3LPb3NScVUuiagO8JTWPD9bFh%2FSsnNq26%2BV6zK2KnlM0f4WtTQgvFlsZT%2BuzqncWK5HFJx1VbqetWSrnm%2BdncY5WOvGi4OitKSq8%2FMYlABRY0FirZaEIhoWO5zm5qE%2FAJyeRnj2giGLiyLvZP4qWQ%2FwNKXYaOec2njdJ9RQ4lA%3D%3D" rel="nofollow">热点账户问题和常用解决方案【上】</a> | <a href="https://link.segmentfault.com/?enc=57422xq%2F15cyBKN15NKfKg%3D%3D.MbA0uDgdxSxbGG3QDQv7lHN8GkZFzG5iHvYg%2FmToRf0%3D" rel="nofollow">公众号:靠谱崔小拽</a> |】</p>
财务系统设计【序】
https://segmentfault.com/a/1190000017783980
2019-01-06T20:24:50+08:00
2019-01-06T20:24:50+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
2
<blockquote>新年伊始,组织团队小伙伴进行了一次头脑风暴,畅想了下财务系统2019的愿景,自己也思考颇多,决定针对【财务系统设计】做个专栏,落笔为记!</blockquote>
<h4>一、一次头脑风暴</h4>
<p>头脑风暴前,除了准备泡面,花生,矿泉水,还列了几个比较现实的问题</p>
<ul>
<li>公司:你做的事情,值多少钱?公司凭什么给你升职,涨薪?财务系统到底还能给公司带来多少收益?</li>
<li>自己:为什么要留下来?干一年财务系统,到底能给每个人带来多大成长?</li>
<li>目标:下次跳槽时,你希望自己成长成什么样子?</li>
<li>落地:规划每个人的模块方向,如何把<code>自身成长需求和业务成长</code>绑票?</li>
</ul>
<p>与我个人而言,期望通过这次头脑风暴,<strong>让团队小伙伴们能够对自己的模块有个规划,能够在业务成长的过程中实现个人技能成长,能够通过促进模块收益来提升自己薪资职级!</strong></p>
<p>无他,也希望各位看官能够思考一下</p>
<h4>二、财务系统设计专栏</h4>
<p>头脑风暴后,小拽也一直在思考,2018年干了一年财务系统了,2019年如何搞?</p>
<p>具体的需求拆解,模块设计,架构图,暂时先不祭出来了,毕竟还需要深入的拆解和剖析!<br>但结合年初的flag,小拽决定2019年完善【<code>财务系统设计</code>】专栏^_^,期望能够通过专栏,<strong>自己能够体系化的梳理下财务系统,抽象出更通用的解决方案!</strong></p>
<p>废话不提先列下2018年亏欠的文章和目录,今年一定补上^_^!</p>
<ul>
<li>热点账户问题思考和常用解决方案</li>
<li>数据最终一致性保证</li>
<li>幂等健设计原则</li>
<li>全局ID生成思考和解决方案</li>
<li>财务系统异步和同步的思考</li>
<li>国际化账务系统思考</li>
<li>财务数仓有哪些坑?</li>
<li>通用账单分级模型设计</li>
<li>账户模型设计和思考</li>
<li>账户流水设计和思考</li>
</ul>
<h4>三、专栏目录</h4>
<p>长远的看,小拽的财务模块设计最终会把所有文章落到各个模块中,暂时先梳理了下目录!</p>
<pre><code>财务系统专栏
├── 在线系统设计和实现
│ ├── 分账模块
│ ├── 提现模块
│ ├── 收银模块
│ ├── 结算模块
│ ├── 记账模块
│ └── 账户模块
├── 支撑系统设计和实现
│ ├── MIS系统
│ ├── openAPI
│ ├── 任务系统
│ ├── 财务网关
│ ├── 数据质量中心
│ └── 监控预警系统
├── 数据中心设计和实现
│ ├── ARCHIVE
│ ├── GraphDB
│ ├── HBASE
│ ├── HIVE
│ ├── KV
│ ├── RDS
│ └── TSDB
└── 离线系统设计和实现
├── 对账引擎
├── 经营分析
├── 结算引擎
├── 财务报表
├── 资金安全
└── 预算引擎</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=SppK40mbmB9YE96QR5hApA%3D%3D.tYDhK45iFb6a44Kac%2FzW490LwdRv5WJjDpaKXYez6hhwVOV9H3t8E7CMVQEDEjJWeF8GA6N%2BRM%2FM%2Fmt90FANLjaqx%2B2bSJ6cuaWyyy3BvzmaWBD7oqp5KQg0qRgT453slklrVzBv8FXegUw4sh%2FjiKZhskV%2BPbqncXT5%2F%2FaXQ7Y%3D" rel="nofollow">财务系统设计【序】</a> | <a href="https://link.segmentfault.com/?enc=hz1VdVt5QD6GRvz2lA11ug%3D%3D.KKIbxpiY2c7DoVHY4eomcCXcrH5hyVA2X0hXXTuqpnU%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
2018回顾
https://segmentfault.com/a/1190000017721469
2019-01-02T23:44:16+08:00
2019-01-02T23:44:16+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
2
<blockquote>岁月不居,时光荏苒,本想写个2018的总结,结果2019都过2天了,三十功名已经泡汤,顺道写些2019展望吧!</blockquote>
<hr>
<h4>工作方面</h4>
<ul>
<li>工作方面,18年离开混了四年的狼厂母校,加入滴滴,没有了小伙伴和团队的呵护,只能独立面对,干了几件还算可以的事儿!花了四个月,从0到1的设计,开发了外卖财务在线系统;又花了四个月,搭建了财务离线系统和支撑系统;最后四个月,财务系统国际化改造!整体来说,成长很快,日子很燃,业务波动大!对于自己,无论从技术架构、业务成长、团队建设,既是机遇也是挑战。2019支撑业务发展的同时,需要加深技术的深度和体系化,增强技能外的视野^_^!</li>
<li>19年个人成长立个flag吧,<strong>一年多没更新的博客重新捡起来,做到每月至少一篇!总计达标20篇吧,其中至少有两篇是技能外的</strong>
</li>
</ul>
<hr>
<h4>投资方面</h4>
<ul>
<li>18年可谓过山车,年中股票整体收益高达150%,年底落到了20%多,总算没亏。教训就是,<strong>止损一定要果断!果断!果断!</strong>经济低迷的环境下,基金逐步加仓,等待周期!投资个小桶装水厂,也是亏了,经验一句话:<strong>做生意,人非常重要,一定想清楚了再干</strong>!18年看好半导体,19年目前比较看好健康体检,旅游领域。19年的核心调整自己的资产配置,按着二八原则递归!实体行业暂时先观望</li>
<li>19年投资方面flag不变,继续保持每周读经济周刊!</li>
</ul>
<hr>
<h4>生活方面</h4>
<ul>
<li>18年有些波澜不惊,在媳妇的组织下,爸妈哥嫂全家旅游了一趟,毕业后一直没有好好陪过父母,多少弥补了些遗憾。年底和媳妇儿去了趟台湾,互联网方面太落后了,吃老本而已,以后也不会再去了!年末赶上天朝洪恩,买了共有产权房,位置很喜欢,上下班方便!大事儿就这些,哈哈,整体感觉自己变懒了,很多事情都是媳妇儿在张罗维持,需要反思!2019期望,自己能够腾出更多的时间和媳妇在国内好好逛逛,毕竟三人世界很快就要替代二人世界了!</li>
<li>19年在生活方面立个flag,哈哈,就不在这里说了,感恩家庭</li>
</ul>
<hr>
<h4>兴趣方面</h4>
<ul>
<li>2018新get两个技能,一个是无人机,很喜欢,换一个角度看看世界,很不一样,后面也逐步学习下原理和拍摄技巧;另一个勉强算是羽毛球了,哈哈,不过现在还是苍蝇拍,也没啥提升的兴趣了,挺好的健身途径,目标保持体重!2019,期望新增一门技能:做饭,最好能考个证(<code>初步查了下新东方厨师周末班都要6000,真贵</code>),毕竟程序员的未来都是开饭店,提前做好准备!</li>
<li>2019 年兴趣爱好方面立个flag,能做一桌菜!</li>
</ul>
<hr>
<p>想说的很多,但是没喝点酒,吹不起来,18年先这样子吧O(∩_∩)O</p>
<p>最后致谢,感谢老婆和家人的支持,有了你们坚实后盾,怎么走都是路!感谢工作中小伙伴,有了你们的信任和配合,稳如狗!</p>
<p><strong>2018,不管愿不愿意,已经成为过去,让我们在2019继续努力,以梦为马,不负韶华!</strong></p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=2M%2F%2Bkj6kyAvMAGlqMKaCzg%3D%3D.Aa19f3Q6Ok4Y3NYmjO6C4IED7BykUgfJGcOLsgVVqy77fcKXDMvBkuF5iCoolzc5UyXaqbTfiLZhoctnET2Qc8t03pLbQmj%2FlJK9FqV8Yiq3n4xE8QF0%2F%2FVD5P0YIamUK%2F73cVHHb0SwNWG87NPiSVCzykwRRIJFRMhNpFnBaOuCZAJSqV8kHW%2BEjHEwXC6tKA2jMjOhKw4Ao2ZPgI5u0ddjR9DwNSe7UWAv9pWHa8xWjbXJuF04MyK0A%2BSLK5ZR1eNEPmgeWOUQucU4a9jHUln4rl4iKh14YV%2F4QOsYNwlr%2FN3RTXs0qQptyqPTduLI4EZ9H%2BhX0hLVWyYDMTDlBg%3D%3D" rel="nofollow">2018回顾</a> | <a href="https://link.segmentfault.com/?enc=Tsk%2BrnAJhIVaaRhxKebSIQ%3D%3D.unRU4Yy2mN8JQz095NwURhVDutpSpLxtWXzLdYQIS2Q%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
【GO学习一】 Hello World
https://segmentfault.com/a/1190000011537146
2017-10-13T10:01:00+08:00
2017-10-13T10:01:00+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>最近项目中需要开发<code>抗并发的db proxy,API GATEWAY</code>等;同时,随着虚拟化的过程中出现各种问题。作为一个老程序员,go语言的学习,已经刻不容缓。</p></blockquote>
<h2>一、基础背景</h2>
<p>Go是Google开发的一种<code>静态强类型、编译型、并发型</code>,并<code>具有垃圾回收</code>功能的编程语言</p>
<p>对于go语言的特性,网上大牛总结,对于个人来说特别看重<code>语言交互和并发性</code>:</p>
<ul>
<li>自动垃圾回收</li>
<li>更丰富的内置类型</li>
<li>函数多返回值</li>
<li>错误处理</li>
<li>匿名函数和闭包</li>
<li>类型和接口</li>
<li>并发编程</li>
<li>反射</li>
<li>语言交互性</li>
</ul>
<h2>二、安装</h2>
<p>建议参考:<br><a href="https://link.segmentfault.com/?enc=MvE0DL3Ia6EnlqGNOl36%2FQ%3D%3D.TiSBWZRKhRHiejTNKiHkr%2BCOu6elJjl2E6dM64wieminooEqS6CDn6bJluvYsHr%2FjXPOzGjqjGRziq8BBp3z2A%3D%3D" rel="nofollow">http://dmdgeeker.com/goBook/d...</a></p>
<p>需要注意的是 gopath一定要配置,配置到自己的workspace即可:</p>
<pre><code># go path change by cuihuan
export GOPATH=/Users/cuixiaohuan/Desktop/workspace/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOPATH</code></pre>
<p>workspace的基本目录规范可以参考:<a href="https://link.segmentfault.com/?enc=lx%2FJXP7b33Qy0AWqNrX4zg%3D%3D.4aPINgQ4PycJpQxpaPws4oht%2BAvAs2wyt%2BcrUlDIPtc%3D" rel="nofollow">https://go-zh.org/doc/code.html</a></p>
<ul>
<li>src 目录包含Go的源文件,它们被组织成包(每个目录都对应一个包),</li>
<li>pkg 目录包含包对象,</li>
<li>bin 目录包含可执行命令。</li>
</ul>
<h2>三、hello world</h2>
<h3>代码</h3>
<pre><code>package main
import "fmt"
func main() {
fmt.Println("Hello World")
}</code></pre>
<p>语言简述:<br>1:package 是必须的,对于独立运行的执行文件,必须是package main<br>2:import 表示引入的包,或者库<br>3:程序中的主函数<br>4:执行函数</p>
<h3>运行:</h3>
<pre><code>cuixiaozhuai:main cuixiaohuan$ go build hello.go
cuixiaozhuai:main cuixiaohuan$ ./hello
Hello World</code></pre>
<p>编译和运行都非常简单,而且比较方便的是跨平台编译</p>
<pre><code># mac 下编译
cuixiaozhuai:main cuixiaohuan$ env GOOS=linux GOARCH=amd64 GOARM=7 go build hello.go
# linux 开发机运行
[work@xx.com ~]$ ./hello
Hello World</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=tQYfdmy7QDrRRuYl6G4uJQ%3D%3D.WBA6hWgLNtLuaeaYmzP2OUV44cJ6Ic2MwKtZSVc2B7Q%3D" rel="nofollow">【GO学习一】 Hello World</a> | <a href="https://link.segmentfault.com/?enc=v4GYA%2FB7WozTg9OR5lcSbw%3D%3D.G%2FY9xn3KlqkfV6iRShdK5SZskNMB9IH6Oxr3KORfeUA%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
【GO学习二】包,函数,常量和变量
https://segmentfault.com/a/1190000011531902
2017-10-12T20:56:59+08:00
2017-10-12T20:56:59+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>主要介绍go语言的基本元素,包引入,变量,函数声明</p></blockquote>
<h2>引入包:</h2>
<p>go语言通过import引入包<br>最佳实践:<code>import顺序:系统package,第三方package,程序自己的package</code></p>
<pre><code>package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("math test number %g ",math.Sqrt(7))
}
# 运行输出
cuixiaozhuai:main cuixiaohuan$ ./hello
math test number 2.6457513110645907</code></pre>
<h2>函数定义</h2>
<p>go中函数可以没有参数或者接受多个参数</p>
<h3>基本格式</h3>
<pre><code>func xx(AA type,aa) Type {
}</code></pre>
<pre><code>package main
import (
"fmt"
)
func funcNoParams() string {
return "Hello no params func"
}
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(funcNoParams())
fmt.Println(add(3, 3))
}
cuixiaozhuai:main cuixiaohuan$ ./hello
Hello no params func
6
</code></pre>
<h3>func 可以多值返回</h3>
<pre><code>func swap(a, b string) (string, string) {
return b, a
}
func main() {
a, b := swap("cuihuan_first", "cuihuan_second");
fmt.Println(a, b)
}
cuixiaozhuai:main cuixiaohuan$ ./hello
cuihuan_second cuihuan_first</code></pre>
<h2>变量操作</h2>
<p>go 中通过var 定义变量,参数在前,类型在后</p>
<pre><code>var boolVal, boolVal2 bool
func main() {
var intVal int
var stringVal string
fmt.Println(boolVal, boolVal2, intVal, stringVal)
}
cuixiaozhuai:main cuixiaohuan$ ./hello
false false 0</code></pre>
<h3>变量默认值</h3>
<p>没有赋值的时候会给上默认值。</p>
<pre><code>var boolVal, boolVal2 bool = true, false
func main() {
// 声明类型
var intVal int = 33
var stringVal string = "xiaozhuai string"
fmt.Println(boolVal, boolVal2, intVal, stringVal)
// 不声明类型
var undefineType1,undefineType2,undefineType3 = "yo yo yo","qiekenao",666
fmt.Println(undefineType1, undefineType2, undefineType3)
}
cuixiaozhuai:main cuixiaohuan$ ./hello
true false 33 xiaozhuai string
yo yo yo qiekenao 666</code></pre>
<h3>:= 赋值</h3>
<p>函数中的赋值可以用 :=代替var 【:= 标示声明又赋值,但是只能用于函数内】</p>
<pre><code>var boolVal, boolVal2 bool = true, false
func main() {
// 声明类型
var intVal int = 33
var stringVal string = "xiaozhuai string"
fmt.Println(boolVal, boolVal2, intVal, stringVal)
// 不声明类型 := 声明赋值
undefineType1,undefineType2,undefineType3 := "yo yo yo","qiekenao",666
fmt.Println(undefineType1, undefineType2, undefineType3)
}</code></pre>
<p>注意:= 简介赋值,但是无法,声明,赋值,定义类型。和var的区别<br>函数外,必须用var func等等</p>
<h3>变量基本类型</h3>
<pre><code>func main() {
// go 变量全部类型和默认值
var boolVal bool
fmt.Println("bool default:",boolVal)
var stringVal string
fmt.Println("string default:",stringVal)
// int 在32位上默认32 64位默认64,uint 和 uintptr类似
var intVal int
var int8Val int8
var int16Val int16
var int32Val int32
var int64Val int64
fmt.Println("int default:",intVal)
fmt.Println("int8 default:",int8Val)
fmt.Println("int16 default:",int16Val)
fmt.Println("int32 default:",int32Val)
fmt.Println("int64 default:",int64Val)
fmt.Println("max int 64:", uint64(1<<64-1))
var uintVal uint
var uint8Val uint8
var uint16Val uint16
var uint32Val uint32
var uint64Val uint64
var uintptrVal uintptr
fmt.Println("uint default:",uintVal)
fmt.Println("uint8 default:",uint8Val)
fmt.Println("uint16 default:",uint16Val)
fmt.Println("uint32 default:",uint32Val)
fmt.Println("uint64 default:",uint64Val)
fmt.Println("uintptr default:",uintptrVal)
// uint8 别名
var byteVal byte
fmt.Println("byte default:",byteVal)
// int 32 别名,代表一个unicode
var runeVal rune
fmt.Println("rune default:",runeVal)
// int 32 别名,代表一个unicode
var floatVal float32
fmt.Println("floatVal default:",floatVal)
// int 32 别名,代表一个unicode
var complexVal complex64
fmt.Println("complex64Val default:",complexVal)
}
cuixiaozhuai:main cuixiaohuan$ ./hello
bool default: false
string default:
int default: 0
int8 default: 0
int16 default: 0
int32 default: 0
int64 default: 0
max int 64: 18446744073709551615
uint default: 0
uint8 default: 0
uint16 default: 0
uint32 default: 0
uint64 default: 0
uintptr default: 0
byte default: 0
rune default: 0
floatVal default: 0
complex64Val default: (0+0i)</code></pre>
<p>int 类型的零值 是0 bool 默认false 字符串为”" 空字符串</p>
<p>18446744073709551615 byte 到底多大 【2097152TB】<br>int 64 可以表示200wTB的数据,那么问题来了,PB的数据如何表示,float<br>这里也可以通俗的理解下:数据库中的int(11)类型作为主键,基本不用担心满,200wTB*8 的数据量,足以</p>
<h3>变量类型推导:</h3>
<pre><code>func main() {
v := 666
f := 666.666
s := "string 666"
i := v
fmt.Printf("666 is of type %T\n", v)
fmt.Printf("666.666 is of type %T\n", f)
fmt.Printf("'string666' is of type %T\n", s)
fmt.Printf("传递 is of type %T\n", i)
}
# 需要注意的是,go中属于强类型,一旦定义之后就不允许改变为其他类型。
cuixiaozhuai:main cuixiaohuan$ ./hello
666 is of type int
666.666 is of type float64
'string666' is of type string
传递 is of type int</code></pre>
<p>另外变量的官方参考文档:<br><a href="https://link.segmentfault.com/?enc=TpZvv7PbPbjWEuk973KG%2FQ%3D%3D.WBSmpq50VUOypVcUCPL8v57cBPgb%2FAZ01X1JxUeuX%2B7Cnlo9OH%2BY0X69Ma5SakfovIhpxalPjWS3y%2FxTH8tK5w%3D%3D" rel="nofollow">https://golang.org/ref/spec#C...</a></p>
<h2>常量:const</h2>
<p>go中常量不能用:=<br>go中常量可以是字符,字符串,布尔和数字类型的值</p>
<pre><code>const PI = 3.14
func main() {
const test = "test const";
fmt.Println(test, PI);
}
cuixiaozhuai:main cuixiaohuan$ ./hello
test const 3.14</code></pre>
理解php单例模式
https://segmentfault.com/a/1190000009996347
2017-06-30T14:10:00+08:00
2017-06-30T14:10:00+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
31
<blockquote><p>单例作为一个最经典的设计模式之一,到底什么是单例?为什么要用单例?怎么设计单例?php中单例如何具体实现?</p></blockquote>
<h2>一、什么是单例</h2>
<p>wiki百科:单例模式,也叫单子模式,是一种常用的软件设计模式。 在应用这个模式时,单例对象的类必须保证只有一个实例存在。 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。</p>
<p>通俗的说,也就是对于<code>某一个功能只能实例化一个对象</code>。</p>
<h2>二、为什么用单例</h2>
<p>实际项目中像数<code>据库查询,日志输出,全局回调,统一校验</code>等模块。这些模块<code>功能单一</code>,但需要<code>多次访问</code>,如果能够<code>全局唯一,多次复用</code>会大大提升性能。这也就是单例存在的必要性。</p>
<p>单例模式的好处:</p>
<ul>
<li><p>1:减少频繁创建,节省了cpu。</p></li>
<li><p>2:静态对象公用,节省了内存。</p></li>
<li><p>3:功能解耦,代码已维护。</p></li>
</ul>
<h2>三、如何设计单例</h2>
<p>通过上面的描述,单例的核心是,<code>实例一次生成,全局唯一,多次调用</code>。因此在单例模式必须包含三要素:</p>
<ul>
<li><p>1:私有化构造函数,私有化clone。也就是不能new,不能clone。【唯一】</p></li>
<li><p>2:拥有一个静态变量,用于保存当前的类。【唯一如何保存】</p></li>
<li><p>3:提供一个公共的访问入口。【可以访问】</p></li>
</ul>
<h2>四、php实现</h2>
<p>php 实现的单例模式</p>
<pre><code><?php
class XiaozhuaiSingleton
{
// 私有化构造方法
private function __construct()
{
}
// 私有化clone方法
private function __clone()
{
}
// 保存实例的静态对象
public static $singleInstance;
/**
* 声明静态调用方法
* 目的:保证该方法的调用全局唯一
*
* @return XiaozhuaiSingleton
*/
public static function getInstance()
{
if (!self::$singleInstance) {
self::$singleInstance = new self();
}
return self::$singleInstance;
}
// 调用单例的方法
public function singletonFunc()
{
echo "call single ton method";
}
}
$singleInstance = XiaozhuaiSingleton::getInstance();
$singleInstance->singletonFunc();
$singleInstance2 = XiaozhuaiSingleton::getInstance();
$singleInstance2->singletonFunc();
// 校验是否是一个实例
var_dump($singleInstance === $singleInstance2); // true ,一个对象
</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=sVTcrPhe3JioU%2FFo8WH6tg%3D%3D.j4wepl19gP3y0y3gfP3G0vk8AN2mwxUqTeJVgjCSLMX3sjXJQc%2FIP5%2BpcKdSMOddaR1gqbAmA0f1dPcliWrpiYGiw4hWPYqs%2BU7MbPEBAAnvGMNCmwpEI9SuGazirtW2" rel="nofollow">理解php单例模式</a> | <a href="https://link.segmentfault.com/?enc=tiGKuNaUN3MXYOPDflG3og%3D%3D.ySkVnFUjMVe3JMsui6HugB2UB5POz8MW4JzEQnGCNmk%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
PHP进程通信
https://segmentfault.com/a/1190000009967836
2017-06-28T18:05:00+08:00
2017-06-28T18:05:00+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
14
<blockquote><p>PHP间进程如何通信,PHP相关的服务的IPC是实现方式,IPC的思想如何用到项目中。</p></blockquote>
<h2>一、linux进程间通信</h2>
<p>理解php间进程通信机制,先了解下linux进程间有哪些通讯机制</p>
<h3>1.1 历史发展</h3>
<p>linux ipc 按照历史来源主要有两大块</p>
<ul>
<li><p>AT&T的system v IPc:管道,FIFO,信号</p></li>
<li><p>BSD的socket Ipc :消息队列,共享内存,信号灯。</p></li>
</ul>
<p><img src="/img/remote/1460000009967843?w=370&h=217" alt="进程发展" title="进程发展"></p>
<h3>1.2 主要方式</h3>
<p>总结起来主要有以下六种方式</p>
<ul>
<li><p>1:管道【pipe】:主要是<code>有关系的进程之间</code>的通讯,例如ls xx |grep xx。</p></li>
<li><p>2:信号【signal】:通过<code>中间进程</code>来管理进程之间的通讯,属于比较复杂的进程间通讯方式。</p></li>
<li>
<p>3:消息队列【message】:消息的链接表,进程生产和消费消费消息队列。</p>
<ul>
<li><p>优势:克服了信号量承载的消息少,管道只能用规定的字节流,同时受到缓冲区大小的约束的问题 (而且读写是有队列的,有一个写,就只有一个能读到,比较简单,不需要同步和互斥)</p></li>
<li><p>缺点:太过简单,处理复杂情况可能会造成饥饿现象</p></li>
</ul>
</li>
<li><p>4:共享内存。多个进程访问同一个内存区。<code>最快的IPC方式</code>,但是需要处理进程间的<code>同步和互斥</code>。 同时也是<code>当下使用最广泛的IPC</code>,例如nginx,框架通讯,配置中心都是该原理。</p></li>
<li><p>5:信号量【semaphore】:主要作为<code>进程间,以及进程内部线程之间</code>的通讯手段。nginx早起的channel机制就类似于信号量</p></li>
<li><p>6:套接字【socket】:<code>不同机器之间</code>的通讯手段。处于tcp-》socket-》http之间的一个协议。</p></li>
</ul>
<h2>二、php进程通讯有哪些方式</h2>
<p>最好的语言php有哪些IPC的方式</p>
<ul>
<li><p>pcntl扩展:主要的进程扩展,完成进程的创建,子进程的创建,也是当前使用比较广的多进程。</p></li>
<li><p>posix扩展:完成posix兼容机通用api,如获取进程id,杀死进程等。主要依赖 IEEE 1003.1 (POSIX.1) ,兼容posix</p></li>
<li><p>sysvmsg扩展:实现system v方式的进程间通信之消息队列。</p></li>
<li><p>sysvsem扩展:实现system v方式的信号量。</p></li>
<li><p>sysvshm扩展:实现system v方式的共享内存。</p></li>
<li><p>sockets扩展:实现socket通信,跨机器,跨平台。</p></li>
</ul>
<p>php也有一些封装好的异步进程处理框架:例如swoole,workman等</p>
<h2>三、与php相关的IPC</h2>
<h3>3.1 nginx的IPC</h3>
<p>nginx的ipc主要有两种:</p>
<ul>
<li><p>早期:channel 机制:类似于信号,标示不同进程以及进程与子进程之间的套接字,同时具有继承关系。<br>缺点:过于复杂,也产生了过多的套接字,造成存储浪费。</p></li>
<li><p>当前主流:共享内存方式:快,写入数据少,方便。</p></li>
</ul>
<blockquote><p>具体可以参见这篇文章:写的非常好 <a href="https://link.segmentfault.com/?enc=jkuGRX%2Fl9aKqxfBgskhGig%3D%3D.cc9VVaL8A2DZhuPSh506e91AmDeiM%2B7dzvNH2DZueSkO4VWG3I%2FDwahp29z%2B%2FKY%2BKhwMQdkisutHDLoxBQ4kYTmwJzCD3a2LaiU2AK%2FV%2BNSm%2BwcYnjc0CRVzwxFX5KIi" rel="nofollow">https://rocfang.gitbooks.io/d...</a></p></blockquote>
<h3>3.2 apache的IPC</h3>
<p>apache:<a href="https://link.segmentfault.com/?enc=UVPmtweIOmNogzliMAvENw%3D%3D.txvKMqwcrZDHlO%2BEQ%2B1d3ZInFSOt9MpxZAQ3kRcc5KfvZaw4cLNVEnxjsO1DZg8w" rel="nofollow">https://arrow.apache.org/docs...</a></p>
<h2>四、实际应用中的IPC</h2>
<p>在平时的项目中,类似于php和linux的IPC的思想大量存在,深入理解。</p>
<ul>
<li><p>socket方式:不同项目间通讯,跨机微服务等等,也是使用最广泛的IPC。</p></li>
<li><p>共享内存方式:配置中心,公共数据库,甚至git都可以看做共享内存的衍生;`共享内存就必须要注意同步和互斥。</p></li>
<li><p>cache :是共享内存和管道结合的思想</p></li>
<li><p>项目流式架构:管道的方式,可以大量的节省空间和时间的通讯方式。</p></li>
</ul>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=SZQ65RqMfIn6oXwjUW60Sg%3D%3D.pi2NrSPwU8kMHflYE5Fo6Sd%2BDyOBNiFS%2BH6rlnukgvdMEcHkOnY5gkdXf%2FezhofoADthUSN7ON2Urs%2BxhVXaGxw2M8lcVQ9lY%2FaTROhYP5M%3D" rel="nofollow">php进程通信</a> | <a href="https://link.segmentfault.com/?enc=NrIF0HuGDw%2BRlrxkVTzVYQ%3D%3D.CIPATBEHssNE7V098SvRWewkML8AWVug6denINiWhhQ%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
CodeIgniter 性能优化
https://segmentfault.com/a/1190000009876214
2017-06-21T19:09:43+08:00
2017-06-21T19:09:43+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
23
<blockquote><p>背景:部署一套PHP微服务接口,需要兼顾性能,开发效率,扩展性。权衡后选择了CodeIgniter;同时优化框架的默认启动项,在qps1000+的压力下整个<code>启动时间优化到5ms</code>左右。</p></blockquote>
<h2>一、选型</h2>
<ul>
<li><p>背景:使用php作为微服务的接口,具有一定的性能要求和并发要求。</p></li>
<li>
<p>方案:</p>
<ul>
<li><p>1:选一个轻量的php框架。具有简单高效的路由,模块化即可。</p></li>
<li><p>2:在框架的基础上,自定义的优化</p></li>
</ul>
</li>
</ul>
<p>从以下几个角度做了简单的比较</p>
<h3>1.1 不同框架的性能</h3>
<p><a href="https://link.segmentfault.com/?enc=8rCY6zszgZBqHna5WoF7bw%3D%3D.eZpBLe3nFYhf0H9YwBM9%2Bl85zliW%2B%2FtOK2M1vvVmDyQQZf9nB0cvXWnw0CGAwLprWqRctVZMbEJ6%2F%2BY%2BNFCMBw%3D%3D" rel="nofollow">https://www.ruilog.com/blog/v...</a><br><a href="https://link.segmentfault.com/?enc=mpf%2BHwMs%2B%2Fiz85j3wfMW0A%3D%3D.yW1RvCcz4hQZzYXdvTFUwJ2uN3Dbcbq6VA2fA5Ep6dM2gaRAKt0gI432axIQRMPKZUumvh5%2FyIs9%2BPRIZCi5lA%3D%3D" rel="nofollow"><img src="/img/remote/1460000009876219?w=1304&h=464" alt="ci performance" title="ci performance"></a></p>
<p>列举了很多数据,除了直接写php之外,ci和lumen的并发和性能不错,在考虑范围内。</p>
<h3>1.2 从流行度上看。</h3>
<p>github排名前四的是laravel,symfony,ci,yii2。最火的是laravel毋庸置疑。但前四均在考虑范围内</p>
<p><a href="https://link.segmentfault.com/?enc=42XuX6sH7UuGswxv3YQ0tA%3D%3D.%2BxrrQ8kPLx1gQe8nsNe4PRH9FsL4IHtuUCISun04zEtFpFmoqk0%2F%2B1va11PbGHtlvu0teY4jAA9bm8WHEX429g%3D%3D" rel="nofollow"><img src="/img/remote/1460000009876220?w=1398&h=1282" alt="ci github" title="ci github"></a></p>
<h3>1.3 从文档来看。</h3>
<p>laraval,ci,yii的中文社区都还不错。</p>
<p>综合考虑之后,在满足功能的情况下,选择性能最好,也易入手的codeIgniter作为基础框架。(<code>整个包才2.5M,删除了web文件夹后更小</code>)</p>
<h2>二、30ms+到10ms[粗读代码]</h2>
<p>框架的基本部署,直接参考官网:<a href="https://link.segmentfault.com/?enc=UAsZDl9otv9aeTur9rzgDw%3D%3D.w7uIqmpepOHpIRy9NybN%2FABDXIgrNXjXN09jO3%2Fp4dA%3D" rel="nofollow">https://codeigniter.org.cn/</a></p>
<h3>2.1 问题描述</h3>
<p>配置了数据库之后,添加了一个默认的controller,一个model,默认加载时间竟然30ms+—_-。瞬间懵逼了,nginx+fpm也就1-2ms,框架竟然30ms,肯定那里配置错了,决定沿着路由追一下。</p>
<h3>2.2 问题追查</h3>
<p>沿着ci的路由顺序追查,从index入,一步一步卡时间。[<code>括号内为运行到的总计数</code>]</p>
<p>-》index.php [<code>1ms</code>]<br>-》core/CodeIgniter.php<br>-》加载常量[<code>1ms</code>] <br>-》加载common(包括log,show,error,is_https等)[1ms]load hooks [<code>2ms</code>] <br>-》加载autoload 方法 <br>-》加载benchmark <br>-》实例化hooks <br>-》实例化pre_controller<br>-》实例化post_controller_constructor<br>-》实例化post_controller<br>-》实例化post_system<br>-》加载config [<code>3ms</code>]<br>-》加载扩展mbstring,iconv,hash,stardard<br>-》load 组件utf8,uri<br>-》load router [<code>4ms</code>]<br>-》load output,input,lang[<code>5ms</code>]<br>-》autoload package,cinfig,helper,language,driver,lib,model,db,cache<br>-》404 &empty$...handle [<code>31ms</code>]<br>-》controller remap(测试性能写了个remap)[<code>35ms</code>]<br>-》controller 业务逻辑 [<code>35ms</code>]</p>
<p>这个ci的加载过程之后,基本一幕了然,在autoload里面耗时25ms。然后对autoload里面8个组件一个一个分析。</p>
<h3>2.3 问题原因</h3>
<p>分析完之后,处理特别简单,下面这行代码</p>
<pre><code>$autoload['libraries'] = array('database');</code></pre>
<p>ci 出于单例和复用角度考虑,选择<code>默认加载database</code>,也就是mysql在框架初始化的过程中默认初始化了。<br>mysql链接在并发情况下,init基本上要耗费10-30ms。直接干掉。<br>干掉之后,压测基本上在10ms左右。</p>
<h2>三、从10ms到5ms [细看代码]</h2>
<h3>3.1 优化目标</h3>
<p>优化了配置等之后,ci在高并发下依然有10ms左右的加载时间,需要结合自身逻辑优化下,删减掉部分不需要的功能和组件。</p>
<h3>3.2 代码分析</h3>
<p>之前在追查问题的过程中,粗读了一遍代码的流程。<br>而需要进一步优化,就需要细看每个模块函数的功能,干掉不需要的,逐步优化。</p>
<p>-》index.php</p>
<ul>
<li><p>作用:加载了部分全局变量,文件路径等入口</p></li>
<li><p>优化:干掉了整个web文件,调整了部分路径</p></li>
</ul>
<p>-》core/CodeIgniter.php</p>
<ul>
<li><p>作用:ci的核心文件,基本上加载了整个模块</p></li>
<li><p>优化:进入内部优化</p></li>
</ul>
<p>-》加载common.php</p>
<ul>
<li><p>作用:框架特别基本的一些函数log,show,error,is_xxx等,800行左右代码</p></li>
<li><p>优化:暂时未处理</p></li>
</ul>
<p>-》composer autoload func <br>-》加载benchmark</p>
<ul>
<li><p>作用:benchmark性能追查工具,设置了全局的开始和结束时间</p></li>
<li><p>优化:<code>直接干掉</code>,全局处理。但是性能需要卡,就在index中初始化了一个入库的timer</p></li>
</ul>
<pre><code>// 定义全局开始追查
list($msec, $sec) = explode(' ', microtime());
define('START_TIME', (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000));
// 定义全局uuid
define('UUID', uniqid('xxx', true));
</code></pre>
<p>-》实例化hooks和预变量</p>
<ul>
<li><p>作用:设计特别棒,对于一些hook或者针对不同模块的预加载函数。</p></li>
<li><p>优化:<code>直接干掉</code>,7,8个hook后期用到再针对性添加</p></li>
</ul>
<p>-》加载config</p>
<ul>
<li><p>作用:对应的是ci的get_config等函数,加载了base_url,uri,cache path等 。</p></li>
<li><p>优化:<code>直接干掉</code>,全局变量占用需要自己加就行,这种不可控的干掉</p></li>
</ul>
<p>-》加载扩展mbstring,iconv,hash,stardard</p>
<ul>
<li><p>作用:一些额外的扩展。</p></li>
<li><p>优化:<code>直接干掉</code>,试了下干掉对功能不影响,ci写的太全面了,后期可以通过加载自己的lib弥补。</p></li>
</ul>
<p>-》load 组件utf8,uri</p>
<ul>
<li><p>作用:uri路由方式处理,utf8的处理。</p></li>
<li><p>优化:<code>直接干掉</code>。</p></li>
</ul>
<p>-》load router [<code>4ms</code>]</p>
<ul>
<li><p>作用:路由。</p></li>
<li><p>优化:<code>干不掉</code>。</p></li>
<li><p>额外:laravel 据说是使用COC最好的框架,看了下ci的router也不错,基本上都是遵循COC</p></li>
</ul>
<p>-》load output,input</p>
<ul>
<li><p>作用:output和浏览器交互输出的处理组件,input包括获取数据array_merge等等。</p></li>
<li><p>优化:<code>干掉</code>,需要自己写</p></li>
</ul>
<p>-》autoload package,cinfig,helper,language,driver,lib,model,db,cache</p>
<ul>
<li><p>作用:各种各样的组建了。</p></li>
<li><p>优化:全<code>不</code>加载</p></li>
</ul>
<p>-》404 &empty$...handle</p>
<ul>
<li><p>作用:异常处理。</p></li>
<li><p>优化:加载</p></li>
</ul>
<p>-》controller remap(测试性能写了个remap)</p>
<ul>
<li><p>作用:指向其他controller。</p></li>
<li><p>优化:无</p></li>
</ul>
<p>-》controller 业务逻辑 </p>
<p>到此将整个框架过程优化完成,初始一下4-5ms感觉还不错。</p>
<h2>四、压测数据</h2>
<h3>4.1 高并发压测</h3>
<p>2000qps+ ,均值约6ms</p>
<h3>4.2 长时间高负载压测</h3>
<p>1500qps 10min,均值约6ms</p>
<p>问题:分析了部分数据的超时分布,约千分之二超过30ms,如下图【这块需要之后优化】<br><a href="https://link.segmentfault.com/?enc=bJzvi8b8g3vnJZTRCPvJ1Q%3D%3D.M%2B3tT2o0DsBpQ7deCt7D4iGDMnXmTQqE8E7tRUh7Ips6VACbnCK7IhuZOU3TGDJuwifdaDPbSyp4ZFzGL30h99rOCIQKbC9Avi51oNCNi48%3D" rel="nofollow"><img src="/img/remote/1460000009876221?w=2486&h=294" alt="time_distribute" title="time_distribute"></a></p>
<h3>4.3 无限发压</h3>
<p>nginx + php 均值:17ms<br>框架部分的均值:9ms 也基本上满足需求<br><a href="https://link.segmentfault.com/?enc=jhNJEQmohyWS7NxTWsEJaw%3D%3D.Le23UgaiZCQZH2aIa65dmK1M0QXOCXM2KmuIicX9mPn8y6V81JyH%2BdVmLSRtjd1DN73IoWeQNN8y00TeqWPbYw%3D%3D" rel="nofollow"><img src="/img/remote/1460000009876222?w=2534&h=216" alt="nginx_time" title="nginx_time"></a></p>
<h2>五、汇总</h2>
<p>ci 是一个比较优秀的轻量级MVC框架,可以用来,业能否支撑1000-2000pqs的业务接口。</p>
<p>最后来一张ci的路由图<br><a href="https://link.segmentfault.com/?enc=aXKFCF8fY4hDQ%2FcVrNaGDA%3D%3D.cnGecSlbWrBdEpp%2BmPPG4tcOfLEO4Yb4u99b1SGZ5hGR%2FDEOM2XHVwwVWi8jlpZIBvfkrWXMEwP3UBrTKcLxxw%3D%3D" rel="nofollow"><img src="/img/remote/1460000009876223?w=3000&h=2396" alt="all" title="all"></a></p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=rsbmfNizWR47F3Xis9qUNw%3D%3D.0uSxpBjkqnpjhY2MLnaogukDbAxyNGO0sjSiGYs0ysuKIv9YZnRZNWMmzlZ6LLvVs7VUie7PHxk0O2ywYiqdGJ0bGG2jgsp%2F501JQoXFbgvwLJgwL7YEvk8IMgmYm1SK" rel="nofollow">【CodeIgniter 性能优化</a> | <a href="https://link.segmentfault.com/?enc=h5w5kRfzwXyBUoIlrhBSBQ%3D%3D.zNiQeJc9ExwEX1dhtkq7ycPcPObMOewPK78wQz3RQ0o%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
【nginx学习一】基本原理初探
https://segmentfault.com/a/1190000008702106
2017-03-15T13:09:56+08:00
2017-03-15T13:09:56+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
2
<blockquote><p>由于性能问题,需要将 apache + php5.2 升级到 nginx + php7,对于nginx的性能和热加载早有耳闻,why nginx so diao。小拽进行了初探,<code>有任何疑问或不准确的地方,欢迎直接开炮</code>!!!</p></blockquote>
<h2>一、nginx现状</h2>
<p>nginx 是当前的<code>使用最广泛的webserver ,支持http正向/反向代理,支持TCP/UDP层代理</code>,来看下netcraft的数据</p>
<p><a href="https://link.segmentfault.com/?enc=rBYGz7wX5bQQdptJRR3znA%3D%3D.EgkKKK66G4Kbx%2BW1KnEstRigtSKoltNF%2FAPd4Bg3vwpZFRaJMq1rQP3Vc5AZyjHeY1Gkv91hn%2FMaBr%2ByVhGHVQ%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702109?w=1494&h=696" alt="webserver_all" title="webserver_all"></a><br><a href="https://link.segmentfault.com/?enc=bcjcoeX2r7ugyeG12sJWVg%3D%3D.k2uPuHkm7E00vJfDVuPLgk%2B3rE4dG3f6Q7Iu72hqEAO8BqRFIs0LRzXW6RW1g5tFtFgK0CCDcDSG3q%2BNgXymqw%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702110?w=1484&h=774" alt="webserver_top" title="webserver_top"></a></p>
<p>nginx在<code>全部网站中占比达到18%</code>,在<code>top millon busest 达到28%</code>,而且一直在增加。当下最时尚的webserver非nginx莫属</p>
<blockquote><p>更全数据可以参考:<a href="https://link.segmentfault.com/?enc=GBrDeLEWP6jPApNjdbTLRA%3D%3D.yWHSyC9Zn%2FnvfWKLhvld%2FF7Mt3d87qopk3lRwMRYCLSTLBfqNx0378I4cq145CpWhj7pLE1WC8NO0rRb0eKaZb9sh%2B0LtH8QoyVBR6djkPFO6D6XHFJae0ivEJArSXOT" rel="nofollow">【netcraft】</a></p></blockquote>
<h2>二、nginx的特点</h2>
<p>深入了解nginx之前,先泛泛的了解下nginx的几个特点</p>
<ul>
<li>
<p>性能好</p>
<ul>
<li><p>非阻塞IO/高并发,支持文件IO</p></li>
<li><p>多worker,thread pool</p></li>
<li><p>基于rbtree的定时器</p></li>
<li><p>系统特性的持续支持</p></li>
</ul>
</li>
<li>
<p>功能强大</p>
<ul>
<li><p>webserver/cache/keepalive/pipeline等等</p></li>
<li><p>各种upstream的支持【fastcgi/http/...】</p></li>
<li><p>输出灵活【chunk/zip/...】</p></li>
<li><p>在不断的发展 http2,tcp,udp,proxy...</p></li>
</ul>
</li>
<li>
<p>运维的友好【这个对于开发和部署很重要】</p>
<ul>
<li><p>配置非常规范【个人认为:约定及规范是最好的实践】</p></li>
<li><p>热加载和热更新【后文会详细介绍,能在二进制的层面热更新】</p></li>
<li><p>日志强大【真的很强的,很多变量支撑】</p></li>
</ul>
</li>
<li><p>扩展强大</p></li>
</ul>
<p>下图是nginx、apache和lighttpd的一个对比。系统压力,内存占用,upstream支持等多个方面都非常不错<br><a href="https://link.segmentfault.com/?enc=qag8WVQ5Nzp9XgCxypOxNg%3D%3D.niguuWpj8sVeO9kygd8tJ4isJ%2FsWCWnwfJ8FB0CYmrZKdOAxsbUTD3JUMuMHxemAvYzaQ%2FsJNwPYFRIcX6%2Fexg%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702111?w=1692&h=800" alt="nginx对比图" title="nginx对比图"></a></p>
<h2>三、nginx的核心机制</h2>
<h3>3.1 运行方式</h3>
<p>一句话简述nginx的运行方式:<code>master-worker多进程模式运行,单线程/非阻塞执行</code></p>
<p>如下官方图:nginx 启动后生成master,master会启动conf数量的<code>worker进程</code>,当用户的请求过来之后,由不同的worker调起<code>执行线程</code>,非阻塞的执行请求。这种运行方式相对于<code>apache的进程执行</code>相对轻量很多,支撑的并发性也会高很多。<br><a href="https://link.segmentfault.com/?enc=teNpDSI%2BJN2DwiBmmBxSEQ%3D%3D.%2B9ehN5ssfZVB3oP0yV5fw8MfwCOEno%2FfAYIszj68A62PhC5SGf%2BHQq0YrBrgsnTKQIncbteb5MG7L%2FYCW%2B%2Fybg%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702112?w=1284&h=804" alt="nginx 基本框架" title="nginx 基本框架"></a></p>
<h3>3.2 进程管理</h3>
<p>nginx是master-worker进程工作模式,那么nginx是如何管理master启程,怎么做到热加载的?</p>
<h4>3.2.1 配置热加载</h4>
<p>官方图很赞,在<code>更换配置之后,master生成新的worker,直到原有的worker全部任务结束kill掉之后</code>。从现象上作证,也就是在relaod配置之后,短时间可能出现超过conf数量的进程,更新完成后,进程会完全改变。</p>
<blockquote><p>不更新、直接替换,这种设计思路在代码部署中也很常见,包括mysql迁移,代码更新,服务尝试,很值的学习。</p></blockquote>
<p><a href="https://link.segmentfault.com/?enc=%2B1D6rptmLtKnIMRqYGjJIQ%3D%3D.79VaQm8Qn4Tza%2B2cIE8pwXWTf%2BEIMApf%2FmEAJkTu%2B8KpTVlvKy6ARG%2BSzzLUAPDSnW3n8Zrf2lUICNKXmw3x%2Bg%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702113?w=1444&h=588" alt="nginx 配置热加载" title="nginx 配置热加载"></a></p>
<h4>3.2.2 版本更新热加载</h4>
<p>了解了worker的热加载之后,理解master就非常简单了,通过信号控制,同时存在两个master,逐步替代。<br><a href="https://link.segmentfault.com/?enc=iWWz3JEqnR2xK4frlDz6Hg%3D%3D.wW6i455n7ri5Oz7ljw4fg3BSY8OkKVKguh70sYpzqSjijgraBfy6TyCT%2BceSjBgnLjsMJVpHDihPQZerUXCG0w%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702114?w=1450&h=524" alt="nginx 线程热加载" title="nginx 线程热加载"></a></p>
<p>关于replace过程中如何细节控制一致性,稳定性,信号控制,log控制等等,敬请期待小拽的进一步探索!</p>
<h3>3.3 处理流程和模块</h3>
<p>启动进程后,请求在nginx内部是如何流转的,nginx内部包括哪些模块?</p>
<h4>3.3.1 worker处理过程</h4>
<ol>
<li><p>【post header】请求到达后首先读取header,log中request time初始时间便从此开始。</p></li>
<li><p>【rewrite】请求相关的配置和参数</p></li>
<li><p>【pre-access】预处理阶段,频率控制,高频绝句</p></li>
<li><p>【acess】权限控制,白名单,403,access deny ,静态文件开放等均有这个模块产生</p></li>
<li><p>【content】这个模块会调用upstream产生内容,这个阶段最重要<code>此处调起了工作线程,调用fastcgi,http,以及各种操作产生内容均在此处</code>。性能优化可能需要确认程序执行时间,对应access log中的upstream time 由此产生,记录了nginx中程序运行的全量时间,而request - upstream 就是网络传输和预处理时间。</p></li>
<li><p>【filter】内容过滤,包括gzip压缩,返回等在此处</p></li>
<li><p>【log】日志的产生</p></li>
<li><p>【重定向】<code>没有这个模块,所有的进行智能单向走,有了这个,在任何阶段都可以产生返回</code>,例如client主动阶段产生499的log,过程可能就是1-》2-》8-》7 over</p></li>
</ol>
<blockquote><p>摘自某ppt的一个图,如侵权,请尽快联系小拽</p></blockquote>
<p><a href="https://link.segmentfault.com/?enc=qruXyS3EVPA7UO86MyvSJQ%3D%3D.YueArqcjXgQmk%2BD0hBYBRwmeK8kXyEtXsEcri58Vs9trNkstCCjp5yjD%2FFhZ5MvDXQiRoiq4jGmphrLDmR2jJw%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702115?w=2060&h=892" alt="nginx 主要流程和模块" title="nginx 主要流程和模块"></a></p>
<p>各个阶段的主要状态机可以参考:<a href="https://link.segmentfault.com/?enc=muyoZiXdoWlJ9EGM8tpuhA%3D%3D.Xta%2FvPz61MTBZ13R7rXhcWSGVH880vPi4Ucot7NdYxVG5268HAD6nceItsoauiuN" rel="nofollow">【跳转】</a></p>
<h3>3.4 请求管理</h3>
<p>了解了worker的工作模式和worker的内部主要模块,那么worker是如何管理请求的?</p>
<h4>3.4.1 任务调度</h4>
<blockquote><p>官方阐述:It’s well known that NGINX uses <code>an asynchronous, event‑driven approach to handling connections</code>. This means that instead of creating another dedicated process or thread for each request (like servers with a traditional architecture), it handles multiple connections and requests in one worker process. To achieve this, NGINX works with sockets in a non‑blocking mode and uses efficient methods such as epoll and kqueue.</p></blockquote>
<p>核心词:<code>异步,事件驱动,链接控制</code></p>
<p>解释的很清楚,<code>nginx并不是通过每个请求都创建线程,而是通过内部管理的调度分配</code>。<br>如下图:此处不翻译了,大家直接看原版<br>epoll详解:<a href="https://link.segmentfault.com/?enc=bBLB%2Bv3y6IKrs%2FIA%2FBptzA%3D%3D.6%2F%2BOlYAVvRi26xWRWQpTt0nT%2F9Tj4pXtLXJBybTQ%2Fo4x8xeZ%2BDENZpUbYvw5sbmd4%2F4ymdvJ0A36BAaNKh%2FJZQ%3D%3D" rel="nofollow">【跳转】</a></p>
<p><a href="https://link.segmentfault.com/?enc=m6CC7fu7BFX%2Few%2FvLiaa7w%3D%3D.tG2Ncv%2FwTLTom8nsRqIe2xYYLxwUldq%2BbWAiT5evDgMlCsGuBfm5mCqaT%2B9lE%2F8mX336VTjr6rAJMCi43b2EHQ%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702116?w=1182&h=938" alt="nginx 任务基本框架" title="nginx 任务基本框架"></a></p>
<h4>3.4.2 线程池</h4>
<p>官方说明</p>
<blockquote><p>Let’s return to our poor sales assistant who delivers goods from a faraway warehouse. But he has become smarter (or maybe he became smarter after being beaten by the crowd of angry clients?) and hired a delivery service. Now when somebody asks for something from the faraway warehouse, instead of going to the warehouse himself, he just drops an order to a delivery service and they will handle the order while our sales assistant will continue serving other customers. Thus only those clients whose goods aren’t in the store are waiting for delivery, while others can be served immediately.</p></blockquote>
<p>小拽认为简而言之:<code>结合实际情况,除了空闲被动给,更多的通过事件驱动主动要</code>,通过这种方式在执行资源紧缺的情况下,达到一个执行资源的优化部署,如下图。<br><a href="https://link.segmentfault.com/?enc=YDRd3hZ4%2F%2FHu%2F02HZYyauQ%3D%3D.8COzXCV%2FpgWmMWvdItaxf9J1g49qyWIzdvfK%2FyoRz3t6Bk05Qu%2F4fk4t%2FF0NwixlMzkigE4rqRno2NB4gFnRaw%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702117?w=1400&h=1140" alt="nginx 任务基本框架" title="nginx 任务基本框架"></a><br>线程池官网详解:<a href="https://link.segmentfault.com/?enc=DVsaj2%2BLaAgO5JWfNDP9BQ%3D%3D.HWC%2BlY7ls1GBrBWO8J%2F6S5LBD8hOu0tvcOYnkNmp88aXBR9FZdbf0%2BlUBdvCqoLzjrun3Hc70hN%2BLPVoL0xfzg%3D%3D" rel="nofollow">【跳转】</a></p>
<h4>3.4.3 事件调度</h4>
<p>请求的具体调度基于事件,例如网络IO,磁盘IO,定时器等均可以对事件进行阻塞,当阻塞的事件空闲时,发出调度请求,完成处理。<br>需要额外提一下,<code>nginx的定时器基于rbtree,红黑树的快速插入和查询保证了nginx事件调度的高效性</code><br>事件框架的处理模型<br><a href="https://link.segmentfault.com/?enc=GLl0Ik8kabni02cXvuGYEQ%3D%3D.qwNKnFOAysa7IAsR9yY3F%2BJdOI6NxrvZPSKFvp%2Bt6xeBGL0%2F1E%2BOvgrIgPXXmF3bI97ZKhMj5LWrr82t54GejQ%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702118?w=968&h=808" alt="nginx 事件" title="nginx 事件"></a><br><a href="https://link.segmentfault.com/?enc=Rq3ilFaJjmO4yC5Umz74DA%3D%3D.HXb9kBbLFbAxZA5a46A8gX%2BOAz6Lj%2BIaMpSAS6SQ0BPMM4dYubdX4aHfR3eDDxJyTVgfRmj%2BCw85WlwCeeuVtg%3D%3D" rel="nofollow"><img src="/img/remote/1460000008702119?w=1030&h=732" alt="nginx epoll" title="nginx epoll"></a></p>
<h2>简单总结</h2>
<ul>
<li><p>性能:nginx 工作模式是master-worker进程方式,执行请求是有更轻量线程完成。</p></li>
<li><p>热加载:nginx 替换非更新的方式是nginx热加载的本质</p></li>
<li><p>功能强大:nginx upstream是在线程层面调度,兼容多种,所以可以扩展很多功能强大</p></li>
<li><p>处理流程:主要的流程过程和模块分离清晰。</p></li>
<li><p>请求处理:通过自身的管理,线程池,异步事件驱动等当来完成任务调度</p></li>
</ul>
<p>再次强调:初探nginx,有疑问或不准确的地方,请直接开炮!!!</p>
<h2>参考文章</h2>
<ul>
<li><p>netcraft:<a href="https://link.segmentfault.com/?enc=NFhkX5hdFm92h0NRajhyPw%3D%3D.ctGvuNXfafzUxcHgUW21L%2BqGMVagd5Ccbwq5gSbdi1mRqfK2x1BjAKyeR2WRpYJVVJC28SbfAfzI9B1J8rHPrBMr8JUq4ioYiD6g32lsUIZtzKZZsReUN%2FS3EEcu1SaK" rel="nofollow">https://news.netcraft.com/arc...</a></p></li>
<li><p>nginx的线程调度设计:<a href="https://link.segmentfault.com/?enc=mVvjxtfT%2Fu%2BjZTsOmIUQMA%3D%3D.TfMamO115N6mygaC8xiSF6LvqG643Aw3jvXzq2hxob9JpH7%2F%2FeIT0E65vmEJxNN7Ysz0ls0It0qQdB%2BBL1RVNBiBCzBTdbmTOigm0zsIdn0%3D" rel="nofollow">https://www.nginx.com/blog/in...</a></p></li>
<li><p>epoll详述:<a href="https://link.segmentfault.com/?enc=GzlSr%2Br6sq8HSt8t21LldA%3D%3D.5I6is%2BpHBnSiWm%2FAC5TEZzYxD7dV96sYiCtDCsGgMUvHhE5cz1%2BtWQC6BYUZBV0IHx9UL7RrqzeCN9J8sHaO8w%3D%3D" rel="nofollow">http://man7.org/linux/man-pag...</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=kWzYly0NXNjVgdZXDe%2BkLQ%3D%3D.f1l9tsB5vPkBbm2Klfi2HS3rv7clp2YeaK7c7u%2BEuQYqmPsxw3Yuq71kCsNlLsZ7" rel="nofollow">http://yaocoder.blog.51cto.co...</a></p></li>
<li><p>线程池:<a href="https://link.segmentfault.com/?enc=4nSwRmmrmMU3x3RyNRaZJw%3D%3D.fmDOK8FCdDs%2B%2Bg8g655etBqTCojpHwlgW%2BY3sBaDYc2FiaZXYHf%2BfubPppU0BlrRgJxQcNBpAejK9QdS6jGSdw%3D%3D" rel="nofollow">https://www.nginx.com/blog/th...</a></p></li>
</ul>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=s3DpmGhLu24jOJw%2BxVo3fw%3D%3D.hdw4SOwBOwk8i%2Fr9Cx5B5fPF%2BrHv%2BwWerwHiYtKa4eE9K8ZUpzYJwhDpV5b0hloF" rel="nofollow">【【nginx学习一】基本原理学习 </a> | <a href="https://link.segmentfault.com/?enc=o6I%2BgLVDkyFS48ID80oMyQ%3D%3D.sJu9n8O7CydL9ke32ctog1V5fSbPwxG0xL66SwHZdk0%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
【redis学习三】简单高可用redis架构实践
https://segmentfault.com/a/1190000008262643
2017-02-06T13:20:10+08:00
2017-02-06T13:20:10+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>背景:支撑线上千万级别的天级查询请求,要求高可用。</p></blockquote>
<h2>一、方案调研</h2>
<h3>1.1 redis版本选择</h3>
<p>redis当前主流版本是redis 2.x 和 redis 3.x,3.0对集群支持比较不错,官方解释如下:</p>
<blockquote><p>Redis是一个开源、基于C语言、基于内存亦可持久化的高性能NoSQL数据库,同时,它还提供了多种语言的API。近日,Redis 3.0在经过6个RC版本后,其正式版终于发布了。Redis 3.0的最重要特征是对Redis集群的支持,此外,该版本相对于2.8版本在性能、稳定性等方面都有了重大提高。</p></blockquote>
<p>综合考虑之后扩展性和稳定性之后,选择版本 <code>redis 3.2.3-1</code>版本进行部署</p>
<h3>1.2 是否选择搭建集群</h3>
<p>是否搭建集群关键要看单机是否能够满足业务需求,做了个简单的数据评估。</p>
<h4>数据量评估</h4>
<ul>
<li><p><strong>测试</strong>:单机写入2000w业务数据,占用内存1.5g,本机126g内存</p></li>
<li><p><strong>评估</strong>:单机的稳定数据承载量:2000w <em> (126/1.56)</em> 0.6 = 96923w</p></li>
<li><p><strong>结论</strong>:9T 的数据承载量,远超当前千万级别的数据量</p></li>
</ul>
<h4>性能评估</h4>
<ul>
<li>
<p><strong>测试</strong>:简单压测了下</p>
<ul>
<li><p>写操作 1000w,80% 在20ms一下 ,98%在30ms,最大218ms,qps 5w/s,总耗时197s</p></li>
<li><p>读操作 1000w,97% 在10ms一下 ,99.99%在24ms,qps 6w/s,总耗时160s</p></li>
</ul>
</li>
<li><p><strong>评估</strong>:当前的调用量在千万每天,qps的话在百/s。</p></li>
<li><p><strong>结论</strong>:当前单机的redis完全满足需求</p></li>
</ul>
<p>因此:在单机远能够满足当下业务需求的情况下,决定不采用的集群的方式来部署redis,减少技术债务风险。</p>
<h3>1.3 初定方案和架构图</h3>
<p>选定了版本和基本部署方案之后,主要考虑服务的<code>容灾和稳定性</code>,经过思考之后采用<code>采用极简的主从从结构,001实时同步数据002和003;001读写,002,003只读</code>,机构图如下</p>
<p><a href="https://link.segmentfault.com/?enc=W72%2FPZVL%2BScNkC4WAiiI2Q%3D%3D.W5x89qpwC0NP1GxZ47sSwKlv67VQE59xMy95bQyVSP7NPzYRqYemIPyOTt9dY%2FkzqDBAbv%2BiEbBLRhmczD7q2A%3D%3D" rel="nofollow"><img src="/img/remote/1460000008262851?w=800&h=705" alt="redis 框架架构" title="redis 框架架构"></a></p>
<h2>二、实现过程</h2>
<h3>2.1 redis安装</h3>
<p>此处略去,参考官方文档 <a href="https://link.segmentfault.com/?enc=iI78%2BCAtTeQARfo5BtGs4g%3D%3D.PPVrJQD510YucbekAwZMWn8Xyvc%2FVJ%2F6GvyuT%2FGoM7s%3D" rel="nofollow">https://redis.io/</a></p>
<h3>2.2 配置读写master</h3>
<ul>
<li><p>修改端口:port 【目的:简单的修改默认端口是最好的防攻击】</p></li>
<li><p>添加密码:pwd</p></li>
<li><p>关闭压缩:rdbcompression no 【硬盘最够,降低cpu的能耗更利于提升性能】</p></li>
<li><p>开启守护进程:daemonize yes 【master开启守护,增加稳定性】</p></li>
<li><p>关闭protect-mode :允许他机器访问</p></li>
<li><p>添加白名单:bind xxx</p></li>
<li><p>修改log地址,pid地址和数据存储地址:logfile pidfile 【便于维护和安全】</p></li>
<li><p>添加慢查询:slowlog-log-slower-than 500 【根据业务需求,便于优化】</p></li>
<li><p>最大内存限制:maxmemory 【考虑稳定性和性能,一般不超过最大内存的60%】</p></li>
</ul>
<h3>2.3 配置只读slave</h3>
<ul>
<li><p>同master</p></li>
<li><p>设置主库:slaveof ip:port</p></li>
<li><p>主库密码:masterauth masterpwd</p></li>
<li><p>只读:slave-read-only yes</p></li>
</ul>
<h3>2.4 启动测试</h3>
<h4>启动主库写入数据</h4>
<p><a href="https://link.segmentfault.com/?enc=PGXUbNLJjmRajlYmbcBk5A%3D%3D.ND8Zq4mH16EeuA1G3lClrjnpzASOzCndksKnYB8KL9m93xNN16HI25lc5mYtx5c%2Bhap2qvAXjoKXmThcJMFEDg%3D%3D" rel="nofollow"><img src="/img/remote/1460000008262852?w=355&h=204" alt="redis 写入主库" title="redis 写入主库"></a></p>
<h4>进入从库查看</h4>
<p>最初没有数据,主库写入之后,从库去到数据</p>
<p><a href="https://link.segmentfault.com/?enc=iCQoCPIbgEwdL3QHJsK2%2Fw%3D%3D.UPjTZSfIaI%2FF7EfVLuUxCQ9MrkUw5vZoSbYik8QMyPBwHGSxH2odVsVV0tiPCwp38nFLxICFTx9MHavHsnNdtw%3D%3D" rel="nofollow"><img src="/img/remote/1460000008262853?w=519&h=91" alt="redis 写入从库" title="redis 写入从库"></a></p>
<h4>查看log确认过程</h4>
<p><a href="https://link.segmentfault.com/?enc=UlkEZ4mbGDHFHQznuLLlQA%3D%3D.MyfiOKZ0lpNsGWQBolko2C49ea%2BWhZhs9yJ%2FZ%2Bms07tex%2FdNYuWtpmrIXjCrJFQqlUcgzXbZo5BJEd5RxaYnXw%3D%3D" rel="nofollow"><img src="/img/remote/1460000008262854?w=511&h=135" alt="redis log" title="redis log"></a></p>
<h2>三、架构能力评估</h2>
<h3>3.1 容灾能力</h3>
<ul>
<li>
<p>主动容灾</p>
<ul>
<li><p>备份:master 全量备份,slave全量备份。</p></li>
<li><p>备份安全:本机保存,hadoop同步保存一份。</p></li>
<li><p>监控和探活:监控机分钟级探活和预警</p></li>
</ul>
</li>
<li>
<p>被动容灾:</p>
<ul>
<li><p>slave 宕机:重启之后直接从master恢复</p></li>
<li><p>master 宕机且硬盘数据为损坏:重启后数据自动恢复且和从库一致。</p></li>
<li><p>master 宕机且数据损坏:删除损坏数据,使用slave1的数据恢复,保证数据一致。</p></li>
<li><p>master 和slave 1 同时宕机:slave2 保证读正常,业务不影响,利用slave2 数据备份恢复master,启动slave 即可</p></li>
<li><p>三台全宕机:服务挂掉,从hadoop获取数据恢复服务。</p></li>
</ul>
</li>
</ul>
<h3>3.2 性能评估</h3>
<p>压测数据,参见方案选择,完全hold住。</p>
<h2>四、问题思考</h2>
<h3>4.1 内存清理策略</h3>
<p>暂时采用:<br>noeviction -> 谁也不删,直接在写操作时返回错误。<br>之后采用:<br>volatile-lru -> 根据LRU算法删除带有过期时间的key。 最少使用算法删除。<br>如果达到内存限制,手工清理,通过监控脚本监控内存情况</p>
<h3>4.2 伸缩性和单节点问题</h3>
<p>扩展slave可以直接扩展,扩展master需要master之间数据同步,暂时是个瓶颈。对于主读业务的需求,暂时问题不大;写需求的话,暂时的想法是代码转写的方式。</p>
<h3>4.3 采用redis sentinal 监听</h3>
<p>默认不错的监听,尝试了下效果不错,还在调研中,配置conf即可,完成后可以查看监听的情况</p>
<pre><code>127.0.0.1:port> INFO Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=redis115,status=ok,address=ip:port,slaves=2,sentinels=1</code></pre>
<h2>五:常用代码</h2>
<pre><code># 强制杀死redis,模仿宕机
ps aux |grep redis |awk '{print $2}'|xargs kill -9
# 重启,指定conf
/home/work/xxx/bin/redis-server /home/work/xxx/etc/redis.conf
# 压测,具体参数可以参考benchmark
[cuihuan@cuihuan bin]$ ./redis-benchmark -h 127.0.0.1 -p 端口 -a 密码 -c 1000 -n 10000000 -d 1024 -r 100000 -t set,get,incr,del
</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=LbCmangpMFpzKVGjTx%2FppQ%3D%3D.nb0piqT%2FT8oiMHhgbNrA2vEl66jz%2FJbvWDX7qpsBIhVRZsen%2BgtYwfRr5FVt2T94" rel="nofollow">【redis学习三】简单高可用redis架构实践</a> | <a href="https://link.segmentfault.com/?enc=WwZ8KG1q6OXfVnJyFPgiDQ%3D%3D.MyIr3m20cLvFCuG0vlsKPXVTUfvWNzZ7EsGd1EPEvtg%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
phpredis单例模式封装
https://segmentfault.com/a/1190000006208258
2016-08-08T19:59:03+08:00
2016-08-08T19:59:03+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>通过单例模式实现对phpredis连接的封装。</p></blockquote>
<h2>直接上代码</h2>
<pre><code><?php
/**
* Class RedisConnManager
*
* 单例模式对redis实例的操作的进一步封装
* 主要目的:防止过多的连接,一个页面只能存在一个声明连接
*
* @author :cuihuan
*/
class RedisManager
{
private static $redisInstance;
/**
* 私有化构造函数
* 原因:防止外界调用构造新的对象
*/
private function __construct(){}
/**
* 获取redis连接的唯一出口
*/
static public function getRedisConn(){
if(!self::$redisInstance instanceof self){
self::$redisInstance = new self;
}
// 获取当前单例
$temp = self::$redisInstance;
// 调用私有化方法
return $temp->connRedis();
}
/**
* 连接ocean 上的redis的私有化方法
* @return Redis
*/
static private function connRedis()
{
try {
$redis_ocean = new Redis();
$redis_ocean->connect(G::$conf['redis-host'], G::$conf['redis-port']);
$redis_ocean->auth(G::$conf['redis-pass']);
}catch (Exception $e){
echo $e->getMessage().'<br/>';
}
return $redis_ocean;
}
}</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=FT3R5Q4oJT%2Fmk7ZXHl4DFA%3D%3D.oV8TNBA7DPa%2BC2WNyOi8hhL%2Fotli95NeKu5K6QHt1HU%3D" rel="nofollow">phpredis单例模式封装</a> | <a href="https://link.segmentfault.com/?enc=mSJX86vEWQTzzlcG1Md0Wg%3D%3D.A0Ubt2lC8wafEVgU3JKtNKVrF8m8LQSFLvufcOg%2FbzY%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
【page-monitor 前端自动化 下篇】 实践应用
https://segmentfault.com/a/1190000006198312
2016-08-07T22:14:37+08:00
2016-08-07T22:14:37+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>通过page-diff的初步调研和源码分析,确定page-diff在前端自动化测试和监控方面做一些事情。本篇主要介绍下,page-diff在具体的实践中的一些应用</p></blockquote>
<h2>核心dom校验</h2>
<p>前端的快速发展,造成前端dom无论结构还是命名经常变化,每次都尽可能关注每个dom的变化,不可能也没有必要。但是<code>核心dom是相对变化较小,但是比较重要</code>,因此可以利用page-monitor 修改关注结构中的核心代码,核心架构的变化。</p>
<p>上图是未修改的代码,下图是忽略footer内部变化<br><a href="https://link.segmentfault.com/?enc=hvEQONdBa70fFGkpo%2BFvvA%3D%3D.eySeX0zHuz7Ek76%2BDFflCpAY6NPfyoHGcD1vcoFOHYhL7%2Fcz3jrW1yK12y%2BXuE7EcflC7kInvk7Dp2P9wYDuf7%2FfWitl4zg%2FN8i1of4pNo9BeqU%2BiQoE9%2B1%2F1Zf1sbfT" rel="nofollow"><img src="/img/remote/1460000006198315" alt="F1AE4B80-CFC6-4A94-AB24-86297DCDD759" title="F1AE4B80-CFC6-4A94-AB24-86297DCDD759"></a><br><a href="https://link.segmentfault.com/?enc=u6eULHNnQrfkHHLGglF6Pw%3D%3D.4FYW7rBzbBaaLTIn5yUMP%2BIWspAwI8gHtj8D9g48gKh581hbujsqBQKXnBf8P5BRTQVtF3WQiXP0PaEAi7F3bDi1kBOdMX3%2Bgspw0kZqvYfUu9xbFiSihz4MoSDxIqJI" rel="nofollow"><img src="/img/remote/1460000006867487?w=958&h=1292" alt="0141A2E8-9B5E-439A-9FEC-1147ACF982DF" title="0141A2E8-9B5E-439A-9FEC-1147ACF982DF"></a><br>实践中可以针对自身的核心dom进行进一步优化</p>
<h2>局部dom校验</h2>
<p>项目中,往往在某一时期特别关心某些板块,或者某些板块相对容易出错;因此,可以利用page-monitor 进行局部dom的细节diff。中篇中对只针对header进行对比diff做了详细介绍,此处不赘述,上图。</p>
<p><a href="https://link.segmentfault.com/?enc=7a8klmx%2BJ0JNqotd%2FMo6tw%3D%3D.67URyMW9UrbH0URyftQiz3QTd1cKzZKOE5Bs%2BGLb%2FYt4uGyvh64Oohmd1JvXXO04c3BpS%2BtAjt5HDnqdlWb%2FC246uB6wlKyjYSpJPDptgowZtnCvBIyu7r2S3E2MXEsL" rel="nofollow"><img src="/img/remote/1460000006198041" alt="FDF6B258-5A69-47D1-B1FF-4EB38D7B8677" title="FDF6B258-5A69-47D1-B1FF-4EB38D7B8677"></a></p>
<h2>算法优化</h2>
<p>由于获取了完整了dom的json,因此可以通过相关阈值的设定或者算法的优化;来对比结果,进行更加优化的分级预警和分析;作者一般对非核心预警超过15%变化会做出预警,超过更高阈值会进一步的预警等等。<br>贴一个dom 细节图<br><a href="https://link.segmentfault.com/?enc=7ievQgACMUxIL3QufF6l%2BA%3D%3D.AduUxVQfUa7DMgnHQVPJrFerdd7lG0AzpS1LTfGe%2FAZzSf3WfSgFAwuho7IaA9MRoD9x1Bs%2F69COK4QbhXty2w%3D%3D" rel="nofollow"><img src="/img/remote/1460000006867488?w=1508&h=1668" alt="dom" title="dom"></a></p>
<h2>其他分析</h2>
<p>小拽通过上面的举例,旨在抛砖引玉,希望page-monitor或者dom结构在前端的自动化测试有一定应用,提升产品质量。</p>
<p>最终再上一张流程图,便于分析<br><a href="https://link.segmentfault.com/?enc=X8FqGxrI1SwuSTMn3t9%2BZQ%3D%3D.sxT7BaOvmBoeR0DhzV7Bwr%2FWGdaiI0nQyOCaXIIcSX4Xpq3bEcd7UToGomrawQaweszR6bJ%2BvGsyqR%2BLY9xE%2Fr%2FF6XE0CzDikPiwYZUTp%2Fu%2F%2BlJpYB2tgmma3XTAmeTD" rel="nofollow"><img src="/img/remote/1460000006833675?w=2933&h=4337" alt="page-diff 流程图" title="page-diff 流程图"></a></p>
<p>相关文章:<br><a href="https://link.segmentfault.com/?enc=Y1HVObaVyvsae1oW9awNjg%3D%3D.1asmhCbCIBa0oWmPYnvaBgD4dS3XPcRAN1MS2Fot5uQ%3D" rel="nofollow">【page-monitor 前端自动化 上篇】 初步调研</a><br><a href="https://link.segmentfault.com/?enc=XDQmHSFuRkQ15P2JmepewA%3D%3D.ntTHO5zOYNEe6ARO3fUtjPtpth2WzOgiGNtFqmYALtQ%3D" rel="nofollow">【page-monitor 前端自动化 中篇】 源码分析</a></p>
【page-monitor 前端自动化 中篇】 源码分析
https://segmentfault.com/a/1190000006198034
2016-08-07T21:45:28+08:00
2016-08-07T21:45:28+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>上篇中初探了page-monitor的一些功能和在前端自动化测试方面的可行性,本篇主要分析下page-monitor的实现方式和源码。</p></blockquote>
<h2>mode-module简介</h2>
<p>page-monitor的存在形式是node-module,依赖于node安装和运行,简单必须了解下node_modules</p>
<p><code>node-module</code>是nodejs的模块,符合commonJs规范【具体规范可以参考:<a href="https://link.segmentfault.com/?enc=mw3K%2B8VfsBrJ0Tdk7%2BTQ1Q%3D%3D.tX7CbbZLk6fOO7UI4cV9kdCL6vXM2M1n5hF5RON0DR%2BpNTROWbpXhOJxhqXmDiNC64ICgmi7S%2BVDONRqzbERNQ%3D%3D" rel="nofollow">http://javascript.ruanyifeng....</a>】</p>
<p>简单描述commonJs规范<br>1:文件即模块,作用域在文件内,不允许重复,不会污染。<br>2:<code>加载依赖出现顺序</code>,加载即运行,重复则利用缓存。</p>
<blockquote><p>多说一句:这是amd 和cmd(commonJs)的本质区别,由于node多运行于服务端,加载比较快,因此比较适合cmd 规范,浏览器端的模块则更适用于cmd的规范,个人理解没有广义的好坏之分</p></blockquote>
<p>方便看源码,贴出node_modole简单构成和主要函数module<br>node内部提供一了一个modle的构造函数,所有的模块都继承和依赖于此模块。<br><a href="https://link.segmentfault.com/?enc=H8A2sjLGA3%2F%2BW%2BdP3Ba6Vw%3D%3D.t9HfoPayZMp6uPi%2FeWrBVnD9XSWTq371at55mcVGijyft2813uvuXkCF90QkS3LYxwS%2B0zhujMb0FvOcPfz9SOI%2ByW55hb%2F5%2FZyR9wF0qD35pzgvVSgzfxIcEeH%2BykjJ" rel="nofollow"><img src="/img/remote/1460000006833673" alt="F4D61DF9-40DC-4DB6-A98E-6BED90406D5A" title="F4D61DF9-40DC-4DB6-A98E-6BED90406D5A"></a><br>node module的引入 require命令。<br>其他加载规则,路径设定不在此赘述。</p>
<h2>page-monitor文件分析</h2>
<p>完整文件目录:<br><a href="https://link.segmentfault.com/?enc=ngG6bwb7GT2TN%2Fq7TwLyag%3D%3D.Wn66E8jbywsnQgjSWs5NWottH9vw5sVFRekd2OW2lDFGEqyKDqP2PYEiUmQdJ6rijTS%2B0Vng4c%2F%2BRNxosOml9hwVWHlCbSQkByQ7136%2BHd9mor%2Fo1x%2FxFO652dYcg15q" rel="nofollow"><img src="/img/remote/1460000006198047" alt="B39BD2DF-7652-49F9-A8FB-F2F691688169" title="B39BD2DF-7652-49F9-A8FB-F2F691688169"></a> </p>
<p>运行生成目录分析:<br><a href="https://link.segmentfault.com/?enc=e32FCfdhzOVEbBYmrjUXPg%3D%3D.fNgy5xAQERm0aLIYbFZgyGIwBY3uecLAWMubcjo7bA8zahG4x5xaVumVknpuk3JynM9W3gxUC6x0x4zqeuKC%2FdtPis%2FulFALy3Rxx5QzbZg%3D" rel="nofollow"><img src="/img/remote/1460000006833674" alt="B39BD2DF-7652-49F9-A8FB-F2F691688169" title="B39BD2DF-7652-49F9-A8FB-F2F691688169"></a> </p>
<p>出了node_module及其组件代码,可用和值的分析的文件index.js 和phantomjs 下面的五个文件。</p>
<h2>分析index.js</h2>
<p>代码中无非变量声明和引用,关键一句引用phantom的命令乳腺</p>
<pre><code>// 多线程启动位置
var proc = spawn(binPath, arr);</code></pre>
<p>通过上面多线程的启动node可以达到高效和并发处理测试任务的需求,分析下arr的内容如下图:看到了 窗口大小,延时,ua,存放地址,diff变量等等</p>
<p><a href="https://link.segmentfault.com/?enc=OB3z%2F5FgEgLa%2FqJ6ogdoyQ%3D%3D.VZBaMEmKHvLyoXU9obJhQeO6Pkug3qJzDbDcSaYUnvBgtJL0l5zJ5tgOU%2Fkwxyq1uWzb3OPB%2BIOiJo0J3yVV323UTw0xa%2BRfO%2BxnRFpq20OzOB11UfmLjH7RXkT8uO3f" rel="nofollow"><img src="/img/remote/1460000006198045" alt="E9B3CEA8-6B99-45CC-A52A-B8E9D90B267C" title="E9B3CEA8-6B99-45CC-A52A-B8E9D90B267C"></a></p>
<h2>分析获取DOM源码</h2>
<p>获取dom的源码主要利用了web api evalution,evalution传入一个xpath的参数,返回一个xpath的对象,之后通过遍历和xpath规则生成规则化的json。<br>贴一个evalution api<br><a href="https://link.segmentfault.com/?enc=%2FC936gn2%2FYGp6FqyuqffMQ%3D%3D.Dr3w4Wxfyxc88YzebZ9EOObm8Oow8gBIhwpVaPevg3cr%2FmrBta5Mqq2rh6S5uK6siWsQPT6d7sBhgma%2B6fA6EV7hL3Pz8eBC%2FM10jPs9G5hEkRkWNuZ%2BZOXilSPgAaPA" rel="nofollow"><img src="/img/remote/1460000006198043" alt="90E47065-DF3B-470E-996C-1900A2EAF354" title="90E47065-DF3B-470E-996C-1900A2EAF354"></a> </p>
<p>为了看懂page-monitor的代码举个栗子</p>
<pre><code># evalution example:
var headings = document.evaluate("/html/body//h2", document, null, XPathResult.ANY_TYPE, null);
/* 检索body中所有H2的所欲.
* 结果存在于一个node的迭代器中 */
var thisHeading = headings.iterateNext();
var alertText = "Level 2 headings in this document are:\n";
while (thisHeading) {
alertText += thisHeading.textContent + "\n";
thisHeading = headings.iterateNext();
}
alert(alertText); // Alerts the text of all h2 elements</code></pre>
<p>通过上面函数和page-monitor中walk.js函数最后一行,可以看出page-monitor 保存了四个元素:属性[name,id等等],节点类型,位置[后期渲染],样式的md5加密[样式仅需要对比是否变化即可]<br>具体内容和dom结构如下:<br><a href="https://link.segmentfault.com/?enc=nMOaOiIApwh1CwBH0Ctr7g%3D%3D.FUygzCugtdEsq2UKelVsXaZAd9iNXNBBM5AKHBhaeZkrGkFKH1FS8q%2FHU8ETyUlY5AT4fMBNzeJ%2FAn1RP%2FAgds%2F6YcjyBBZFYDS0NX2e9okY%2BJFutOl6HgV81vYuYnLM" rel="nofollow"><img src="/img/remote/1460000006198037" alt="30FA5132-7903-466A-B866-588311812C47" title="30FA5132-7903-466A-B866-588311812C47"></a><br>对应的具体dom结构<br><a href="https://link.segmentfault.com/?enc=66aWClyBKRU0%2FtpRLGlGiw%3D%3D.a4GZ3ZSFV94dUbIVlhnNXcj4r9nRN0pjfiw1g%2B2fXcjgNeRTZVQfWWwGPYzbNXNnUYF%2FHwtiQywBB4RuVuBNxAXKS%2BW6XsUjiUMEx3yf3GixDI5BxVnnL21GeWqlvLFr" rel="nofollow"><img src="/img/remote/1460000006198039" alt="FBEE1F3F-7A81-4AF0-8E05-7E0BED5B5291" title="FBEE1F3F-7A81-4AF0-8E05-7E0BED5B5291"></a></p>
<h2>diff.js 代码</h2>
<p>diff代码主要两个作用</p>
<ul>
<li><p>1:获取差异</p></li>
<li><p>2:渲染差异<br>其中对比的策略:</p></li>
</ul>
<p>历史完全每个对比现在:获取更新和删除的内容<br>现在完全每个对比历史:获取更新和新增的内容<br>具体可以参考代码</p>
<h2>其他api和源码简单修改</h2>
<p>必须了解的web api 还有一个是querySeletor 也就是检索的api,参考地址<br><a href="https://link.segmentfault.com/?enc=9DNqu%2B3od9hZmNhUD1ma%2FA%3D%3D.c7qE3V4wQhewT9qccK%2FdesVkuMw8nC7BXCd3hWS%2BRCClCVF7IH0xZPHiYT6WjfuB6aUjiirkElS1bZnrnwzx0d4tjliaT14MpaWzIEF2970%3D" rel="nofollow">document.querySelector()</a><br>了解了这个api就可以做一件事情:<code>不对全局dom diff,只对特别关心的dom进行diff</code><br>实现方式:修改querySelector的根节点为Header<br>获取的dom结构如下:根节点为header<br><a href="https://link.segmentfault.com/?enc=ssGeXLuYE3PP5ZYz%2BY%2F2yw%3D%3D.pwyYHzdB%2BFQNcXBKvqf2MDAXm4x%2FkXjPy78tSNsdRVv2%2BLNou%2BOD6uvSH8qoa1Jeu0gntK8UVma%2BN7Y%2FY7ILczAAc6SacUath818uKRWHRJ6MvdTIrFG%2BRs%2BRbsN1wkL" rel="nofollow"><img src="/img/remote/1460000006198037" alt="30FA5132-7903-466A-B866-588311812C47" title="30FA5132-7903-466A-B866-588311812C47"></a></p>
<p>获取的页面截图如下:<br><a href="https://link.segmentfault.com/?enc=9HGz%2BXHiYXrZ%2B4kKOADcHQ%3D%3D.gE%2F2zeUFiZupFIAkzQnclUMhTMNa4n1qOxGWSp%2FP0eQIdXZO3%2BZVUbnewjQaA6%2FnpRHwbzD%2FeBcpvEVca4djMn%2BPmP5QgE2vF3pTJQiO5kxvNEjHXI7e1BPdUV%2FtFlJc" rel="nofollow"><img src="/img/remote/1460000006198041" alt="FDF6B258-5A69-47D1-B1FF-4EB38D7B8677" title="FDF6B258-5A69-47D1-B1FF-4EB38D7B8677"></a></p>
<h2>代码流程图</h2>
<p><a href="https://link.segmentfault.com/?enc=YXs%2F4nWfj5s3TN7Et721mw%3D%3D.4f7mkLtymU8qyPCSFwmlc5MiXe1GmP%2FulE%2B%2BOZeAVc4yV3%2BCj%2Fgv84j8v2pyXsHrLPRXHQuzqFtMR6JP2CoGIA%3D%3D" rel="nofollow"><img src="/img/remote/1460000006833675" alt="page-diff 流程图" title="page-diff 流程图"></a></p>
<h2>总结</h2>
<p>本次在调研page-monitor的基础上,对page-monitor的源码实现进行分析;同时利用相关api修改,来只对核心页面进行获取优化。下一篇将会进一步思考page-monitor的应用。</p>
<p>相关文章:<br><a href="https://link.segmentfault.com/?enc=AUqAolHPPMzRbXk2qWOa9g%3D%3D.WDqdEU4NLWR3WN211gRkqBfMCaO39oOhohCOoLJ5BKk%3D" rel="nofollow">【page-monitor 前端自动化 上篇】 初步调研</a><br><a href="https://link.segmentfault.com/?enc=gzT%2FB8ksN%2Fz6ieAn4GmlJA%3D%3D.QaDmmJAmD67hO%2BHs2c7K9RjKNwAm496BgXb4Bdc6TqQ%3D" rel="nofollow">【page-monitor 前端自动化 下篇】 实践应用</a></p>
【page-monitor 前端自动化 上篇】初步调研
https://segmentfault.com/a/1190000006197427
2016-08-07T20:17:57+08:00
2016-08-07T20:17:57+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>前端自动化测试主要在于:<code>变化快,不稳定,兼容性复杂</code>;故而,想通过较低的成本维护较为通用的自动化case比较困难。本文旨在<code>通过page-monitor获取和分析dom结构,调研能否通过监控和分析核心dom,来进行前端自动化测试</code>。</p></blockquote>
<h2>一:page-monitor 介绍</h2>
<p>page-monitor:通过xpath获取dom节点结构,之后可视化的渲染出页面的差异。<br>github地址:<a href="https://link.segmentfault.com/?enc=4sdp1NrdNQVvQkxJYHjrlQ%3D%3D.JHCuoKloovkC%2BZ7boPpMHE2wvsF49LsQf6WWgdUG%2FnoFxIF7M9%2BLCC2KOsDMnoYr" rel="nofollow">https://github.com/fouber/pag...</a><br>基本原理:<code>利用xpath获取页面的dom结构,存储为结构化的json,对比两次的json之间的差异,利用phantom渲染页面和差异页面</code>。</p>
<p>先上个初次试用的图</p>
<p><a href="https://link.segmentfault.com/?enc=9UGWGJeyGCklQQ81iphFoA%3D%3D.EZWHxAQP%2FbU%2BsscLbF41GhFMxFp%2Fmqg6wcj5sAzVgjtl48wYDvf%2FuglPAIyLEKR6G83%2BcmzEppvyCD6fZhgHlI9v2BwvnVYrI9FHRGXwyw4qCjiWomA4mPaclByNePrt" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/08/927EE33C-AD06-4EC3-9E5C-B8C3D2029AA3-212x300.png" alt="927EE33C-AD06-4EC3-9E5C-B8C3D2029AA3" title="927EE33C-AD06-4EC3-9E5C-B8C3D2029AA3"></a></p>
<h2>二:初次试用</h2>
<h3>2.1 安装</h3>
<pre><code># page-monitor 依赖于 phantomjs
npm install phantomjs
npm install page-monitor</code></pre>
<p>注意:phantomJs较大,如果比较慢 可以用brew安装,并且<code>page-monitor最多兼容phantom1.98</code></p>
<pre><code># 调整phantom为1.98 版本
MacBook-Pro:~ cuixiaohuan$ brew link phantomjs198
Linking /usr/local/Cellar/phantomjs198/1.9.8... 2 symlinks created
MacBook-Pro:~ cuixiaohuan$ phantomjs -v
1.9.8</code></pre>
<p>2.2 初次运行:<br>写一个test.js 代码如下:</p>
<pre><code>var Monitor = require('page-monitor');
var url = 'http://www.baidu.com';
var monitor = new Monitor(url);
monitor.capture(function(code){
console.log(monitor.log); // from phantom
console.log('done, exit [' + code + ']');
});</code></pre>
<p>运行效果</p>
<pre><code>MacBook-Pro:test cuixiaohuan$ node test.js
{ debug:
[ 'mode: 11',
'need diff',
'loading: http://www.baidu.com',
'page.viewportSize = {"width":320,"height":568}',
'page.settings.resourceTimeout = 20000',
'page.settings.userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"',
'loaded: http://www.baidu.com',
'delay before render: 0ms',
'walk tree',
'save capture [/Users/cuixiaohuan/Desktop/workspace/test/pagemonitor/test/www.baidu.com/Lw==/1461155680901]',
'screenshot [/Users/cuixiaohuan/Desktop/workspace/test/pagemonitor/test/www.baidu.com/Lw==/1461155680901/screenshot.jpg]',
'Unsafe JavaScript attempt to access frame with URL about:blank from frame with URL file:///Users/cuixiaohuan/Desktop/workspace/test/pagemonitor/test/node_modules/page-monitor/phantomjs/index.js. Domains, protocols and ports must match.' ],
warning: [],
info: [],
error: [],
notice: [] }
done, exit [0]</code></pre>
<h3>2.2 生成对比页面</h3>
<p>test.js code</p>
<pre><code>monitor.diff(1408947323420, 1408947556898, function(code){
console.log(monitor.log.info); // diff result
console.log('[DONE] exit [' + code + ']');
});</code></pre>
<p>运行</p>
<pre><code>MacBook-Pro:test cuixiaohuan$ node test.js
[ '{"diff":{"left":"1461155680901","right":"1461163758667","screenshot":"/Users/cuixiaohuan/Desktop/workspace/test/pagemonitor/test/www.baidu.com/Lw==/diff/1461155680901-1461163758667.jpg","count":{"add":2,"remove":2,"style":0,"text":9}}}' ]
[DONE] exit [0]</code></pre>
<h3>2.3 对比页面效果如下图</h3>
<p><a href="https://link.segmentfault.com/?enc=3ZXAYlGkiyARmk3nY7gW4w%3D%3D.IvX9yOfAzSwlFsLJJitVDCgMJteHZrIWX8CrrOl3qH1aeB12%2BCKDkW62CyqJ2RJEy5eAluz0MwmgqCHPmJEKGPlHi4q1H2Q%2FqSkRIv8CTYPqQz1hOFACIMx2Gkk7rkaU" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/08/927EE33C-AD06-4EC3-9E5C-B8C3D2029AA3-212x300.png" alt="927EE33C-AD06-4EC3-9E5C-B8C3D2029AA3" title="927EE33C-AD06-4EC3-9E5C-B8C3D2029AA3"></a></p>
<h3>2.4 目录初步分析</h3>
<p>通过目录和运行结果<br>1:每个时间利用phantom生成一张截图【保存现场】和一个dome的tree.json【对比dom】 【生成过程看下源码】<br>2:diff 调用tree.json 比较区中的区别【位置,内容生成和对比过程之后看下源码?】<br>3:利用当时保存的截图渲染生成的结果</p>
<p><a href="https://link.segmentfault.com/?enc=fYyQtmWPT9EMaDKkmWbzxw%3D%3D.OlDDa%2B5w01NpFA1JCjdikfRM20l%2FPfbLB5zSwwMX4hCtojDmhCJKQaJB68g1HYXqR2oGmrUPV7ry%2B1hxwuHDdDXTq76YkZwzZo0X3BF1Hs2vOAlEj3%2BLdUJndeCRf5Hl" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/08/F2838502-FF7E-4DC8-9C7F-01687F3DB3E9-300x151.png" alt="F2838502-FF7E-4DC8-9C7F-01687F3DB3E9" title="F2838502-FF7E-4DC8-9C7F-01687F3DB3E9"></a></p>
<h2>三:dom diff工具page monitor 调研初步结论:</h2>
<ul>
<li><p>1:dom的diff 是可行的。</p></li>
<li><p>2:page monitor 现有主要功能:抽取不同时间段的页面做页面domdiff<br>使用过程中缺陷:</p></li>
</ul>
<p>1:依赖太多,依赖node,依赖phantom,<br>2:接口太少,现在直接提供的就两个一个保存现场,一个diff。不方便dom定制和阈值定制。</p>
<h2>四:应用价值思考和下一步</h2>
<p>如果能对dom树的处理更完善一些,应用价值还是挺高的,例如<code>核心dom的diff,局部dom的diff,时效性dom(例如:时间tag必须变化,不变化则为bug)的变更检验,兼容性dom的check等等</code></p>
<p>下一步调研:<br>看下源码中,分析dom生成tree过程,对比tree过程,展现tree过程。</p>
<p>相关文章:<br><a href="https://link.segmentfault.com/?enc=cOMzslbjSNKOEWzHlGHkww%3D%3D.3CEB5mLlAJ0FUXGK1u9BmruRQATuAiltLWw%2Bdl6w%2FRQ%3D" rel="nofollow">【page-monitor 前端自动化 中篇】 源码分析</a><br><a href="https://link.segmentfault.com/?enc=qL5CHhxpjldWE4FL3Mtibg%3D%3D.wg3egX2s0Wztce%2FLY7033Neng%2FNpqulvRP2PcvhacQE%3D" rel="nofollow">【page-monitor 前端自动化 下篇】 实践应用</a></p>
fsck修复linux文件损坏
https://segmentfault.com/a/1190000006195207
2016-08-07T13:59:48+08:00
2016-08-07T13:59:48+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p><code>数据一定要备份,最好多机备份,代码一定要ci</code>。</p></blockquote>
<h2>背景和损失</h2>
<p>背景:机房事故,突然关机,硬盘年老失修,造成很多文件不可用。如图</p>
<p><a href="https://link.segmentfault.com/?enc=3jJUp9oCLQ7SHPkv3f59Eg%3D%3D.DhDbsTGnp7a8Zzjn534xg0IdUGbyaswAPZvMDR7V5gTQtF9VzFqz5U8dTjwwRAejWwANgA4QhjOIu%2BMJXcbhNEXRx7kmk660gtZgLUfzTj23i8YX3gwRdo370ifY3ffj" rel="nofollow"><img src="/img/remote/1460000006768856" alt="11441496-18D2-4576-8F5F-C2BF7B84F458" title="11441496-18D2-4576-8F5F-C2BF7B84F458"></a></p>
<p>面临损失:<br>作为一名靠谱程序员,数据库单机多机备份,程序版本控制这些都是有的【如果没有,一定要加上】;但这次有一个重要影响,就是git中commit之后,没有push的文件全损坏了,损坏了,坏了,了。。。。</p>
<h2>分析原因</h2>
<p>op给出的说法是网络波动,造成机房故障,机器重启。但从结果看,文件系统乱掉了,而且乱掉的文件主要分两类:</p>
<ul>
<li><p>1:当时正在写入和操作的文件。例如运行的脚本,正在写的文件,samba建立网络映射的文件,git实时文件。</p></li>
<li><p>2:内存里的数据,例如memcache里的数据等等</p></li>
</ul>
<h2>处理:fsck修复。</h2>
<h3>1:查看硬盘挂载:</h3>
<p>df查看下磁盘的挂载位置。</p>
<p><a href="https://link.segmentfault.com/?enc=WqxMEPIB4tyZmwWiCE6V7w%3D%3D.CcPqtrbEoAfndyfO5TVHQF6NnBD%2F5c%2FM9tsXe4dRQOBZlM4jPFAmHgxlT%2BAEW8CTAtobUf%2BUyNwXgMFouhVxCBybeyJwhtXPGZVQUv2TPjQXbtJdSYjCC5yg42zeUufc" rel="nofollow"><img src="/img/remote/1460000006195219" alt="F60D7BE5-D89D-4C74-9927-6EE2DD6157DF" title="F60D7BE5-D89D-4C74-9927-6EE2DD6157DF"></a></p>
<h3>2:操作挂起:<code>不挂起可能会出现数据恢复中断</code>。</h3>
<p>报错:直接挂起会出现 dev is busy,如下图<br><a href="https://link.segmentfault.com/?enc=ULiLcmIS8kDX7hgSaJuMqw%3D%3D.Iws1kq%2FxA62z6uIFuyYuKxPkSYChyOmeGqeT8CG3%2FLYtugS%2FywSZZKG1QiuRBcNGX7HVMPzekEqeGJnJT2tMapSKAxX%2Fo6XO2dv7UHFPaZ8LMjCG9MXzr3WkZGT8hITl" rel="nofollow"><img src="/img/remote/1460000006195221" alt="59423A93-F29C-4A30-AB92-17408E9E6556" title="59423A93-F29C-4A30-AB92-17408E9E6556"></a><br>用:umout -l /dev/sda3<br><a href="https://link.segmentfault.com/?enc=9Yw6vvkaRCIqp0QFYmNZyA%3D%3D.6ssf5uShjb3jMdPDg%2FIj3o%2BhocS%2BSjBwkjFSvwtKPoff5phbXqxKtvEbhTUL1b06hUMKmgHhUNURfeNaotrvV3bR1sji8seD8p8xi%2BTIzzy6iyvZUvEwViNptmwFt5fY" rel="nofollow"><img src="/img/remote/1460000006195223" alt="3C64703C-CF4A-47EB-9FE3-91CD7542E8FA" title="3C64703C-CF4A-47EB-9FE3-91CD7542E8FA"></a></p>
<pre><code>#umount -l <挂载点|设备>
此命令将会断开设备并关闭打开该设备的全部句柄。
通常,您可以使用 eject <挂载点|设备>命令弹出碟片</code></pre>
<h3>3:fsck 扫盘</h3>
<pre><code>fsck -f /dev/sda3</code></pre>
<p>注意ext2 还会进行e2fsck 再扫一遍,此为正常操作<br><a href="https://link.segmentfault.com/?enc=5VtBMBNG4STve2Dac2c2BQ%3D%3D.HTFJKKZwlUnYu0ohBvS2c0KP%2BLFNsVhlsG%2F3SXDvvCyVpbgDhIUOEVUlZ5h2O1yFtU1Fj8khT4CT3mY0Y5ySVz%2Fy9Pef0GzRNMjxyovtz5qM6dKlv%2BjImHSK4HYpM3uG" rel="nofollow"><img src="/img/remote/1460000006195225" alt="C9347418-9644-4841-9A2C-3FFB1157E5D8" title="C9347418-9644-4841-9A2C-3FFB1157E5D8"></a></p>
<h3>4:扫盘结束后,挂载驱动盘</h3>
<h3>5:寻找和恢复文件</h3>
<p>把.git 内的文件全部整理,导出,一个一个寻找自己需要的文件,找到了久违的文件。<br><a href="https://link.segmentfault.com/?enc=S1aNPQ%2FzXcr0LHpZBz4FsA%3D%3D.9W2ULL78JznYgTQCL0xCUBfLfJaH3bf%2FypBto7ZHxoTUStJv2CdgalKrSWlhJhNcfhwPBx8OkIpSyw8ghKOyf8Fb3Qe5fMp9XjzlWqBJjlg%2FPbx23WpfI48jvkz3N%2FtT" rel="nofollow"><img src="/img/remote/1460000006195227" alt="3B14EF17-2C86-411F-BF16-4FFC15637C4D" title="3B14EF17-2C86-411F-BF16-4FFC15637C4D"></a></p>
<h2>后续</h2>
<p>一句话:<code>代码一定要ci,数据一定要容灾</code></p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=wV1VgPadZ1nXZyOWRodskg%3D%3D.p5nCCMMz%2FBitm4r1wGhBZmPVvALgJdvnpQtETCu5Zjg%3D" rel="nofollow">fsck修复linux文件损坏</a> | <a href="https://link.segmentfault.com/?enc=h9E7eyHBUgN1FxGpgGCDxA%3D%3D.FbjGsOKxmXLTIgyhWkI6FZKdnHVnLS3hExM7KmOULws%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
中型存储架构实践探索
https://segmentfault.com/a/1190000005013177
2016-04-26T21:40:44+08:00
2016-04-26T21:40:44+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>最近一直在做平台优化:对于中小型的站点,<code>如何在资源有限的情况下,实现一个稳定,高效,靠谱的存储方案</code>。下图是小拽个人在时间过程使用的一个存储架构。拿出来分享交流一下,也希望得到指点改进!</p></blockquote>
<h2>先上图</h2>
<p><a href="https://link.segmentfault.com/?enc=Jv0C%2BFbwGLnymVBRGW37aw%3D%3D.rEIMBkC1JitNCUAa40ocUOiDjxoh2r41CZaLx28jJUIc1aFyGtL31uq%2FFYwuvJfizMJ6fmdaoCTxLRR0NGY%2FHkMrfivfwON1LuBJddqxMgoAoZ3W5HF%2FQQUHIaD5BB45" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/02/%E5%AD%98%E5%82%A8%E6%9E%B6%E6%9E%84-1024x996.jpg" alt="存储架构" title="存储架构"></a></p>
<h2>首先说思想</h2>
<p>思想就一个:<code>权衡资源和业务需求</code></p>
<blockquote><p>简单解释一下:对于架构的理解,个人非常认同百度架构师tandai的一句话:<code>架构设计本质上是折衷的艺术</code>,如果你有足够量的高速存储和高性能的机器,那么完全可以用足量的cache,足量的离线计算存储,来提升时效性;同样,如果你的机器不足,资源不足,那么就可以通过可接受的时间消耗来节省存储空间。</p></blockquote>
<p>架构基本组件:</p>
<ul>
<li><p>至少两台机器。【保证物理容灾】</p></li>
<li><p>三个mysql实例。【一主两从,一主不解释;一从主要用于实时备份,暂叫容灾从;一从用于离线计算,cache更新,非时效性的数据抓取,暂叫api从;】</p></li>
<li><p>ameoba 负责负载均衡和读写分离【暂时用着还可以】。</p></li>
<li><p>redis 负责缓存,预取,存储cache。【可以换成其他】</p></li>
<li><p>一个抗高并发的中间件。【暂时只加了antispam组件,高并发并未处理,可能系统负载比较平均,qpd几千万 ,但是并未出现qps峰值】</p></li>
</ul>
<p>that's all,这些组件对于一个操作尚可的程序员来说,部署一整套肯定不会特别麻烦,相对于其他大型的架构来说,略显简单;但是,麻雀虽小,五脏俱全,下面从架构必备的几个角度分析一下。</p>
<h2>安全性(Failover)</h2>
<p>任何一个架构首要考虑的是数据安全和容灾。小拽的架构中做了哪些</p>
<h3>数据库全量备份</h3>
<p>这个就是一个简单脚本,对api从库在闲暇时间【晚上3-4点】进行全量导出备份,同时scp到另一台机器一份。(之所以对api库,是因为api库主要负责非失效性的查询和计算)</p>
<pre><code># crontab 每天3点进行数据库备份 (cuihuan)
# 0 3 * * * sh /home/disk6/mysql/bin/backup.sh
# 每天备份,保存最近30天的
DATE=$(date +%Y%m%d)
/home/xxx/bin/mysqldump -uroot -pxxx db > /home/xxx/bak_sql/db_$DATE.sql;
find /home/xxx/bak_sql/ -mtime +30 -name '*.sql' -exec rm -rf {} \;</code></pre>
<h3>数据库增量备份</h3>
<p>增量备份主要从两个角度</p>
<ul>
<li><p>binlog中定期备份sql;</p></li>
<li><p>是采用主从库之后,从库会定时的备份主库信息,同时,对api库采用数据完全一致,对容灾库则设置只同步update 和insert;这样完备的保证了数据的安全。</p></li>
</ul>
<h2>可用性(Availabilty)</h2>
<p>数据的安全排第一,毋庸置疑;次之排平台的可用性,也毫无争议。可用性最简单的一个指标则为:<code>不卡</code>。</p>
<h3>cache</h3>
<p>cache是提升查询时效性最有效的一个手段,小拽在框架中主要应用了两种cache,满足不同的业务需求。(所有关于cache的使用,一定要注意时效性和一致性,时效性和一致性,时效性和一致性)</p>
<ul>
<li><p>普通的cache。即用户搜索或者查询之后的结果存在redis里面,下次查询使用。</p></li>
<li><p>预取的cache。即预测用户要查询的内容,放到cache里面。举几个栗子,用户首页内容一定要存cache里面;用户在看page1的时候,可以后台预测用户会看page2,提前取过来等等,这些策略和自己的实际业务紧密结合。</p></li>
</ul>
<p>关于时效性和一致性再多说一句:一定要注意及时更新,例如用户写操作,点击操作,都需要在后台触发cache的主动更新,否则可能造成数据一致性错误。</p>
<h3>分库分表</h3>
<p><strong>中小型的架构中,存储的瓶颈往往在于读。</strong></p>
<p>随着数据的增加,读库的成本越来越大,一个sql很可能会造成锁死整个库,一条sql 10+s也是常有的事情;因此,解决读库的瓶颈,可以大大提升系统的可用性;小拽的实践中主要应用了分库,分表。</p>
<h3>分库</h3>
<p>之所以要分库,是因为<code>二八原则的存在,80%的用户操作集中于20%的数据</code>。</p>
<p>举个栗子:实践过程中小拽有个月库,只存本月的数据,基本上80%+的用户操作数据,都会命中这个库。</p>
<p>分库的原则有很多,例如时间原则,业务原则,数据逻辑原则等等;总之在您的框架中,当db扛不住的时候就分库,分层级。</p>
<h3>分表</h3>
<p>分表的思想和分库类似,只是粒度更小,不在赘述。</p>
<h2>扩展性(Scabability)</h2>
<p>小拽的架构中,扩展性主要从三个方面考虑</p>
<ul>
<li><p>1:数据库的扩展性。如果资源允许N主N从都是可以的,基本上不会影响业务操作。</p></li>
<li><p>2:缓存的扩展。缓存基本上也是单独部署的,redis,memcache等均可以,变更成本不大。</p></li>
<li><p>3:高并发和负载均衡。这块属于大型网站需要考虑的,暂时只采用了ameaba进行负载均衡的扩展,高并发预留接口。</p></li>
</ul>
<h2>权衡(Balance)</h2>
<p>所有的架构和技术,最终都要落实到和业务需求权衡。</p>
<p>上面的架构最大的优势其实就是:简单,搭建起来非常容易,这就够了。</p>
<p>作为一名码农,只有在实践的过程中,不断发现系统的瓶颈,权衡现有资源和需求,解决和处理问题,才能成为一名靠谱的码农。</p>
<p>以上只是小拽在实践过程中的一点小小心的,欢迎大家到小站交流(<a href="https://link.segmentfault.com/?enc=pogfFGKsIyGJI3jelMJY3g%3D%3D.6HjbQCuGXudkesH8paD%2FMAOERkSUNknLX%2BzScIPdey8%3D" rel="nofollow">http://cuihuan.net</a>)。</p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=yBBL2iBlqbiezFSJ6cQqHg%3D%3D.SbT07q0PFqzMcJgfnan1YipDFgVI9BG%2BzconSGAOqPo%3D" rel="nofollow">中型存储架构实践探索</a> | <a href="https://link.segmentfault.com/?enc=t2gQNiGMusidK%2BiZd9iZaA%3D%3D.e5jJOafBpWIWGP9JXVtcAAIv%2FB%2BLTaZebWCEnLMSi%2BU%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
【chrome 插件二】添加菜单和添加消息提醒
https://segmentfault.com/a/1190000004969298
2016-04-19T20:42:50+08:00
2016-04-19T20:42:50+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
3
<blockquote><p>上一篇中简单的接触了chrome插件,并且草草的制作一个chrome 插件(-_-只中看,不能用);这次主要学习,<code>browse action api制作菜单制作和调用系统提醒</code>。</p></blockquote>
<h2>browse action</h2>
<p>browse action 包括四部分:一个图标,一个tooltip,一个badge和一个pophtml<br>先上代码和效果</p>
<pre><code> "browser_action": {
"default_title": "反劫持工具",
"default_icon": "image/icon_19.png",
"default_popup": "html/popup.html"
},</code></pre>
<p><a href="https://link.segmentfault.com/?enc=UXLgujrfDsahhV4a9NBiIQ%3D%3D.nE5wg0W8vkdDLebkzbjnScsmdFjnh%2B5oZyz3AMgOhQebVOcW%2BRQAJ4aJMVtUODdmHJJiptND%2BvQGomEX5Z0z98Pg%2BrllbXSZUue%2FiGQRhs8S5xmYCeNiO01DiXgFSWxa" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/9E62D576-098D-4B4A-8C4C-1E845D23E094.png" alt="red" title="red"></a></p>
<h3>图标</h3>
<p>图标优化:<code>最好是19px,这样基本占满</code>,可以直接使用图标也可以用h5 canvas element,同时,<code>图片一定要是背景透明的</code>。</p>
<p>ps处理后页面效果如下:</p>
<p><a href="https://link.segmentfault.com/?enc=AI5fR5nzUSH8bSdgoQ92CA%3D%3D.xACJAYwZJBUDh%2FxRraIb5xkDWCWhPXZQFdo%2B9X3%2FE49eSsJaHKIuvNNZnHBywjgkXGcDTNaC7MyXTT9PFFMFAd6CFnVU3pNIE%2BKplvcFl73GL9zp%2BQ79Ng5kTZewJ2Ga" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/C84884DF-38DB-4402-8BEF-181795A51FAF.png" alt="title" title="title"></a></p>
<h3>tooltip</h3>
<p>直接设置default_title 效果是鼠标经过显示标题效果</p>
<h3>badge:</h3>
<p>这个相当于设置图片文字和背景色:提供了两个方法:设置badge文字和颜色可以分别使用setBadgeText()andsetBadgeBackgroundColor()。</p>
<h3>pophtml:创建菜单</h3>
<p>上码:<code>能用代码说话的,不用文字</code><br>js</p>
<pre><code>/**
* @author: cuixiaohuan
* Date: 16/4/19
* Time: 下午9:41
*/
/**
* 点击菜单的事件
*
* @param e
*/
function click(e) {
chrome.tabs.executeScript(null,
{
// 更改背景色
code: "document.body.style.backgroundColor='" + e.target.id + "'"
}
);
window.close();
}
/**
* 页面加载完成后,监听事件
*/
document.addEventListener('DOMContentLoaded', function () {
var divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', click);
}
});
</code></pre>
<p>html</p>
<pre><code><!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Set Page Color Popup</title>
<style>
body {
overflow: hidden;
margin: 0px;
padding: 0px;
background: white;
}
div:first-child {
margin-top: 0px;
}
div {
cursor: pointer;
text-align: center;
padding: 1px 3px;
font-family: sans-serif;
font-size: 0.8em;
width: 100px;
margin-top: 1px;
background: #cccccc;
}
div:hover {
background: #aaaaaa;
}
#red {
border: 1px solid red;
color: red;
}
#blue {
border: 1px solid blue;
color: blue;
}
#green {
border: 1px solid green;
color: green;
}
#yellow {
border: 1px solid yellow;
color: yellow;
}
</style>
<script src="../script/changeBackgroud.js"></script>
</head>
<body>
<div id="red">红色小拽</div>
<div id="blue">绿色小拽</div>
<div id="green">蓝色小拽</div>
<div id="yellow">换色小拽</div>
</body>
</html></code></pre>
<p>效果图<br><a href="https://link.segmentfault.com/?enc=6xYxJYKpClPsTbKGWuXMUA%3D%3D.gkafqAFAi%2F3LTBsBnCI0I3K42gaNUDpd6fMlv4S91EM4uJHBhWGbj8EUE10WkEXdbFXPmGkmLSyPin7oqwmzdOdw7Phc3lqpGluCUdD4XY6s7jX6JsPd75qWo6%2Fwq77R" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/4AA82B9D-759E-434E-A0B5-8F2F0376F16D.png" alt="blue" title="blue"></a></p>
<h2>调用系统提醒</h2>
<p>notification api 官方文档:<a href="https://link.segmentfault.com/?enc=tIQhoR9xQNvCTphS4fdQ1Q%3D%3D.OHYThtoLkVWzxkMALHAqqGOz7ebjNC6fmuW4CuRQHk8DZkcdixmHBf34njUKoi4nUrcgvH%2B%2B%2BtsmE0tEn2D5GA%3D%3D" rel="nofollow">https://developer.chrome.com/extensions/notifications</a><br>注意</p>
<ul>
<li><p>chrome32 之前的预警接口不太一样,文档中已经说明。</p></li>
<li><p>使用预警一定要加上权限统一</p></li>
</ul>
<pre><code> "permissions": [
"notifications"
],</code></pre>
<p>调用系统提醒代码</p>
<pre><code>
// 用户授权
if (Notification.permission == "granted") {
Notification.requestPermission();
}
/**
* 调用系统提醒
*
* 第一次进入页面需要授权,之后弹出提醒
*/
function notifyMe() {
if (!Notification) {
alert('Desktop notifications not available in your browser. Try Chromium.');
return;
}
if (Notification.permission !== "granted"){
Notification.requestPermission();
} else {
var notification = new Notification('小拽提醒', {
icon: 'http://cuihuan.net/wp-content/themes/quench-master/images/cuihuan_title.jpg',
body: "别点击,点击跳转'靠谱崔小拽'"
});
notification.onclick = function () {
window.open("http://cuihuan.net");
};
}
}
</code></pre>
<p>初次进去提醒,授权<br><a href="https://link.segmentfault.com/?enc=31a70RFZolYE9ojLXON4VQ%3D%3D.bBm%2B9%2F%2BD49Zrgjbv42bFlNw5VT22BSPsj%2FTv7ucI5TwKOc4iOUurwqRnIgmWptpEWiS5iVJIJVybuPzc9MH4fKFnoFEtYOZUz4U9sV8Y5omZQc96bLb%2F3x%2BbSzftZr9a" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/8971AFAA-2C56-4A2F-962B-BBA0A8E0E7A4.png" alt="notify_allow" title="notify_allow"></a></p>
<p>提醒效果如右上角所示<br><a href="https://link.segmentfault.com/?enc=l1eh4E9aXAyQNuvjAbTZ0A%3D%3D.hQmHOZF3UuIbaqU17%2F6BfR%2F9JVzq6GocGgtaRp8eg%2Bl9UZNM%2BxaVNSJhkXCXSELilEPU76f4WnOSVz%2Fh9SSnv0DwZ1KkXJBqbw%2BIa9RwnnUTN3pRO%2FMQC2RnjQNf%2B%2FzN" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/04A3AF6D-69FA-423A-8447-F5984F32762C.png" alt="notify" title="notify"></a></p>
<p>通过菜单和提醒,我们基本就可以完成一个简单的闹钟提醒,每隔30分钟提醒,码农扭扭脑袋,伸伸懒腰,小心肩周炎-_-!</p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=4LZ7o0TwSS0dyuGoYj0Oww%3D%3D.M%2FkJ8zmrQJAp84JWitsdQvGHlO%2Bh704vZGYd%2FEKuQcM%3D" rel="nofollow">【chrome 插件二】弹出菜单和系统提醒学习</a> | <a href="https://link.segmentfault.com/?enc=FLltG1kDaZ9QZRT%2FzDJrKw%3D%3D.9SUTOd1Yc94RFjqftresXkjJNTWKsa0WO8UglBPeYH8%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
【chrome 插件一】开发一个简单chrome浏览器插件
https://segmentfault.com/a/1190000004933553
2016-04-13T20:59:03+08:00
2016-04-13T20:59:03+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
7
<blockquote><p>chrome 之所以越来越好用,很大一部分原因归功于功能丰富的插件;对于chrome忠实用户来说,了解和开发一款适合自己的chrome插件,确实是一件很cool的事情。</p></blockquote>
<h2>了解chrome 插件</h2>
<p>chrome 插件个人理解:<code>就是一个html + js +css + image的一个web应用</code>;不同于普通的web应用,<code>chrome插件除了兼容普通的js,json,h5等api,还可以调用一些浏览器级别的api,例如收藏夹,历史记录等。</code></p>
<p>推荐两个网站了解和入门<br>谷歌官方API:<a href="https://link.segmentfault.com/?enc=gQBjNbVQtL3QDVzL6rwkLA%3D%3D.E4R7jGq4lAOsRHQyns5BIPmRCTOtwKtutWBa2Ui8YXTsYOPwSIhKdA43cpEfO5I1SCPkl2OG%2BAJRGLLfaxNjLg%3D%3D" rel="nofollow">https://developer.chrome.com/extensions/getstarted</a><br>360的文档:<a href="https://link.segmentfault.com/?enc=TkHmj608b775IJNrrXfHrQ%3D%3D.VLi0pqprKdJJlIT%2FsBp%2FLikeGyKlbroP81Dbr4Ci8SouMWG91a6Z7XwJqcRnntdihGZykplghfkgxg7Rr8dRmg%3D%3D" rel="nofollow">http://open.chrome.360.cn/extension_dev/overview.html</a></p>
<h2>开始写第一个插件</h2>
<h3>文件结构</h3>
<p>一个简单的demo,文件目录如下<br><a href="https://link.segmentfault.com/?enc=5BCrm%2F9nhAVsJb7tcOMxFg%3D%3D.VbPni2xlWmu8jrh2GAd%2BjChJe9mYXLHCtNXyTEJIG3qXpH3ZtcmHkSAU%2FmVk0LKTV7djkZPH%2FEuFKG6tFzbujFMiuhiJIVVeaV%2BDMned%2BjMX8ondzBeUSraw7l3WqkbW" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/7A980C32-7D7B-44F6-AC5D-C0CC02F79ACA.png" alt="7A980C32-7D7B-44F6-AC5D-C0CC02F79ACA" title="7A980C32-7D7B-44F6-AC5D-C0CC02F79ACA"></a><br>和普通的web文件没有什么区别,简单介绍下</p>
<ul>
<li><p>html:存放html页面</p></li>
<li><p>js :存放js</p></li>
<li><p>locales :存放了一个多语言的兼容【可无】</p></li>
<li><p>image :放了两张图片【初期图标】</p></li>
<li><p>manifest :核心入口文件</p></li>
</ul>
<h3>写一个manifest</h3>
<p>api参考文档 :<a href="https://link.segmentfault.com/?enc=FH9JPfXfmb1oGAtBGBduKw%3D%3D.6c4cM%2BfnxcHq7J6tH4sPqDdvGpwcjGmd4UUzYvnxCFutFkoVdFzrQtuedWg9Ac9fcjlmrBE7%2BSbg%2FB4TYj%2BaTA%3D%3D" rel="nofollow">http://open.chrome.360.cn/extension_dev/manifest.html</a></p>
<p>直接上代码:</p>
<pre><code>{
"name": "hijack analyse plug",
"version": "0.0.1",
"manifest_version": 2,
// 简单描述
"description": "chrome plug analyse and guard the http hijack",
"icons": {
"16": "image/icon16.png",
"48": "image/icon48.png"
},
// 选择默认语言
"default_locale": "en",
// 浏览器小图表部分
"browser_action": {
"default_title": "反劫持",
"default_icon": "image/icon16.png",
"default_popup": "html/test.html"
},
// 引入一个脚本
"content_scripts": [
{
"js": ["script/test.js"],
// 在什么情况下使用该脚本
"matches": [
"http://*/*",
"https://*/*"
],
// 什么情况下运行【文档加载开始】
"run_at": "document_start"
}
],
// 应用协议页面
"permissions": [
"http://*/*",
"https://*/*"
]
}</code></pre>
<p>test.js 文件</p>
<pre><code>/**
* @author: cuixiaohuan
* Date: 16/4/13
* Time: 下午8:41
*/
(function(){
/**
* just test for run by self
*/
console.log('begin');
})();</code></pre>
<p>test.html 文件</p>
<pre><code><!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>just for test</title>
</head>
<body>
<h3>test</h3>
</body>
</html></code></pre>
<h3>运行插件</h3>
<p>chrome 中输入:chrome://extensions<br>选择加载已解压的插件-》选择文件根目录即可。<br>效果如下:</p>
<p><a href="https://link.segmentfault.com/?enc=SAjuKm4rETMbmdF5scfN4A%3D%3D.z5UYMPkSiuRRlF2lypcSkytUHPJS0etyjOH%2BL92g18frOSKYQaQ%2BOra%2FQwUib7xm5aVgm5rZ0hTF4z5a63n%2BGS4t0ZTOCtTXpFTUe%2F45Z2q%2Bapm2jpG1kWfeIUXm5knR" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/475BA7E7-29F3-48AF-9990-2B73FF1B4B56.png" alt="生效插件" title="生效插件"></a><br>一个基本的插件变完成了,勾选已启用,随便打开一个网页,会看到log中输出如下</p>
<p><a href="https://link.segmentfault.com/?enc=NQZNaEqOGtsTD%2FFNQhU8xQ%3D%3D.Z5wNitcIqhUugBZwSBRrKA4s7Iz8R5oCcZ6SD0ygPkPfTiaYTwPszM7GxG%2B7SgYanMljyyQ1dnrf5EEBYXu3uvRkaaLrFeGKyaIHuZHrkBoFwo%2BJ1KCXOqJmcWHEpEW%2F" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/50F1039A-71C0-463F-A60D-C95527985C7E.png" alt="运行效果" title="运行效果"></a></p>
<p>点击页面上面的小图标如下图:<br><a href="https://link.segmentfault.com/?enc=G2mtstXab%2FJ2qpdoTvvXrg%3D%3D.1A1fw1n2AXU7Om0byJqALtEPFjiKtO6m0MhF17pNfrqQSP3pg4dH9Y1mLa%2BxovQMFYtA%2BOiNnfVly8qzBWCl4EA28e%2FBEAsjxw2X3zNJW31gax9PkR2XzBtPdCbi%2BYoo" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/04/25CF59DD-0666-4A6F-ACA3-683D1FEEA346.png" alt="右侧展示" title="右侧展示"></a></p>
<h2>优化建议</h2>
<p>一个小的插件已经完成,但是还有更多的api和有趣的事情可以去做。下面是360文档中给出一些优化建议,共勉。</p>
<ul>
<li><p>确认 Browser actions 只使用在大多数网站都有功能需求的场景下。确认 Browser actions 没有使用在少数网页才有功能的场景, 此场景请使用page actions。</p></li>
<li><p>确认你的图标尺寸尽量占满19x19的像素空间。 Browser action 的图标应该看起来比page action的图标更大更重。</p></li>
<li><p>尽量使用alpha通道并且柔滑你的图标边缘,因为很多用户使用themes,你的图标应该在在各种背景下都表现不错。不要不停的闪动你的图标,这很惹人反感。</p></li>
</ul>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=t1Bww%2FkssHAuMMEFTtAD6Q%3D%3D.xffqjEVnwJSWaV8xKXSCgDtYhPrukG9caXblHvdko50sp0bnJK8qRndj9MCt03vtgqZNY%2B9DvJJK1e2OYz3m0YY9ggp6fbmqmz3aXruG4jDlnaC%2F8gvcwQ0w77ILJR79bilWfWh4NLkF8Jlr7t8KcA%3D%3D" rel="nofollow">【chrome 插件一】开发一个简单chrome浏览器插件</a> | <a href="https://link.segmentfault.com/?enc=uYB3s2hoIeCrNPMuAsNMsA%3D%3D.97Ca%2FXHR4%2BTALskJsO5nblsEytHf0m7YnIQRvAt562E%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
mysql数据导库常用操作
https://segmentfault.com/a/1190000004565290
2016-03-08T19:41:01+08:00
2016-03-08T19:41:01+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>工作中经常遇到:一个数据库导入新的数据库实例中,或者一个数据库中的某些表导入新的数据库中,常用操作,总结一下。</p></blockquote>
<h2>部分数据表导入新库</h2>
<ul><li><p>单表导入新库的sql为</p></li></ul>
<pre><code># CREATE TABLE 新表 SELECT * FROM 旧表
create table `dashboard`.`xx` (select * from dashboard_stb.xxx);</code></pre>
<ul><li><p>多表导入新库(将所有的stability_* 导入新库)<br>多表的时候,只需选出需要的表即可。</p></li></ul>
<pre><code># 拼接处所有的导出sql
mysql> select concat ('create table `dashboard`.',table_name,'(select * from `dashboard_stb`.',table_name,');') from information_schema.tables where table_name like "stability_%";
+--------------------------------------------------------------------------------------------------------+
| concat ('create table `dashboard`.',table_name,'(select * from `dashboard_stb`.',table_name,');') |
+--------------------------------------------------------------------------------------------------------+
| create table `dashboard`.stability_capacity(select * from `dashboard_stb`.stability_capacity); |
| create table `dashboard`.stability_dailymaxflow(select * from `dashboard_stb`.stability_dailymaxflow); |
| create table `dashboard`.stability_indextype(select * from `dashboard_stb`.stability_indextype); |
| create table `dashboard`.stability_ktraceagent(select * from `dashboard_stb`.stability_ktraceagent);
# 此处省略N行
+--------------------------------------------------------------------------------------------------------+</code></pre>
<p>粘贴进去直接运行即可</p>
<pre><code>mysql> create table `dashboard`.stability_maccpu(select * from `dashboard_stb`.stability_maccpu);
Query OK, 138138 rows affected (2.16 sec)
Records: 138138 Duplicates: 0 Warnings: 0
mysql> create table `dashboard`.stability_macmem(select * from `dashboard_stb`.stability_macmem);
Query OK, 138137 rows affected (2.07 sec)
Records: 138137 Duplicates: 0 Warnings: 0
mysql> create table `dashboard`.stability_macssd(select * from `dashboard_stb`.stability_macssd);
Query OK, 138139 rows affected (2.17 sec)
Records: 138139 Duplicates: 0 Warnings: 0
# 此处省略N行</code></pre>
<h2>全库备份导入新库</h2>
<p>数据库全量导出为sql</p>
<pre><code>mysqldump -uxxx -pxxx dashboard > dashboard.sql</code></pre>
<p>通过sql建立新库</p>
<pre><code># 建新库
mysql> create databases dashboard_new
# 导入数据
./mysql -uxxx -p --default-character-set=utf8 dashboard_new < dashboard.sql</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=n7BTI7sOxCh9sAVfQdQWtA%3D%3D.oKgX3gTjIN97p%2B05oc08tPFgzN3I%2F8ZRjS8Jpj4RVN2Nlg66qldD8%2Bb%2BqmJxqVQCSlD5yDjfQwrqOMx0Mz1FvwY5Y6U729UpiczpLlRNfsLoxnw8vJeaLFAwNy6P3ZIUWLjOv0BumYiMiaYSdKGwQA%3D%3D" rel="nofollow">mysql 简单全量备份和快速恢复</a> | <a href="https://link.segmentfault.com/?enc=SOXW68uC0MXDRZBDvYTDWQ%3D%3D.UQ%2BO4dxyJA92yixBnOgk%2BroxUsmcyOt35x4%2FrGUbMhg%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
postMessage处理iframe 跨域问题
https://segmentfault.com/a/1190000004512967
2016-02-29T22:22:01+08:00
2016-02-29T22:22:01+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
10
<blockquote><p>背景:由于同源策略存在,javascript的跨域一直都是一个棘手的问题。<code>父页面无法直接获取iframe内部的跨域资源;同时,iframe内部的跨域资源也无法将信息直接传递给父页面</code>。</p></blockquote>
<h2>一:传统的解决方式。</h2>
<p>传统的iframe资源解决方式:主要通过通过中间页面代理,此处不再赘述,参考<a href="https://link.segmentfault.com/?enc=DCF8wjiv1Hk%2FTdj8Frx1tw%3D%3D.%2FdFs3%2BpKUItZErsZFAm8Z146%2FGMXbvmrIea3G2z5%2BD9u8oI0RisVGszNrP7t7COY" rel="nofollow">中间页获取跨域iframe</a></p>
<h2>二:html5 postMessage的产生</h2>
<p>随着HTML5的发展,html5工作组提供了两个重要的接口:<code>postMessage(send) 和 onmessage</code>。这两个接口有点类似于websocket,可以实现两个跨域站点页面之间的数据传递。</p>
<p><a href="https://link.segmentfault.com/?enc=6GBXmcx%2BOwj3penyAVbJCA%3D%3D.COn65e4qyGiFka9SUH2C4zhgiP2piDAZslntX9QchVPoJ3kQdAmib7qC0h2822LFbh8AX%2F8WD8fX7UfMqcdgYdPUSkaCuz5QhQp4z1bkILV0X6zpMOgKd%2Bd1TQvzvG%2BrwnEo%2FRMZwwSvV5bG%2F1xlfw%3D%3D" rel="nofollow">postMessage API</a></p>
<p>下面是实践过程中两个小栗子:分别父页面传递信息给iframe,iframe传递信息给父页面。</p>
<h2>三:iframe获取父页面信息</h2>
<p>话不多说,直接上码:<br>参考demo:<a href="https://link.segmentfault.com/?enc=oaZxsb7iU4MKlgGrE9l8FA%3D%3D.qkBjvMtCOJhzRqpbY1Lw0EST241l3vUqjt85i2QEhcLOV13DIvRZ0FluSZejG8XUDw4aqWzmJm6aOgkiZjerBg%3D%3D" rel="nofollow">父页面传给子页面demo</a></p>
<h3>父页面代码</h3>
<pre><code><html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>崔涣 iframe postmessage 父页面</title>
<script type="text/JavaScript">
function sendIt() {
// 通过 postMessage 向子窗口发送数据
document.getElementById("otherPage").contentWindow
.postMessage(
document.getElementById("message").value,
"http://cuihuan.net:8003"
);
}
</script>
</head>
<body>
<!-- 通过 iframe 嵌入子页面 -->
<iframe src="http://cuihuan.net:8003/test.html" id="otherPage"></iframe>
<br/>
<br/>
<input type="text" id="message"/>
<input type="button" value="Send to child.com" onclick="sendIt()"/>
</body>
</html></code></pre>
<h3>子页面代码</h3>
<pre><code><html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>崔涣测试子页面信息</title>
<script type="text/JavaScript">
//event 参数中有 data 属性,就是父窗口发送过来的数据
window.addEventListener("message", function( event ) {
// 把父窗口发送过来的数据显示在子窗口中
document.getElementById("content").innerHTML+=event.data+"<br/>";
}, false );
</script>
</head>
<body>
this is the 8003 port for cuixiaozhuai
<div id="content"></div>
</body>
</html></code></pre>
<p>demo 效果如下图:两个跨域页面之间,父页面给子页面传递数据。<br><a href="https://link.segmentfault.com/?enc=lvhYOgR%2Bd6lPliacR7NiXg%3D%3D.41tBHNNQ2%2BpE7aRRKTtJC40OltmTntDXyGFEhHqsq1vnGJ5BDCiWrwsG%2BH4u5TyoMKB5p6JQBt%2FcYZGH%2B6Mm9gyq3%2Bt6hwAtWABoEQPa8CLVlriXM5v%2BWdzy4WxadeKP" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/02/16743333-E2AF-40E5-8DD2-0CBCE8919C66.png" alt="16743333-E2AF-40E5-8DD2-0CBCE8919C66" title="16743333-E2AF-40E5-8DD2-0CBCE8919C66"></a></p>
<h2>四:iframe传递信息给父页面</h2>
<p>参考demo:<a href="https://link.segmentfault.com/?enc=XoOqF1yP4phuVWSKu4QbLg%3D%3D.QGMh8AKxtjTRsJokLzIRlBnIz0Na62IT4yzoFkm4ccvpuXPzUQWzgu3WMcC%2FNsvTUBK16x8seEqceTcHWUh7XA%3D%3D" rel="nofollow">跨域子页面传给父页面demo</a></p>
<h3>父页面代码</h3>
<pre><code><html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>崔涣测试父页面</title>
<script type="text/JavaScript">
//event 参数中有 data 属性,就是父窗口发送过来的数据
window.addEventListener("message", function( event ) {
// 把父窗口发送过来的数据显示在子窗口中
document.getElementById("content").innerHTML+=event.data+"<br/>";
}, false );
</script>
</head>
<body>
<iframe src="http://cuihuan.net:8003/iframeSon.html" id="otherPage"></iframe>
<br/>
this is the 1015 port for cuixiaozhuai。
<div id="content"></div>
</body>
</html></code></pre>
<h3>子页面代码</h3>
<pre><code><html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>崔小涣iframe postmessage 测试页面</title>
<script type="text/JavaScript">
function sendIt() {
// 子页面给父页面传输信息
parent.postMessage(
document.getElementById("message").value,
"http://cuihuan.net:1015"
);
}
</script>
</head>
<body>
<br/>
this is the port for cuixiaozhuai
<input type="text" id="message"/>
<input type="button" value="Send to child.com" onclick="sendIt()"/>
</body>
</html></code></pre>
<p>demo 效果如下图:两个跨域页面之间,子页面传递数据给父页面传递数据。<br><a href="https://link.segmentfault.com/?enc=CAHyrTzFtNLdhk%2ByZ%2FpI5A%3D%3D.XyDHQcm%2Fl8NQq2w6%2Fb2cuCuZUKBSR8Bw5LqWyld%2F8UdMnpIQH0EtMxayK7vVj6zgP3uGNFlcGD9tgL1UH9Ibxwlg6qlgU7Aa6Di0pqS9EEE0aWOORFYpqCYY%2BiA%2F22Nr" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/02/22433B4C-8836-4546-93DB-7A896F8B4B37.png" alt="22433B4C-8836-4546-93DB-7A896F8B4B37" title="22433B4C-8836-4546-93DB-7A896F8B4B37"></a></p>
<h2>五:postmessage简单分析和安全问题</h2>
<p>postmessage 传送过来的信息如下图,<br><a href="https://link.segmentfault.com/?enc=R6xbNIkD5ys4%2FIXnd7jn7g%3D%3D.eImQXX4evi6FgPtJypWi9Md0d5xLerii4fdLiuOvcBDCYxgzs0ZjBJ4Z8ez9w3P17ZRBECjVHq4E16bSHwAeyw0oZH4THZNN5e4qlIh0jWOg9nuaoR7%2BfK5sCLL5OAwt" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/02/BABB5101-CD9D-448C-99EE-910334405D3D.png" alt="BABB5101-CD9D-448C-99EE-910334405D3D" title="BABB5101-CD9D-448C-99EE-910334405D3D"></a></p>
<p>几乎包含了所有应该有的信息。甚至data中可以包含object,出于安全考虑可以域的校验,数据规则的校验安全校验,如下代码</p>
<pre><code>
window.addEventListener('message', function (event) {
//校验函数是否合法
var checkMessage = function () {
// 只获取需要的域,并非所有都可以跨域
if (event.origin != "need domain") {
return false;
}
var message = event.data;
// 传输数据类型校验
if (typeof(message) !== 'object') {
return false;
}
// message 的rule中包含xxx则为xxx需要字段。
return message.rule === "xxx";
};
if (checkMessage()) {
// 通过校验进行相关操作
addDetailFunc(event);
}
});</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=rMTAINywDn9BXVagV9FZLw%3D%3D.41dksRZPtrzGviC3TueioS6H2DimQlfJswMIsyt4KC0g6wUtEio3HY8Rgr1IVMqrv6G66xvCEiEB4dR45wJYLJwaVcPyLt5ZKQwZ%2B%2BQVqYYwbFYBsvlvM6%2BMnYMN%2BRPFBeKT1jyfWG9%2FnyrHjoTzKA%3D%3D" rel="nofollow">postMessage处理iframe 跨域问题</a> | <a href="https://link.segmentfault.com/?enc=SrA3MGWZ86nxSSiqDom55A%3D%3D.zmhRRsS7NQZCIk84FCT9WmGDxlinDj3dHx0XERypFmA%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
php爬虫:知乎用户数据爬取和分析
https://segmentfault.com/a/1190000004357994
2016-01-24T18:37:39+08:00
2016-01-24T18:37:39+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
15
<blockquote><p>背景说明:小拽利用php的curl写的爬虫,实验性的爬取了知乎5w用户的基本信息;同时,针对爬取的数据,进行了简单的分析呈现。<a href="https://link.segmentfault.com/?enc=AxpjxZygpf73ATBmW56u%2BQ%3D%3D.knTwkAoCjvkYJwMu327M75WUNLLxLJmSy2KkJ4txFx7wo5F7kazVzvQECklUX17SvdWuOYp3kextUwVWIVln5w%3D%3D" rel="nofollow">demo 地址</a></p></blockquote>
<p>php的spider代码和用户dashboard的展现代码,整理后上传github,在个人博客和公众号更新代码库,程序仅供娱乐和学习交流;如果有侵犯知乎相关权益,请尽快联系本人删除。</p>
<h2>无图无真相</h2>
<p>移动端分析数据截图</p>
<p><a href="https://link.segmentfault.com/?enc=q4qyzEsWRhtTG4995Xy%2FFQ%3D%3D.zP3gflXrybnl%2FnvaKbdqkAwPhzdf4qFIho9WGRldOyehWjPY5lZYnvPVhh1cb7vII6ZUCyj5kjilcrynn59f5w%2FW4mVa3PqcmKaYNfsNPVM%3D" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/01/wise_web_spider.png" alt="wise_web_spider" title="wise_web_spider"></a></p>
<p>pc端分析数据截图</p>
<p><a href="https://link.segmentfault.com/?enc=PotM7TWDb6uY%2BzuQbqFIiQ%3D%3D.zqEDrTmC1P8T2z9ixBUplIkjnedFFoQC3vbNhGP4ajIrJn0pfObvoYYSSvb7TBYiJCtlC%2FTEe16sSoT3v%2FdWMw%3D%3D" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2016/01/web_spider-629x1024.png" alt="web_spider" title="web_spider"></a></p>
<p>整个爬取,分析,展现过程大概分如下几步,小拽将分别介绍</p>
<ul>
<li><p>curl爬取知乎网页数据</p></li>
<li><p>正则分析知乎网页数据</p></li>
<li><p>数据数据入库和程序部署</p></li>
<li><p>数据分析和呈现</p></li>
</ul>
<h2>curl爬取网页数据</h2>
<p>PHP的curl扩展是PHP支持的,允许你与各种服务器使用各种类型的协议进行连接和通信的库。是一个非常便捷的抓取网页的工具,同时,支持多线程扩展。</p>
<p>本程序抓取的是知乎对外提供用户访问的个人信息页面<a href="https://link.segmentfault.com/?enc=fNYHPNq6oxq5VHQTx7CMGg%3D%3D.t03vTVzF0z6Z%2BMGjApwQ3H2Cni7hN%2BHYNODbZ8c9k7eJPbQpuNTlK01tkazSzvqT" rel="nofollow">https://www.zhihu.com/people/xxx,</a>抓取过程需要携带用户cookie才能获取页面。直接上码</p>
<ul>
<li>
<p>获取页面cookie</p>
<pre><code>// 登录知乎,打开个人中心,打开控制台,获取cookie
document.cookie
"_za=67254197-3wwb8d-43f6-94f0-fb0e2d521c31; _ga=GA1.2.2142818188.1433767929; q_c1=78ee1604225d47d08cddd8142a08288b23|1452172601000|1452172601000; _xsrf=15f0639cbe6fb607560c075269064393; cap_id="N2QwMTExNGQ0YTY2NGVddlMGIyNmQ4NjdjOTU0YTM5MmQ=|1453444256|49fdc6b43dc51f702b7d6575451e228f56cdaf5d"; __utmt=1; unlock_ticket="QUJDTWpmM0lsZdd2dYQUFBQVlRSlZUVTNVb1ZaNDVoQXJlblVmWGJ0WGwyaHlDdVdscXdZU1VRPT0=|1453444421|c47a2afde1ff334d416bafb1cc267b41014c9d5f"; __utma=51854390.21428dd18188.1433767929.1453187421.1453444257.3; __utmb=51854390.14.8.1453444425011; __utmc=51854390; __utmz=51854390.1452846679.1.dd1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __utmv=51854390.100-1|2=registration_date=20150823=1^dd3=entry_date=20150823=1"</code></pre>
</li>
<li>
<p>抓取个人中心页面<br> 通过curl,携带cookie,先抓取本人中心页面</p>
<pre><code>/**
* 通过用户名抓取个人中心页面并存储
*
* @param $username str :用户名 flag
* @return boolean :成功与否标志
*/
public function spiderUser($username)
{
$cookie = "xxxx" ;
$url_info = 'http://www.zhihu.com/people/' . $username; //此处cui-xiao-zhuai代表用户ID,可以直接看url获取本人id
$ch = curl_init($url_info); //初始化会话
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_COOKIE, $cookie); //设置请求COOKIE
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$result = curl_exec($ch);
file_put_contents('/home/work/zxdata_ch/php/zhihu_spider/file/'.$username.'.html',$result);
return true;
}</code></pre>
</li>
</ul>
<h2>正则分析网页数据</h2>
<h3>分析新链接,进一步爬取</h3>
<p>对于抓取过来的网页进行存储,<code>要想进行进一步的爬取,页面必须包含有可用于进一步爬取用户的链接</code>。通过对知乎页面分析发现:在个人中心页面中有关注人和部分点赞人和被关注人。<br>如下所示</p>
<pre><code>// 抓取的html页面中发现了新的用户,可用于爬虫
<a class="zm-item-link-avatar avatar-link" href="/people/new-user" data-tip="p$t$new-user">
</code></pre>
<p>ok,这样子就可以通过自己-》关注人-》关注人的关注人-》。。。进行不断爬取。接下来就是通过正则匹配提取该信息</p>
<pre><code>// 匹配到抓取页面的所有用户
preg_match_all('/\/people\/([\w-]+)\"/i', $str, $match_arr);
// 去重合并入新的用户数组,用户进一步抓取
self::$newUserArr = array_unique(array_merge($match_arr[1], self::$newUserArr));</code></pre>
<p>到此,整个爬虫过程就可以顺利进行了。<br>如果需要大量的抓取数据,可以研究下<code>curl_multi</code>和<code>pcntl</code>进行多线程的快速抓取,此处不做赘述。</p>
<h3>分析用户数据,提供分析</h3>
<p>通过正则可以进一步匹配出更多的该用户数据,直接上码。</p>
<pre><code>
// 获取用户头像
preg_match('/<img.+src=\"?([^\s]+\.(jpg|gif|bmp|bnp|png))\"?.+>/i', $str, $match_img);
$img_url = $match_img[1];
// 匹配用户名:
// <span class="name">崔小拽</span>
preg_match('/<span.+class=\"?name\"?>([\x{4e00}-\x{9fa5}]+).+span>/u', $str, $match_name);
$user_name = $match_name[1];
// 匹配用户简介
// class bio span 中文
preg_match('/<span.+class=\"?bio\"?.+\>([\x{4e00}-\x{9fa5}]+).+span>/u', $str, $match_title);
$user_title = $match_title[1];
// 匹配性别
//<input type="radio" name="gender" value="1" checked="checked" class="male"/> 男&nbsp;&nbsp;
// gender value1 ;结束 中文
preg_match('/<input.+name=\"?gender\"?.+value=\"?1\"?.+([\x{4e00}-\x{9fa5}]+).+\;/u', $str, $match_sex);
$user_sex = $match_sex[1];
// 匹配地区
//<span class="location item" title="北京">
preg_match('/<span.+class=\"?location.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_city);
$user_city = $match_city[1];
// 匹配工作
//<span class="employment item" title="人见人骂的公司">人见人骂的公司</span>
preg_match('/<span.+class=\"?employment.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_employment);
$user_employ = $match_employment[1];
// 匹配职位
// <span class="position item" title="程序猿"><a href="/topic/19590046" title="程序猿" class="topic-link" data-token="19590046" data-topicid="13253">程序猿</a></span>
preg_match('/<span.+class=\"?position.+\"?.+\"([\x{4e00}-\x{9fa5}]+).+\">/u', $str, $match_position);
$user_position = $match_position[1];
// 匹配学历
// <span class="education item" title="研究僧">研究僧</span>
preg_match('/<span.+class=\"?education.+\"?.+\"([\x{4e00}-\x{9fa5}]+)\">/u', $str, $match_education);
$user_education = $match_education[1];
// 工作情况
// <span class="education-extra item" title='挨踢'>挨踢</span>
preg_match('/<span.+class=\"?education-extra.+\"?.+>([\x{4e00}-
\x{9fa5}]+)</u', $str, $match_education_extra);
$user_education_extra = $match_education_extra[1];
// 匹配关注话题数量
// class="zg-link-litblue"><strong>41 个话题</strong></a>
preg_match('/class=\"?zg-link-litblue\"?><strong>(\d+)\s.+strong>/i', $str, $match_topic);
$user_topic = $match_topic[1];
// 关注人数
// <span class="zg-gray-normal">关注了
preg_match_all('/<strong>(\d+)<.+<label>/i', $str, $match_care);
$user_care = $match_care[1][0];
$user_be_careed = $match_care[1][1];
// 历史浏览量
// <span class="zg-gray-normal">个人主页被 <strong>17</strong> 人浏览</span>
preg_match('/class=\"?zg-gray-normal\"?.+>(\d+)<.+span>/i', $str, $match_browse);
$user_browse = $match_browse[1];
</code></pre>
<h2>数据入库和程序优化</h2>
<p>在抓取的过程中,有条件的话,一定要通过redis入库,确实能提升抓取和入库效率。没有条件的话只能通过sql优化。这里来几发心德。</p>
<ul>
<li><p>数据库表设计索引一定要慎重。在spider爬取的过程中,建议出了用户名,左右字段都不要索引,包括主键都不要,<code>尽可能的提高入库效率</code>,试想5000w的数据,每次添加一个,建立索引需要多少消耗。等抓取完毕,需要分析数据时,批量建立索引。</p></li>
<li>
<p>数据入库和更新操作,一定要批量。 mysql 官方给出的增删改的建议和速度:<a href="https://link.segmentfault.com/?enc=CbLMfWkaNaaB8akWbkCKfA%3D%3D.0QAQiaqsbc6lHKhPAzn5P0Sjny84iORG2Y3jLuVwYY355%2B2K7uvfbYu3wHgA4Uedm3Be8wDNisODii30FYeqdw%3D%3D" rel="nofollow">http://dev.mysql.com/doc/refman/5.7/en/insert-speed.html</a></p>
<pre><code># 官方的最优批量插入
INSERT INTO yourtable VALUES (1,2), (5,5), ...;</code></pre>
</li>
<li>
<p>部署操作。程序在抓取过程中,有可能会出现异常挂掉,为了保证高效稳定,尽可能的写一个定时脚本。每隔一段时间干掉,重新跑,这样即使异常挂掉也不会浪费太多宝贵时间,毕竟,time is money。</p>
<pre><code>#!/bin/bash
# 干掉
ps aux |grep spider |awk '{print $2}'|xargs kill -9
sleep 5s
# 重新跑
nohup /home/cuixiaohuan/lamp/php5/bin/php /home/cuixiaohuan/php/zhihu_spider/spider_new.php & </code></pre>
</li>
</ul>
<h2>数据分析呈现</h2>
<p>数据的呈现主要使用echarts 3.0,感觉对于移动端兼容还不错。兼容移动端的页面响应式布局主要通过几个简单的css控制,代码如下</p>
<pre><code>/*兼容性和响应式div设计*/
@media screen and (max-width: 480px) {
body{
padding: 0 ;
}
.adapt-div {
width: 100% ;
float: none ;
margin: 20px 0;
}
.half-div {
height: 350px ;
margin-bottom: 10px;
}
.whole-div {
height: 350px;
}
}
<!-- 整块完整布局,半块在web端采用float的方式,移动端去掉-->
.half-div {
width: 48%;
height: 430px;
margin: 1%;
float: left
}
.whole-div {
width: 98%;
height: 430px;
margin: 1%;
float: left
}
</code></pre>
<h2>不足和待学习</h2>
<p>整个过程中涉及php,shell,js,css,html,正则等语言和部署等基础知识,但还有诸多需要改进完善,小拽特此记录,后续补充例:</p>
<ul>
<li><p>php 采用multicul进行多线程。</p></li>
<li><p>正则匹配进一步优化</p></li>
<li><p>部署和抓取过程采用redis提升存储</p></li>
<li><p>移动端布局的兼容性提升</p></li>
<li><p>js的模块化和sass书写css。</p></li>
</ul>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=fRNUD%2FHK%2FbgC%2BuMl9uDiMg%3D%3D.myIzfroVKqXqFT%2BdkAJTbLbr3AY75tEBbNeVi48Zh7%2Bh2%2Fhz%2FllxjQUmKoSrhxZ9OPO46d%2FsYKrSWl5hw5zvWuN9TivJ1IjAVsEzt%2BBTgzJgtleyn8FCaBphMCfiSABrhb4za8xte0zKG3cpP3OMBhKxc7Zzf%2FBMrSLRcU%2BRANJm6KLXWiEwpnjeYaYZaD%2Bktw2%2B27nB8D9Q8zOqGqVzYbvTRfOaMwpEb0GHW7ihRNE%3D" rel="nofollow">php爬虫:知乎用户数据爬取和分析</a> | <a href="https://link.segmentfault.com/?enc=6FfuB2QwRsRnB5hv0veUKg%3D%3D.8dJTZgHHo%2Fg7jdyFjMQcZnLSrfolqXrnf6Qymi0zDxw%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
YII2数据库查询实践
https://segmentfault.com/a/1190000004279639
2016-01-10T09:37:38+08:00
2016-01-10T09:37:38+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>初探yii2框架,对增删改查,关联查询等数据库基本操作的简单实践。</p></blockquote>
<h2>数据库配置。</h2>
<p>/config/db.php 进行数据库配置<br>配置可以参考<a href="https://link.segmentfault.com/?enc=k77g4rSDRI1jXXzp6oql1Q%3D%3D.9MSvQkrXqgBTtd4ZJQbyBDCJHGZ1hUUrGL%2BlsV0943p87F7AN249lJEhFKoIXzVRJvk%2BwKYhA%2FIEm7COOTGk1A%3D%3D" rel="nofollow"> yii文档 </a><br>实践过程中有个test库-》test表-》两条记录如下</p>
<pre><code>mysql> select * from test;
+----+--------+
| id | name |
+----+--------+
| 1 | zhuai |
| 2 | heng |
+----+--------+
18 rows in set (0.00 sec)</code></pre>
<h2>sql 查询方式</h2>
<p>yii2 提供了原始的数据库查询方式findBySql;同时,<code>通过占位符的方式,自动进行了基本的sql注入防御</code>。上码</p>
<pre><code>// 最基本的查询方式
$sql = "select * from test where 1";
$res = Test::findBySql($sql)->all();
var_dump(count($res)); // res->2
// findbysql 防止sql注入方式
$id = '1 or 1=1';
$sql = "select * from test where id = " . $id;
$res = Test::findBySql($sql)->all();
var_dump(count($res)); // res-> 2
$sql = "select * from test where id = :id";
// 定位符会自动防止sql 注入
$res = Test::findBySql($sql,array(":id"=>$id))->all();
var_dump(count($res)); // res->1
</code></pre>
<h2>activeRecord查询方式</h2>
<p>每个框架除了原有的sql方式,都会提供相应的封装的查询方式,yii2亦然。</p>
<h3>创建model</h3>
<p>yii的model基本方式如下,代码如下不赘述。</p>
<pre><code><?php
namespace app\models;
use Yii;
use yii\db\ActiveRecord;
class Test extends ActiveRecord
{
// 可无,对应表:默认类名和表名匹配,则无需此函数
public static function tableName()
{
return 'test';
}
// 可无,验证器:主要用于校验各个字段
public function rules(){
return [
['id', 'integer'],
['name', 'string', 'length' => [0, 100]],
];
}
}</code></pre>
<p>使用的时候需要引入model</p>
<pre><code>use app\models\Test;</code></pre>
<h3>增加操作</h3>
<pre><code>// add 操作
$test = new Test();
$test->name = 'test';
// 合法性校验
$test->validate();
if($test->hasErrors()){
echo "数据不合法";
die;
}
$test->save();</code></pre>
<h3>查询操作</h3>
<p>查询操作先上官方文档<br><a href="https://link.segmentfault.com/?enc=9C8Qb%2BWeqhJDV5qruqVOrA%3D%3D.VTZK%2Bpwu6su%2FvcqxjuOaFxdd8N4Y%2BFHfmBetnLT0MofMl8O2qpJmaxIDyBuAz22eUND1UjyJVNlkR7XRa4fGag%3D%3D" rel="nofollow"> activeRecord doc</a><br><a href="https://link.segmentfault.com/?enc=fd5Aaoz57eDaZuMm8ydoHA%3D%3D.0sZD6VsfQjsFIToMYC7GVVgGBNTRdghgXo44UewI2fwOmhN8yGd%2FlYH%2Fmjkjxv2MJ7S%2FXJn%2FRKR3WLiMC2deiw%3D%3D" rel="nofollow"> where doc</a><br>需要强调的是:yii查询提供了特别多丰富的库,例如代码中的批量查询处理等等,细节可以看文档。</p>
<pre><code>// select
// id = 1
$res = Test::find()->where(['id' => 1])->all();
var_dump(count($res)); //1
// id > 0
$res = Test::find()->where(['>','id',0])->all();
var_dump(count($res)); //2
// id > =1 id <=2
$res = Test::find()->where(['between','id',1,2])->all();
var_dump(count($res)); //2
// name字段like
$res = Test::find()->where(['like', 'name', 'cuihuan'])->all();
var_dump(count($res)); //2
// 查询的使用 obj->array
$res = Test::find()->where(['between','id',1,2])->asArray()->all();
var_dump($res[0]['id']); //2
// 批量查询,对于大内存操作的批量查询
foreach (Test::find()->batch(1) as $test) {
var_dump(count($test));
}</code></pre>
<h3>删除操作</h3>
<pre><code>// delete
// 选出来删除
$res = Test::find()->where(['id'=>1])->all();
$res[0]->delete();
// 直接删除
var_dump(Test::deleteAll('id>:id', array(':id' => 2)));</code></pre>
<h3>修改操作</h3>
<p>除了代码中方式,yii2直接提供update操作。</p>
<pre><code>// 活动记录修改
$res = Test::find()->where(['id'=>4])->one();
$res->name = "update";
$res->save();</code></pre>
<h3>关联查询操作</h3>
<p>关联查询示例中两个表:<br>一个学生表(student):id ,name;<br>一个分数表(score):id,stu_id,score</p>
<pre><code>// 相应学生的所有score
$stu = Student::find()->where(['name'=>'xiaozhuai'])->one();
var_dump($stu->id);
// 基本获取
$scores_1 = $stu->hasMany('app\model\Score',['stu_id'=>$stu->id])->asArray()->all();
$scores_2 = $stu->hasMany(Score::className(),['stu_id'=>'id'])->asArray()->all();
var_dump($scores_1);
var_dump($scores_2);</code></pre>
<p>两种关联查询方式;但是,在controller进行相关操作,代码显的过于混乱,在model中封装调用</p>
<p>首先在student model中封装相关关联调用函数</p>
<pre><code><?php
namespace app\models;
use Yii;
use yii\db\ActiveRecord;
class Student extends ActiveRecord
{
public static function tableName()
{
return 'student';
}
// 获取分数信息
public function getScores()
{
$scores = $this->hasMany(Score::className(), ['stu_id' => 'id'])->asArray()->all();
return $scores;
}
}
</code></pre>
<p>之后直接调用,两种调用方式</p>
<pre><code>// 函数封装之后调用
$scores = $stu->getScores();
var_dump($scores);
// 利用__get 的自动调用的方式
$scores = $stu->scores;
var_dump($scores);
</code></pre>
<h2>最后</h2>
<p>上面在yii2的部署和使用过程中的一些基本的增删改查,关联查询等操作。但是如果想要将yii2的db操作使用好,还要看文档大法:<br><a href="https://link.segmentfault.com/?enc=4PC3gcVnLMeoGHNh8SQt2g%3D%3D.c8IaUvPEVE5jCa%2BC52uyR7rcBaG3GH9XD9S43NeujWOGiGCKi3EweroUJJjkDtXovqii5IacAPTQt4oKmuUuzA%3D%3D" rel="nofollow"> activeRecord doc</a></p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=SQZjcuzGowMRfV4ZmnD8Fg%3D%3D.1pEjwHTOBtsgeveCUXHQE9GmbpZ6oOaWW8FbM2mY9mP6n0NqIYMD%2BBNZd56oHYgxO35eY4irYH1TlCvkH8XN3rVxg%2F%2B1VNzJ7wjewqa2UuYzMdxXyTvrKMmAJ9O6z%2F%2BITg4RkH8n3WtNM4uPPET9RQ%3D%3D" rel="nofollow">YII2数据库查询实践</a> | <a href="https://link.segmentfault.com/?enc=uxQmmjBpHy0%2F7W2Jx3QUUA%3D%3D.CSmNzrSIwJkEb7ndfr8wybdfD5ofZ2gzPeeujae%2FOAg%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
机器多次恶意提交攻击简单防范
https://segmentfault.com/a/1190000004262782
2016-01-07T08:22:13+08:00
2016-01-07T08:22:13+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
3
<blockquote><p>先说背景:机器不断的发送请求或者恶意提交,会给服务器造成很大压力;针对这种攻击<code>最优的策略是判断提交次数,产生动态验证码</code>,即<code>判断ip规定时间内重复发送达到N次弹出验证码</code>。下面是小拽在实践过程中一个简单的识别ip,利用session记录和防御的过程。</p></blockquote>
<h2>识别和校验ip</h2>
<p>过程如下;</p>
<ul>
<li><p>识别ip</p></li>
<li><p>ip属于白名单直接通过[白名单策略:内网ip+指定ip表]</p></li>
<li><p>利用session存储ip的请求时间戳</p></li>
<li><p>校验规定时间内ip的请求次数</p></li>
<li><p>采取相应的措施</p></li>
</ul>
<pre><code>/**
* 获取和校验ip;同时防止短时间内多次提交
*
* @notice :弹出验证码,需要替换掉echo $echo_str 即可。
* @return string :返回校验成功的ip
*/
protected function getAndCheckIP()
{
// 获取环境ip
if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown"))
$ip = getenv("HTTP_CLIENT_IP");
else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown"))
$ip = getenv("HTTP_X_FORWARDED_FOR");
else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown"))
$ip = getenv("REMOTE_ADDR");
else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown"))
$ip = $_SERVER['REMOTE_ADDR'];
else
$ip = "unknown";
// check 环境ip
if (!$this->isWhiteList($ip)) {
$echo_str = "提交过于频繁,请稍后再试!";
// 构建ip的时间栈数据
if (!is_array($_SESSION[$ip])) {
$_SESSION[$ip] = array();
}
if (isset($_SESSION[$ip][0])) {
$_SESSION[$ip][] = time();
// session 保存时间为6小时。清理session
$post_interval_first = time() - $_SESSION[$ip][0];
if ($post_interval_first > 21600) {
$_SESSION[$ip] = array();
}
// 两次提交小于1s,禁止提交
$post_interval_pre = time() - $_SESSION[$ip][count($_SESSION[$ip]) - 3];
if ($post_interval_pre < 1) {
echo $echo_str;
exit;
};
// 您在10s内已经提交了3请求,禁止提交
$post_interval_third = time() - $_SESSION[$ip][count($_SESSION[$ip]) - 3];
if (isset($_SESSION[$ip][3]) && ($post_interval_third < 10)) {
echo $echo_str;
exit;
}
// 您在1分钟期间已经提交了5请求,禁止提交
$post_interval_fifth = time() - $_SESSION[$ip][count($_SESSION[$ip]) - 3];
if (isset($_SESSION[$ip][5]) && ($post_interval_fifth < 60)) {
echo $echo_str;
exit;
}
// 6小时内提交10次,禁止提交
if (isset($_SESSION[$ip][10])) {
echo $echo_str;
exit;
}
} else {
$_SESSION[$ip][] = time();
}
}
return ($ip);
}
</code></pre>
<h2>白名单策略</h2>
<p>白名单策略采用:内网ip放行和特定ip放行</p>
<pre><code>/**
* 检验是否存在于白名单中
*
* @param $ip :校验的ip
* @return bool :校验结果
*/
function isWhiteList($ip){
/**
* 内网ip默认全部存在于白名单中
*/
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)){
return true;
}
// 是否在写死的whitelist 里面
return in_array($ip,$this->_WHTTE_LIST);
}
</code></pre>
<h2>防攻击策略</h2>
<p>小拽采用的比较简单的策略,如上面代码,实际过程中可以结合业务需求。</p>
<ul>
<li><p>1s内禁止重复提交</p></li>
<li><p>5s内提交上限3次</p></li>
<li><p>60s内提交上限5次</p></li>
<li><p>6小时内提交上限10次</p></li>
</ul>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=EJLUntRa9C8M7LpO%2FggqoQ%3D%3D.GgysrmMjrfgkqTeOb2MJ%2BvF439cZyADXWJP88fsg7MxWkmMLi6bQ%2Bm0UUhaI7q1d1g7tD3NKf9OBpZe6v8pMis4Shzmyo%2F4Fxgds9cvOdWjAy2eBzHaFM2dcJHx%2FiimzaLuzX0W2%2FfP4EZbxkzNUO1zyBU8vcRg1ZVG0IFpidN0BLlpMIXEtANaagOCAQfuYbMNZH37ZtlxBtkk3wb4bYQ%3D%3D" rel="nofollow">机器多次恶意提交攻击简单防范</a> | <a href="https://link.segmentfault.com/?enc=4V9skBG%2BVqDPs8NHL6g01Q%3D%3D.tOVuuxqcjxfvAYqbXyL6QuTrWr1XFBce%2F1DIbj3q1Zw%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
检验mysql主从备份,读写分离
https://segmentfault.com/a/1190000004239415
2016-01-03T12:30:02+08:00
2016-01-03T12:30:02+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>先说背景:mysql的主从部署,读写分离,负载均衡之后;需要简单测试和校验一下,在实践中写了个简单的php脚本和校验过程,mark一下,方便再次部署校验。</p></blockquote>
<h2>数据库部署和实践</h2>
<p>数据库在实践中,<code>往往需要进行多机主从备份保证安全</code>,这个毋庸置疑;<code>进行读写分离和负载均衡可以极大的提升mysql的读写性能</code>。作者在实践中采用阿里的ameoba进行了读写分离和负载均衡操作。细节步骤参考小拽文章:<a href="https://link.segmentfault.com/?enc=v%2BXIMVJUjoVSxZWpr9Kqow%3D%3D.E2GUDYdzk7ywkL5ERDtYuoM1sEhrHurnFusHiVCMmDQ%3D" rel="nofollow"> mysql主从备份,读写分离和负载均衡实践 </a></p>
<p>那么问题来了,部署完了,校验也需慎重,下面是简单的校验过程。</p>
<h2>php简单读写库脚本</h2>
<p>上码:能用代码说的,最好不用文字说话!</p>
<pre><code><?php
/**
* 进行读写分离的校验
* @notice :需要关闭主从备份的情况下进行
* 原理:打开主从,写主库,从库获取数据,校验主从备份;关闭主从写ameoba,校验读写分离和负载
*
* @author: cuixiaohuan
* Date: 15/12/29
* Time: 下午9:10
*/
class ReadAndWriteTest {
// ameoba 设定端口,校验读写时,放主库配置
const IP ="ip:port";
const PWD ="pwd";
const USER ="user";
const DB ="db";
public function __construct(){
error_reporting(E_ALL ^ E_DEPRECATED);
$this->initDb();
$this->_writeTest();
$this->_selectTest();
}
/**
* 进行10次读操作
*/
public function _selectTest(){
for ($i = 0; $i < 10; $i++) {
$read_sql = 'select * from test limit 10';
$g_result = mysql_query($read_sql);
var_dump($g_result);
mysql_free_result($g_result);
}
}
/**
* 进行10次写操作
*/
public function _writeTest(){
for ($i = 0; $i < 10; $i++) {
$id = uniqid();
$content = "pingce" . uniqid();
$write_sql = 'INSERT INTO `test`(`test`, `test1`) VALUES ("' . $id . '","' . $content . '")';
$g_result = mysql_query($write_sql);
var_dump($g_result);
}
}
/**
* 初始化数据库连接信息 info
*/
private function initDb()
{
$crowd_conn = mysql_pconnect(self::IP, self::USER, self::PWD);
if (!$crowd_conn) {
die("Could not connect:" . mysql_error());
}
$crowd_db = mysql_select_db(self::DB, $crowd_conn);
}
}
$rw = new ReadAndWriteTest();</code></pre>
<h2>主从备份校验</h2>
<ul>
<li><p>开启slave</p></li>
<li><p>调整数据库信息为mysql,主库信息,运行脚本。</p></li>
<li><p>查看从库的log,有如下写入操作,说明实时主从备份成功。</p></li>
</ul>
<pre><code>151231 15:36:21 4 Query start slave
14 Connect Out pingce@10.95.112.120:3666
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e5c85","pingce5684d957e5cf2")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e7937","pingce5684d957e7982")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e8e96","pingce5684d957e8ee4")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957ea2c2","pingce5684d957ea2eb")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eb565","pingce5684d957eb5b3")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957ec7ee","pingce5684d957ec83e")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eda2f","pingce5684d957eda78")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eeca4","pingce5684d957eecf0")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eff16","pingce5684d957eff61")
15 Query COMMIT /* implicit, from Xid_log_event */
15 Query BEGIN
15 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957f121e","pingce5684d957f126d")
15 Query COMMIT /* implicit, from Xid_log_event */</code></pre>
<h2>检验读写分离</h2>
<ul><li><p>读写分离,首先需要关闭从机器上的slave。原因:存在主从的话,无法通过log查看出读写分离操作。</p></li></ul>
<pre><code>mysql> stop slave;
Query OK, 0 rows affected (0.08 sec)</code></pre>
<ul><li><p>运行脚本:如下信息标示,运行成功。</p></li></ul>
<pre><code>[cuixiaohuan TestScript]$ /home/work/lamp/php5/bin/php ReadAndWriteTest.php
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e5c85","pingce5684d957e5cf2")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e7937","pingce5684d957e7982")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e8e96","pingce5684d957e8ee4")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957ea2c2","pingce5684d957ea2eb")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eb565","pingce5684d957eb5b3")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957ec7ee","pingce5684d957ec83e")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eda2f","pingce5684d957eda78")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eeca4","pingce5684d957eecf0")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eff16","pingce5684d957eff61")bool(true)
INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957f121e","pingce5684d957f126d")bool(true)
resource(5) of type (mysql result)
resource(6) of type (mysql result)
resource(7) of type (mysql result)
resource(8) of type (mysql result)
resource(9) of type (mysql result)
resource(10) of type (mysql result)
resource(11) of type (mysql result)
resource(12) of type (mysql result)
resource(13) of type (mysql result)
resource(14) of type (mysql result)</code></pre>
<ul><li><p>查询读写库的log</p></li></ul>
<blockquote><p>解释:之所以主库放一个读写库,是因为有些要求超高一致性的数据,备份可能会有延迟;所以,主库承担读写操作,和高负载。</p></blockquote>
<pre><code>#读写机器log: 进行了10次写和 四次读
151231 15:29:27 19 Query set names gbk^@
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e5c85","pingce5684d957e5cf2")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e7937","pingce5684d957e7982")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957e8e96","pingce5684d957e8ee4")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957ea2c2","pingce5684d957ea2eb")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eb565","pingce5684d957eb5b3")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957ec7ee","pingce5684d957ec83e")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eda2f","pingce5684d957eda78")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eeca4","pingce5684d957eecf0")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957eff16","pingce5684d957eff61")
19 Query INSERT INTO `test`(`test`, `test1`) VALUES ("5684d957f121e","pingce5684d957f126d")
19 Query select * from test limit 10
151231 15:29:28 19 Query select * from test limit 10
19 Query select * from test limit 10
19 Query select * from test limit 10
19 Query select * from test limit 10</code></pre>
<ul><li><p>查看读库的log</p></li></ul>
<pre><code># 只进行了读操作,校正了数据库的读写分离操作。
151231 15:29:20 4 Query stop slave
151231 15:29:27 3 Query set names gbk^@
3 Query select * from test limit 10
3 Query select * from test limit 10
3 Query select * from test limit 10
3 Query select * from test limit 10
3 Query select * from test limit 10
3 Query select * from test limit 10</code></pre>
<h2>最后</h2>
<p>一句话:打开slave,校验主从备份;关闭slave,校验读写分离。</p>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=%2F0tLA3mO5M9VaZ6nAMUBpA%3D%3D.U4C8ynXql43b0QZZv%2BbQGfE5e4Jul3NQXd3KSs3gkIZAnVRlMMpBfboxAk6fRmb6xzgfwzJpH3q%2BB%2FO1hpToV%2BBv3PZGZgA6g7Z23rqli4OS6OmJqholheCSxIia%2BTv2mmcItuOHXu5mrQyBlZauV15F1aDxj0YNMfoWsoxtXktWatyNjEPzl16pbYtyeymy" rel="nofollow"> 检验mysql主从备份,读写分离 </a> | <a href="https://link.segmentfault.com/?enc=4SHKp2PKGdp0YFu1A0lFfQ%3D%3D.K%2Fl%2BdoMxX9TqGGesvUfckDXUfVnC0BhT4dSgc13Ymd4%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
php反射调用private方法实践
https://segmentfault.com/a/1190000004166279
2015-12-18T17:31:38+08:00
2015-12-18T17:31:38+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>问题背景:单测中有个普遍性的问题,<code>被侧类中的private方法无法直接调用</code>。小拽在处理过程中通过<code>反射改变方法权限,进行单测</code>,分享一下,直接上代码。</p></blockquote>
<h2>简单被测试类</h2>
<p>生成一个简单的被测试类,只有个private方法。</p>
<pre><code><?php
/**
* 崔小涣单测的基本模板。
*
* @author cuihuan
* @date 2015/11/12 22:15:31
* @version $Revision:1.0$
**/
class MyClass {
/**
* 私有方法
*
* @param $params
* @return bool
*/
private function privateFunc($params){
if(!isset($params)){
return false;
}
echo "test success";
return $params;
}
}</code></pre>
<h2>单测代码</h2>
<pre><code><?php
/***************************************************************************
*
* $Id: MyClassTest T,v 1.0 PsCaseTest cuihuan Exp$
*
**************************************************************************/
/**
* 崔小涣单测的基本模板。
*
* @author cuihuan
* @date 2015/11/12 22:09:31
* @version $Revision:1.0$
**/
require_once ('./MyClass.php');
class MyClassTest extends PHPUnit_Framework_TestCase {
const CLASS_NAME = 'MyClass';
const FAIL = 'fail';
protected $objMyClass;
/**
* @brief setup: Sets up the fixture, for example, opens a network connection.
*
* 可以看做phpunit的构造函数
*/
public function setup() {
date_default_timezone_set('PRC');
$this->objMyClass = new MyClass();
}
/**
* 利用反射,对类中的private 和 protect 方法进行单元测试
*
* @param $strMethodName string :反射函数名
* @return ReflectionMethod obj :回调对象
*/
protected static function getPrivateMethod($strMethodName) {
$objReflectClass = new ReflectionClass(self::CLASS_NAME);
$method = $objReflectClass->getMethod($strMethodName);
$method->setAccessible(true);
return $method;
}
/**
* @brief :测试private函数的调用
*/
public function testPrivateFunc()
{
$testCase = 'just a test string';
// 反射该类
$testFunc = self::getPrivateMethod('privateFunc');
$res = $testFunc->invokeArgs($this->objMyClass, array($testCase));
$this->assertEquals($testCase, $res);
$this->expectOutputRegex('/success/i');
// 捕获没有参数异常测试
try {
$testFunc->invokeArgs($this->transfer2Pscase, array());
} catch (Exception $expected) {
$this->assertNotNull($expected);
return true;
}
$this->fail(self::FAIL);
}
}</code></pre>
<h2>运行结果</h2>
<pre><code>cuihuan:test cuixiaohuan$ phpunit MyClassTest.php
PHPUnit 4.8.6 by Sebastian Bergmann and contributors.
Time: 103 ms, Memory: 11.75Mb
OK (1 test, 3 assertions)</code></pre>
<h2>关键代码分析</h2>
<p>封装了一个,被测类方法的反射调用;同时,<code>返回方法之前处理方法的接入权限为true</code>,便可以访问private的函数方法。</p>
<pre><code>/**
* 利用反射,对类中的private 和 protect 方法进行单元测试
*
* @param $strMethodName string :反射函数名
* @return ReflectionMethod obj :回调对象
*/
protected static function getPrivateMethod($strMethodName) {
$objReflectClass = new ReflectionClass(self::CLASS_NAME);
$method = $objReflectClass->getMethod($strMethodName);
$method->setAccessible(true);
return $method;
}
</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=Q%2BGIu7y%2BGnsd%2BJqQNZfJOw%3D%3D.pOqu2KRQyCoCYB0On7InwJSefpEia9v%2FJxuw1%2FebXLU1mkW50HIVcwVPndE2CFl4VW2J4vbzv3lKzV%2F69U4xKePg1Aqe95l%2BDPQqjD%2BIiS1Dn7LPgnMmqnix8SSVxU8DkXgbe2LI0pfFldX5ny9e1mdfrvjqtcWJiFEnU%2BCmqh0%3D" rel="nofollow">phpunit单测中调用private方法处理</a> | <a href="https://link.segmentfault.com/?enc=qGuVpq4Y8FoG7aSdbDaJ%2Fw%3D%3D.fvu3lJoncvP4aYEbsNOW1nViisseBxiCMqtc95%2F71dY%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
【高并发简单解决方案】redis队列缓存 + mysql 批量入库 + php离线整合
https://segmentfault.com/a/1190000004136250
2015-12-12T13:18:28+08:00
2015-12-12T13:18:28+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
54
<blockquote><p>需求背景:有个<code>调用统计日志存储和统计需求</code>,要求存储到mysql中;存储数据高峰能达到日均千万,瓶颈在于<code>直接入库并发太高,可能会把mysql干垮</code>。</p></blockquote>
<h2>问题分析</h2>
<p>思考:应用网站架构的衍化过程中,应用最新的框架和工具技术固然是最优选择;但是,如果能在<code>现有的框架的基础上提出简单可依赖的解决方案</code>,未尝不是一种提升自我的尝试。</p>
<p>解决:</p>
<ul>
<li><p>问题一:要求日志最好入库;但是,直接入库mysql确实扛不住,批量入库没有问题,done。【批量入库和直接入库性能差异<a href="https://link.segmentfault.com/?enc=ROzrkrp4v2647E1VAZlWpQ%3D%3D.Z9%2FfZz0qNjylGU%2B7DQbCwBX5wNSICVIi%2BnARPUbE7Tk%3D" rel="nofollow">参考文章</a>】</p></li>
<li><p>问题二:批量入库就需要有高并发的消息队列,决定采用redis list 仿真实现,而且方便回滚。</p></li>
<li><p>问题三:日志量毕竟大,保存最近30条足矣,决定用php写个离线统计和清理脚本。</p></li>
</ul>
<p>done,下面是小拽的简单实现过程</p>
<h2>一:设计数据库表和存储</h2>
<ul>
<li><p>考虑到log系统对数据库的性能更多一些,稳定性和安全性没有那么高,<code>存储引擎自然是只支持select insert 没有索引的archive</code>。如果确实有update需求,也可以采用myISAM。</p></li>
<li><p>考虑到log是实时记录的所有数据,数量可能巨大,<code>主键采用bigint,自增即可</code>。</p></li>
<li><p>考虑到log系统<code>以写为主,统计采用离线计算,字段均不要出现索引</code>,因为一方面可能会影响插入数据效率,另外读时候会造成死锁,影响写数据。</p></li>
</ul>
<h2>二:redis存储数据形成消息队列</h2>
<p>由于高并发,尽可能简单,直接,上代码。</p>
<pre><code><?php
/***************************************************************************
*
* 获取到的调用日志,存入redis的队列中.
* $Id$
*
**************************************************************************/
/**
* @file saveLog.php
* @date 2015/11/06 20:47:13
* @author:cuihuan
* @version $Revision$
* @brief
*
**/
// 获取info
$interface_info = $_GET['info'];
// 存入redis队列
$redis = new Redis();
$redis->connect('xx', 6379);
$redis->auth("password");
// 加上时间戳存入队列
$now_time = date("Y-m-d H:i:s");
$redis->rPush("call_log", $interface_info . "%" . $now_time);
$redis->close();
/* vim: set ts=4 sw=4 sts=4 tw=100 */
?></code></pre>
<h2>三:数据定时批量入库。</h2>
<p>定时读取redis消息队列里面的数据,批量入库。</p>
<pre><code><?php
/**
* 获取redis消息队列中的脚本,拼接sql,批量入库。
* @update 2015-11-07 添加失败消息队列回滚机制
*
* @Author:cuihuan
* 2015-11-06
* */
// init redis
$redis_xx = new Redis();
$redis_xx->connect('ip', port);
$redis_xx->auth("password");
// 获取现有消息队列的长度
$count = 0;
$max = $redis_xx->lLen("call_log");
// 获取消息队列的内容,拼接sql
$insert_sql = "insert into fb_call_log (`interface_name`, `createtime`) values ";
// 回滚数组
$roll_back_arr = array();
while ($count < $max) {
$log_info = $redis_cq01->lPop("call_log");
$roll_back_arr = $log_info;
if ($log_info == 'nil' || !isset($log_info)) {
$insert_sql .= ";";
break;
}
// 切割出时间和info
$log_info_arr = explode("%",$log_info);
$insert_sql .= " ('".$log_info_arr[0]."','".$log_info_arr[1]."'),";
$count++;
}
// 判定存在数据,批量入库
if ($count != 0) {
$link_2004 = mysql_connect('ip:port', 'user', 'password');
if (!$link_2004) {
die("Could not connect:" . mysql_error());
}
$crowd_db = mysql_select_db('fb_log', $link_2004);
$insert_sql = rtrim($insert_sql,",").";";
$res = mysql_query($insert_sql);
// 输出入库log和入库结果;
echo date("Y-m-d H:i:s")."insert ".$count." log info result:";
echo json_encode($res);
echo "</br>\n";
// 数据库插入失败回滚
if(!$res){
foreach($roll_back_arr as $k){
$redis_xx->rPush("call_log", $k);
}
}
// 释放连接
mysql_free_result($res);
mysql_close($link_2004);
}
// 释放redis
$redis_cq01->close();
?></code></pre>
<h2>四:离线天级统计和清理数据脚本</h2>
<pre><code>?php
/**
* static log :每天离线统计代码日志和删除五天前的日志
*
* @Author:cuihuan
* 2015-11-06
* */
// 离线统计
$link_2004 = mysql_connect('ip:port', 'user', 'pwd');
if (!$link_2004) {
die("Could not connect:" . mysql_error());
}
$crowd_db = mysql_select_db('fb_log', $link_2004);
// 统计昨天的数据
$day_time = date("Y-m-d", time() - 60 * 60 * 24 * 1);
$static_sql = "get sql";
$res = mysql_query($static_sql, $link_2004);
// 获取结果入库略
// 清理15天之前的数据
$before_15_day = date("Y-m-d", time() - 60 * 60 * 24 * 15);
$delete_sql = "delete from xxx where createtime < '" . $before_15_day . "'";
try {
$res = mysql_query($delete_sql);
}catch(Exception $e){
echo json_encode($e)."\n";
echo "delete result:".json_encode($res)."\n";
}
mysql_close($link_2004);
?></code></pre>
<h2>五:代码部署</h2>
<p>主要是部署,批量入库脚本的调用和天级统计脚本,crontab例行运行。</p>
<pre><code># 批量入库脚本
*/2 * * * * /home/cuihuan/xxx/lamp/php5/bin/php /home/cuihuan/xxx/batchLog.php >>/home/cuihuan/xxx/batchlog.log
# 天级统计脚本
0 5 * * * /home/cuihuan/xxx/php5/bin/php /home/cuihuan/xxx/staticLog.php >>/home/cuihuan/xxx/staticLog.log</code></pre>
<blockquote><p>总结:相对于其他复杂的方式处理高并发,这个解决方案简单有效:通过redis缓存抗压,mysql批量入库解决数据库瓶颈,离线计算解决统计数据,通过定期清理保证库的大小。</p></blockquote>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=c0HdwBqPzqdkaSes5k1drA%3D%3D.hPWbJ%2FHkBTZ%2Bp4dFpGK5kJRLa%2Fpe6hbu43gb2Jxnd%2Bgf9Z6jYM26PaB%2BrzmHtL35%2Fai9nQRKom7ygn1hAp091HrKsYVygvBBzwnvm1mdPFGcOZvxSyP7Yu6yif6sEC2uAxKiDjfOqlCRQuu4zqDmTczC8FzQYSOGirj1vhQ1oFgNkNfsK6BHxUB1MD2lemGA0Edvfgrh4RR6hLcoB15aMFsTpznnta0%2BGBuLFUUdaIvLL4BcNInfXlvhfqgfyym7xwCYm16CAMiPEF%2B38YJ%2FU7aHQxt%2BnBwMtQU5hdMXKRjzChNDyZcxSu1IxK9TU00ufNVnkR3Rywb00yChfipBc4ic5%2BAIZ6cu27vOs%2Bj5MXg%3D" rel="nofollow">高并发简单解决方案</a> | <a href="https://link.segmentfault.com/?enc=1C6ToRHMrN2iBcAIMCOWWg%3D%3D.DgfmxwfcJoxXZgd35La%2FTImd0XHnL1hp3nbS8bupicA%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
mysql 简单全量备份和快速恢复
https://segmentfault.com/a/1190000004134267
2015-12-11T20:13:27+08:00
2015-12-11T20:13:27+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>一个简单的mysql全量备份脚本,备份最近15天的数据。</p></blockquote>
<h2>备份</h2>
<pre><code>#每天备份mysql数据库(保存最近15天的数据脚本)
DATE=$(date +%Y%m%d)
/home/cuixiaohuan/lamp/mysql5/bin/mysqldump -uuser -ppassword need_db > /home/cuixiaohuan/bak_sql/mysql_dbxx_$DATE.sql;
find /home/cuixiaohuan/bak_sql/ -mtime +15 -name '*.sql' -exec rm -rf {} \;</code></pre>
<h2>恢复</h2>
<p>mysql 数据导入</p>
<pre><code>drop databases need_db;
create databases need_db;</code></pre>
<p>导入数据:必须设定编码进行恢复</p>
<pre><code>./mysql -uroot -p --default-character-set=utf8 need_db < xx.sql
</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=fASNNod0UryAMpg3%2FJsJdw%3D%3D.n1inSwgjP15f5TIU1CgTfeEEoDcuFOakn%2B48Vrw5O4ouMKQ%2FB5UOGKSxiRMCV3DeuwAI0STRn6DIdKZ6k5TowKUkaY5UqpUxyojC1tXSHbwAp%2FBpYAzbIeNpeYtK1R68woxU4AzRpk1Tb1yXPZSAnPvRwu0x%2BwI9HxeyuP%2BS4um6VlC2jQVXYQFlJJn5gV9l" rel="nofollow">mysql 简单全量备份和快速恢复</a> | <a href="https://link.segmentfault.com/?enc=2XECCmUV9Cg%2Fa%2BmHReCbSA%3D%3D.EsWhASvZkIBMRbujKB25Re76k4qHmsAYlsd83bNRDmw%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
linux 查找清理大文件
https://segmentfault.com/a/1190000004116475
2015-12-08T19:29:15+08:00
2015-12-08T19:29:15+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>linux 经常硬盘空间不足,往往是由于一些大文件造成;之前寻找大文件总是很头疼,速度特别慢。<br>经学弟介绍使用:<code>du -sh * |grep G</code> 查找和清理速度不错,分享一下清理过程。</p></blockquote>
<h2>查看系统存储状态</h2>
<pre><code>[cuihuan:~ cuixiaohuan]$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 8.2G 6.7G 1.6G 82% /
/dev/sda3 1.4T 1.3T 50G 97% /home</code></pre>
<p>1.5T的硬盘占用了97%,确实不够用了,必须着手清理一下</p>
<h2>查找大文件</h2>
<p>主要使用查找命令:<code>du -sh * |grep G</code><br>从根文件开始查找</p>
<pre><code>[cuihuan:~]$ du -sh * | grep G
。。。
73G mongodb
103G mxm
2.1G online
613G thirdparty [binggo!!!]
。。。</code></pre>
<p>bingo 613g,看来这个主要矛盾了,进一步分析该文件</p>
<pre><code>[cuihuan:~ mysql5]$ du -sh *
116M bin
904K include
13M lib
17M libexec
458G log [-__]
8.0K my.cnf
76M mysql-test
13M share
2.9M sql-bench
4.0K test
4.0K tmp
101G var [-_-]
[cuihuan:~ mysql5]$ cd log
[cuihuan:~ log]$ ls -lh
total 458G
-rw-rw---- 1 work work 870K Dec 2 17:42 mysql.err
-rw-rw---- 1 work work 3.9K Mar 24 2015 mysql.err-old
-rw-rw---- 1 work work 446G Dec 3 15:19 mysql.log 【-__】
-rw-rw---- 1 work work 11G Dec 3 15:10 slow.log</code></pre>
<p>经过分析看到mysql.log的日志占了446G,着手清理下。【mysql log 好的保存方式是:天级导出,清理n天之前的log,此处不再赘述】</p>
<h2>清理之后效果</h2>
<p>清理 mysql.log 和var 之后<br>清理了大约<code>500g</code>的空间</p>
<pre><code>[cuihuan:~ var]$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 8.2G 6.7G 1.6G 82% /
/dev/sda3 1.4T 757G 584G 57% /home</code></pre>
<p>【转载请注明:<a href="https://link.segmentfault.com/?enc=OTRXcf590IDzrx0TwmXP3A%3D%3D.PhUJlKt03QWfEbtHbel5pCejZwTUBbq3aFkkjdlBy%2BRwffZ4DxRLRYCI762nolyRbvsig8bxYPmKBuZmoItyBkSwZCNw0FL90Ami1ch949EfPQcqrxVKujyNAbuGQknECO1DuTB0fO29PsMJuv%2F7lA%3D%3D" rel="nofollow">linux 查找清理大文件</a> | <a href="https://link.segmentfault.com/?enc=Zt0NvW6ssGi2Y%2FLv%2B4vv0g%3D%3D.aKGtGk8O7t0qLnmC9%2B%2By%2FogR9qzSDr%2BpGDQmPRuC37E%3D" rel="nofollow">靠谱崔小拽</a> 】</p>
echarts动态获取数据实例
https://segmentfault.com/a/1190000004106175
2015-12-06T21:42:19+08:00
2015-12-06T21:42:19+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
4
<blockquote><p>echarts动态获取数据库的实时数据的简单实例。实例演示: <a href="https://link.segmentfault.com/?enc=W0nYgjk6mxFilUz6oTATsg%3D%3D.8aWUKWNIV7e19dK1Qc8WqeXtinYofRM27hIc2rsjssOFw3UuFQ8i7NbQowRUy0139rH8kDS%2FeQumsFrocSxJB1v9qK%2BJRV2rCR3eHhNpTXo%3D" rel="nofollow"> 跳转demo </a></p></blockquote>
<h2>引入echarts 文件。</h2>
<p>引入echarts的文件方式有多种,比较推荐模块化的引入方式。<br>小拽的简单demo是直接引入文件,提供一个下载地址 :<a href="https://link.segmentfault.com/?enc=Ghu6Mjb49kOgqyzaWBhFlg%3D%3D.xoTLDC0xyUGVD7dfxZdEcp9mGyTsj2zBmAl5RXiBMmlzvqsJCyJtDqSxxhbwDFWveKwNH1xd6VbMnl46kzYH9g%3D%3D" rel="nofollow"> 点击下载 </a></p>
<h2>html 部分代码</h2>
<p>一块div 用于echart的展现。</p>
<pre><code><div id="echart_show" style="height:500px">这里是一个布局展现</div></code></pre>
<h2>js 部分代码</h2>
<pre><code>$(document).ready(function () {
// 绘制反馈量图形
var init_echarts = function () {
var refreshChart = function (show_data) {
my_demo_chart = echarts.init(document.getElementById('echart_show'));
my_demo_chart.showLoading({
text: '加载中...',
effect: 'whirling'
});
var echarts_all_option = {
title: {
text: '动态数据',
subtext: '纯属虚构'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['最新成交价', '预购队列']
},
toolbox: {
show: true,
feature: {
mark: {show: true},
dataView: {show: true, readOnly: false},
magicType: {show: true, type: ['line', 'bar']},
restore: {show: true},
saveAsImage: {show: true}
}
},
dataZoom: {
show: false,
start: 0,
end: 100
},
xAxis: [
{
type: 'category',
boundaryGap: true,
data: (function () {
var now = new Date();
var res = [];
var len = 10;
while (len--) {
res.unshift(now.toLocaleTimeString().replace(/^\D*/, ''));
now = new Date(now - 2000);
}
return res;
})()
},
{
type: 'category',
boundaryGap: true,
data: (function () {
var res = [];
var len = 10;
while (len--) {
res.push(len + 1);
}
return res;
})()
}
],
yAxis: [
{
type: 'value',
scale: true,
name: '价格',
boundaryGap: [0.2, 0.2]
},
{
type: 'value',
scale: true,
name: '预购量',
boundaryGap: [0.2, 0.2]
}
],
series: [
{
name: '预购队列',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
// 获取到数据库的数据
data: show_data[0]
},
{
name: '最新成交价',
type: 'line',
// 实时获取的数据
data:show_data[1]
}
]
};
my_demo_chart.hideLoading();
my_demo_chart.setOption(echarts_all_option);
};
// 获取原始数据
$.ajax({
url: "http://cuihuan.net:1015/demo_file/echarts_realtime_demo/get_data.php",
data: {type: "2"},
success: function (data) {
// 根据数据库取到结果拼接现在结果
refreshChart(eval(data));
}
});
};
// 开启实时获取数据更新
$("#getData").on("click",function() {
var timeTicket;
var lastData = 11;
var axisData;
clearInterval(timeTicket);
timeTicket = setInterval(function () {
// 获取实时更新数据
$.ajax({
url: "http://cuihuan.net:1015/demo_file/echarts_realtime_demo/get_data.php",
data: {type: "new"},
success: function (data) {
// 根据条件转换成相应的api 转化为echart 需要的数据
// todo 更新数据采用随机更新的方式
lastData += Math.random() * ((Math.round(Math.random() * 10) % 2) == 0 ? 1 : -1);
lastData = lastData.toFixed(1) - 0;
axisData = (new Date()).toLocaleTimeString().replace(/^\D*/, '');
// 动态数据接口 addData
my_demo_chart.addData([
[
0, // 系列索引
Math.round(Math.random() * 1000), // 新增数据
true, // 新增数据是否从队列头部插入
false // 是否增加队列长度,false则自定删除原有数据,队头插入删队尾,队尾插入删队头
],
[
1, // 系列索引
lastData, // 新增数据
false, // 新增数据是否从队列头部插入
false, // 是否增加队列长度,false则自定删除原有数据,队头插入删队尾,队尾插入删队头
axisData // 坐标轴标签
]
]);
}
});
}, 2100);
// 关闭更新操作
$("#stopData").on("click", function () {
clearInterval(timeTicket);
});
});
// 默认加载
var default_load = (function () {
init_echarts();
})();
});</code></pre>
<h2>php 部分代码</h2>
<pre><code>/**
* 连接数据库获取数据
*
* User: cuixiaohuan
* Date: 15/12/4
* Time: 下午6:47
*/
// 获取请求的类型
$type = $_GET['type'];
// 连接服务器
$link = mysql_connect('ip:port', 'user', 'password');
if (!$link) {
die("Could not connect:" . mysql_error());
}
// 获取test库数据
$crowd_db = mysql_select_db('test', $link);
$day_time = date("Y-m-d");
// 根据传输过来的数据获取数据
$static_sql = "select v from test where id = " . $type . " limit 10";
// 获取数据之后返回
$res = mysql_query($static_sql, $link_2004);
if ($res) {
// 将结果进行入库操作
$row = mysql_fetch_row($res);
if($row[0]){
echo $row[0];
}
mysql_free_result($res);
}</code></pre>
<h2>简单说明</h2>
<p>小拽的demo中,根据echarts的api,绘制出了原有的图形展示;之后,对js设置了个定时任务,实时的去获取数据库中可能新入库的数据,接口通过php简单调用数据库实现。</p>
<p><a href="https://link.segmentfault.com/?enc=kere%2B2zQP7LBtdFr8y%2BK1w%3D%3D.%2FVV9GZacri2PcsdJ4EBKdPO5fpiJiPsDdKTTg36UydE%3D" rel="nofollow"> 小拽个人小站 </a></p>
crontab 误删除恢复
https://segmentfault.com/a/1190000004091550
2015-12-03T21:30:00+08:00
2015-12-03T21:30:00+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>操作过程中:crontab被全部干掉了,利用log恢复过程记录。</p></blockquote>
<h2>事故原因分析:</h2>
<p>回忆自己操作过程中,未进行crontab的清空,网上查了下原因,并且复现了下。可能原因如下:</p>
<p><strong>如果在SSH远程终端中敲下“crontab”命令之后,远程连接被一些原因(比如 糟糕的网络,程序异常)意外终止了,那么Crontab计划任务就会被操作系统所清空。听起来很不可思议,但是经过在虚拟机上的多次测试,它确确实实的发生了。测试方式为 用SecureCRT开一个SSH窗口,然后敲下命令“crontab”,接着在“任务管理器”中直接杀掉SecureCRT进程,再通过另外一个SSH窗口执行“crontab -l”,就会发现,所有的计划任务都不存在了。在今天事故发生的时间点上,就有人在服务器上遇到了这样的情况。</strong></p>
<h2>恢复操作</h2>
<ul><li><p>获取完整日志和cmd日志。<br>从日志中恢复出一份最近几天的完整crontab 运行日志和cmd日志,并存储,用于之后完善和核准例行时间。</p></li></ul>
<pre><code>cat /var/log/cron | grep -i "`which cron`" > ./all_temp
cat ./all_temp | grep -v "<command>” > ./cmd_temp</code></pre>
<ul><li><p>获取所有crontab指令。<br>从日志中恢复一份去重的crontab log。【相当于所有的crontab命令】</p></li></ul>
<pre><code>awk -F'(' '/crond/{a[$3]=$0}END{for(i in a)print a[i]}' /var/log/cron* >crontab.txt</code></pre>
<ul><li><p>手工恢复:<br>从crontab.txt 中找出每一条指令,然后在cmd_temp 中匹配运行次数,重新编辑crontab 添加</p></li></ul>
<h2>反思工作</h2>
<p>防止类似事件再次发生,写个简单shell脚本,每天对crontab进行备份,备份最近15天的数据。过期清楚</p>
<pre><code>#!/bin/bash
# 每天对crontab 进行备份 ,同时删除最近15天的数据
DATE=$(date +%Y%m%d)
crontab -l > /home/work/bak/crontab_$DATE.bak
find /home/work/bak/ -mtime +15 -name '*.bak' -exec rm -rf {} \;</code></pre>
<p><a href="https://link.segmentfault.com/?enc=N0ogKTBVe5smbFaCiG1BHw%3D%3D.CK%2FPXtqtgHG6M%2B1WrEX3OrO9Txiu1WCQLuHgPeGlLCg%3D" rel="nofollow"> 本人小站原文链接 </a></p>
jsonp + php 跨域实例
https://segmentfault.com/a/1190000004078955
2015-12-01T20:34:25+08:00
2015-12-01T20:34:25+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote>
<p>由于跨域的存在,使资源交互在不同域名间变的复杂和安全。对于跨域数据传输,当数据长度较小(<strong>get的长度内</strong>),jsonp是一种较好的解决方案。</p>
<p>分享一个自己在jsonp使用过程中的demo。</p>
</blockquote>
<p>关于跨域可以参考:<a href="https://link.segmentfault.com/?enc=DgKyWUcmKDZKSbG4xHm0LA%3D%3D.ypxIOOahAnxq6Z57kbFaHE4oBnjbSYBzq2CN40QSpb6AH0EEMhVLrZFoq%2FZfVUV2zUs7aXN8OxKPkvyS9L8X%2Bg%3D%3D" rel="nofollow">跨域总结与解决办法</a></p>
<h2>jsonp的js端调用</h2>
<p>主要功能:通过jsonp向服务器,调用相应接口,获应数据;根据获取数据结果做出相应回调。</p>
<pre><code>/**
* jsonp demo
* 通过回调函数,进行获取之后的事件加载
*
* @author:cuihuan
* @private
*/
_jsonpDemo:function(callback){
$.ajax({
url: "http://your_site_url",
type: 'GET',
dataType: 'JSONP',
success: function (data) {
if (data && data.status) {
if (data.status == "0") {
// failure solve
...
} else if (data.status == 500) {
// server error log
_sendInternalLog(data.info);
} else if (data.status == 1) {
//success solve
...
}
// callback func
(callback && typeof(callback) === "function" ) && callback();
}
},
error: function () {
_sendFailLog();
}
})
}
</code></pre>
<h2>jsonp 服务器端 (php)</h2>
<pre><code>
/**
* 接口返回相应数据
*
* status: 0 标示失败,1标示成功,500发生错误
* return: jsonp
*/
public function actionGetJsonPInfo()
{
try {
$data = getNeedData()
if ($data['status'] == "success") {
$res = array("status" => "1", 'info' => $data['info']);
}else{
$res = array("status" => "0", 'info' => '0');
}
}catch (Exception $e){
$res = array("status" => "500", 'info'=> $e);
}
// jsonp 通过get请求的返回数据形式
if (isset ($_GET['callback'])) {
header("Content-Type: application/json");
echo $_GET['callback']."(".json_encode($res).")";
}
}
</code></pre>
<h2>总结</h2>
<ul>
<li><p>目前来说,数据量小的跨域传输,jsonp是一种很好的解决方案。</p></li>
<li><p>jsonp在data中可以自动识别,res.status,res.info等状态位,比较方便。</p></li>
<li><p>php端的接受代码最好不要采用 Access-Control-Allow-Origin:* 风险太大。</p></li>
</ul>
<p><a href="https://link.segmentfault.com/?enc=3UTHLCK0ZufSA6LWRmQegg%3D%3D.icx9mnw04oSy8vZ2WWkOKrcs2BtbNJfpN0gBK0fhUYc%3D" rel="nofollow"> 本人小站原文 </a></p>
php 操作不同数据库
https://segmentfault.com/a/1190000004072548
2015-11-30T19:53:30+08:00
2015-11-30T19:53:30+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>php脚本经常,处理处理不同机器上,不同数据库之间数据;而且脚本特别容易写错,抽取了个工作中最常用到的多库同步,特此记忆!</p></blockquote>
<h2>上代码</h2>
<p>举个php操作不同数据库,进行数据同步的栗子。</p>
<pre><code> /**
* 同步库1的数据到库2
*
* @author :cuihuan
* @date :2015-10-11
*/
public function synchDbDiff()
{
// 连接库1
$crowd_conn_1 = mysql_connect('ip_1:port_1', 'name_1', 'pw_1');
if (!$crowd_conn_1) {
die("Could not connect:" . mysql_error());
}
mysql_select_db('test_data', $crowd_conn_1);
// 连接库2
$crowd_conn_2 = mysql_connect('ip_2:port_2', 'name_2', 'pw_2');
if (!$crowd_conn_1) {
die("Could not connect:" . mysql_error());
}
mysql_select_db('test_data', $crowd_conn_2);
//获取未同步的数据
$get_data_sql = "SELECT `id`, `text` FROM `fb_conversation` WHERE `flag` = 1";
$c_result = mysql_query($get_data_sql, $crowd_conn_1);
$this->check_res($c_result);
if ($c_result) {
while ($row = mysql_fetch_array($c_result, MYSQL_NUM)) {
// 更新同步
$new_data_sql = "update from fb_conversation set text =" . $row[1] . " where id = " . $row[0];
$res = mysql_query($new_data_sql, $crowd_conn_2);
$this->check_res($c_result);
}
}
}</code></pre>
<p><a href="https://link.segmentfault.com/?enc=1X6o92Ib3NjlbC%2BXT1uS5A%3D%3D.tXljijQNsrpmwxsbgZOgsJVvAHfTKkZQHS5s9QD6QjM%3D" rel="nofollow"> 个人小站原文链接 </a></p>
【redis学习一】基本概念和基本操作
https://segmentfault.com/a/1190000004058181
2015-11-28T09:15:54+08:00
2015-11-28T09:15:54+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<h2>redis 基础</h2>
<p><strong>官网地址</strong>:<a href="https://link.segmentfault.com/?enc=dvep1z40N1OKVxF7b01lkg%3D%3D.bQI41z%2Bzo8wotDXUGK9egTkVv%2FRMEuBFN4NxdnXgkDc%3D" rel="nofollow"> </a><a href="https://link.segmentfault.com/?enc=r3UvkjMgigiRuDQKI3hO8g%3D%3D.X%2BvattCcA6ni6uRAJJY3Ch4saZUbFoX7FL0pLRE%2BihE%3D" rel="nofollow">http://redis.io/</a> </p>
<p><strong>基本介绍</strong>:redis 是一个ansi c编写,支持网络的,基于内存的可持久化的日执行,kv数据库;10年期redis有vmare主持开发。</p>
<p>支持数据类型:redis支持strings,hashes list set sorted等结构。</p>
<p>持久化:存储于内存或虚拟内存中,有两种持久化的方式:</p>
<ul>
<li><p>:截图,内存数据不断写入硬盘【性能较高】</p></li>
<li><p>:log:记录每次更新的日志。【稳定性好】</p></li>
</ul>
<p>支持主从同步,性能非常优秀。<br>提供多种语言的api 基本上知道的都有。</p>
<p>使用场景:(个人觉的可以有的使用场景)<br>1:权限【权限每次都要入库校验,放在前端不靠谱,放在cache最合适】<br>2:缓存【例如批量操作数据,可以先缓存】<br>3:预取【例如topN数据,另外可能用到的数据,提前取出来,加快页面加载】<br>4:构建消息队列【可以根据redis的数据结构list,构造;数据批量入库,加快页面相应等方面不错】。<br>5:计数器【类似于批量入库的原理,可以计数,redis原子性的,可以精确支持】<br>6:其他场景还在不断探索中。</p>
<h2>安装和基本操作</h2>
<p>安装参考:<a href="https://link.segmentfault.com/?enc=qREqFmRVDULr4OiSG43igw%3D%3D.Gm3TcHcrqUncJfnL5%2B2RuhhXZXnDSAb8RV6N0hx6h9UzmQ%2BUzuQCMR6CL8bSb1Ie" rel="nofollow"> </a><a href="https://link.segmentfault.com/?enc=ulE6%2BS96aH6QjRQB8Xb8AQ%3D%3D.EcagkanAI%2FsbzjqcLrXcGG%2BdqJ9NEo8HDCWTYgzbFpUZjWYJgq9zfI6JB5oAVepg" rel="nofollow">http://www.runoob.com/redis/redis-install.html</a> </p>
<p>基本操作:<a href="https://link.segmentfault.com/?enc=Tb0OI9fehJFBvp3Jx6RUuQ%3D%3D.gaE3L%2FRYv9Vnx9b%2Fivxa2pWzUaCkEIb7nsTD8QZivd8%3D" rel="nofollow"> 官方文档地址 </a></p>
<p>启动:./redis-server<br>关闭:redis-cli shutdown</p>
<pre><code>[cuihuan bin]$ ./redis-cli shutdown
[cuihuan bin]$ ./redis-server
[325] 24 Sep 18:49:06.632 # Warning: no config file specified, using the default config. In order to specify a config file use ./redis-server /path/to/redis.conf
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 2.6.10 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in stand alone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 325
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
[325] 24 Sep 18:49:06.633 # Server started, Redis version 2.6.10
[325] 24 Sep 18:49:06.633 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
[325] 24 Sep 18:49:06.673 * DB loaded from disk: 0.039 seconds
[325] 24 Sep 18:49:06.673 * The server is now ready to accept connections on port 6379
</code></pre>
<p>安装成功的标志 redis-cli 可以成功进入</p>
<pre><code>[cuihuan bin]$ ./redis-cli
redis 127.0.0.1:6379></code></pre>
<blockquote><p>注意:使用过程中如果是保密性高的数据,可以设置登录密码,增加安全想;但如果是简单数据,则可以不设置,优点就是速度稍快。</p></blockquote>
<p>redis 基本配置:<br>daemonize: yes 标示在后台运行<br>bind:绑定请求的地址<br>port:端口号,默认6379<br>timeout:默认客户端连接超时 多长时间不操作关闭(默认永久,此处改为3600)</p>
<p>loglevel: log等级<br>databases:默认连接数据库的个数 【此处为8】<br>slaveof 主从库</p>
<p>masterauth :密码验证<br>requirepass:是否需要密码</p>
<p>maxclients :最大客户机个数 设置为10000<br>maxmemory:最大内存个数 6625156 [机器内存32G,分配大约6g]</p>
<p>最基本的操作<br>set name xxx<br>get name xxx<br>del name xxx<br>exists name xxx</p>
<p>举个栗子</p>
<pre><code># get val
redis 127.0.0.1:6379> get name
"cuixiaohuan"
# set val
redis 127.0.0.1:6379> set name cuixiaohuan_2
OK
redis 127.0.0.1:6379> get name
"cuixiaohuan_2"
# check exists; if exists return 1
redis 127.0.0.1:6379> exists name
(integer) 1
# del val
redis 127.0.0.1:6379> del name
(integer) 1
redis 127.0.0.1:6379> get name
(nil)</code></pre>
<p>其他常用操作:</p>
<pre><code># ping :check connect
redis 127.0.0.1:6379> ping
PONG
# dbsize :check size
redis 127.0.0.1:6379> dbsize
(integer) 1
#flush :clear db
redis 127.0.0.1:6379> dbsize
(integer) 1
redis 127.0.0.1:6379> flushdb
OK
redis 127.0.0.1:6379> dbsize
(integer) 0
</code></pre>
<p><a href="https://link.segmentfault.com/?enc=EuWHSmNl7Aclf8i6ThJnoQ%3D%3D.2UYi9FQ622tWfWyF1FfW6MbqdelOZ1o1%2BwCLn0V7FEU%3D" rel="nofollow"> 个人小站原文链接 </a></p>
phpUnit 安装,实例和简单部署
https://segmentfault.com/a/1190000004040212
2015-11-24T20:49:27+08:00
2015-11-24T20:49:27+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>背景:一个小脚本,保证稳定为主;所以试用了下phpunit,快捷方便</p></blockquote>
<h3>phpunit 的安装</h3>
<p>phpunit是一个轻量级的php单元测试框架,通过pear安装<br>安装过程</p>
<pre><code>wget https://phar.phpunit.de/phpunit.phar
chmod +x phpunit.phar
sudo mv phpunit.phar /usr/local/bin/phpunit
phpunit --version</code></pre>
<p>成功之后显示如下:</p>
<pre><code>cuihuan:~ cuixiaohuan$ phpunit --version
PHPUnit 4.8.6 by Sebastian Bergmann and contributors.</code></pre>
<h3>简单试用</h3>
<p>测试类集成框架</p>
<p><strong>class PsCaseTest extends PHPUnit_Framework_TestCase{}</strong></p>
<p>其中phpunit<br>默认首先执行 setup<br>默认最后执行 teardown</p>
<p>举个栗子:</p>
<pre><code>
<?php
/***************************************************************************
*
* $Id: PsCaseTest,v 1.0 PsCaseTest cuihuan Exp$
*
**************************************************************************/
/**
* @file PsCaseTest.php
* @author cuihuan
* @date 2015/09/11 10:09:31
* @version $Revision:1.0$
* @brief pscase接口单元测试
*
**/
require_once dirname(__FILE__) . ('/PsCase.php');
class PsCaseTest extends PHPUnit_Framework_TestCase
{
/**
* @var object pscase类
*/
protected $pscase;
/**
* @brief setup: Sets up the fixture, for example, opens a network connection.
*
* This method is called before a test is executed.
*/
public function setup(){
$this->pscase = new PsCase();
}
/**
* @brief teardown: Tears down the fixture, for example, closes a network connection.
*
* This method is called after a test is executed.
*/
public function teardown(){
}
/**
* @brief : 测试config文件的获取
*
*/
public function testGetConfig()
{
$this->assertEquals(true,$this->pscase->debugText("11"));
}
}
</code></pre>
<h3>运行</h3>
<p>运行方式:phpunit —bootstrap [源文件] 测试文件 <br>具体如下:</p>
<pre><code>cuihuande:newcode cuixiaohuan$ phpunit --bootstrap ./PsCase.php ./PsCaseTest.php
32015-09-11 02:09:36:11<br>
5
Time: 116 ms, Memory: 11.75Mb
OK (1 test, 1 assertion) 【表示运行成功】
</code></pre>
<h3>部署</h3>
<p>部署就不不赘述了,写个shell脚本,crontab天极运行,加个报警邮件,简单的单元测试ok,从此再也不用担心错误和回归测试了。</p>
<p><a href="https://link.segmentfault.com/?enc=M47969WwydteDtMoLVHYiQ%3D%3D.XGk0rSbfT1CLsmLdAUeZDozS4OpzavFjnwAshN7aRow%3D" rel="nofollow"> 个人小站原文链接 </a></p>
canvas 生成和合并图片
https://segmentfault.com/a/1190000004032270
2015-11-23T12:02:47+08:00
2015-11-23T12:02:47+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
2
<blockquote><p>先说背景:工作中遇到一个问题,file组件上传图片,file是可以上传n张图片;但是,后台逻辑历史原因,只能展现一张。因此:考虑到成本,决定在前端<strong>将多张图片合并成一张给后端</strong>。</p></blockquote>
<h3>先上代码</h3>
<pre><code>_mergeImage2Canvas:function() {
// 获取file上传和展现的图片。一般file上传之后,有个小图标展现。
var imgs = $(".img_files");
if (!imgs) {
return false;
}
// 创建原始图像
// 原因:file上传之后,展现往往是个缩略图,无法取到真正大小
for (var i = 0; i < imgs.length; i++) {
var fbwImg = document.createElement("img");
var fbwImgID = "temp_img_id" + i;
$("#" + fbwImgID).remove();
fbwImg.src = imgs[i].src;
fbwImg.className = "temp-img-class";
// 不显示,仅供调用
fbwImg.style.display = "none";
// 临时区域扩展
$("#temp_section").append(fbwImg);
}
// 合并原始图片,生成一个新的base64 图片
var getOriginImgBase64 = function (oriImgs) {
if (!oriImgs) {
return false;
}
// 获取canvas的宽高
// 原因:canvas需要首先指定宽高,所以需要提前获取最终的宽高
var maxWidth = 0;
var height = 0;
for (var i = 0; i < oriImgs.length; i++) {
var img = oriImgs[i];
if (img.width > maxWidth) {
maxWidth = img.width;
}
height += img.height;
}
// 设定canvas
var canvas = document.createElement("canvas");
canvas.width = maxWidth + 10;
canvas.height = height + 10;
var ctx = canvas.getContext("2d");
// 留5margin
var dheight = 5;
for (var j = 0; j < oriImgs.length; j++) {
var img = oriImgs[j];
var cheight = img.height;
var cwidth = img.width;
// 留5 margin
ctx.drawImage(img, 5, dheight, cwidth, cheight);
dheight = dheight + cheight + 5;
}
// 生成的base64 放在需要的一个全局变量中。
fbw_img_data = canvas.toDataURL('image/png');
// 清理
$(".temp_img_class").remove();
};
// 之所以使用timer,考虑到dom树如果没有加载完成,会取到高度有误差
var imgTimer = null;
imgTimer = setTimeout(function () {
getOriginImgBase64($(".temp_img_class"));
if (imgTimer) {
clearTimeout(imgTimer);
}
}, 300);
}</code></pre>
<h3>合成效果</h3>
<p>图片一:小站logo</p>
<p><a href="https://link.segmentfault.com/?enc=xHiJBLg8EvEfOVJVNgcICg%3D%3D.NENXwuQ4ZnFymNrm4INj6xfSFMAo6%2FBNPDUbZ0%2F1CEfBvju6AlnlSPTRxk4cpduRHejWD2xSJSxHjp%2BHZZvwDNyFtJcF9xBLR5xKVh0Pi2s%3D" rel="nofollow"><img src="/img/remote/1460000004840101" alt="cuihuan_title (1)" title="cuihuan_title (1)"></a> </p>
<p>图片二:小图标:<br><a href="https://link.segmentfault.com/?enc=Ty04sDQtMJmWCyfLPkjN%2FQ%3D%3D.3cvQ3NgP41swdXVU2fGgsUl2qCV73y%2FnHUVEYeOofh3eqE6SECqGN%2FAYJnYjWD8p0IpCgDgVzCU%2Bt8X6gS7%2FBA%3D%3D" rel="nofollow"><img src="/img/remote/1460000004840103" alt="del" title="del"></a></p>
<p>合成效果:</p>
<p><a href="https://link.segmentfault.com/?enc=R9y6NZP%2Fp6xO4mK3Kz10ZQ%3D%3D.SSitsXNzvoHxE0jMDnaM6rQnJyTgZsuDbZhjEV8ta52wpZUUo8J3tz4mQBeO3JN%2F" rel="nofollow"><img src="/img/remote/1460000008233903" alt="下载" title="下载"></a></p>
<h3>原理简介</h3>
<p>主要是通过canvas 获取多个图片的base64编码,之后通过drawImage 函数合并和toDataUrl的方式合成。</p>
<h3>问题思考</h3>
<ul>
<li><p>问题一:必须支持canvas,否则还需要后台统一跑脚本处理。</p></li>
<li><p>问题二:性能消耗过大。append img 和base64代码对dom的消耗都挺大,尤其是在移动端,很容易造成崩溃。解决办法:设定最大宽度,将图片等比缩放,这样子就少了向dom扩展元素这部分的损耗。</p></li>
<li><p>问题三:base64 在传输上性能消耗也挺大,没有file原生的好。</p></li>
</ul>
<p>因此:出了必须前端搞定,最好的方式,还是在后台跑脚本运行合并。</p>
<p><a href="https://link.segmentfault.com/?enc=eBGuyhADroP5d00JVxrIag%3D%3D.X%2BGUvU3FUHWJ5MAf09ONA6ROGUt6QX0Y39t2PWGS5S8bNp0ZWGO7iMgsn9ZXXVNjoqG3MKScPhZY3rp7Wg3fpw%3D%3D" rel="nofollow"> 个人小站原文链接 </a></p>
php 处理unicode解码
https://segmentfault.com/a/1190000004013109
2015-11-18T18:14:36+08:00
2015-11-18T18:14:36+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>备忘:这个算是解码比较靠谱的,亲测。参考自stackoverflow,mark 分享</p></blockquote>
<pre><code>// change unicode to unt-8
function replace_unicode_escape_sequence($match) {
return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
}
function unicode_decode($str) {
return preg_replace_callback('/u([0-9a-f]{4})/i', 'replace_unicode_escape_sequence', $str);
}
$str = unicode_decode('{"u5173u952eu8bcd":[{"","key":"u767eu5ea6"}]}');</code></pre>
<p>问题:使用过程中,遇到一个问题:就是当多次调用改函数的时候,出现错误。原因在于函数名方式调用问题。</p>
<p>如果编码是 \u5173\u952e\u8bcd 正则改为/\u([0-9a-f]{4})/i 即可<br><a href="https://link.segmentfault.com/?enc=CCsaJ93F%2F5Af7QRyKKZOJg%3D%3D.8k1P6Sr%2F2r%2FOUsQM970%2Fj0pcNGi8FRUUI9dFtj3GWtw%3D" rel="nofollow"> 个人小站原文链接 </a></p>
Nginx实践一:centos apache更换为nginx
https://segmentfault.com/a/1190000004011180
2015-11-18T13:14:14+08:00
2015-11-18T13:14:14+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>背景介绍: 阿里云,512M内存(最屌丝配置),搭建lamp 环境,除去 mysql分配了100M左右(这个不能再少了),http竟然占用了200多M,太庞大,决定换为较轻量级,高并发的nginx。</p></blockquote>
<h3>背景数据</h3>
<p>如下图所示:系统也就500M ,出了mysql占用的100M, httpd 占了1/2 还多(经常达到十几个进程),剩余50M,有时更少不能忍,经常造成数据库崩掉,写了个自动重启脚本,但觉的不是治本之策</p>
<pre><code>
# 统计apache 进程个数
ps aux|grep httpd | wc –l
</code></pre>
<p><a href="https://link.segmentfault.com/?enc=yltGMs09gNtVID6pD8zMiw%3D%3D.QAUH4TfgT9ueCNolaZ68z7meKcjLTACdT%2F3JXoSVvMWXVYpqRpnMn6aosSEHiQWkjnQ4T037zShO5xu3ks3O0OObjO2arKpFDaUyXRrhlts%2BVNHxri%2BCz6iAwqmIKBfD" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2015/11/8C2E5AE4-0A5E-479E-8C20-1CFF0C35F6D7-1024x621.png" alt="ngnix 服务器占用" title="ngnix 服务器占用"></a></p>
<h3>解决策略</h3>
<ul>
<li><p>1:针对Apache进行优化。包括优化worker运行方式等等。可以参考<a href="https://link.segmentfault.com/?enc=yAi%2F0n2vbwQi6ESbdkptkA%3D%3D.NWjyYSkRkMMhwSl3MY2wBFH9r9VbvHDta5yn0T4EIf%2FT7RyFCbccNuZMw%2B%2BhMJjb" rel="nofollow"> apache优化 </a></p></li>
<li><p>2 :更换轻量级服务器。采用nginx 或者lighthttpd等更轻量的服务器。传说中Nginx大法负载均衡和高并发略胜一筹,决定实践一把。</p></li>
</ul>
<h3>apache替换为nginx</h3>
<ul>
<li>
<p>1: 停掉apache<br> sudo service httpd stop</p>
<p>注意:以防万一,最好不好提前卸掉。</p>
</li>
<li><p>2:安装nginx<br> yum install nginx</p></li>
<li>
<p>3:启动nginx<br> sudo nginx</p>
<p>安装成功之后,启动成功如下图 <a href="https://link.segmentfault.com/?enc=maIPpfZmegNwqVZe3X0jOQ%3D%3D.RRDNNp2n01LukK9fpsWT%2FfyXazHgANoN%2FulLh4r%2FCvpRJzutiWMOXarIMfeNwzRXXJWCftfroHS5rYgIcKyk0QWLIynCYTbpdYl4tx3%2BVHcPlAJn67MRcQki%2F0CBma21" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2015/11/CB5A50FB-8B68-4F21-A6F4-BDC7AF6C93B2-1024x302.png" alt="CB5A50FB-8B68-4F21-A6F4-BDC7AF6C93B2" title="CB5A50FB-8B68-4F21-A6F4-BDC7AF6C93B2"></a></p>
</li>
<li><p>4:简单配置nginx<br>主要是简单修改下log【方便追查问题】 和 web_root 对应文件【快速启用网站】</p></li>
<li>
<p>5:重启nginx<br> [root@iZ25xlozdf2Z nginx]# nginx -s quit<br> [root@iZ25xlozdf2Z nginx]# nginx</p>
<p>如下图,配置web目录成功! <a href="https://link.segmentfault.com/?enc=8IR8P6hWRvu21dIowKVtmg%3D%3D.C%2BSipgO5%2FDPqkAL5MeJWLoaYT2oMBMKLi8T2AnfHEs4EeDR60caCQWcTju%2Fo%2B7sXiaLqNBNjkZAVcVZ4oEJCK6V5ASdmQSd4SNewo%2FgGkRANgiS1%2B%2BXu5y7hbukqrjNS" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2015/11/BAEF603F-CA9C-436E-B870-3E70C11542D0.png" alt="BAEF603F-CA9C-436E-B870-3E70C11542D0" title="BAEF603F-CA9C-436E-B870-3E70C11542D0"></a></p>
</li>
<li>
<p>6:添加php 支持<br>安装php-fpm<br> yum install php-fpm</p>
<p>nginx.conf设置<br> location ~ \.php$ {</p>
<pre><code> root /var/www/html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
include fastcgi_params;</code></pre>
<p>}</p>
</li>
<li><p>7:重新启动服务,网站回复。<br><a href="https://link.segmentfault.com/?enc=qoFJsB2AZtvkn6cCuWa2PQ%3D%3D.iDVs3vQS4acQ6XeD320mJVDN0V8PcEXAqjRzHu0gW7%2BFwlSoLydG1Df0Zo%2FI0EFWZlR%2BPXRtqqoWr216C0%2Fh8REkYqeuE7kIKsKcoGiIOcVjfW9LiEbk0VQXNQMAvTCy" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2015/11/A8D2AD14-7AB9-4421-AF40-B1BE0A5355C3-1024x718.png" alt="A8D2AD14-7AB9-4421-AF40-B1BE0A5355C3" title="A8D2AD14-7AB9-4421-AF40-B1BE0A5355C3"></a></p></li>
<li><p>8:耗存简单对比 如下图:基本上节省了200M,虽然这个可能是运行初期数据;但是,还是确实轻了不少,每个服务占存基本上1/4,线程也少了不少。内存占用方面表现,感觉尚可,接下就看性能了 <a href="https://link.segmentfault.com/?enc=TyqGyOB12owyS5ZCY7Z4kw%3D%3D.t0yO7KEE5NzCrGw26iv0lLSLTzeqS8Ok%2Fnr5doprhFbpNW%2Fexa23XX51dlhkSzcZeaphOL1GmmVtZi4ZLqHjjQzJuuKE8oZw%2Fyudi4Pq%2FjkL4GAEiTBOsjTh8V1aCcDQ" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2015/11/E773D2EE-2F51-4113-AAE1-939CD88DCAEE-1024x667.png" alt="E773D2EE-2F51-4113-AAE1-939CD88DCAEE" title="E773D2EE-2F51-4113-AAE1-939CD88DCAEE"></a></p></li>
</ul>
<h3>后续</h3>
<p>初次接触nginx,整体感觉还不错。后续,进行基本的防攻击,多端口设置,和性能配置。</p>
<p><a href="https://link.segmentfault.com/?enc=EGjvL8%2B1JhIu%2FN9MZCkn8Q%3D%3D.gs1es9C8IOxtpuGJv1Vsb3GquCLmHPnadhoJqJ%2Byqzw%3D" rel="nofollow"> 个人小站原文链接 </a></p>
Nginx实践二:nginx端口配置,域名重定向设置
https://segmentfault.com/a/1190000004008446
2015-11-17T21:16:24+08:00
2015-11-17T21:16:24+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
0
<blockquote><p>nginx替换apache之后,需要进行两个基本设置,一是:域名绑定和重定向,防止盗链,死链,参考文章<a href="https://link.segmentfault.com/?enc=M%2BpzL0GIRXCwipMuRBEmwg%3D%3D.EsVzY4%2FnCWGjyreyrq%2FL6iUebIXatsXUFDRqaes7QVE%3D" rel="nofollow"> apache 防盗链 </a>;二是:设置多个端口,一个端口显然无法满足需求。</p></blockquote>
<h3>域名防盗链设置</h3>
<p>域名防盗链主要通过,设定服务器域名,非域名重定向到现有域名(相对于之前的黑名单,我太单纯了,流量可以重定向利用一下)。</p>
<p>配置nginx.conf</p>
<pre><code># default 默认只能server_name 访问
listen 80 default ;
server_name cuihuan.net;
# 重定向
if ($host != "cuihuan.net") {
rewrite ^/(.*)$ http://cuihuan.net/$1 permanent;
}</code></pre>
<p>解释:首先80端口默认只能域名访问 ,默认的域名cuihuan.net。 对于所有非cuihuan.net 的过来的数据直接引流的cuihuan.net。如下图【这个战斗力为五的渣渣还挂在我的页面】</p>
<p><img src="http://cuihuan.net/wp-content/uploads/2015/11/4BF263AE-6ACD-4634-9000-795C0FB5F323-1024x851.png" alt="4BF263AE-6ACD-4634-9000-795C0FB5F323" title="4BF263AE-6ACD-4634-9000-795C0FB5F323"></p>
<p>进行了转码后还可以避免搜索引擎抓的域名出现死链。</p>
<p><a href="https://link.segmentfault.com/?enc=s%2Fhv3vEEb8awO2CPIqZqEg%3D%3D.56x2%2Bf1k8jQofjKSM1P1pbuM0LI6yrl2U8B%2Fw6GLVWvpGkMJfoZHUZFosbcsTAnfFFY8EmRcNRR%2FFlhu9iGHqyvcpGhs1do0XBfCgM1vR5aIReJU6R7a54Ud43jHrCFe" rel="nofollow"><img src="http://cuihuan.net/wp-content/uploads/2015/11/18AC69FB-9C9D-4CE6-8D9A-8F3BFC40D75C-1024x467.png" alt="18AC69FB-9C9D-4CE6-8D9A-8F3BFC40D75C" title="18AC69FB-9C9D-4CE6-8D9A-8F3BFC40D75C"></a></p>
<h3>配置多端口:</h3>
<p>这个就简单了,直接把上面配置好的server copy一个挂上其他web服务或者phpadmin等等</p>
<pre><code>server {
listen 8002 default ;
server_name cuihuan.net;
if ($host != "cuihuan.net") {
rewrite ^/(.*)$ http://cuihuan.net/$1 permanent;
}
location / {
root /var/www/weixin;
index index.php;
}
location ~ \.php$ {
root /var/www/weixin;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/weixin$fastcgi_script_name;
include fastcgi_params;
}
# set nginx stutus
location /NginxStatus{
stub_status on;
access_log on;
auth_basic "NginxStatus";
auth_basic_user_file conf/htpasswd;
}
#set deny all file
error_page 404 /404.html;
location = /var/www/wordpress/40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /home/www/wordpress/50x.html {
}
}</code></pre>
<blockquote><p>对于nginx搭建小网站来说,这个是基本的配置。个人感觉相对于之前<a href="https://link.segmentfault.com/?enc=yxZd1uWW9MqkUPqVavwwmQ%3D%3D.Hsj0SdHks6GQtsXfDc2dsgEAr5TzjRdfcg1LC7E6TEs%3D" rel="nofollow"> apache 防盗链配置 </a>来说难易差不多。</p></blockquote>
<p><a href="https://link.segmentfault.com/?enc=NY%2FlI5BkMVz7WdU9auixVA%3D%3D.q9WlXK32chxYPiOvThZnBABH68uO2Y1%2BCiat7gH8T48%3D" rel="nofollow"> 相关文章:Nginx实践一:centos apache更换为nginx </a></p>
<p><a href="https://link.segmentfault.com/?enc=fAWf9GhBU3q17ncSHOP8KQ%3D%3D.iqFkRDgGsrttjYe%2FZKLxUYDQrbITgUZEQJk7aAOTZ5U%3D" rel="nofollow"> 个人小站原文链接 </a></p>
【redis学习二】多php版本下phpredis扩展安装
https://segmentfault.com/a/1190000003830937
2015-10-08T21:21:09+08:00
2015-10-08T21:21:09+08:00
崔小拽
https://segmentfault.com/u/cuixiaozhuai
1
<blockquote><p>背景:安装完redis之后,需要安装phpredis扩展,才能让php操作redis;本机有多个php版本,安装过程中遇到的坑分享一下。</p></blockquote>
<h2>一 下载</h2>
<p>git上下载redis的扩展包</p>
<pre><code>git clone https://github.com/nicolasff/phpredis
</code></pre>
<h2>二 挂载和configure</h2>
<p>在shell中输入 phpize 【注意:多个php版本的时候需要指定】</p>
<pre><code> ./configure </code></pre>
<p>【phpize是用来扩展php扩展模块的,通过phpize可以建立php的外挂模块】</p>
<p>注意:(phpize 如果包含多个php,必须指定位置)</p>
<pre><code>cuihuan:phpredis cuixiaohuan$ ../php/bin/phpize
Configuring for:
PHP Api Version: 20121113
Zend Module Api No: 20121212
Zend Extension Api No: 220121212
Cannot find autoconf. Please check your autoconf installation and the
$PHP_AUTOCONF environment variable. Then, rerun this script.
</code></pre>
<p>报错的话需要安装:brew install autoconf [phpize 报错] 否则没有phpize</p>
<pre><code>[work@cuixiaozhuai phpredis]$ ../php/bin/phpize
Configuring for:
PHP Api Version: 20041225
Zend Module Api No: 20060613
Zend Extension Api No: 220060519
[work@cuixiaozhuai phpredis]$ ./configure --with-php-config=/home/work/thirdparty/php5/bin/php-config
</code></pre>
<p>当存在多个版本的php的时候,需要指定配置文件</p>
<pre><code> ./configure --with-php-config=/home/work/thirdparty/php5/bin/php-config
</code></pre>
<h2>三 编译和安装</h2>
<p>make 之后最好make test <br> make install</p>
<pre><code>cuihuan:phpredis cuixiaohuan$ make
。。。
Build complete.
Don't forget to run 'make test'.
cuihuan:phpredis cuixiaohuan$ make test
cuihuan:phpredis cuixiaohuan$ make install
</code></pre>
<h2>四 问题修复</h2>
<p>【已修复,但是原因可能不太准确】<br>make编译报错</p>
<pre><code>.libs/redis_cluster.o(.data.rel.local+0x0): In function `ht_free_seed':
/home/work/thirdparty/php5/php5/phpredis/redis_cluster.c:226: multiple definition of `arginfo_scan'
.libs/redis.o(.data.rel.local+0xe0):/home/work/thirdparty/php5/php5/p hpredis/redis.c:452: first defined here
/usr/bin/ld: Warning: size of symbol `arginfo_scan' changed from 160 in .libs/redis.o to 200 in .libs/redis_cluster.o
.libs/redis_cluster.o(.data.rel.local+0xe0): In function `create_cluster_context':
/home/work/thirdparty/php5/php5/phpredis/redis_cluster.c:276: multiple definition of `arginfo_kscan'
.libs/redis.o(.data.rel.local+0x0):/home/work/thirdparty/php5/php5/phpredis/redis.c:364: first defined here
collect2: ld returned 1 exit status
make: *** [redis.la] Error 1
</code></pre>
<p>最初以为是php多个版本生成install问题,采用./configure 指定php版本,指定php位置。<br>但是效果还是有问题。<br>最终通过修改redis_cluester.c 中,注释掉了这两个重复的</p>
<pre><code> 40
41 /* Argument info for HSCAN, SSCAN, HSCAN */
42 /*ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2)
43 ZEND_ARG_INFO(0, str_key)
44 ZEND_ARG_INFO(1, i_iterator)
45 ZEND_ARG_INFO(0, str_pattern)
46 ZEND_ARG_INFO(0, i_count)
47 ZEND_END_ARG_INFO();
48 */
49
50 /* Argument infor for SCAN */
51 /*
52 ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 2)
53 ZEND_ARG_INFO(1, i_iterator)
54 ZEND_ARG_INFO(0, str_node)
55 ZEND_ARG_INFO(0, str_pattern)
56 ZEND_ARG_INFO(0, i_count)
57 ZEND_END_ARG_INFO();
58 */
</code></pre>
<h2>五 简单测试</h2>
<pre><code><?php
$redis = new Redis();
$conn = $redis->connect('127.0.0.1',6379);
echo "redis pass and status show</br>";
var_dump($redis->ping());
$redis->set('test_key','test_value');
echo "test set val=".$redis->get('test_key')."</br>";
$redis->setnx('unique_key',"unique_val");
$redis->setnx('unique_key',"unique_val_2");
echo $redis->get("unique_key");
sleep(60);
echo 'is exist'.$redis->exists('test_60s');
echo 'not has value'.$redis->get('test_60s');
$redis->delete('test_key','test_60s');
</code></pre>
<p><a href="https://link.segmentfault.com/?enc=RNdp9KU4WvhOr%2Fzb8Uc8Og%3D%3D.urSn0m%2FI%2FGdgfLC7uJuF7eH1IjRhcxGVATCc6beZddY%3D" rel="nofollow"> 个人小站原文链接 </a></p>