<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾php推荐</title>
    <link>https://itindex.net/tags/php</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/tags/php</link>
    </image>
    <item>
      <title>Golang 大杀器之跟踪剖析 trace</title>
      <link>https://itindex.net/detail/59821-golang-%E8%B7%9F%E8%B8%AA-trace</link>
      <description>&lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746456?w=1254&amp;h=692" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在 Go 中有许许多多的分析工具，在之前我有写过一篇 《Golang 大杀器之性能剖析 PProf》 来介绍 PProf，如果有小伙伴感兴趣可以去我博客看看。&lt;/p&gt;
 &lt;p&gt;但单单使用 PProf 有时候不一定足够完整，因为在真实的程序中还包含许多的隐藏动作，例如 Goroutine 在执行时会做哪些操作？执行/阻塞了多长时间？在什么时候阻止？在哪里被阻止的？谁又锁/解锁了它们？GC 是怎么影响到 Goroutine 的执行的？这些东西用 PProf 是很难分析出来的，但如果你又想知道上述的答案的话，你可以用本文的主角   &lt;code&gt;go tool trace&lt;/code&gt; 来打开新世界的大门。目录如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746457?w=1278&amp;h=786" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;初步了解&lt;/h2&gt;
 &lt;pre&gt;  &lt;code&gt;import (
    &amp;quot;os&amp;quot;
    &amp;quot;runtime/trace&amp;quot;
)

func main() {
    trace.Start(os.Stderr)
    defer trace.Stop()

    ch := make(chan string)
    go func() {
        ch &amp;lt;- &amp;quot;EDDYCJY&amp;quot;
    }()

    &amp;lt;-ch
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;生成跟踪文件：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ go run main.go 2&amp;gt; trace.out&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;启动可视化界面：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ go tool trace trace.out
2019/06/22 16:14:52 Parsing trace...
2019/06/22 16:14:52 Splitting trace...
2019/06/22 16:14:52 Opening browser. Trace viewer is listening on http://127.0.0.1:57321&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;查看可视化界面：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746458" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;View trace：查看跟踪&lt;/li&gt;
  &lt;li&gt;Goroutine analysis：Goroutine 分析&lt;/li&gt;
  &lt;li&gt;Network blocking profile：网络阻塞概况&lt;/li&gt;
  &lt;li&gt;Synchronization blocking profile：同步阻塞概况&lt;/li&gt;
  &lt;li&gt;Syscall blocking profile：系统调用阻塞概况&lt;/li&gt;
  &lt;li&gt;Scheduler latency profile：调度延迟概况&lt;/li&gt;
  &lt;li&gt;User defined tasks：用户自定义任务&lt;/li&gt;
  &lt;li&gt;User defined regions：用户自定义区域&lt;/li&gt;
  &lt;li&gt;Minimum mutator utilization：最低 Mutator 利用率&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;Scheduler latency profile&lt;/h3&gt;
 &lt;p&gt;在刚开始查看问题时，除非是很明显的现象，否则不应该一开始就陷入细节，因此我们一般先查看 “Scheduler latency profile”，我们能通过 Graph 看到整体的调用开销情况，如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746459" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;演示程序比较简单，因此这里就两块，一个是   &lt;code&gt;trace&lt;/code&gt; 本身，另外一个是   &lt;code&gt;channel&lt;/code&gt; 的收发。&lt;/p&gt;
 &lt;h3&gt;Goroutine analysis&lt;/h3&gt;
 &lt;p&gt;第二步看 “Goroutine analysis”，我们能通过这个功能看到整个运行过程中，每个函数块有多少个有 Goroutine 在跑，并且观察每个的 Goroutine 的运行开销都花费在哪个阶段。如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746460" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通过上图我们可以看到共有 3 个 goroutine，分别是   &lt;code&gt;runtime.main&lt;/code&gt;、  &lt;code&gt;runtime/trace.Start.func1&lt;/code&gt;、  &lt;code&gt;main.main.func1&lt;/code&gt;，那么它都做了些什么事呢，接下来我们可以通过点击具体细项去观察。如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746461" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;同时也可以看到当前 Goroutine 在整个调用耗时中的占比，以及 GC 清扫和 GC 暂停等待的一些开销。如果你觉得还不够，可以把图表下载下来分析，相当于把整个 Goroutine 运行时掰开来看了，这块能够很好的帮助我们  &lt;strong&gt;对 Goroutine 运行阶段做一个的剖析，可以得知到底慢哪，然后再决定下一步的排查方向&lt;/strong&gt;。如下：&lt;/p&gt;
 &lt;table&gt;
  &lt;tr&gt;
   &lt;th&gt;名称&lt;/th&gt;
   &lt;th&gt;含义&lt;/th&gt;
   &lt;th&gt;耗时&lt;/th&gt;
&lt;/tr&gt;

  &lt;tr&gt;
   &lt;td&gt;Execution Time&lt;/td&gt;
   &lt;td&gt;执行时间&lt;/td&gt;
   &lt;td&gt;3140ns&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Network Wait Time&lt;/td&gt;
   &lt;td&gt;网络等待时间&lt;/td&gt;
   &lt;td&gt;0ns&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Sync Block Time&lt;/td&gt;
   &lt;td&gt;同步阻塞时间&lt;/td&gt;
   &lt;td&gt;0ns&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Blocking Syscall Time&lt;/td&gt;
   &lt;td&gt;调用阻塞时间&lt;/td&gt;
   &lt;td&gt;0ns&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Scheduler Wait Time&lt;/td&gt;
   &lt;td&gt;调度等待时间&lt;/td&gt;
   &lt;td&gt;14ns&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;GC Sweeping&lt;/td&gt;
   &lt;td&gt;GC 清扫&lt;/td&gt;
   &lt;td&gt;0ns&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;GC Pause&lt;/td&gt;
   &lt;td&gt;GC 暂停&lt;/td&gt;
   &lt;td&gt;0ns&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;h3&gt;View trace&lt;/h3&gt;
 &lt;p&gt;在对当前程序的 Goroutine 运行分布有了初步了解后，我们再通过 “查看跟踪” 看看之间的关联性，如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746462" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这个跟踪图粗略一看，相信有的小伙伴会比较懵逼，我们可以依据注解一块块查看，如下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;时间线：显示执行的时间单元，根据时间维度的不同可以调整区间，具体可执行    &lt;code&gt;shift&lt;/code&gt; +    &lt;code&gt;?&lt;/code&gt; 查看帮助手册。&lt;/li&gt;
  &lt;li&gt;堆：显示执行期间的内存分配和释放情况。&lt;/li&gt;
  &lt;li&gt;协程：显示在执行期间的每个 Goroutine 运行阶段有多少个协程在运行，其包含 GC 等待（GCWaiting）、可运行（Runnable）、运行中（Running）这三种状态。&lt;/li&gt;
  &lt;li&gt;OS 线程：显示在执行期间有多少个线程在运行，其包含正在调用 Syscall（InSyscall）、运行中（Running）这两种状态。&lt;/li&gt;
  &lt;li&gt;虚拟处理器：每个虚拟处理器显示一行，虚拟处理器的数量一般默认为系统内核数。&lt;/li&gt;
  &lt;li&gt;协程和事件：显示在每个虚拟处理器上有什么 Goroutine 正在运行，而连线行为代表事件关联。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746463" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;点击具体的 Goroutine 行为后可以看到其相关联的详细信息，这块很简单，大家实际操作一下就懂了。文字解释如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Start：开始时间&lt;/li&gt;
  &lt;li&gt;Wall Duration：持续时间&lt;/li&gt;
  &lt;li&gt;Self Time：执行时间&lt;/li&gt;
  &lt;li&gt;Start Stack Trace：开始时的堆栈信息&lt;/li&gt;
  &lt;li&gt;End Stack Trace：结束时的堆栈信息&lt;/li&gt;
  &lt;li&gt;Incoming flow：输入流&lt;/li&gt;
  &lt;li&gt;Outgoing flow：输出流&lt;/li&gt;
  &lt;li&gt;Preceding events：之前的事件&lt;/li&gt;
  &lt;li&gt;Following events：之后的事件&lt;/li&gt;
  &lt;li&gt;All connected：所有连接的事件&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;View Events&lt;/h3&gt;
 &lt;p&gt;我们可以通过点击 View Options-Flow events、Following events 等方式，查看我们应用运行中的事件流情况。如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746464?w=3104&amp;h=1878" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通过分析图上的事件流，我们可得知这程序从   &lt;code&gt;G1 runtime.main&lt;/code&gt; 开始运行，在运行时创建了 2 个 Goroutine，先是创建   &lt;code&gt;G18 runtime/trace.Start.func1&lt;/code&gt;，然后再是   &lt;code&gt;G19 main.main.func1&lt;/code&gt; 。而同时我们可以通过其 Goroutine Name 去了解它的调用类型，如：  &lt;code&gt;runtime/trace.Start.func1&lt;/code&gt; 就是程序中在   &lt;code&gt;main.main&lt;/code&gt; 调用了   &lt;code&gt;runtime/trace.Start&lt;/code&gt; 方法，然后该方法又利用协程创建了一个闭包   &lt;code&gt;func1&lt;/code&gt; 去进行调用。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746465" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在这里我们结合开头的代码去看的话，很明显就是   &lt;code&gt;ch&lt;/code&gt; 的输入输出的过程了。&lt;/p&gt;
 &lt;h2&gt;结合实战&lt;/h2&gt;
 &lt;p&gt;今天生产环境突然出现了问题，机智的你早已埋好   &lt;code&gt;_ &amp;quot;net/http/pprof&amp;quot;&lt;/code&gt; 这个神奇的工具，你麻利的执行了如下命令：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;curl    &lt;a href="http://127.0.0.1" rel="nofollow noreferrer"&gt;http://127.0.0.1&lt;/a&gt;:6060/debug/pprof/trace?seconds=20 &amp;gt; trace.out&lt;/li&gt;
  &lt;li&gt;go tool trace trace.out&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;View trace&lt;/h3&gt;
 &lt;p&gt;你很快的看到了熟悉的 List 界面，然后不信邪点开了 View trace 界面，如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746466" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;完全看懵的你，稳住，对着合适的区域执行快捷键   &lt;code&gt;W&lt;/code&gt; 不断地放大时间线，如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746467" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;经过初步排查，你发现上述绝大部分的 G 竟然都和   &lt;code&gt;google.golang.org/grpc.(*Server).Serve.func&lt;/code&gt; 有关，关联的一大串也是   &lt;code&gt;Serve&lt;/code&gt; 所触发的相关动作。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746468" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这时候有经验的你心里已经有了初步结论，你可以继续追踪 View trace 深入进去，不过我建议先鸟瞰全貌，因此我们再往下看 “Network blocking profile” 和 “Syscall blocking profile” 所提供的信息，如下：&lt;/p&gt;
 &lt;h3&gt;Network blocking profile&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746469" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;Syscall blocking profile&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746470?w=1472&amp;h=996" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通过对以上三项的跟踪分析，加上这个泄露，这个阻塞的耗时，这个涉及的内部方法名，很明显就是哪位又忘记关闭客户端连接了，赶紧改改改。&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;通过本文我们习得了   &lt;code&gt;go tool trace&lt;/code&gt; 的武林秘籍，它能够跟踪捕获各种执行中的事件，例如 Goroutine 的创建/阻塞/解除阻塞，Syscall 的进入/退出/阻止，GC 事件，Heap 的大小改变，Processor 启动/停止等等。&lt;/p&gt;
 &lt;p&gt;希望你能够用好 Go 的两大杀器 pprof + trace 组合，此乃排查好搭档，谁用谁清楚，即使他并不万能。&lt;/p&gt;
 &lt;h2&gt;参考&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://about.sourcegraph.com/go/an-introduction-to-go-tool-trace-rhys-hiltner" rel="nofollow noreferrer"&gt;https://about.sourcegraph.com...&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.itcodemonkey.com/article/5419.html" rel="nofollow noreferrer"&gt;https://www.itcodemonkey.com/...&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://making.pusher.com/go-tool-trace/" rel="nofollow noreferrer"&gt;https://making.pusher.com/go-...&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://golang.org/cmd/trace/" rel="nofollow noreferrer"&gt;https://golang.org/cmd/trace/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://docs.google.com/document/d/1FP5apqzBgr7ahCCgFO-yoVhk4YZrNIDNf9RybngBc14/pub" rel="nofollow noreferrer"&gt;https://docs.google.com/docum...&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://godoc.org/runtime/trace" rel="nofollow noreferrer"&gt;https://godoc.org/runtime/trace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019746471" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>golang php 后端</category>
      <guid isPermaLink="true">https://itindex.net/detail/59821-golang-%E8%B7%9F%E8%B8%AA-trace</guid>
      <pubDate>Fri, 12 Jul 2019 19:40:00 CST</pubDate>
    </item>
    <item>
      <title>一套基础自动化部署搭建过程</title>
      <link>https://itindex.net/detail/59819-%E5%9F%BA%E7%A1%80-%E8%87%AA%E5%8A%A8%E5%8C%96</link>
      <description>&lt;h2&gt;问题背景&lt;/h2&gt;
 &lt;p&gt;公司初创技术团队，没有任何基础设施的情况下，需要搭建一系列code管理以及自动化部署等工具....所以  &lt;br /&gt;引发了下面一系列的部署过程，历时两天，中间也是碰到各种问题，但最终把基本工具全部搭建成功，耶~，下面带大家一起看下此次搭建过程。&lt;/p&gt;
 &lt;h2&gt;资源&lt;/h2&gt;
 &lt;p&gt;服务器一台，CentOS的，公司就给了一台配置较低的服务器当临时服务器，没办法了，就这么搞吧。&lt;/p&gt;
 &lt;blockquote&gt;思考：如果我用传统的yum来安装，肯定会被我装乱七八糟，还不方便自己管理维护，左右为难的情况下我选择了Dokcer，可能有些小伙伴问Docker是啥怎么用，不要慌这里有传送门：  &lt;a href="https://segmentfault.com/a/1190000016887826"&gt;Linux docker-compose 实战&lt;/a&gt;、   &lt;a href="https://segmentfault.com/a/1190000016254236#articleHeader0"&gt;[进阶篇]docker编排PHP开发坏境&lt;/a&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;测试环境搭建&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbgmdS?w=567&amp;h=272" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;为了快速搭建一套PHP测试环境我决定用  &lt;a href="https://laradock.io/" rel="nofollow noreferrer"&gt;laradock&lt;/a&gt;了，虽然文件很多，但是里面封装的东西也是比较全的，后期开发不知道会用到什么技术，就决定先用这个，随时可以启动用得到的服务。  &lt;br /&gt;   laradock官方文档给的介绍也很全面，我这里采用的方式是部署多套项目方式目录结构如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;+ laradock
+ project-1
+ project-2
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;开始使用：&lt;/h3&gt;
 &lt;ol&gt;
  &lt;li&gt;git clone    &lt;a href="https://github.com/laradock/laradock.git" rel="nofollow noreferrer"&gt;https://github.com/laradock/l...&lt;/a&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;cp env-example .env&lt;/p&gt;
   &lt;blockquote&gt;env 里面的配置可以修改的，可以根据自己情况进行调整&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;docker-compose up -d  nginx php-fpm mysql  redis&lt;/p&gt;
   &lt;blockquote&gt;后面可以加上你要启动的程序，像rabbitmq、mongo等等，需要的时候追加在尾部就可以启动，首次启动时间有点长&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;docker-compose exec --user=laradock workspace bash&lt;/p&gt;
   &lt;blockquote&gt;像laravel等项目肯定少不了composer，执行这个命令进入工作区，就可以执行composer了&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;docker-compose exec nginx nginx -s reload&lt;/p&gt;
   &lt;blockquote&gt;肯定有人会问我更改了nginx配置是不是每次都要进入nginx容器去重启，或者把整个容器都重启了呀，不用的，执行这个命令就可以重启nginx了。nginx站点配置文件在哪里呢：./laradock/nginx/sites/&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;先运行下试试吧。&lt;/p&gt;
 &lt;h2&gt;GitLab&lt;/h2&gt;
 &lt;p&gt;现在运行环境已经搭建好了，我代码应该怎么存放呢，其实有很多的选择，要根据自己的实际情况出发，如：GitHub、码云、GitLab等等，我选择的是GitLab，刚开始没有发现laradock其实提供了GitLab服务。。只能自己折腾了,其实很简单。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;docker \
run -d  \
-p 443:443 \
-p 8080:80 \
-p 222:22 \
--name gitlab \
--restart always \
-v /home/gitlab/config:/etc/gitlab \
-v /home/gitlab/logs:/var/log/gitlab \
-v /home/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce
#参数解释：
#-d：让容器后台运行
#-p：暴露端口，把容器的443端口指向到宿主机443端口，宿主机8080-&amp;gt;容器80，宿主机222-&amp;gt;容器22
#宿主机端口可以根据自己的情况自己定制
#-name 给你的容器起个名，只要不和现有的重复就可以
#--restart 当容器退出时docker是否重启
#-v 这就是挂载磁盘了，把宿主机的目录挂载到容器中，这么做哪怕是容器坏了我的内容也不会丢失。
#宿主机/home/gitlab/config目录挂载到容器中/etc/gitlab目录，宿主机目录根据自己情况定
#gitlab/gitlab-ce 这个就是要启动的镜像，如果镜像不存在，docker会自动下载最新版
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;容器启动成功之后会有一段时间来启动GitLab，看到启动成功立马访问是访问不到的，稍微等一下就可以了，配置文件都在你指定的宿主机目录下可以修改，需要修改的内容如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;  gitlab_rails[&amp;apos;gitlab_ssh_host&amp;apos;] = &amp;apos;宿主机IP&amp;apos;#宿主机的IP地址
  gitlab_rails[&amp;apos;gitlab_shell_ssh_port&amp;apos;] = 222#暴露给宿主机的ssh端口
  external_url &amp;apos;域名&amp;apos;#分配给gitlab的域名，可以用nginx做反向代理到8080端口
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;Jenkins&lt;/h2&gt;
 &lt;p&gt;实现自动化部署有很多种方式如：webhook、Jenkins、Travis CI等等，我的选择是Jenkins，因为之前部署用过所以这里依旧用了这个，laradock依旧提供了Jenkins服务，又是因为没发现自己搭建了一个。。。看来下次要先多看看在动手了。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;docker run -d \
     --name myjenkins \
     -p 8181:8080 \
     -p 50000:50000 \
     -v /home/jenkins/:/var/jenkins_home \
     jenkins/jenkins
     #参数解释
     #-d：让容器后台运行
     #-p：暴露端口，宿主机8181-&amp;gt;容器8080，宿主机50000-&amp;gt;容器50000
     #-v 这就是挂载磁盘了，把宿主机的目录挂载到容器中，这么做哪怕是容器坏了我的内容也不会丢失。
     #宿主机/home/jenkins/目录挂载到容器中/var/jenkins_home目录，宿主机目录根据自己情况定&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;是不是也很简单~。~ ，但是Jenkins安装成功后会有一个初始化密码，怎么查看初始化密码呢，有两种方式：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;#第一种方式
docker logs &amp;lt;你的容器名字&amp;gt; #这样就可以查看容器输出的内容
#第二种方式
cat /home/jenkins/secrets/initialAdminPassword#这里好的/home/jenkins需要替换成你挂载的目录
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;jenkins配置&lt;/h2&gt;
 &lt;h3&gt;初始化&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWip?w=1000&amp;h=948" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;输入你上面获取到的初始化密码，下一步我选择的是推荐安装，后面自己需要的自己可以再去插件库安装。&lt;/p&gt;
 &lt;h3&gt;插件安装&lt;/h3&gt;
 &lt;p&gt;初始化成功之后前往系统设置-&amp;gt;插件管理安装几个必要插件：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Publish Over SSH

GitLab Plugin #因为是使用的Gitlab所以要安装这个

NodeJS Plugin #我们前端使用的Vue所以要用到node
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;开始征程&lt;/h3&gt;
 &lt;p&gt;一、基础配置&lt;/p&gt;
 &lt;p&gt;安装完插件之后需要配置一些基础内容如jdk、node、ssh等&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;ssh安装&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;进入：Manage Jenkins-&amp;gt; Configure System &lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWte?w=2154&amp;h=1198" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;点击高级：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWtu?w=1556&amp;h=1122" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;点击Test Configuration测试是否连接成功&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;ul&gt;  &lt;li&gt;jdk、git&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;进入：Manage Jenkins-&amp;gt; Global Tool Configuration&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWvk?w=2206&amp;h=1302" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;blockquote&gt;我这里jdk、git采用自动安装，jdk安装时候需要一个账号，去注册下就可以&lt;/blockquote&gt;
 &lt;ul&gt;  &lt;li&gt;nodejs安装&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWvB?w=2120&amp;h=1112" title="clipboard.png"&gt;&lt;/img&gt;  &lt;br /&gt;]&lt;/p&gt;
 &lt;p&gt;二、创建工程&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWpK?w=2088&amp;h=1352" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;新建一个工程&lt;/li&gt;
  &lt;li&gt;选择自由风格&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;三、配置工程&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWp6?w=2162&amp;h=1248" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;blockquote&gt;设置最多保留几个版本构建&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWq4?w=2092&amp;h=942" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;blockquote&gt;使用git源码工具，输入git地址、添加git用户&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWvY?w=1884&amp;h=612" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;if [ -f &amp;quot;test.tar.gz&amp;quot; ];then
rm testv.tar.gz
fi
tar -zcvf test.tar.gz ./*&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;这里为什么要把项目打包呢，因为下面要配置的ssh不支持文件夹传输，所以需要先将内容进行打包然后去服务器进行解包&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbuWzh?w=2002&amp;h=1356" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;blockquote&gt;全部保存好就可以去构建了。耶~&lt;/blockquote&gt;
 &lt;h2&gt;谢谢观赏&lt;/h2&gt;
 &lt;p&gt;长时间没写文章了，这个篇幅较长，谢谢耐心观看，希望对您有所帮助，也希望大家提供下不同的意见，找到更有效的方式来完成，谢谢！&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>php jenkins nginx docker gitlab</category>
      <guid isPermaLink="true">https://itindex.net/detail/59819-%E5%9F%BA%E7%A1%80-%E8%87%AA%E5%8A%A8%E5%8C%96</guid>
      <pubDate>Thu, 11 Jul 2019 10:52:44 CST</pubDate>
    </item>
    <item>
      <title>浅谈 gRPC</title>
      <link>https://itindex.net/detail/59758-grpc</link>
      <description>&lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019552245" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;原文地址：  &lt;a href="https://github.com/EDDYCJY/blog/blob/master/golang/gRPC/2019-06-28-talking-grpc.md" rel="nofollow noreferrer"&gt;浅谈 gRPC&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;gRPC 在 Go 语言中大放异彩，越来越多的小伙伴在使用，最近也在公司安利了一波，希望能通过这篇文章能带你一览 gRPC 的爱与恨。本文篇幅较长，希望你做好阅读准备，目录如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608506" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;简述&lt;/h2&gt;
 &lt;p&gt;gRPC  是一个高性能、开源和通用的 RPC 框架，面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本，分别是：grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持。&lt;/p&gt;
 &lt;p&gt;gRPC 基于 HTTP/2 标准设计，带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。这些特性使得其在移动设备上表现更好，更省电和节省空间占用。&lt;/p&gt;
 &lt;h2&gt;调用模型&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000011982961?w=968&amp;h=621" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;1、客户端（gRPC Stub）调用 A 方法，发起 RPC 调用。&lt;/p&gt;
 &lt;p&gt;2、对请求信息使用 Protobuf 进行对象序列化压缩（IDL）。&lt;/p&gt;
 &lt;p&gt;3、服务端（gRPC Server）接收到请求后，解码请求体，进行业务逻辑处理并返回。&lt;/p&gt;
 &lt;p&gt;4、对响应结果使用 Protobuf 进行对象序列化压缩（IDL）。&lt;/p&gt;
 &lt;p&gt;5、客户端接受到服务端响应，解码请求体。回调被调用的 A 方法，唤醒正在等待响应（阻塞）的客户端调用并返回响应结果。&lt;/p&gt;
 &lt;h2&gt;调用方式&lt;/h2&gt;
 &lt;h3&gt;一、Unary RPC：一元 RPC&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000016583379?w=505&amp;h=319" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;Server&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;type SearchService struct{}

func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
    return &amp;amp;pb.SearchResponse{Response: r.GetRequest() + &amp;quot; Server&amp;quot;}, nil
}

const PORT = &amp;quot;9001&amp;quot;

func main() {
    server := grpc.NewServer()
    pb.RegisterSearchServiceServer(server, &amp;amp;SearchService{})

    lis, err := net.Listen(&amp;quot;tcp&amp;quot;, &amp;quot;:&amp;quot;+PORT)
    ...

    server.Serve(lis)
}&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;创建 gRPC Server 对象，你可以理解为它是 Server 端的抽象对象。&lt;/li&gt;
  &lt;li&gt;将 SearchService（其包含需要被调用的服务端接口）注册到 gRPC Server。 的内部注册中心。这样可以在接受到请求时，通过内部的 “服务发现”，发现该服务端接口并转接进行逻辑处理。&lt;/li&gt;
  &lt;li&gt;创建 Listen，监听 TCP 端口。&lt;/li&gt;
  &lt;li&gt;gRPC Server 开始 lis.Accept，直到 Stop 或 GracefulStop。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;Client&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;func main() {
    conn, err := grpc.Dial(&amp;quot;:&amp;quot;+PORT, grpc.WithInsecure())
    ...
    defer conn.Close()

    client := pb.NewSearchServiceClient(conn)
    resp, err := client.Search(context.Background(), &amp;amp;pb.SearchRequest{
        Request: &amp;quot;gRPC&amp;quot;,
    })
    ...
}&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;创建与给定目标（服务端）的连接句柄。&lt;/li&gt;
  &lt;li&gt;创建 SearchService 的客户端对象。&lt;/li&gt;
  &lt;li&gt;发送 RPC 请求，等待同步响应，得到回调后返回响应结果。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;二、Server-side streaming RPC：服务端流式 RPC&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000016583367?w=505&amp;h=315" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;Server&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;func (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error {
    for n := 0; n &amp;lt;= 6; n++ {
        stream.Send(&amp;amp;pb.StreamResponse{
            Pt: &amp;amp;pb.StreamPoint{
                ...
            },
        })
    }

    return nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;Client&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.List(context.Background(), r)
    ...
    
    for {
        resp, err := stream.Recv()
        if err == io.EOF {
            break
        }
        ...
    }

    return nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;三、Client-side streaming RPC：客户端流式 RPC&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000016583368?w=505&amp;h=319" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;Server&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;func (s *StreamService) Record(stream pb.StreamService_RecordServer) error {
    for {
        r, err := stream.Recv()
        if err == io.EOF {
            return stream.SendAndClose(&amp;amp;pb.StreamResponse{Pt: &amp;amp;pb.StreamPoint{...}})
        }
        ...

    }

    return nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;Client&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.Record(context.Background())
    ...
    
    for n := 0; n &amp;lt; 6; n++ {
        stream.Send(r)
    }

    resp, err := stream.CloseAndRecv()
    ...

    return nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;四、Bidirectional streaming RPC：双向流式 RPC&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000016583369?w=505&amp;h=319" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;Server&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;func (s *StreamService) Route(stream pb.StreamService_RouteServer) error {
    for {
        stream.Send(&amp;amp;pb.StreamResponse{...})
        r, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        ...
    }

    return nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;Client&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.Route(context.Background())
    ...

    for n := 0; n &amp;lt;= 6; n++ {
        stream.Send(r)
        resp, err := stream.Recv()
        if err == io.EOF {
            break
        }
        ...
    }

    stream.CloseSend()

    return nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;客户端与服务端是如何交互的&lt;/h2&gt;
 &lt;p&gt;在开始分析之前，我们要先 gRPC 的调用有一个初始印象。那么最简单的就是对 Client 端调用 Server 端进行抓包去剖析，看看整个过程中它都做了些什么事。如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608507" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Magic&lt;/li&gt;
  &lt;li&gt;SETTINGS&lt;/li&gt;
  &lt;li&gt;HEADERS&lt;/li&gt;
  &lt;li&gt;DATA&lt;/li&gt;
  &lt;li&gt;SETTINGS&lt;/li&gt;
  &lt;li&gt;WINDOW_UPDATE&lt;/li&gt;
  &lt;li&gt;PING&lt;/li&gt;
  &lt;li&gt;HEADERS&lt;/li&gt;
  &lt;li&gt;DATA&lt;/li&gt;
  &lt;li&gt;HEADERS&lt;/li&gt;
  &lt;li&gt;WINDOW_UPDATE&lt;/li&gt;
  &lt;li&gt;PING&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们略加整理发现共有十二个行为，是比较重要的。在开始分析之前，建议你自己先想一下，它们的作用都是什么？大胆猜测一下，带着疑问去学习效果更佳。&lt;/p&gt;
 &lt;h3&gt;行为分析&lt;/h3&gt;
 &lt;h4&gt;Magic&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608508?w=1754&amp;h=670" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Magic 帧的主要作用是建立 HTTP/2 请求的前言。在 HTTP/2 中，要求两端都要发送一个连接前言，作为对所使用协议的最终确认，并确定 HTTP/2 连接的初始设置，客户端和服务端各自发送不同的连接前言。&lt;/p&gt;
 &lt;p&gt;而上图中的 Magic 帧是客户端的前言之一，内容为   &lt;code&gt;PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n&lt;/code&gt;，以确定启用 HTTP/2 连接。&lt;/p&gt;
 &lt;h4&gt;SETTINGS&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608509?w=1658&amp;h=648" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608510?w=1678&amp;h=660" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;SETTINGS 帧的主要作用是设置这一个连接的参数，作用域是整个连接而并非单一的流。&lt;/p&gt;
 &lt;p&gt;而上图的 SETTINGS 帧都是空 SETTINGS 帧，图一是客户端连接的前言（Magic 和 SETTINGS 帧分别组成连接前言）。图二是服务端的。另外我们从图中可以看到多个 SETTINGS 帧，这是为什么呢？是因为发送完连接前言后，客户端和服务端还需要有一步互动确认的动作。对应的就是带有 ACK 标识 SETTINGS 帧。&lt;/p&gt;
 &lt;h4&gt;HEADERS&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608511?w=1662&amp;h=650" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;HEADERS 帧的主要作用是存储和传播 HTTP 的标头信息。我们关注到 HEADERS 里有一些眼熟的信息，分别如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;method：POST&lt;/li&gt;
  &lt;li&gt;scheme：http&lt;/li&gt;
  &lt;li&gt;path：/proto.SearchService/Search&lt;/li&gt;
  &lt;li&gt;authority：:10001&lt;/li&gt;
  &lt;li&gt;content-type：application/grpc&lt;/li&gt;
  &lt;li&gt;user-agent：grpc-go/1.20.0-dev&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;你会发现这些东西非常眼熟，其实都是 gRPC 的基础属性，实际上远远不止这些，只是设置了多少展示多少。例如像平时常见的   &lt;code&gt;grpc-timeout&lt;/code&gt;、  &lt;code&gt;grpc-encoding&lt;/code&gt; 也是在这里设置的。&lt;/p&gt;
 &lt;h4&gt;DATA&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608512" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;DATA 帧的主要作用是装填主体信息，是数据帧。而在上图中，可以很明显看到我们的请求参数 gRPC 存储在里面。只需要了解到这一点就可以了。&lt;/p&gt;
 &lt;h4&gt;HEADERS, DATA, HEADERS&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608513" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在上图中 HEADERS 帧比较简单，就是告诉我们 HTTP 响应状态和响应的内容格式。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="imgae" src="https://segmentfault.com/img/remote/1460000019608514?w=1668&amp;h=760" title="imgae"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在上图中 DATA 帧主要承载了响应结果的数据集，图中的 gRPC Server 就是我们 RPC 方法的响应结果。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608515" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在上图中 HEADERS 帧主要承载了 gRPC 状态 和 gRPC 状态消息，图中的   &lt;code&gt;grpc-status&lt;/code&gt; 和   &lt;code&gt;grpc-message&lt;/code&gt; 就是我们的 gRPC 调用状态的结果。&lt;/p&gt;
 &lt;h3&gt;其它步骤&lt;/h3&gt;
 &lt;h4&gt;WINDOW_UPDATE&lt;/h4&gt;
 &lt;p&gt;主要作用是管理和流的窗口控制。通常情况下打开一个连接后，服务器和客户端会立即交换 SETTINGS 帧来确定流控制窗口的大小。默认情况下，该大小设置为约 65 KB，但可通过发出一个 WINDOW_UPDATE 帧为流控制设置不同的大小。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608516" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;PING/PONG&lt;/h4&gt;
 &lt;p&gt;主要作用是判断当前连接是否仍然可用，也常用于计算往返时间。其实也就是 PING/PONG，大家对此应该很熟。&lt;/p&gt;
 &lt;h3&gt;小结&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608517?w=1756&amp;h=1228" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;在建立连接之前，客户端/服务端都会发送   &lt;strong&gt;连接前言&lt;/strong&gt;（Magic+SETTINGS），确立协议和配置项。&lt;/li&gt;
  &lt;li&gt;在传输数据时，是会涉及滑动窗口（WINDOW_UPDATE）等流控策略的。&lt;/li&gt;
  &lt;li&gt;传播 gRPC 附加信息时，是基于 HEADERS 帧进行传播和设置；而具体的请求/响应数据是存储的 DATA 帧中的。&lt;/li&gt;
  &lt;li&gt;请求/响应结果会分为 HTTP 和 gRPC 状态响应两种类型。&lt;/li&gt;
  &lt;li&gt;客户端发起 PING，服务端就会回应 PONG，反之亦可。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这块 gRPC 的基础使用，你可以看看我另外的   &lt;a href="https://github.com/EDDYCJY/blog#grpc%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95" rel="nofollow noreferrer"&gt;《gRPC 入门系列》&lt;/a&gt;，相信对你一定有帮助。&lt;/p&gt;
 &lt;h2&gt;浅谈理解&lt;/h2&gt;
 &lt;h3&gt;服务端&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608518?w=1331&amp;h=780" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;为什么四行代码，就能够起一个 gRPC Server，内部做了什么逻辑。你有想过吗？接下来我们一步步剖析，看看里面到底是何方神圣。&lt;/p&gt;
 &lt;h3&gt;一、初始化&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;// grpc.NewServer()
func NewServer(opt ...ServerOption) *Server {
    opts := defaultServerOptions
    for _, o := range opt {
        o(&amp;amp;opts)
    }
    s := &amp;amp;Server{
        lis:    make(map[net.Listener]bool),
        opts:   opts,
        conns:  make(map[io.Closer]bool),
        m:      make(map[string]*service),
        quit:   make(chan struct{}),
        done:   make(chan struct{}),
        czData: new(channelzData),
    }
    s.cv = sync.NewCond(&amp;amp;s.mu)
    ...

    return s
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这块比较简单，主要是实例 grpc.Server 并进行初始化动作。涉及如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;lis：监听地址列表。&lt;/li&gt;
  &lt;li&gt;opts：服务选项，这块包含 Credentials、Interceptor 以及一些基础配置。&lt;/li&gt;
  &lt;li&gt;conns：客户端连接句柄列表。&lt;/li&gt;
  &lt;li&gt;m：服务信息映射。&lt;/li&gt;
  &lt;li&gt;quit：退出信号。&lt;/li&gt;
  &lt;li&gt;done：完成信号。&lt;/li&gt;
  &lt;li&gt;czData：用于存储 ClientConn，addrConn 和 Server 的channelz 相关数据。&lt;/li&gt;
  &lt;li&gt;cv：当优雅退出时，会等待这个信号量，直到所有 RPC 请求都处理并断开才会继续处理。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;二、注册&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;pb.RegisterSearchServiceServer(server, &amp;amp;SearchService{})&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;步骤一：Service API interface&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;// search.pb.go
type SearchServiceServer interface {
    Search(context.Context, *SearchRequest) (*SearchResponse, error)
}

func RegisterSearchServiceServer(s *grpc.Server, srv SearchServiceServer) {
    s.RegisterService(&amp;amp;_SearchService_serviceDesc, srv)
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;还记得我们平时编写的 Protobuf 吗？在生成出来的   &lt;code&gt;.pb.go&lt;/code&gt; 文件中，会定义出 Service APIs interface 的具体实现约束。而我们在 gRPC Server 进行注册时，会传入应用 Service 的功能接口实现，此时生成的   &lt;code&gt;RegisterServer&lt;/code&gt; 方法就会保证两者之间的一致性。&lt;/p&gt;
 &lt;h4&gt;步骤二：Service API IDL&lt;/h4&gt;
 &lt;p&gt;你想乱传糊弄一下？不可能的，请乖乖定义与 Protobuf 一致的接口方法。但是那个   &lt;code&gt;&amp;amp;_SearchService_serviceDesc&lt;/code&gt; 又有什么作用呢？代码如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;// search.pb.go
var _SearchService_serviceDesc = grpc.ServiceDesc{
    ServiceName: &amp;quot;proto.SearchService&amp;quot;,
    HandlerType: (*SearchServiceServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: &amp;quot;Search&amp;quot;,
            Handler:    _SearchService_Search_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: &amp;quot;search.proto&amp;quot;,
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这看上去像服务的描述代码，用来向内部表述 “我” 都有什么。涉及如下:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;ServiceName：服务名称&lt;/li&gt;
  &lt;li&gt;HandlerType：服务接口，用于检查用户提供的实现是否满足接口要求&lt;/li&gt;
  &lt;li&gt;Methods：一元方法集，注意结构内的    &lt;code&gt;Handler&lt;/code&gt; 方法，其对应最终的 RPC 处理方法，在执行 RPC 方法的阶段会使用。&lt;/li&gt;
  &lt;li&gt;Streams：流式方法集&lt;/li&gt;
  &lt;li&gt;Metadata：元数据，是一个描述数据属性的东西。在这里主要是描述    &lt;code&gt;SearchServiceServer&lt;/code&gt; 服务&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;步骤三：Register Service&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;func (s *Server) register(sd *ServiceDesc, ss interface{}) {
    ...
    srv := &amp;amp;service{
        server: ss,
        md:     make(map[string]*MethodDesc),
        sd:     make(map[string]*StreamDesc),
        mdata:  sd.Metadata,
    }
    for i := range sd.Methods {
        d := &amp;amp;sd.Methods[i]
        srv.md[d.MethodName] = d
    }
    for i := range sd.Streams {
        ...
    }
    s.m[sd.ServiceName] = srv
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在最后一步中，我们会将先前的服务接口信息、服务描述信息给注册到内部   &lt;code&gt;service&lt;/code&gt; 去，以便于后续实际调用的使用。涉及如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;server：服务的接口信息&lt;/li&gt;
  &lt;li&gt;md：一元服务的 RPC 方法集&lt;/li&gt;
  &lt;li&gt;sd：流式服务的 RPC 方法集&lt;/li&gt;
  &lt;li&gt;mdata：metadata，元数据&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;小结&lt;/h4&gt;
 &lt;p&gt;在这一章节中，主要介绍的是 gRPC Server 在启动前的整理和注册行为，看上去很简单，但其实一切都是为了后续的实际运行的预先准备。因此我们整理一下思路，将其串联起来看看，如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608519?w=1540&amp;h=790" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;三、监听&lt;/h3&gt;
 &lt;p&gt;接下来到了整个流程中，最重要也是大家最关注的监听/处理阶段，核心代码如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;func (s *Server) Serve(lis net.Listener) error {
    ...
    var tempDelay time.Duration 
    for {
        rawConn, err := lis.Accept()
        if err != nil {
            if ne, ok := err.(interface {
                Temporary() bool
            }); ok &amp;amp;&amp;amp; ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay &amp;gt; max {
                    tempDelay = max
                }
                ...
                timer := time.NewTimer(tempDelay)
                select {
                case &amp;lt;-timer.C:
                case &amp;lt;-s.quit:
                    timer.Stop()
                    return nil
                }
                continue
            }
            ...
            return err
        }
        tempDelay = 0

        s.serveWG.Add(1)
        go func() {
            s.handleRawConn(rawConn)
            s.serveWG.Done()
        }()
    }
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Serve 会根据外部传入的 Listener 不同而调用不同的监听模式，这也是   &lt;code&gt;net.Listener&lt;/code&gt; 的魅力，灵活性和扩展性会比较高。而在 gRPC Server 中最常用的就是   &lt;code&gt;TCPConn&lt;/code&gt;，基于 TCP Listener 去做。接下来我们一起看看具体的处理逻辑，如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608520?w=1540&amp;h=790" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;循环处理连接，通过    &lt;code&gt;lis.Accept&lt;/code&gt; 取出连接，如果队列中没有需处理的连接时，会形成阻塞等待。&lt;/li&gt;
  &lt;li&gt;若    &lt;code&gt;lis.Accept&lt;/code&gt; 失败，则触发休眠机制，若为第一次失败那么休眠 5ms，否则翻倍，再次失败则不断翻倍直至上限休眠时间 1s，而休眠完毕后就会尝试去取下一个 “它”。&lt;/li&gt;
  &lt;li&gt;若    &lt;code&gt;lis.Accept&lt;/code&gt; 成功，则重置休眠的时间计数和启动一个新的 goroutine 调用    &lt;code&gt;handleRawConn&lt;/code&gt; 方法去执行/处理新的请求，也就是大家很喜欢说的 “每一个请求都是不同的 goroutine 在处理”。&lt;/li&gt;
  &lt;li&gt;在循环过程中，包含了 “退出” 服务的场景，主要是硬关闭和优雅重启服务两种情况。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;客户端&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608521" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;一、创建拨号连接&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;// grpc.Dial(&amp;quot;:&amp;quot;+PORT, grpc.WithInsecure())
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
    cc := &amp;amp;ClientConn{
        target:            target,
        csMgr:             &amp;amp;connectivityStateManager{},
        conns:             make(map[*addrConn]struct{}),
        dopts:             defaultDialOptions(),
        blockingpicker:    newPickerWrapper(),
        czData:            new(channelzData),
        firstResolveEvent: grpcsync.NewEvent(),
    }
    ...
    chainUnaryClientInterceptors(cc)
    chainStreamClientInterceptors(cc)

    ...
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;code&gt;grpc.Dial&lt;/code&gt; 方法实际上是对于   &lt;code&gt;grpc.DialContext&lt;/code&gt; 的封装，区别在于   &lt;code&gt;ctx&lt;/code&gt; 是直接传入   &lt;code&gt;context.Background&lt;/code&gt;。其主要功能是  &lt;strong&gt;创建&lt;/strong&gt;与给定目标的客户端连接，其承担了以下职责：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;初始化 ClientConn&lt;/li&gt;
  &lt;li&gt;初始化（基于进程 LB）负载均衡配置&lt;/li&gt;
  &lt;li&gt;初始化 channelz&lt;/li&gt;
  &lt;li&gt;初始化重试规则和客户端一元/流式拦截器&lt;/li&gt;
  &lt;li&gt;初始化协议栈上的基础信息&lt;/li&gt;
  &lt;li&gt;相关 context 的超时控制&lt;/li&gt;
  &lt;li&gt;初始化并解析地址信息&lt;/li&gt;
  &lt;li&gt;创建与服务端之间的连接&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;连没连&lt;/h4&gt;
 &lt;p&gt;之前听到有的人说调用   &lt;code&gt;grpc.Dial&lt;/code&gt; 后客户端就已经与服务端建立起了连接，但这对不对呢？我们先鸟瞰全貌，看看正在跑的 goroutine。如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608522?w=1548&amp;h=374" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们可以有几个核心方法一直在等待/处理信号，通过分析底层源码可得知。涉及如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;func (ac *addrConn) connect()
func (ac *addrConn) resetTransport()
func (ac *addrConn) createTransport(addr resolver.Address, copts transport.ConnectOptions, connectDeadline time.Time)
func (ac *addrConn) getReadyTransport()&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在这里主要分析 goroutine 提示的   &lt;code&gt;resetTransport&lt;/code&gt; 方法，看看都做了啥。核心代码如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;func (ac *addrConn) resetTransport() {
    for i := 0; ; i++ {
        if ac.state == connectivity.Shutdown {
            return
        }
        ...
        connectDeadline := time.Now().Add(dialDuration)
        ac.updateConnectivityState(connectivity.Connecting)
        newTr, addr, reconnect, err := ac.tryAllAddrs(addrs, connectDeadline)
        if err != nil {
            if ac.state == connectivity.Shutdown {
                return
            }
            ac.updateConnectivityState(connectivity.TransientFailure)
            timer := time.NewTimer(backoffFor)
            select {
            case &amp;lt;-timer.C:
                ...
            }
            continue
        }

        if ac.state == connectivity.Shutdown {
            newTr.Close()
            return
        }
        ...
        if !healthcheckManagingState {
            ac.updateConnectivityState(connectivity.Ready)
        }
        ...

        if ac.state == connectivity.Shutdown {
            return
        }
        ac.updateConnectivityState(connectivity.TransientFailure)
    }
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在该方法中会不断地去尝试创建连接，若成功则结束。否则不断地根据   &lt;code&gt;Backoff&lt;/code&gt; 算法的重试机制去尝试创建连接，直到成功为止。从结论上来讲，单纯调用   &lt;code&gt;DialContext&lt;/code&gt; 是异步建立连接的，也就是并不是马上生效，处于   &lt;code&gt;Connecting&lt;/code&gt; 状态，而正式下要到达   &lt;code&gt;Ready&lt;/code&gt; 状态才可用。&lt;/p&gt;
 &lt;h4&gt;真的连了吗&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608523?w=2474&amp;h=758" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在抓包工具上提示一个包都没有，那么这算真正连接了吗？我认为这是一个表述问题，我们应该尽可能的严谨。如果你真的想通过   &lt;code&gt;DialContext&lt;/code&gt; 方法就打通与服务端的连接，则需要调用   &lt;code&gt;WithBlock&lt;/code&gt; 方法，虽然会导致阻塞等待，但最终连接会到达   &lt;code&gt;Ready&lt;/code&gt; 状态（握手成功）。如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608524" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;二、实例化 Service API&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;type SearchServiceClient interface {
    Search(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*SearchResponse, error)
}

type searchServiceClient struct {
    cc *grpc.ClientConn
}

func NewSearchServiceClient(cc *grpc.ClientConn) SearchServiceClient {
    return &amp;amp;searchServiceClient{cc}
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这块就是实例 Service API interface，比较简单。&lt;/p&gt;
 &lt;h3&gt;三、调用&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;// search.pb.go
func (c *searchServiceClient) Search(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*SearchResponse, error) {
    out := new(SearchResponse)
    err := c.cc.Invoke(ctx, &amp;quot;/proto.SearchService/Search&amp;quot;, in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;proto 生成的 RPC 方法更像是一个包装盒，把需要的东西放进去，而实际上调用的还是   &lt;code&gt;grpc.invoke&lt;/code&gt; 方法。如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;func invoke(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error {
    cs, err := newClientStream(ctx, unaryStreamDesc, cc, method, opts...)
    if err != nil {
        return err
    }
    if err := cs.SendMsg(req); err != nil {
        return err
    }
    return cs.RecvMsg(reply)
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;通过概览，可以关注到三块调用。如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;newClientStream：获取传输层 Trasport 并组合封装到 ClientStream 中返回，在这块会涉及负载均衡、超时控制、 Encoding、 Stream 的动作，与服务端基本一致的行为。&lt;/li&gt;
  &lt;li&gt;cs.SendMsg：发送 RPC 请求出去，但其并不承担等待响应的功能。&lt;/li&gt;
  &lt;li&gt;cs.RecvMsg：阻塞等待接受到的 RPC 方法响应结果。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;连接&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;// clientconn.go
func (cc *ClientConn) getTransport(ctx context.Context, failfast bool, method string) (transport.ClientTransport, func(balancer.DoneInfo), error) {
    t, done, err := cc.blockingpicker.pick(ctx, failfast, balancer.PickOptions{
        FullMethodName: method,
    })
    if err != nil {
        return nil, nil, toRPCErr(err)
    }
    return t, done, nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在   &lt;code&gt;newClientStream&lt;/code&gt; 方法中，我们通过   &lt;code&gt;getTransport&lt;/code&gt; 方法获取了 Transport 层中抽象出来的 ClientTransport 和 ServerTransport，实际上就是获取一个连接给后续 RPC 调用传输使用。&lt;/p&gt;
 &lt;h3&gt;四、关闭连接&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;// conn.Close()
func (cc *ClientConn) Close() error {
    defer cc.cancel()
    ...
    cc.csMgr.updateState(connectivity.Shutdown)
    ...
    cc.blockingpicker.close()
    if rWrapper != nil {
        rWrapper.close()
    }
    if bWrapper != nil {
        bWrapper.close()
    }

    for ac := range conns {
        ac.tearDown(ErrClientConnClosing)
    }
    if channelz.IsOn() {
        ...
        channelz.AddTraceEvent(cc.channelzID, ted)
        channelz.RemoveEntry(cc.channelzID)
    }
    return nil
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;该方法会取消 ClientConn 上下文，同时关闭所有底层传输。涉及如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Context Cancel&lt;/li&gt;
  &lt;li&gt;清空并关闭客户端连接&lt;/li&gt;
  &lt;li&gt;清空并关闭解析器连接&lt;/li&gt;
  &lt;li&gt;清空并关闭负载均衡连接&lt;/li&gt;
  &lt;li&gt;添加跟踪引用&lt;/li&gt;
  &lt;li&gt;移除当前通道信息&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;Q&amp;amp;A&lt;/h2&gt;
 &lt;h3&gt;1. gRPC Metadata 是通过什么传输？&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608525" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;2. 调用 grpc.Dial 会真正的去连接服务端吗？&lt;/h3&gt;
 &lt;p&gt;会，但是是异步连接的，连接状态为正在连接。但如果你设置了   &lt;code&gt;grpc.WithBlock&lt;/code&gt; 选项，就会阻塞等待（等待握手成功）。另外你需要注意，当未设置   &lt;code&gt;grpc.WithBlock&lt;/code&gt; 时，ctx 超时控制对其无任何效果。&lt;/p&gt;
 &lt;h3&gt;3. 调用 ClientConn 不 Close 会导致泄露吗？&lt;/h3&gt;
 &lt;p&gt;会，除非你的客户端不是常驻进程，那么在应用结束时会被动地回收资源。但如果是常驻进程，你又真的忘记执行   &lt;code&gt;Close&lt;/code&gt; 语句，会造成的泄露。如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3.1. 客户端&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608526" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3.2. 服务端&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608527" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3.3. TCP&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608528" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;4. 不控制超时调用的话，会出现什么问题？&lt;/h3&gt;
 &lt;p&gt;短时间内不会出现问题，但是会不断积蓄泄露，积蓄到最后当然就是服务无法提供响应了。如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image" src="https://segmentfault.com/img/remote/1460000019608529" title="image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;5. 为什么默认的拦截器不可以传多个？&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;func chainUnaryClientInterceptors(cc *ClientConn) {
    interceptors := cc.dopts.chainUnaryInts
    if cc.dopts.unaryInt != nil {
        interceptors = append([]UnaryClientInterceptor{cc.dopts.unaryInt}, interceptors...)
    }
    var chainedInt UnaryClientInterceptor
    if len(interceptors) == 0 {
        chainedInt = nil
    } else if len(interceptors) == 1 {
        chainedInt = interceptors[0]
    } else {
        chainedInt = func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error {
            return interceptors[0](ctx, method, req, reply, cc, getChainUnaryInvoker(interceptors, 0, invoker), opts...)
        }
    }
    cc.dopts.unaryInt = chainedInt
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;当存在多个拦截器时，取的就是第一个拦截器。因此结论是允许传多个，但并没有用。&lt;/p&gt;
 &lt;h3&gt;6. 真的需要用到多个拦截器的话，怎么办？&lt;/h3&gt;
 &lt;p&gt;可以使用   &lt;a href="https://github.com/grpc-ecosystem/go-grpc-middleware" rel="nofollow noreferrer"&gt;go-grpc-middleware&lt;/a&gt; 提供的   &lt;code&gt;grpc.UnaryInterceptor&lt;/code&gt; 和   &lt;code&gt;grpc.StreamInterceptor&lt;/code&gt; 链式方法，方便快捷省心。&lt;/p&gt;
 &lt;p&gt;单单会用还不行，我们再深剖一下，看看它是怎么实现的。核心代码如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;func ChainUnaryClient(interceptors ...grpc.UnaryClientInterceptor) grpc.UnaryClientInterceptor {
    n := len(interceptors)
    if n &amp;gt; 1 {
        lastI := n - 1
        return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
            var (
                chainHandler grpc.UnaryInvoker
                curI         int
            )

            chainHandler = func(currentCtx context.Context, currentMethod string, currentReq, currentRepl interface{}, currentConn *grpc.ClientConn, currentOpts ...grpc.CallOption) error {
                if curI == lastI {
                    return invoker(currentCtx, currentMethod, currentReq, currentRepl, currentConn, currentOpts...)
                }
                curI++
                err := interceptors[curI](currentCtx, currentMethod, currentReq, currentRepl, currentConn, chainHandler, currentOpts...)
                curI--
                return err
            }

            return interceptors[0](ctx, method, req, reply, cc, chainHandler, opts...)
        }
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;当拦截器数量大于 1 时，从   &lt;code&gt;interceptors[1]&lt;/code&gt; 开始递归，每一个递归的拦截器   &lt;code&gt;interceptors[i]&lt;/code&gt; 会不断地执行，最后才真正的去执行   &lt;code&gt;handler&lt;/code&gt; 方法。同时也经常有人会问拦截器的执行顺序是什么，通过这段代码你得出结论了吗？&lt;/p&gt;
 &lt;h3&gt;7. 频繁创建 ClientConn 有什么问题？&lt;/h3&gt;
 &lt;p&gt;这个问题我们可以反向验证一下，假设不公用 ClientConn 看看会怎么样？如下:&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;func BenchmarkSearch(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        conn, err := GetClientConn()
        if err != nil {
            b.Errorf(&amp;quot;GetClientConn err: %v&amp;quot;, err)
        }
        _, err = Search(context.Background(), conn)
        if err != nil {
            b.Errorf(&amp;quot;Search err: %v&amp;quot;, err)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;输出结果：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    ... connection error: desc = &amp;quot;transport: Error while dialing dial tcp :10001: socket: too many open files&amp;quot;
    ... connection error: desc = &amp;quot;transport: Error while dialing dial tcp :10001: socket: too many open files&amp;quot;
    ... connection error: desc = &amp;quot;transport: Error while dialing dial tcp :10001: socket: too many open files&amp;quot;
    ... connection error: desc = &amp;quot;transport: Error while dialing dial tcp :10001: socket: too many open files&amp;quot;
FAIL
exit status 1&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;当你的应用场景是存在高频次同时生成/调用 ClientConn 时，可能会导致系统的文件句柄占用过多。这种情况下你可以变更应用程序生成/调用 ClientConn 的模式，又或是池化它，这块可以参考   &lt;a&gt;grpc-go-pool&lt;/a&gt; 项目。&lt;/p&gt;
 &lt;h3&gt;8. 客户端请求失败后会默认重试吗？&lt;/h3&gt;
 &lt;p&gt;会不断地进行重试，直到上下文取消。而重试时间方面采用 backoff 算法作为的重连机制，默认的最大重试时间间隔是 120s。&lt;/p&gt;
 &lt;h3&gt;9. 为什么要用 HTTP/2 作为传输协议？&lt;/h3&gt;
 &lt;p&gt;许多客户端要通过 HTTP 代理来访问网络，gRPC 全部用 HTTP/2 实现，等到代理开始支持 HTTP/2 就能透明转发 gRPC 的数据。不光如此，负责负载均衡、访问控制等等的反向代理都能无缝兼容 gRPC，比起自己设计 wire protocol 的 Thrift，这样做科学不少。@ctiller @滕亦飞&lt;/p&gt;
 &lt;h3&gt;10. 在 Kubernetes 中 gRPC 负载均衡有问题？&lt;/h3&gt;
 &lt;p&gt;gRPC 的 RPC 协议是基于 HTTP/2 标准实现的，HTTP/2 的一大特性就是不需要像 HTTP/1.1 一样，每次发出请求都要重新建立一个新连接，而是会复用原有的连接。&lt;/p&gt;
 &lt;p&gt;所以这将导致 kube-proxy 只有在连接建立时才会做负载均衡，而在这之后的每一次 RPC 请求都会利用原本的连接，那么实际上后续的每一次的 RPC 请求都跑到了同一个地方。&lt;/p&gt;
 &lt;p&gt;注：使用 k8s service 做负载均衡的情况下&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;gRPC 基于 HTTP/2 + Protobuf。&lt;/li&gt;
  &lt;li&gt;gRPC 有四种调用方式，分别是一元、服务端/客户端流式、双向流式。&lt;/li&gt;
  &lt;li&gt;gRPC 的附加信息都会体现在 HEADERS 帧，数据在 DATA 帧上。&lt;/li&gt;
  &lt;li&gt;Client 请求若使用 grpc.Dial 默认是异步建立连接，当时状态为 Connecting。&lt;/li&gt;
  &lt;li&gt;Client 请求若需要同步则调用 WithBlock()，完成状态为 Ready。&lt;/li&gt;
  &lt;li&gt;Server 监听是循环等待连接，若没有则休眠，最大休眠时间 1s；若接收到新请求则起一个新的 goroutine 去处理。&lt;/li&gt;
  &lt;li&gt;grpc.ClientConn 不关闭连接，会导致 goroutine 和 Memory 等泄露。&lt;/li&gt;
  &lt;li&gt;任何内/外调用如果不加超时控制，会出现泄漏和客户端不断重试。&lt;/li&gt;
  &lt;li&gt;特定场景下，如果不对 grpc.ClientConn 加以调控，会影响调用。&lt;/li&gt;
  &lt;li&gt;拦截器如果不用 go-grpc-middleware 链式处理，会覆盖。&lt;/li&gt;
  &lt;li&gt;在选择 gRPC 的负责均衡模式时，需要谨慎。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;参考&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://doc.oschina.net/grpc" rel="nofollow noreferrer"&gt;http://doc.oschina.net/grpc&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md" rel="nofollow noreferrer"&gt;https://github.com/grpc/grpc/...&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://juejin.im/post/5b88a4f56fb9a01a0b31a67e" rel="nofollow noreferrer"&gt;https://juejin.im/post/5b88a4...&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.ibm.com/developerworks/cn/web/wa-http2-under-the-hood/index.html" rel="nofollow noreferrer"&gt;https://www.ibm.com/developer...&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/grpc/grpc-go/issues/1953" rel="nofollow noreferrer"&gt;https://github.com/grpc/grpc-...&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.zhihu.com/question/52670041" rel="nofollow noreferrer"&gt;https://www.zhihu.com/questio...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>php grpc golang</category>
      <guid isPermaLink="true">https://itindex.net/detail/59758-grpc</guid>
      <pubDate>Sat, 29 Jun 2019 20:00:00 CST</pubDate>
    </item>
    <item>
      <title>Redis是单线程的，但Redis为什么这么快？</title>
      <link>https://itindex.net/detail/59080-redis-%E5%8D%95%E7%BA%BF%E7%A8%8B-redis</link>
      <description>&lt;p&gt;近乎所有与Java相关的面试都会问到缓存的问题，基础一点的会问到什么是“二八定律”、什么是“热数据和冷数据”，复杂一点的会问到缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题，这些看似不常见的概念，都与我们的缓存服务器相关，一般常用的缓存服务器有Redis、Memcached等，而笔者目前最常用的也只有Redis这一种。&lt;/p&gt;
 &lt;p&gt;如果你在以前面试的时候还没有遇到过面试官问你《为什么说Redis是单线程的以及Redis为什么这么快！》，那么你看到这篇文章的时候，你应该觉得是一件很幸运的事情！如果你刚好是一位高逼格的面试官，你也可以拿这道题去面试对面“望穿秋水”般的小伙伴，测试一下他的掌握程度。&lt;/p&gt;
 &lt;p&gt;好啦！步入正题！我们先探讨一下Redis是什么，Redis为什么这么快、然后在探讨一下为什么Redis是单线程的？&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;一.Redis简介&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Redis是一个开源的内存中的数据结构存储系统，它可以用作：数据库、缓存和消息中间件。&lt;/p&gt;
 &lt;p&gt;它支持多种类型的数据结构，如字符串（String），散列（Hash），列表（List），集合（Set），有序集合（Sorted Set或者是ZSet）与范围查询，Bitmaps，Hyperloglogs 和地理空间（Geospatial）索引半径查询。其中常见的数据结构类型有：String、List、Set、Hash、ZSet这5种。&lt;/p&gt;
 &lt;p&gt;Redis 内置了复制（Replication），LUA脚本（Lua scripting）， LRU驱动事件（LRU eviction），事务（Transactions） 和不同级别的磁盘持久化（Persistence），并通过 Redis哨兵（Sentinel）和自动分区（Cluster）提供高可用性（High Availability）。&lt;/p&gt;
 &lt;p&gt;Redis也提供了持久化的选项，这些选项可以让用户将自己的数据保存到磁盘上面进行存储。根据实际情况，可以每隔一定时间将数据集导出到磁盘（快照），或者追加到命令日志中（AOF只追加文件），他会在执行写命令时，将被执行的写命令复制到硬盘里面。您也可以关闭持久化功能，将Redis作为一个高效的网络的缓存数据功能使用。&lt;/p&gt;
 &lt;p&gt;Redis不使用表，他的数据库不会预定义或者强制去要求用户对Redis存储的不同数据进行关联。&lt;/p&gt;
 &lt;p&gt;数据库的工作模式按存储方式可分为：硬盘数据库和内存数据库。Redis 将数据储存在内存里面，读写数据的时候都不会受到硬盘 I/O 速度的限制，所以速度极快。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（1）硬盘数据库的工作模式：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbk4ou?w=679&amp;h=372" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（2）内存数据库的工作模式：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbk4oR?w=635&amp;h=336" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;看完上述的描述，对于一些常见的Redis相关的面试题，是否有所认识了，例如：什么是Redis、Redis常见的数据结构类型有哪些、Redis是如何进行持久化的等。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;二.Redis到底有多快？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库，由C语言编写，官方提供的数据是可以达到100000+的QPS（每秒内查询次数）。&lt;/p&gt;
 &lt;p&gt;这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差！&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbk4oT?w=646&amp;h=372" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;横轴是连接数，纵轴是QPS。此时，这张图反映了一个数量级，希望大家在面试的时候可以正确的描述出来，不要问你的时候，你回答的数量级相差甚远！&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;三.Redis为什么这么快？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;1、完全基于内存，绝大部分请求是纯粹的内存操作，非常快速。数据存在内存中，类似于HashMap，HashMap的优势就是查找和操作的时间复杂度都是O(1)；&lt;/p&gt;
 &lt;p&gt;2、数据结构简单，对数据操作也简单，Redis中的数据结构是专门进行设计的；&lt;/p&gt;
 &lt;p&gt;3、采用单线程，避免了不必要的上下文切换和竞争条件，也不存在多进程或者多线程导致的切换而消耗 CPU，不用去考虑各种锁的问题，不存在加锁释放锁操作，没有因为可能出现死锁而导致的性能消耗；&lt;/p&gt;
 &lt;p&gt;4、使用多路I/O复用模型，非阻塞IO；&lt;/p&gt;
 &lt;p&gt;5、使用底层模型不同，它们之间底层实现方式以及与客户端之间通信的应用协议不一样，Redis直接自己构建了VM 机制 ，因为一般的系统调用系统函数的话，会浪费一定的时间去移动和请求；&lt;/p&gt;
 &lt;p&gt;以上几点都比较好理解，下边我们针对多路 I/O 复用模型进行简单的探讨：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（1）多路 I/O 复用模型&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力，在空闲的时候，会把当前线程阻塞掉，当有一个或多个流有 I/O 事件时，就从阻塞态中唤醒，于是程序就会轮询一遍所有的流（epoll 是只轮询那些真正发出了事件的流），并且只依次顺序的处理就绪的流，这种做法就避免了大量的无用操作。&lt;/p&gt;
 &lt;p&gt;这里“多路”指的是多个网络连接，“复用”指的是复用同一个线程。&lt;/p&gt;
 &lt;p&gt;采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求（尽量减少网络 IO 的时间消耗），且 Redis 在内存中操作数据的速度非常快，也就是说内存内的操作不会成为影响Redis性能的瓶颈，主要由以上几点造就了 Redis 具有很高的吞吐量。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;四.那么为什么Redis是单线程的？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;我们首先要明白，上边的种种分析，都是为了营造一个Redis很快的氛围！官方FAQ表示，因为Redis是基于内存的操作，CPU不是Redis的瓶颈，Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现，而且CPU不会成为瓶颈，那就顺理成章地采用单线程的方案了（毕竟采用多线程会有很多麻烦！）。&lt;/p&gt;
 &lt;p&gt;看到这里，你可能会气哭！本以为会有什么重大的技术要点才使得Redis使用单线程就可以这么快，没想到就是一句官方看似糊弄我们的回答！但是，我们已经可以很清楚的解释了为什么Redis这么快，并且正是由于在单线程模式的情况下已经很快了，就没有必要在使用多线程了！&lt;/p&gt;
 &lt;p&gt;但是，我们使用单线程的方式是无法发挥多核CPU 性能，不过我们可以通过在单机开多个Redis 实例来完善！&lt;/p&gt;
 &lt;p&gt;警告1：这里我们一直在强调的单线程，只是在处理我们的网络请求的时候只有一个线程来处理，一个正式的Redis Server运行的时候肯定是不止一个线程的，这里需要大家明确的注意一下！例如Redis进行持久化的时候会以子进程或者子线程的方式执行（具体是子线程还是子进程待读者深入研究）；例如我在测试服务器上查看Redis进程，然后找到该进程下的线程：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbk4o5?w=694&amp;h=162" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;ps命令的“-T”参数表示显示线程（Show threads, possibly with SPID column.）“SID”栏表示线程ID，而“CMD”栏则显示了线程名称。&lt;/p&gt;
 &lt;p&gt;警告2：在上图中FAQ中的最后一段，表述了从Redis 4.0版本开始会支持多线程的方式，但是，只是在某一些操作上进行多线程的操作！所以该篇文章在以后的版本中是否还是单线程的方式需要读者考证！&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;五.注意点&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;1、我们知道Redis是用”单线程-多路复用IO模型”来实现高性能的内存数据服务的，这种机制避免了使用锁，但是同时这种机制在进行sunion之类的比较耗时的命令时会使redis的并发下降。&lt;/p&gt;
 &lt;p&gt;因为是单一线程，所以同一时刻只有一个操作在进行，所以，耗时的命令会导致并发的下降，不只是读并发，写并发也会下降。而单一线程也只能用到一个CPU核心，所以可以在同一个多核的服务器中，可以启动多个实例，组成master-master或者master-slave的形式，耗时的读命令可以完全在slave进行。&lt;/p&gt;
 &lt;p&gt;需要改的redis.conf项：&lt;/p&gt;
 &lt;p&gt;pidfile /var/run/redis/redis_6377.pid #pidfile要加上端口号&lt;/p&gt;
 &lt;p&gt;port 6377 #这个是必须改的&lt;/p&gt;
 &lt;p&gt;logfile /var/log/redis/redis_6377.log #logfile的名称也加上端口号&lt;/p&gt;
 &lt;p&gt;dbfilename dump_6377.rdb #rdbfile也加上端口号&lt;/p&gt;
 &lt;p&gt;2、“我们不能任由操作系统负载均衡，因为我们自己更了解自己的程序，所以，我们可以手动地为其分配CPU核，而不会过多地占用CPU，或是让我们关键进程和一堆别的进程挤在一起。”&lt;/p&gt;
 &lt;p&gt;CPU 是一个重要的影响因素，由于是单线程模型，Redis 更喜欢大缓存快速 CPU， 而不是多核。&lt;/p&gt;
 &lt;p&gt;在多核 CPU 服务器上面，Redis 的性能还依赖NUMA 配置和处理器绑定位置。最明显的影响是 redis-benchmark 会随机使用CPU内核。为了获得精准的结果，需要使用固定处理器工具（在 Linux 上可以使用 taskset）。最有效的办法是将客户端和服务端分离到两个不同的 CPU 来高校使用三级缓存。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;六.扩展&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;以下也是你应该知道的几种模型，祝你的面试一臂之力！&lt;/p&gt;
 &lt;p&gt;1、单进程多线程模型：MySQL、Memcached、Oracle（Windows版本）；&lt;/p&gt;
 &lt;p&gt;2、多进程模型：Oracle（Linux版本）；&lt;/p&gt;
 &lt;p&gt;3、Nginx有两类进程，一类称为Master进程(相当于管理进程)，另一类称为Worker进程（实际工作进程）。启动方式有两种：&lt;/p&gt;
 &lt;p&gt;（1）单进程启动：此时系统中仅有一个进程，该进程既充当Master进程的角色，也充当Worker进程的角色。&lt;/p&gt;
 &lt;p&gt;（2）多进程启动：此时系统有且仅有一个Master进程，至少有一个Worker进程工作。&lt;/p&gt;
 &lt;p&gt;（3）Master进程主要进行一些全局性的初始化工作和管理Worker的工作；事件处理是在Worker中进行的。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbk4pF?w=663&amp;h=395" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>redis php</category>
      <guid isPermaLink="true">https://itindex.net/detail/59080-redis-%E5%8D%95%E7%BA%BF%E7%A8%8B-redis</guid>
      <pubDate>Fri, 14 Dec 2018 11:33:02 CST</pubDate>
    </item>
    <item>
      <title>从零开始搭建创业公司后台技术栈</title>
      <link>https://itindex.net/detail/58285-%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B-%E5%88%9B%E4%B8%9A%E5%85%AC%E5%8F%B8-%E5%90%8E%E5%8F%B0</link>
      <description>&lt;p&gt;说到后台技术栈，脑海中是不是浮现的是这样一幅图？&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack1.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图1]&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/import.png"&gt;&lt;/img&gt;有点眼晕，以上只是我们会用到的一些语言的合集，而且只是语言层面的一部分，就整个后台技术栈来说，这只是一个开始，从语言开始，还有很多很多的内容。今天要说的后台是大后台的概念，放在服务器上的东西都属于后台的东西，比如使用的框架，语言，数据库，服务，操作系统等等，整个后台技术栈我的理解包括4个层面的内容：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;语言： 用了哪些开发语言，如：c++/java/go/php/python/ruby等等；&lt;/li&gt;
  &lt;li&gt;组件：用了哪些组件，如：MQ组件，数据库组件等等；&lt;/li&gt;
  &lt;li&gt;流程：怎样的流程和规范，如：开发流程，项目流程，发布流程，监控告警流程，代码规范等等；&lt;/li&gt;
  &lt;li&gt;系统：系统化建设，上面的流程需要有系统来保证，如：规范发布流程的发布系统，代码管理系统等等；&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;结合以上的的4个层面的内容，整个后台技术栈的结构如图2所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack2.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图2 后台技术栈结构]&lt;/p&gt;
 &lt;p&gt;以上的这些内容都需要我们从零开始搭建，在创业公司，没有大公司那些完善的基础设施，需要我们从开源界，从云服务商甚至有些需要自己去组合，去拼装，去开发一个适合自己的组件或系统以达成我们的目标。咱们一个个系统和组件的做选型，最终形成我们的后台技术栈。&lt;/p&gt;
 &lt;h2&gt;一、各系统组件选型&lt;/h2&gt;
 &lt;h3&gt;1、项目管理/Bug管理/问题管理&lt;/h3&gt;
 &lt;p&gt;项目管理软件是整个业务的需求，问题，流程等等的集中地，大家的跨部门沟通协同大多依赖于项目管理工具。有一些 SAAS 的项目管理服务可以使用，但是很多时间不满足需求，此时我们可以选择一些开源的项目，这些项目本身有一定的定制能力，有丰富的插件可以使用，一般的创业公司需求基本上都能得到满足，常用的项目如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;Redmine： 用 Ruby 开发的，有较多的插件可以使用，能自定义字段，集成了项目管理，BUG 问题跟踪，WIKI 等功能，不过好多插件 N 年没有更新了;&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Phabricator: 用 PHP 开发的，facebook 之前的内部工具，开发这工具的哥们离职后自己搞了一个公司专门做这个软件，集成了代码托管， Code Review，任务管理，文档管理，问题跟踪等功能，强烈推荐较敏捷的团队使用；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Jira：用 Java 开发的，有用户故事，task 拆分，燃尽图等等，可以做项目管理，也可以应用于跨部门沟通场景，较强大；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;悟空CRM ：这个不是项目管理，这个是客户管理，之所以在这里提出来，是因为在 To B 的创业公司里面，往往是以客户为核心来做事情的，可以将项目管理和问题跟进的在悟空 CRM 上面来做，他的开源版本已经基本实现了 CR&amp;lt; 的核心 功能，还带有一个任务管理功能，用于问题跟进，不过用这个的话，还是需要另一个项目管理的软件协助，顺便说一嘴，这个系统的代码写得很难维护，只能适用于客户规模小（1万以内）时。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;2、DNS&lt;/h3&gt;
 &lt;p&gt;DNS 是一个很通用的服务，创业公司基本上选择一个合适的云厂商就行了，国内主要是两家：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;阿里万网：阿里 2014 年收购了万网，整合了其域名服务，最终形成了现在的阿里万网，其中就包含 DNS 这块的服务；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;腾讯 DNSPod: 腾讯 2012 年以 4000 万收购 DNSPod 100% 股份，主要提供域名解析和一些防护功能；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;如果你的业务是在国内，主要就是这两家，选 一个就好，像今日头条这样的企业用的也是 DNSPod 的服务，除非一些特殊的原因才需要自建，比如一些 CDN 厂商，或者对区域有特殊限制的。要实惠一点用阿里最便宜的基础版就好了，要成功率高一些，还是用DNSPod 的贵的那种。&lt;/p&gt;
 &lt;p&gt;在国外还是选择亚马逊吧，阿里的 DNS 服务只有在日本和美国有节点，东南亚最近才开始部点， DNSPod 也只有美国和日本，像一些出海的企业，其选择的云服务基本都是亚马逊。&lt;/p&gt;
 &lt;p&gt;如果是线上产品，DNS 强烈建议用付费版，阿里的那几十块钱的付费版基本可以满足需求。如果还需要一些按省份或按区域调试的逻辑，则需要加钱，一年也就几百块，省钱省力。&lt;/p&gt;
 &lt;p&gt;如果是国外，优先选择亚马逊，如果需要国内外互通并且有自己的 APP 的话，建议还是自己实现一些容灾逻辑或者智能调度，因为没有一个现成的 DNS 服务能同时较好的满足国内外场景，或者用多个域名，不同的域名走不同的 DNS 。&lt;/p&gt;
 &lt;h3&gt;3、LB(负载均衡)&lt;/h3&gt;
 &lt;p&gt;LB(负载均衡)是一个通用服务，一般云厂商的 LB 服务基本都会如下功能：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;支持四层协议请求（包括 TCP、UDP 协议）;&lt;/li&gt;
  &lt;li&gt;支持七层协议请求（包括 HTTP、HTTPS 协议）;&lt;/li&gt;
  &lt;li&gt;集中化的证书管理系统支持 HTTPS 协议;&lt;/li&gt;
  &lt;li&gt;健康检查;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;如果你线上的服务机器都是用的云服务，并且是在同一个云服务商的话，可以直接使用云服务商提供的 LB 服务，如阿里云的 SLB，腾讯云的 CLB， 亚马逊 的 ELB 等等。如果是自建机房基本都是 LVS + Nginx。&lt;/p&gt;
 &lt;h3&gt;4、CDN&lt;/h3&gt;
 &lt;p&gt;CDN 现在已经是一个很红很红的市场，基本上只能挣一些辛苦钱，都是贴着成本在卖。国内以网宿为龙头，他们家占据整个国内市场份额的40%以上，后面就是腾讯，阿里。网宿有很大一部分是因为直播的兴起而崛起。&lt;/p&gt;
 &lt;p&gt;国外，Amazon 和 Akamai 合起来占比大概在 50%，曾经的国际市场老大 Akamai 拥有全球超一半的份额，在 Amazon CDN入局后，份额跌去了将近 20%，众多中小企业都转向后者，Akamai 也是无能为力。&lt;/p&gt;
 &lt;p&gt;国内出海的 CDN 厂商，更多的是为国内的出海企业服务，三家大一点的 CDN 服务商里面也就网宿的节点多一些，但是也多不了多少。阿里和腾讯还处于前期阶段，仅少部分国家有节点。&lt;/p&gt;
 &lt;p&gt;就创业公司来说，CDN 用腾讯云或阿里云即可，其相关系统较完善，能轻松接入，网宿在系统支持层面相对较弱一些，而且还贵一些。并且，当流量上来后，CDN 不能只用一家，需要用多家，不同的 CDN 在全国的节点覆盖不一样，而且针对不同的客户云厂商内部有些区分客户集群，并不是全节点覆盖（但有些云厂商说自己是全网节点），除了节点覆盖的问题，多 CDN 也在一定程度上起到容灾的作用。&lt;/p&gt;
 &lt;h3&gt;5、RPC框架&lt;/h3&gt;
 &lt;p&gt;维基百科对 RPC 的定义是：远程过程调用（Remote Procedure Call，RPC）是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序，而程序员无需额外地为这个交互作用编程。&lt;/p&gt;
 &lt;p&gt;通俗来讲，一个完整的RPC调用过程，就是 Server 端实现了一个函数，客户端使用 RPC 框架提供的接口，调用这个函数的实现，并获取返回值的过程。&lt;/p&gt;
 &lt;p&gt;业界 RPC 框架大致分为两大流派，一种侧重跨语言调用，另一种是偏重服务治理。&lt;/p&gt;
 &lt;p&gt;跨语言调用型的 RPC 框架有 Thrift、gRPC、Hessian、Hprose 等。这类 RPC 框架侧重于服务的跨语言调用，能够支持大部分的语言进行语言无关的调用，非常适合多语言调用场景。但这类框架没有服务发现相关机制，实际使用时需要代理层进行请求转发和负载均衡策略控制。&lt;/p&gt;
 &lt;p&gt;其中，gRPC 是 Google 开发的高性能、通用的开源 RPC 框架，其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计，基于 ProtoBuf(Protocol Buffers) 序列化协议开发，且支持众多开发语言。本身它不是分布式的，所以要实现框架的功能需要进一步的开发。&lt;/p&gt;
 &lt;p&gt;Hprose(High Performance Remote Object Service Engine) 是一个 MIT 开源许可的新型轻量级跨语言跨平台的面向对象的高性能远程动态通讯中间件。&lt;/p&gt;
 &lt;p&gt;服务治理型的 RPC 框架的特点是功能丰富，提供高性能的远程调用、服务发现及服务治理能力，适用于大型服务的服务解耦及服务治理，对于特定语言(Java)的项目可以实现透明化接入。缺点是语言耦合度较高，跨语言支持难度较大。国内常见的冶理型 RPC 框架如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Dubbo: Dubbo 是阿里巴巴公司开源的一个 Java 高性能优秀的服务框架，使得应用可通过高性能的 RPC 实现服务的输出和输入功能，可以和 Spring 框架无缝集成。当年在淘宝内部，Dubbo 由于跟淘宝另一个类似的框架 HSF 有竞争关系，导致 Dubbo 团队解散，最近又活过来了，有专职同学投入。&lt;/li&gt;
  &lt;li&gt;DubboX: DubboX 是由当当在基于 Dubbo 框架扩展的一个 RPC 框架，支持 REST 风格的远程调用、Kryo/FST 序列化，增加了一些新的feature。&lt;/li&gt;
  &lt;li&gt;Motan: Motan 是新浪微博开源的一个 Java 框架。它诞生的比较晚，起于 2013 年，2016 年 5 月开源。Motan 在微博平台中已经广泛应用，每天为数百个服务完成近千亿次的调用。&lt;/li&gt;
  &lt;li&gt;rpcx: rpcx 是一个类似阿里巴巴   &lt;a href="http://dubbo.io/" target="_blank"&gt;Dubbo&lt;/a&gt; 和微博    &lt;a href="https://github.com/weibocom/motan" target="_blank"&gt;Motan&lt;/a&gt; 的分布式的 RPC 服务框架，基于 Golang net/rpc 实现。但是 rpcx 基本只有一个人在维护，没有完善的社区，使用前要慎重，之前做 Golang 的 RPC 选型时也有考虑这个，最终还是放弃了，选择了 gRPC，如果想自己自研一个 RPC 框架，可以参考学习一下。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;6、名字发现/服务发现&lt;/h3&gt;
 &lt;p&gt;名字发现和服务发现分为两种模式，一个是客户端发现模式，一种是服务端发现模式。&lt;/p&gt;
 &lt;p&gt;框架中常用的服务发现是客户端发现模式。&lt;/p&gt;
 &lt;p&gt;所谓服务端发现模式是指客户端通过一个负载均衡器向服务发送请求，负载均衡器查询服务注册表并把请求路由到一台可用的服务实例上。现在常用的负载均衡器都是此类模式，常用于微服务中。&lt;/p&gt;
 &lt;p&gt;所有的名字发现和服务发现都要依赖于一个可用性非常高的服务注册表，业界常用的服务注册表有如下三个：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;etcd，一个高可用、分布式、一致性、key-value方式的存储，被用在分享配置和服务发现中。两个著名的项目使用了它：k8s和Cloud Foundry。&lt;/li&gt;
  &lt;li&gt;consul，一个发现和配置服务的工具，为客户端注册和发现服务提供了API，Consul还可以通过执行健康检查决定服务的可用性。&lt;/li&gt;
  &lt;li&gt;Apache Zookeeper，是一个广泛使用、高性能的针对分布式应用的协调服务。Apache Zookeeper本来是 Hadoop 的子工程，现在已经是顶级工程了。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;除此之外也可以自己实现服务实现，或者用 Redis 也行，只是需要自己实现高可用性。&lt;/p&gt;
 &lt;h3&gt;7、关系数据库&lt;/h3&gt;
 &lt;p&gt;关系数据库分为两种，一种是传统关系数据，如 Oracle, MySQL，Maria, DB2，PostgreSQL 等等，另一种是 NewSQL，即至少要满足以下五点的新型关系数据库：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;
   &lt;p&gt;完整地支持SQL，支持JOIN / GROUP BY /子查询等复杂SQL查询；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;支持传统数据标配的 ACID 事务，支持强隔离级别。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;具有弹性伸缩的能力，扩容缩容对于业务层完全透明。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;真正的高可用，异地多活、故障恢复的过程不需要人为的接入，系统能够自动地容灾和进行强一致的数据恢复。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;具备一定的大数据分析能力&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;传统关系数据库用得最多的是 MySQL，成熟，稳定，一些基本的需求都能满足，在一定数据量级之前基本单机传统数据库都可以搞定，而且现在较多的开源系统都是基于 MySQL，开箱即用，再加上主从同步和前端缓存，百万 pv 的应用都可以搞定了。不过 CentOS 7 已经放弃了 MySQL，而改使用 MariaDB。MariaDB 数据库管理系统是 MySQ L的一个分支，主要由开源社区在维护，采用GPL 授权许可。开发这个分支的原因之一是：甲骨文公司收购了 MySQL 后，有将 MySQ L闭源的潜在风险，因此社区采用分支的方式来避开这个风险。&lt;/p&gt;
 &lt;p&gt;在 Google 发布了   &lt;a href="http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/41344.pdf" target="_blank"&gt;F1: A Distributed SQL Database That Scales&lt;/a&gt; 和   &lt;a href="http://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf" target="_blank"&gt;Spanner: Google’s Globally-Distributed Databasa &lt;/a&gt;之后，业界开始流行起 NewSQL。于是有了 CockroachDB，于是有了 奇叔公司的 TiDB。国内已经有比较多的公司使用 TiDB，之前在创业公司时在大数据分析时已经开始应用 TiDB，当时应用的主要原因是 MySQL 要使用分库分表，逻辑开发比较复杂，扩展性不够。&lt;/p&gt;
 &lt;h3&gt;8、NoSQL&lt;/h3&gt;
 &lt;p&gt;NoSQL 顾名思义就是 Not-Only SQL，也有人说是 No – SQL, 个人偏向于Not – Only SQL,它并不是用来替代关系库，而是作为关系型数据库的补充而存在。&lt;/p&gt;
 &lt;p&gt;常见 NoSQL 有4个类型：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;键值，适用于内容缓存，适合混合工作负载并发高扩展要求大的数据集，其优点是简单，查询速度快，缺点是缺少结构化数据，常见的有 Redis, Memcache, BerkeleyDB 和 Voldemort 等等；&lt;/li&gt;
  &lt;li&gt;列式，以列簇式存储，将同一列数据存在一起，常见于分布式的文件系统，其中以 Hbase，Cassandra 为代表。Cassandra 多用于写多读少的场景，国内用得比较多的有 360，大概 1500 台机器的集群，国外大规模使用的公司比较多，如 Ebay，Instagram，Apple 和沃尔玛等等；&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;文档，数据存储方案非常适用承载大量不相关且结构差别很大的复杂信息。性能介于 kv 和关系数据库之间，它的灵感来于 lotus notes，常见的有 MongoDB，CouchDB 等等；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;图形，图形数据库擅长处理任何涉及关系的状况。社交网络，推荐系统等。专注于构建关系图谱，需要对整个图做计算才能得出结果，不容易做分布式的集群方案，常见的有 Neo4J，InfoGrid 等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;除了以上4种类型，还有一些特种的数据库，如对象数据库，XML 数据库，这些都有针对性对某些存储类型做了优化的数据库。&lt;/p&gt;
 &lt;p&gt;在实际应用场景中，何时使用关系数据库，何时使用 NoSQL，使用哪种类型的数据库，这是我们在做架构选型时一个非常重要的考量，甚至会影响整个架构的方案。&lt;/p&gt;
 &lt;h3&gt;9、消息中间件&lt;/h3&gt;
 &lt;p&gt;消息中间件在后台系统中是必不可少的一个组件，一般我们会在以下场景中使用消息中间件：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;异步处理：异步处理是使用消息中间件的一个主要原因，在工作中最常见的异步场景有用户注册成功后需要发送注册成功邮件、缓存过期时先返回老的数据，然后异步更新缓存、异步写日志等等；通过异步处理，可以减少主流程的等待响应时间，让非主流程或者非重要业务通过消息中间件做集中的异步处理。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;系统解耦：比如在电商系统中，当用户成功支付完成订单后，需要将支付结果给通知ERP系统、发票系统、WMS、推荐系统、搜索系统、风控系统等进行业务处理；这些业务处理不需要实时处理、不需要强一致，只需要最终一致性即可，因此可以通过消息中间件进行系统解耦。通过这种系统解耦还可以应对未来不明确的系统需求。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;削峰填谷：当系统遇到大流量时，监控图上会看到一个一个的山峰样的流量图，通过使用消息中间件将大流量的请求放入队列，通过消费者程序将队列中的处理请求慢慢消化，达到消峰填谷的效果。最典型的场景是秒杀系统，在电商的秒杀系统中下单服务往往会是系统的瓶颈，因为下单需要对库存等做数据库操作，需要保证强一致性，此时使用消息中间件进行下单排队和流控，让下单服务慢慢把队列中的单处理完，保护下单服务，以达到削峰填谷的作用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;业界消息中间件是一个非常通用的东西，大家在做选型时有使用开源的，也有自己造轮子的，甚至有直接用 MySQL 或 Redis 做队列的，关键看是否满足你的需求，如果是使用开源的项目，以下的表格在选型时可以参考：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack3.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图3]&lt;/p&gt;
 &lt;p&gt;以上图的纬度为：名字 成熟度所属社区/公司 文档 授权方式 开发语言支持的协议 客户端支持的语言 性能 持久化 事务 集群 负载均衡 管理界面 部署方式 评价&lt;/p&gt;
 &lt;h3&gt;10 、代 码管理&lt;/h3&gt;
 &lt;p&gt;代码是互联网创业公司的命脉之一，代码管理很重要，常见的考量点包括两块：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;安全和权限管理，将代码放到内网并且对于关系公司命脉的核心代码做严格的代码控制和机器的物理隔离；&lt;/li&gt;
  &lt;li&gt;代码管理工具，Git 作为代码管理的不二之选，你值得拥有。Gitlab 是当今最火的开源 Git 托管服务端，没有之一，虽然有企业版，但是其社区版基本能满足我们大部分需求，结合 Gerrit 做 Code review，基本就完美了。当然 Gitlab 也有代码对比，但没Gerrit 直观。Gerrit 比 Gitlab 提供了更好的代码检查界面与主线管理体验，更适合在对代码质量有高要求的文化下使用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;11、持续集成&lt;/h3&gt;
 &lt;p&gt;持续集成简，称 CI(continuous integration)， 是一种软件开发实践，即团队开发成员经常集成他们的工作，每天可能会发生多次集成。每次集成都通过自动化的构建（包括编译，发布，自动化测试）来验证，从而尽早地发现集成错误。持续集成为研发流程提供了代码分支管理/比对、编译、检查、发布物输出等基础工作，为测试的覆盖率版本编译、生成等提供统一支持。&lt;/p&gt;
 &lt;p&gt;业界免费的持续集成工具中系统我们有如下一些选择：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;Jenkins：Jjava写的 有强大的插件机制，MIT协议开源 （免费，定制化程度高，它可以在多台机器上进行分布式地构建和负载测试）。Jenkins可以算是无所不能，基本没有 Jenkins 做不了的，无论从小型团队到大型团队 Jenkins 都可以搞定。 不过如果要大规模使用，还是需要有人力来学习和维护。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;TeamCity： TeamCity与Jenkins相比使用更加友好，也是一个高度可定制化的平台。但是用的人多了，TeamCity就要收费了。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Strider： Strider 是一个开源的持续集成和部署平台，使用 Node.js 实现，存储使用的是 MongoDB，BSD 许可证，概念上类似 Travis 和Jenkins。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;GitLabCI：从GitLab8.0开始，GitLab CI 就已经集成在 GitLab，我们只要在项目中添加一个 .gitlab-ci.yml 文件，然后添加一个Runner，即可进行持续集成。并且 Gitlab 与 Docker 有着非常好的相互协作的能力。免费版与付费版本不同可以参见这里：    &lt;a href="https://about.gitlab.com/products/feature-comparison/" target="_blank"&gt;https://about.gitlab.com/products/feature-comparison/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Travis：Travis 和 Github 强关联；闭源代码使用 SaaS 还需考虑安全问题； 不可定制；开源项目免费，其它收费；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Go: Go是ThoughtWorks公司最新的Cruise Control的化身。除了 ThoughtWorks 提供的商业支持，Go是免费的。它适用于Windows，Mac和各种Linux发行版。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;12、日志系统&lt;/h3&gt;
 &lt;p&gt;日志系统一般包括打日志，采集，中转，收集，存储，分析，呈现，搜索还有分发等。一些特殊的如染色，全链条跟踪或者监控都可能需要依赖于日志系统实现。日志系统的建设不仅仅是工具的建设，还有规范和组件的建设，最好一些基本的日志在框架和组件层面加就行了，比如全链接跟踪之类的。&lt;/p&gt;
 &lt;p&gt;对于常规日志系统ELK能满足大部分的需求，ELK 包括如下组件：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;ElasticSearch 是个开源分布式搜索引擎，它的特点有：分布式，零配置，自动发现，索引自动分片，索引副本机制，restful风格接口，多数据源，自动搜索负载等。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Logstash 是一个完全开源的工具，它可以对你的日志进行收集、分析，并将其存储供以后使用。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Kibana 是一个开源和免费的工具，它可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面，可以帮助汇总、分析和搜索重要数据日志。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Filebeat 已经完全替代了 Logstash-Forwarder 成为新一代的日志采集器，同时鉴于它轻量、安全等特点，越来越多人开始使用它。&lt;/p&gt;
 &lt;p&gt;因为免费的 ELK 没有任何安全机制，所以这里使用了 Nginx 作反向代理，避免用户直接访问 Kibana 服务器。加上配置 Nginx 实现简单的用户认证，一定程度上提高安全性。另外，Nginx 本身具有负载均衡的作用，能够提高系统访问性能。ELK 架构如图4所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack4_elk.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图4] ELK 流程图&lt;/p&gt;
 &lt;p&gt;对于有实时计算的需求，可以使用 Flume+Kafka+Storm+MySQL方案，一 般架构如图5所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack5_flume.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图5] 实时分析系统架构图&lt;/p&gt;
 &lt;p&gt;其中：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Flume 是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的日志收集系统，支持在日志系统中定制各类数据发送方，用于收集数据;同时，Flume 提供对数据进行简单处理，并写到各种数据接受方(可定制)的能力。&lt;/li&gt;
  &lt;li&gt;Kafka 是由 Apache 软件基金会开发的一个开源流处理平台，由 Scala 和 Java 编写。其本质上是一个“按照分布式事务日志架构的大规模发布/订阅消息队列”，它以可水平扩展和高吞吐率而被广泛使用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Kafka 追求的是高吞吐量、高负载，Flume 追求的是数据的多样性，二者结合起来简直完美。&lt;/p&gt;
 &lt;h3&gt;13、监控系统&lt;/h3&gt;
 &lt;p&gt;监控系统只包含与后台相关的，这里主要是两块，一个是操作系统层的监控，比如机器负载，IO，网络流量，CPU，内存等操作系统指标的监控。另一个是服务质量和业务质量的监控，比如服务的可用性，成功率，失败率，容量，QPS 等等。常见业务的监控系统先有操作系统层面的监控（这部分较成熟），然后扩展出其它监控，如 zabbix，小米的 open-falcon，也有一出来就是两者都支持的，如 prometheu s。如果对业务监控要求比较高一些，在创业选型中建议可以优先考虑 prometheus。这里有一个有趣的分布，如图6所示&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack6_monitort.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图6 监控系统分布]&lt;/p&gt;
 &lt;p&gt;亚洲区域使用 zabbix 较多，而美洲和欧洲，以及澳大利亚使用 prometheus 居多，换句话说，英文国家地区（发达国家？）使用prometheus 较多。&lt;/p&gt;
 &lt;p&gt;Prometheus 是由 SoundCloud 开发的开源监控报警系统和时序列数据库( TSDB )。Prometheus 使用 Go 语言开发，是 Google BorgMon 监控系统的开源版本。相对于其它监控系统使用的 push 数据的方式，prometheus 使用的是 pull 的方式，其架构如图7所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack7_prometheus.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图7] prometheus架构图&lt;/p&gt;
 &lt;p&gt;如上图所示，prometheus 包含的主要组件如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;Prometheus Server 主要负责数据采集和存储，提供 PromQL 查询语言的支持。Server 通过配置文件、文本文件、Zookeeper、Consul、DNS SRV Lookup等方式指定抓取目标。根据这些目标会，Server 定时去抓取 metric s数据，每个抓取目标需要暴露一个 http 服务的接口给它定时抓取。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;客户端SDK：官方提供的客户端类库有 go、java、scala、python、ruby，其他还有很多第三方开发的类库，支持 nodejs、php、erlang 等。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Push Gateway 支持临时性 Job 主动推送指标的中间网关。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Exporter Exporter 是Prometheus的一类数据采集组件的总称。它负责从目标处搜集数据，并将其转化为 Prometheus 支持的格式。与传统的数据采集组件不同的是，它并不向中央服务器发送数据，而是等待中央服务器主动前来抓取。Prometheus提供多种类型的 Exporter 用于采集各种不同服务的运行状态。目前支持的有数据库、硬件、消息中间件、存储系统、HTTP服务器、JMX等。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;alertmanager：是一个单独的服务，可以支持 Prometheus 的查询语句，提供十分灵活的报警方式。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Prometheus HTTP API的查询方式，自定义所需要的输出。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;Grafana 是一套开源的分析监视平台，支持 Graphite, InfluxDB, OpenTSDB, Prometheus, Elasticsearch, CloudWatch 等数据源，其 UI 非常漂亮且高度定制化。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;创业公司选择 Prometheus + Grafana 的方案，再加上统一的服务框架(如 gRPC )，可以满足大部分中小团队的监控需求。&lt;/p&gt;
 &lt;h3&gt;14、配置系统&lt;/h3&gt;
 &lt;p&gt;随着程序功能的日益复杂，程序的配置日益增多：各种功能的开关、降级开关，灰度开关，参数的配置、服务器的地址、数据库配置等等，除此之外，对后台程序配置的要求也越来越高：配置修改后实时生效，灰度发布，分环境、分用户，分集群管理配置，完善的权限、审核机制等等，在这样的大环境下，传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求，业界有如下两种方案：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;基于 zk 和 etcd，支持界面和 api ，用数据库来保存版本历史，预案，走审核流程，最后下发到 zk 或 etcd 这种有推送能力的存储里（服务注册本身也是用 zk 或 etcd，选型就一块了）。客户端都直接和 zk 或 etcd 打交道。至于灰度发布，各家不同，有一种实现是同时发布一个需要灰度的 IP 列表，客户端监听到配置节点变化时，对比一下自己是否属于该列表。PHP 这种无状态的语言和其他 zk/etcd 不支持的语言，只好自己在客户端的机器上起一个 Agent 来监听变化，再写到配置文件或共享内存，如 360 的 Qconf。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;基于运维自动化的配置文件的推送，审核流程，配置数据管理和方案一类似，下发时生成配置文件，基于运维自动化工具如Puppet，Ansible 推送到每个客户端，而应用则定时重新读取这个外部的配置文件，灰度发布在下发配置时指定IP列表。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;创业公司前期不需要这种复杂，直接上 zk，弄一个界面管理 zk 的内容，记录一下所有人的操作日志，程序直连 zk，或者或者用Qconf 等基于 zk 优化后的方案。&lt;/p&gt;
 &lt;h3&gt;15、发布系统/部署系统&lt;/h3&gt;
 &lt;p&gt;从软件生产的层面看，代码到最终服务的典型流程如图8所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack8_deploy.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图8 流程图]&lt;/p&gt;
 &lt;p&gt;从上图中可以看出，从开发人员写下代码到服务最终用户是一个漫长过程，整体可以分成三个阶段：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;从代码（Code）到成品库（Artifact）这个阶段主要对开发人员的代码做持续构建并把构建产生的制品集中管理，是为部署系统准备输入内容的阶段。&lt;/li&gt;
  &lt;li&gt;从制品到可运行服务 这个阶段主要完成制品部署到指定环境，是部署系统的最基本工作内容。&lt;/li&gt;
  &lt;li&gt;从开发环境到最终生产环境 这个阶段主要完成一次变更在不同环境的迁移，是部署系统上线最终服务的核心能力。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;发布系统集成了制品管理，发布流程，权限控制，线上环境版本变更，灰度发布，线上服务回滚等几方面的内容，是开发人员工作结晶最终呈现的重要通道。开源的项目中没有完全满足的项目，如果只是 Web 类项目，Walle、Piplin 都是可用的，但是功能不太满足，创业初期可以集成 Jenkins + Gitlab + Walle (可以考虑两天时间完善一下)，以上方案基本包括 制品管理，发布流程，权限控制，线上环境版本变更，灰度发布（需要自己实现），线上服务回滚等功能。&lt;/p&gt;
 &lt;h3&gt;16、跳板机&lt;/h3&gt;
 &lt;p&gt;跳板机面对的是需求是要有一种能满足角色管理与授权审批、信息资源访问控制、操作记录和审计、系统变更和维护控制要求，并生成一些统计报表配合管理规范来不断提升IT内控的合规性，能对运维人员操作行为的进行控制和审计，对误操作、违规操作导致的操作事故，快速定位原因和责任人。其功能模块一般包括：帐户管理、认证管理、授权管理、审计管理等等&lt;/p&gt;
 &lt;p&gt;开源项目中，Jumpserver 能够实现跳板机常见需求，如授权、用户管理、服务器基本信息记录等，同时又可批量执行脚本等功能；其中录像回放、命令搜索、实时监控等特点，又能帮助运维人员回溯操作历史，方便查找操作痕迹，便于管理其他人员对服务器的操作控制。&lt;/p&gt;
 &lt;h3&gt;17、机器管理&lt;/h3&gt;
 &lt;p&gt;机器管理的工具选择的考量可以包含以下三个方面：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;是否简单，是否需要每台机器部署agent（客户端）&lt;/li&gt;
  &lt;li&gt;语言的选择（puppet/chef vsansible/saltstack）开源技术，不看官网不足以熟练，不懂源码不足以精通；Puppet、Chef基于Ruby开发，ansible、saltstack基于python开发的&lt;/li&gt;
  &lt;li&gt;速度的选择(ansiblevssaltstack) ansible基于SSH协议传输数据，Saltstack使用消息队列zeroMQ传输数据；大规模并发的能力对于几十台-200台规模的兄弟来讲，ansible的性能也可接受，如果一次操作上千台，用salt好一些。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;如图9所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_statck9_mach.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图9 机器管理软件对比]&lt;/p&gt;
 &lt;p&gt;一般创业公司选择 Ansible 能解决大部问题，其简单，不需要安装额外的客户端，可以从命令行来运行，不需要使用配置文件。至于比较复杂的任务，Ansible 配置通过名为 Playbook 的配置文件中的 YAML 语法来加以处理。Playbook 还可以使用模板来扩展其功能。&lt;/p&gt;
 &lt;h2&gt;二、创业公司的选择&lt;/h2&gt;
 &lt;h3&gt;1、选择合适的语言&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;选择团队熟悉的/能掌控的，创业公司人少事多，无太多冗余让研发团队熟悉新的语言，能快速上手，能快速出活，出了问题能快速解决的问题的语言才是好的选择。&lt;/li&gt;
  &lt;li&gt;选择更现代一些的，这里的现代是指语言本身已经完成一些之前需要特殊处理的特性，比如内存管理，线程等等。&lt;/li&gt;
  &lt;li&gt;选择开源轮子多的或者社区活跃度高的，这个原则是为了保证在开发过程中减少投入，有稳定可靠的轮子可以使用，遇到问题可以在网上快速搜索到答案。&lt;/li&gt;
  &lt;li&gt;选择好招人的 一门合适的语言会让创业团队减少招聘的成本，快速招到合适的人。&lt;/li&gt;
  &lt;li&gt;选择能让人有兴趣的 与上面一点相关，让人感兴趣，在后面留人时有用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;2、选择合适的组件和云服务商&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;选择靠谱的云服务商；&lt;/li&gt;
  &lt;li&gt;选择云服务商的组件；&lt;/li&gt;
  &lt;li&gt;选择成熟的开源组件，而不是最新出的组件；&lt;/li&gt;
  &lt;li&gt;选择采用在一线互联网公司落地并且开源的，且在社区内形成良好口碑的产品；&lt;/li&gt;
  &lt;li&gt;开源社区活跃度；&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;选择靠谱的云服务商，其实这是一个伪命题，因为哪个服务商都不靠谱，他们所承诺的那些可用性问题基本上都会在你的身上发生，这里我们还是需要自己做一些工作，比如多服务商备份，如用CDN，你一定不要只选一家，至少选两家，一个是灾备，保持后台切换的能力，另一个是多点覆盖，不同的服务商在CDN节点上的资源是不一样的。&lt;/p&gt;
 &lt;p&gt;选择了云服务商以后，就会有很多的产品你可以选择了，比较存储，队列这些都会有现成的产品，这个时候就纠结了，是用呢？还是自己在云主机上搭呢？在这里我的建议是前期先用云服务商的，大了后再自己搞，这样会少掉很多运维的事情，但是这里要多了解一下云服务商的组件特性以及一些坑，比如他们内网会经常断开，他们升级也会闪断，所以在业务侧要做好容错和规避。&lt;/p&gt;
 &lt;p&gt;关于开源组件，尽可能选择成熟的，成熟的组件经历了时间的考验，基本不会出大的问题，并且有成套的配套工具，出了问题在网上也可以很快的找到答案，你所遇到的坑基本上都有人踩过了。&lt;/p&gt;
 &lt;h3&gt;3、制定流程和规范&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;制定开发的规范，代码及代码分支管理规范，关键性代码仅少数人有权限；&lt;/li&gt;
  &lt;li&gt;制定发布流程规范，从发布系统落地；&lt;/li&gt;
  &lt;li&gt;制定运维规范；&lt;/li&gt;
  &lt;li&gt;制定数据库操作规范，收拢数据库操作权限；&lt;/li&gt;
  &lt;li&gt;制定告警处理流程，做到告警有人看有人处理；&lt;/li&gt;
  &lt;li&gt;制定汇报机制，晨会/周报；&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;4、自研和选型合适的辅助系统&lt;/h3&gt;
 &lt;p&gt;所有的流程和规范都需要用系统来固化，否则就是空中楼阁，如何选择这些系统呢？参照上个章节咱们那些开源的，对比一下选择的语言，组件之类的，选择一个最合适的即可。&lt;/p&gt;
 &lt;p&gt;比如项目管理的，看下自己是什么类型的公司，开发的节奏是怎样的，瀑布，敏捷的 按项目划分，还是按客户划分等等，平时是按项目组织还是按任务组织等等&lt;/p&gt;
 &lt;p&gt;比如日志系统，之前是打的文本，那么上一个elk，规范化一些日志组件，基本上很长一段时间内不用考虑日志系统的问题，最多拆分一下或者扩容一下。等到组织大了，自己搞一个日志系统。&lt;/p&gt;
 &lt;p&gt;比如代码管理，项目管理系统这些都放内网，安全，在互联网公司来说，属于命脉了，命脉的东西还是放在别人拿不到或很难拿到的地方会比较靠谱一些。&lt;/p&gt;
 &lt;h3&gt;5、选择过程中需要思考的问题&lt;/h3&gt;
 &lt;p&gt;技术栈的选择有点像做出了某种承诺，在一定的时间内这种承诺没法改变，于是我们需要在选择的时候有一些思考。&lt;/p&gt;
 &lt;p&gt;看前面内容，有一个词出现了三次，合适，选择是合适的，不是最好，也不是最新，是最合适，适合是针对当下，这种选择是最合适的吗？比如用 Go 这条线的东西，技术比较新，业界组件储备够吗？组织内的人员储备够吗？学习成本多少？写出来的东西能满足业务性能要求吗？能满足时间要求吗？&lt;/p&gt;
 &lt;p&gt;向未来看一眼，在一年到三年内，我们需要做出改变吗？技术栈要做根本性的改变吗？如果组织发展很快，在 200 人，500 人时，现有的技术栈是否需要大动？&lt;/p&gt;
 &lt;p&gt;创业过程中需要考虑成本，这里的成本不仅仅是花费多少钱，付出多少工资，有时更重要的是时间成本，很多业务在创业时大家拼的就是时间，就是一个时间窗，过了就没你什么事儿了。&lt;/p&gt;
 &lt;h2&gt;三、基于云的创业公司后台技术架构&lt;/h2&gt;
 &lt;p&gt;结合上面内容的考量，在对一个个系统和组件的做选型之后，以云服务为基础，一个创业公司的后台技术架构如图10所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://phppan.gitbooks.io/startup-tech/content/assets/svr_stack10_arch.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;[图10 后台技术架构]&lt;/p&gt;
 &lt;h2&gt;参考资料&lt;/h2&gt;
 &lt;p&gt;  &lt;a href="http://database.51cto.com/art/201109/291781.htm" target="_blank"&gt;http://database.51cto.com/art/201109/291781.htm&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://zh.wikipedia.org/wiki/Kafka" target="_blank"&gt;https://zh.wikipedia.org/wiki/Kafka&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://prometheus.io/docs/introduction/overview/" target="_blank"&gt;https://prometheus.io/docs/introduction/overview/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://deadline.top/2016/11/23/%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E9%82%A3%E7%82%B9%E4%BA%8B/" target="_blank"&gt;http://deadline.top/2016/11/23/配置中心那点事/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.fit2cloud.com/2016/01/26/deployment-system.html" target="_blank"&gt;http://blog.fit2cloud.com/2016/01/26/deployment-system.html&lt;/a&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;除了眼前的苟且,还有架构与远方。&lt;/p&gt;
 &lt;p&gt;介绍创业路上的技术选型和架构、大型网站架构、高性能高可用可扩展架构实现，技术管理等相关话题，紧跟业界主流步伐。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.phppan.com/wp-content/uploads/2016/09/qrcode_for_gh_5d3f534e15fe_344-1.jpg"&gt;   &lt;img alt="qrcode_for_gh_5d3f534e15fe_344 (1)" height="344" src="http://www.phppan.com/wp-content/uploads/2016/09/qrcode_for_gh_5d3f534e15fe_344-1.jpg" width="344"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>PHP</category>
      <guid isPermaLink="true">https://itindex.net/detail/58285-%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B-%E5%88%9B%E4%B8%9A%E5%85%AC%E5%8F%B8-%E5%90%8E%E5%8F%B0</guid>
      <pubDate>Sun, 22 Apr 2018 15:55:04 CST</pubDate>
    </item>
    <item>
      <title>如何解决PHP里大量数据循环时内存耗尽的问题</title>
      <link>https://itindex.net/detail/55110-php-%E9%87%8F%E6%95%B0-%E5%BE%AA%E7%8E%AF</link>
      <description>&lt;p&gt;最近在开发一个PHP程序时遇到了下面的错误：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;PHP Fatal error: Allowed memory size of 268 435 456 bytes exhausted&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;错误信息显示允许的最大内存已经耗尽。遇到这样的错误起初让我很诧异，但转眼一想，也不奇怪，因为我正在开发的这个程序是要用一个  &lt;code&gt;foreach&lt;/code&gt;循环语句在一个有4万条记录的表里全表搜索具有特定特征的数据，也就是说，一次要把4万条数据取出，然后逐条检查每天数据。可想而知，4万条数据全部加载到内存中，内存不爆才怪。&lt;/p&gt;
 &lt;p&gt;毕竟编程这么多年，我隐约记得PHP里提供有非一次全部加载数据的API，是像处理流媒体那样，随用随取随丢、数据并不会积累在内存的查询方法。经过简单的搜索，果然在官方网站上找到的正确的用法。&lt;/p&gt;
 &lt;p&gt;这个问题在PHP的官方网站上叫  &lt;a href="http://php.net/manual/zh/mysqlinfo.concepts.buffering.php"&gt;缓冲查询和非缓冲查询(Buffered and Unbuffered queries)&lt;/a&gt;。PHP的查询缺省模式是缓冲模式。也就是说，查询数据结果会一次全部提取到内存里供PHP程序处理。这样给了PHP程序额外的功能，比如说，计算行数，将指针指向某一行等。更重要的是程序可以对数据集反复进行二次查询和过滤等操作。但这种缓冲查询模式的缺陷就是消耗内存，也就是用空间换速度。&lt;/p&gt;
 &lt;p&gt;相对的，另外一种PHP查询模式是非缓冲查询，数据库服务器会一条一条的返回数据，而不是一次全部返回，这样的结果就是PHP程序消耗较少的内存，但却增加了数据库服务器的压力，因为数据库会一直等待PHP来取数据，一直到数据全部取完。&lt;/p&gt;
 &lt;p&gt;很显然，缓冲查询模式适用于小数据量查询，而非缓冲查询适应于大数据量查询。&lt;/p&gt;
 &lt;p&gt;对于PHP的缓冲模式查询大家都知道，下面列举的例子是如何执行非缓冲查询API。&lt;/p&gt;
 &lt;h2&gt;非缓冲查询方法一: mysqli&lt;/h2&gt;
 &lt;pre&gt;&amp;lt;?php
$mysqli  = new mysqli(&amp;quot;localhost&amp;quot;, &amp;quot;my_user&amp;quot;, &amp;quot;my_password&amp;quot;, &amp;quot;world&amp;quot;);
$uresult = $mysqli-&amp;gt;query(&amp;quot;SELECT Name FROM City&amp;quot;, MYSQLI_USE_RESULT);

if ($uresult) {
   while ($row = $uresult-&amp;gt;fetch_assoc()) {
       echo $row[&amp;apos;Name&amp;apos;] . PHP_EOL;
   }
}
$uresult-&amp;gt;close();
?&amp;gt;&lt;/pre&gt;
 &lt;h2&gt;非缓冲查询方法二: pdo_mysql&lt;/h2&gt;
 &lt;pre&gt;&amp;lt;?php
$pdo = new PDO(&amp;quot;mysql:host=localhost;dbname=world&amp;quot;, &amp;apos;my_user&amp;apos;, &amp;apos;my_pass&amp;apos;);
$pdo-&amp;gt;setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

$uresult = $pdo-&amp;gt;query(&amp;quot;SELECT Name FROM City&amp;quot;);
if ($uresult) {
   while ($row = $uresult-&amp;gt;fetch(PDO::FETCH_ASSOC)) {
       echo $row[&amp;apos;Name&amp;apos;] . PHP_EOL;
   }
}
?&amp;gt;&lt;/pre&gt;
 &lt;h2&gt;非缓冲查询方法三: mysql&lt;/h2&gt;
 &lt;pre&gt;&amp;lt;?php
$conn = mysql_connect(&amp;quot;localhost&amp;quot;, &amp;quot;my_user&amp;quot;, &amp;quot;my_pass&amp;quot;);
$db   = mysql_select_db(&amp;quot;world&amp;quot;);

$uresult = mysql_unbuffered_query(&amp;quot;SELECT Name FROM City&amp;quot;);
if ($uresult) {
   while ($row = mysql_fetch_assoc($uresult)) {
       echo $row[&amp;apos;Name&amp;apos;] . PHP_EOL;
   }
}
?&amp;gt;&lt;/pre&gt;
 &lt;div&gt;
  &lt;div&gt;
   &lt;h3&gt;相关文章&lt;/h3&gt;
   &lt;ul&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/94475/"&gt;PHP 底层的运行机制与原理&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/17211/"&gt;提高PHP代码质量的36个技巧&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/80383/"&gt;重构遗留代码（8）：一个整洁架构的依赖反转&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/17661/"&gt;全面解析PHP的糟糕设计 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/54201/"&gt;PHP开发者应了解的24个库&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/25010/"&gt;PHP超时处理全面总结&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/87795/"&gt;PHP设计模式漫谈之迭代器模式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/5172/"&gt;10个实用的PHP正则表达式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/95364/"&gt;15个实用的PHP正则表达式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/67875/"&gt;PHP中该怎样防止SQL注入？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;  &lt;a href="http://blog.jobbole.com/97620/"&gt;如何解决PHP里大量数据循环时内存耗尽的问题&lt;/a&gt;，首发于  &lt;a href="http://blog.jobbole.com"&gt;博客 - 伯乐在线&lt;/a&gt;。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>PHP 开发 php</category>
      <guid isPermaLink="true">https://itindex.net/detail/55110-php-%E9%87%8F%E6%95%B0-%E5%BE%AA%E7%8E%AF</guid>
      <pubDate>Sat, 23 Jan 2016 23:27:46 CST</pubDate>
    </item>
    <item>
      <title>如何使用工具进行线上 PHP 性能追踪及分析？</title>
      <link>https://itindex.net/detail/55359-%E5%B7%A5%E5%85%B7-%E7%BA%BF%E4%B8%8A-php</link>
      <description>&lt;p&gt;  &lt;strong&gt;工作了一两年的 PHPer 大概都多多少少知道一些性能分析的工具，比如 Xdebug、xhprof、New Relic 、OneAPM。使用基于 Xdebug 进行 PHP 的性能分析，对于本地开发环境来说是够用了，但如果是线上环境的话，   &lt;a href="http://news.oneapm.com/tag/xdebug/"&gt;xdebug&lt;/a&gt; 消耗较大，配置也不够灵活。相比 Xdebug ，xhprof 性能消耗较小，但是 xhprof 注入代码后我们还需要实现保存 xhprof 数据以及展示数据的 UI，听起来似乎又是一大堆工作。&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;很多人都知道，New Relic 和 OneAPM 是两款类似的性能分析工具，通过简单的安装之后，就有现成的图表和分析数据可用。前一段时间尝试过线上使用 New Relic ，估计是因为墙的原因，造成了   &lt;code&gt;php-fpm&lt;/code&gt; 进程阻塞，具体表现为   &lt;code&gt;netstat&lt;/code&gt; 中   &lt;code&gt;php-fpm&lt;/code&gt; 开启的端口始终不回收，墙内环境使用墙外服务器很难保证服务的稳定性，所以今天主要介绍一下国内这款 OneAPM   &lt;a href="http://www.oneapm.com/ai/php.html"&gt;PHP性能分析产品&lt;/a&gt;。&lt;/p&gt;

 &lt;h2&gt;PHP Agent 的安装与简易用法&lt;/h2&gt;

 &lt;p&gt;注册账户后，   &lt;a href="http://www.oneapm.com/"&gt;OneAPM&lt;/a&gt; 会提供一个    &lt;code&gt;License Key&lt;/code&gt;，下载 PHP Agent 之后，执行安装脚本：&lt;/p&gt;

 &lt;h4&gt;1. 解压 Agent 安装包&lt;/h4&gt;

 &lt;p&gt;  &lt;code&gt;tar -xzf OneAPM_php_Agent_latest.tar.gz&lt;/code&gt;&lt;/p&gt;

 &lt;h4&gt;2.定位至「安装包所在路径」&lt;/h4&gt;

 &lt;p&gt;  &lt;code&gt;cd oneapm-php5-linux-install-script&lt;/code&gt;&lt;/p&gt;

 &lt;h4&gt;3. 执行安装脚本&lt;/h4&gt;

 &lt;p&gt;  &lt;code&gt;sudo ./oneapm-install install --license=BQ4NSVlMX399eAhNWUdfVE790d1&lt;/code&gt;&lt;/p&gt;

 &lt;p&gt;如果提示未找到 PHP 路径或安装失败，执行下面这条一键安装命令：&lt;/p&gt;

 &lt;p&gt;  &lt;code&gt;sudo ./oneapm-install install --php-path=/usr/local/php5/bin --php-ini-file=/usr/local/php5/etc/php.ini --license=BQ4NSVlMX399eAhNWUdfVE790d1&lt;/code&gt;&lt;/p&gt;

 &lt;p&gt;根据服务器 PHP 环境修改上面命令中 PHP 路径、php.ini 路径和授权码，修改后执行这一键安装命令。&lt;/p&gt;

 &lt;p&gt;等待安装脚本执行。若出现以下信息，则安装成功。&lt;/p&gt;

 &lt;p&gt;  &lt;code&gt;OneAPM is now installed on your system. Congratulations! Restart your web server or servers.
Any question join qq group:321095806 or contact http://support.oneapm.com&lt;/code&gt;&lt;/p&gt;

 &lt;p&gt;安装完成之后，重启 Apache 或 php-fpm。然后，稍等片刻，等待 OneAPM 接收 Agent 发送的数据。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="php1" src="http://news.oneapm.com/content/images/2016/03/php_1-1.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h2&gt;PHP 性能追踪及分析&lt;/h2&gt;

 &lt;h3&gt;总览&lt;/h3&gt;

 &lt;p&gt;Dashboard 中查看到具体某个时间段整个系统的稳定程度，我们在图上看到了一个异常波峰，时间在早上6点左右，通过列表筛选器移除 WEB External 后看图。其他业务都很正常，执行到最后 PHP 层，平均时间也只用了 10ms 左右。回到上图点击波峰的指示器可以看到具体明细。&lt;/p&gt;

 &lt;p&gt;应用吞吐量：是指应用程序每分钟被调用的次数（cpm，即 Calls Per Minute），吞吐量可以反映应用系统对于用户请求的响应能力。&lt;/p&gt;

 &lt;p&gt;响应时间图主要由 4 部分组成：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;Web 事务：应用中 Web 事务的响 应时间;&lt;/li&gt;
  &lt;li&gt;Database：应用中 SQL 语句的响应时间;&lt;/li&gt;
  &lt;li&gt;WEB External ：HTTP 请求第三方服务调用;&lt;/li&gt;
  &lt;li&gt;后台任务：代表应用中 后台任务的执行时间;&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;  &lt;img alt="php2" src="http://news.oneapm.com/content/images/2016/03/php_2-1.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h3&gt;外部服务 WEB External&lt;/h3&gt;

 &lt;p&gt;WEB External 外部服务，是指应用在运行时所调用的其他外部应用提供的服务，通常由第三方通过 API 提供，使调用者可以使用第三方提供的相应服务。&lt;/p&gt;

 &lt;p&gt;刚才我们在总览页面发现 WEB External 耗时很多，当打开详情时可以明显看到，原来是微信的接口在6点钟抽了。同样该页面还可以监控到第三方服务调用的响应情况。比如 217ms 的   &lt;code&gt;api.hitokoto.us&lt;/code&gt; 服务。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="php3" src="http://news.oneapm.com/content/images/2016/03/php_3-1.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h3&gt;SQL 缓慢的监控&lt;/h3&gt;

 &lt;p&gt;OneAPM 数据库功能可以选择数据库类型，包括“All、Database、选择 Database，则展示数据库的相关信息，同时会增加展示“增、 删、改、查”4 类 SQL 语句的响应时间和吞吐量图;Database、MongoDB、Redis、Memcache(d)”5 项。SQL 语句栏可以按照“总响应时间从长到短”、“平均响应时间从长到短”、“吞吐量从高到低”来进行排序。&lt;/p&gt;

 &lt;p&gt;举例：通过 Web 事务的响应时间占比查看到一个脚本执行时间相对过长，通过下图可以看到数据库查询占了579ms。通过切换到详情页面，可以看到整个脚本的调用过程，最终发现是程序   &lt;code&gt;mysqli.php:88&lt;/code&gt; 行执行的查询占用了过长的时间。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="php4" src="http://news.oneapm.com/content/images/2016/03/php_4-1.jpg"&gt;&lt;/img&gt;
  &lt;img alt="php5" src="http://news.oneapm.com/content/images/2016/03/php_5-1.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;以上只是通过 OneAPM 持续检查程序稳定性的一个基本方法。&lt;/p&gt;

 &lt;p&gt;程序在日常运行中由于受到的访问量不同，很有可能在某个时间点上出现大面积的延迟，比如并发突然增高或访问某一部分接口的比例突然过高，而平时 Apdex 指标却看起来非常漂亮，那么这个时候通过 OneAPM 就很容易发现程序中影响性能的部分，从而继续改进或优化代码。&lt;/p&gt;

 &lt;h2&gt;性能实战：Wordpress 怎么分析性能瓶颈？&lt;/h2&gt;

 &lt;p&gt;起因是这样子的，朋友有个国外的网站(针对外国人)，最近网站挂掉了，就帮忙各种修复了。
但是我本地测试了下网页访问非常慢，其实在我给搞之前也一直如此。。&lt;/p&gt;

 &lt;p&gt;如下图，第一个请求异常的慢。这个是   &lt;code&gt;contact-us&lt;/code&gt; 页面的访问，不涉及太多 sql 查询，也没啥图片。
我点了几个页面都是第一个请求异常的慢 (&amp;gt;10s).&lt;/p&gt;

 &lt;p&gt;内存，各种都正常，MySQL 慢查询也看了没问题。。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="php6" src="http://news.oneapm.com/content/images/2016/03/php_6-1.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;后来终于通过 OneAPM 发现，主要原因在于： wp 使用的主题有个会检查主题最新更新事件，判断依据就是，  &lt;code&gt;$now-$last&lt;/code&gt; 如果大于 6 个小时，就去主题官网检查下更新，而   &lt;code&gt;$last&lt;/code&gt; 打印出来居然是 2014 的，主题已经 N 久没更新了。。。所以每次请求都会有检查，好像他们主题网站已经跪掉了，所以每次检查需要 10 秒钟。。。&lt;/p&gt;

 &lt;p&gt;去掉之后，第一个请求时间瞬间从十几秒二十秒降到 1 秒了，除了首页，其他页面算是秒开了~~&lt;/p&gt;

 &lt;p&gt;有图为证：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="php7" src="http://news.oneapm.com/content/images/2016/03/php_7-1.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;  &lt;strong&gt;本文已经征得原作者同意，授权在 OneAPM 官方技术博客发布。想阅读更多技术文章，可以点击   &lt;a href="http://news.oneapm.com/?utm_source=Community&amp;utm_medium=Article&amp;utm_term=%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E5%B7%A5%E5%85%B7%E8%BF%9B%E8%A1%8C%E7%BA%BF%E4%B8%8A%20PHP%20%E6%80%A7%E8%83%BD%E8%BF%BD%E8%B8%AA%E5%8F%8A%E5%88%86%E6%9E%90%EF%BC%9F&amp;utm_content=wk321-327&amp;utm_campaign=AiPHPArti&amp;from=jsgvsaae"&gt;这里&lt;/a&gt;！&lt;/strong&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Application Insight PHP New Relic WordPress Xdebug</category>
      <guid isPermaLink="true">https://itindex.net/detail/55359-%E5%B7%A5%E5%85%B7-%E7%BA%BF%E4%B8%8A-php</guid>
      <pubDate>Tue, 22 Mar 2016 11:30:47 CST</pubDate>
    </item>
    <item>
      <title>中文进行繁简转换</title>
      <link>https://itindex.net/detail/55975-%E4%B8%AD%E6%96%87-%E7%B9%81%E7%AE%80%E8%BD%AC%E6%8D%A2</link>
      <description>&lt;p&gt;中文的简繁转换看起来是一个简单的工作，但是细想下还是有一些问题的，因为字符间并不是简单的一一对应，不仅存在一简对多繁，也存在多简对一繁。比如：&lt;/p&gt; &lt;pre&gt;头发发展 -&amp;gt; 頭髮發展
萝卜卜卦 -&amp;gt; 蘿蔔卜卦
秒表表达 -&amp;gt; 秒錶表達
晾干乾坤 -&amp;gt; 晾乾乾坤&lt;/pre&gt; &lt;p&gt;要正确完成这样的转换，就要在单字对应关系之外加上词组对应。可以常用的工具有：Office Word、Google翻译及ConvertZ，除了字形以外，不同地区有些名称对应的叫法也不一样，如中国大陆说的计算机「程序」，台湾就是「程式」。为了满足大陆、台湾、香港、新加坡和马来西亚等不同地区中文用户的需求，在ZhengZhu等维基人的努力下，维基百科自2004年底开始逐步发展出一套比较成熟的简繁转换方案。分别记录简繁字体的对应关系和不同地区的用词习惯。这个办法的实用性已在现有30万中文维基条目的应用中得到验证。其不同地区包括：「大陆简体」、「马新简体」、「港澳繁体」、「台湾正体」等&lt;/p&gt;
 &lt;p&gt;中文维基目前正在使用的内置简繁转换表地址在这里：  &lt;a href="https://doc.wikimedia.org/mediawiki-core/REL1_25/php/ZhConversion_8php_source.html"&gt;https://doc.wikimedia.org/mediawiki-core/REL1_25/php/ZhConversion_8php_source.html&lt;/a&gt;或从  &lt;a href="https://www.mediawiki.org/wiki/Download"&gt;MediaWiki 源码包&lt;/a&gt;中的 includes/ZhConversion.php文件获取。&lt;/p&gt;
 &lt;p&gt;这个php文件包含了$zh2Hant、$zh2Hans、$zh2CN、$zh2HK、$zh2TW、$zh2SG这6个数组，分别对应中文-&amp;gt;繁体、中文-&amp;gt;简体、中文-&amp;gt;大陆用词、中文-&amp;gt;香港用词、中文-&amp;gt;台湾用词和中文-&amp;gt;新加坡用词的转换规则。例如从大陆简体到台灣正體的转换就是先将中文(允许简繁混用)转为繁体，再替换其中的台湾用词。如果不涉及复杂长句的简单应用，实现起来十分容易，将对应关系读入字典，用最大正向匹配算法查找词组替换就好。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;PHP&lt;/strong&gt;  &lt;strong&gt;实现&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;我们将Mediawiki上面的代码拷贝下来，然后保存为：ZhConversion.php，这仅仅是一些数组，我们还需要做进一步的处理，我们新建一个转换文件：convert.php，然后引入ZhConversion.php文件，自定义转换函数，通过strtr() 函数转换字符串中特定的字符。&lt;/p&gt; &lt;pre&gt;global $zh2Hans, $zh2Hant, $zh2TW, $zh2CN, $zh2SG, $zh2HK;
require_once( dirname(__FILE__) . &amp;apos;/ZhConversion.php&amp;apos;);

function zhconversion($str, $variant) {
    global $wpcc_langs;
    return $wpcc_langs[$variant][0]($str);
}

function zhconversion_hant($str) {
    global $zh2Hant;
    return strtr($str, $zh2Hant );
}

function zhconversion_hans($str) {
    global $zh2Hans;
    return strtr($str, $zh2Hans);
}

function zhconversion_cn($str) {
    global $zh2Hans, $zh2CN;
    return strtr(strtr($str, $zh2CN), $zh2Hans);
}

function zhconversion_tw($str) {
    global $zh2Hant, $zh2TW;
    return strtr(strtr($str, $zh2TW), $zh2Hant);
}

function zhconversion_sg($str) {
    global $zh2Hans, $zh2SG;
    return strtr(strtr($str, $zh2SG), $zh2Hans);
}

function zhconversion_hk($str) {
    global $zh2Hant, $zh2HK;
    return strtr(strtr($str, $zh2HK), $zh2Hant);
}

function zhconversion_zh($str) {
    return $str;
}&lt;/pre&gt; &lt;p&gt;最后，在需要使用的页面调用函数即可。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Python&lt;/strong&gt;  &lt;strong&gt;实现&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;zhconv 供基于 MediaWiki 词汇表的最大正向匹配简繁转换，支持地区词转换：zh-cn, zh-tw, zh-hk, zh-sg, zh-hans, zh-hant。Python 2、3通用。与其他Python中文简繁转换程序相比：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/skydark/nstools/blob/master/zhtools/langconv.py"&gt;langconv&lt;/a&gt;太复杂，而且不支持地区词转换&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://code.google.com/p/pyzh/"&gt;Pyzh&lt;/a&gt; 简单，只支持字对字&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/BYVoid/OpenCC"&gt;OpenCC&lt;/a&gt;和   &lt;a href="https://pypi.python.org/pypi/opencc-python"&gt;opencc-python&lt;/a&gt;是一个比较完整准确的解决方案，但不适合纯 Python 环境和对精确度要求不高的需要。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;下载源码：  &lt;a href="https://github.com/gumblex/zhconv"&gt;https://github.com/gumblex/zhconv&lt;/a&gt;，解压后转到相应目录，执行：  &lt;pre&gt;python setup.py install&lt;/pre&gt;&lt;/p&gt;
 &lt;p&gt;使用方法：&lt;/p&gt; &lt;pre&gt;# -*- coding: utf-8 -*-

import zhconv
print zhconv.convert(u&amp;apos;大衛碧咸在寮國見到了布希&amp;apos;,&amp;apos;zh-cn&amp;apos;)&lt;/pre&gt; &lt;p&gt;注意：Python 2下必须是unicode字符串，python3下必须str&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Ruby&lt;/strong&gt;  &lt;strong&gt;实现&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://github.com/siuying/zhconv"&gt;https://github.com/siuying/zhconv&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;目前的程序是基于最大正向匹配实现的，如果需要更高的要求，可以引入中文分词技术。&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:   &lt;ol&gt;
    &lt;li&gt;     &lt;a href="http://www.biaodianfu.com/cherrypy.html" rel="bookmark" title="CherryPy&amp;#23433;&amp;#35013;&amp;#35843;&amp;#35797;&amp;#35760;&amp;#24405;"&gt;CherryPy安装调试记录 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://www.biaodianfu.com/php-idcard-checksum.html" rel="bookmark" title="PHP&amp;#36523;&amp;#20221;&amp;#35777;&amp;#26657;&amp;#39564;"&gt;PHP身份证校验 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://www.biaodianfu.com/goo-gl-php-api.html" rel="bookmark" title="Goo.gl PHP API"&gt;Goo.gl PHP API &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 PHP Python</category>
      <guid isPermaLink="true">https://itindex.net/detail/55975-%E4%B8%AD%E6%96%87-%E7%B9%81%E7%AE%80%E8%BD%AC%E6%8D%A2</guid>
      <pubDate>Mon, 19 Sep 2016 19:34:16 CST</pubDate>
    </item>
    <item>
      <title>PHP判断访客是否移动端浏览器访问</title>
      <link>https://itindex.net/detail/52856-php-%E8%AE%BF%E5%AE%A2-%E7%A7%BB%E5%8A%A8</link>
      <description>&lt;p&gt;今天要给大家分享一段PHP代码，该代码的功能是用来判断访客是否移动端浏览器访问，该功能的实现思路是通过HTTP_X_WAP_PROFILE、HTTP_VIA、HTTP_USER_AGENT等信息来判断访客是否通过移动端浏览器访问PHP网站。以下是PHP代码：&lt;/p&gt;
 &lt;pre&gt;/**
 * 是否移动端访问访问
 *
 * @return bool
 */
function isMobile()
{ 
    // 如果有HTTP_X_WAP_PROFILE则一定是移动设备
    if (isset ($_SERVER[&amp;apos;HTTP_X_WAP_PROFILE&amp;apos;]))
    {
        return true;
    } 
    // 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
    if (isset ($_SERVER[&amp;apos;HTTP_VIA&amp;apos;]))
    { 
        // 找不到为flase,否则为true
        return stristr($_SERVER[&amp;apos;HTTP_VIA&amp;apos;], &amp;quot;wap&amp;quot;) ? true : false;
    } 
    // 脑残法，判断手机发送的客户端标志,兼容性有待提高
    if (isset ($_SERVER[&amp;apos;HTTP_USER_AGENT&amp;apos;]))
    {
        $clientkeywords = array (&amp;apos;nokia&amp;apos;,
            &amp;apos;sony&amp;apos;,
            &amp;apos;ericsson&amp;apos;,
            &amp;apos;mot&amp;apos;,
            &amp;apos;samsung&amp;apos;,
            &amp;apos;htc&amp;apos;,
            &amp;apos;sgh&amp;apos;,
            &amp;apos;lg&amp;apos;,
            &amp;apos;sharp&amp;apos;,
            &amp;apos;sie-&amp;apos;,
            &amp;apos;philips&amp;apos;,
            &amp;apos;panasonic&amp;apos;,
            &amp;apos;alcatel&amp;apos;,
            &amp;apos;lenovo&amp;apos;,
            &amp;apos;iphone&amp;apos;,
            &amp;apos;ipod&amp;apos;,
            &amp;apos;blackberry&amp;apos;,
            &amp;apos;meizu&amp;apos;,
            &amp;apos;android&amp;apos;,
            &amp;apos;netfront&amp;apos;,
            &amp;apos;symbian&amp;apos;,
            &amp;apos;ucweb&amp;apos;,
            &amp;apos;windowsce&amp;apos;,
            &amp;apos;palm&amp;apos;,
            &amp;apos;operamini&amp;apos;,
            &amp;apos;operamobi&amp;apos;,
            &amp;apos;openwave&amp;apos;,
            &amp;apos;nexusone&amp;apos;,
            &amp;apos;cldc&amp;apos;,
            &amp;apos;midp&amp;apos;,
            &amp;apos;wap&amp;apos;,
            &amp;apos;mobile&amp;apos;
            ); 
        // 从HTTP_USER_AGENT中查找手机浏览器的关键字
        if (preg_match(&amp;quot;/(&amp;quot; . implode(&amp;apos;|&amp;apos;, $clientkeywords) . &amp;quot;)/i&amp;quot;, strtolower($_SERVER[&amp;apos;HTTP_USER_AGENT&amp;apos;])))
        {
            return true;
        } 
    } 
    // 协议法，因为有可能不准确，放到最后判断
    if (isset ($_SERVER[&amp;apos;HTTP_ACCEPT&amp;apos;]))
    { 
        // 如果只支持wml并且不支持html那一定是移动设备
        // 如果支持wml和html但是wml在html之前则是移动设备
        if ((strpos($_SERVER[&amp;apos;HTTP_ACCEPT&amp;apos;], &amp;apos;vnd.wap.wml&amp;apos;) !== false) &amp;amp;&amp;amp; (strpos($_SERVER[&amp;apos;HTTP_ACCEPT&amp;apos;], &amp;apos;text/html&amp;apos;) === false || (strpos($_SERVER[&amp;apos;HTTP_ACCEPT&amp;apos;], &amp;apos;vnd.wap.wml&amp;apos;) &amp;lt; strpos($_SERVER[&amp;apos;HTTP_ACCEPT&amp;apos;], &amp;apos;text/html&amp;apos;))))
        {
            return true;
        } 
    } 
    return false;
}&lt;/pre&gt;
 &lt;p&gt;代码比较完整，有兴趣的同学可以多做一些测试，有任何bug可以在评论中留言。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>WEB开发 收集转载 编程开发 PHP 浏览器</category>
      <guid isPermaLink="true">https://itindex.net/detail/52856-php-%E8%AE%BF%E5%AE%A2-%E7%A7%BB%E5%8A%A8</guid>
      <pubDate>Tue, 03 Mar 2015 18:52:08 CST</pubDate>
    </item>
    <item>
      <title>HHVM 是如何提升 PHP 性能的？</title>
      <link>https://itindex.net/detail/52808-hhvm-%E6%8F%90%E5%8D%87-php</link>
      <description>&lt;h2&gt;背景&lt;/h2&gt;
 &lt;p&gt;HHVM 是 Facebook 开发的高性能 PHP 虚拟机，宣称比官方的快9倍，我很好奇，于是抽空简单了解了一下，并整理出这篇文章，希望能回答清楚两方面的问题：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;HHVM 到底靠谱么？是否可以用到产品中？&lt;/li&gt;
  &lt;li&gt;它为什么比官方的 PHP 快很多？到底是如何优化的？&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;你会怎么做？&lt;/h2&gt;
 &lt;p&gt;在讨论 HHVM 实现原理前，我们先设身处地想想：假设你有个 PHP 写的网站遇到了性能问题，经分析后发现很大一部分资源就耗在 PHP 上，这时你会怎么优化 PHP 性能？&lt;/p&gt;
 &lt;p&gt;比如可以有以下几种方式：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;方案1，迁移到性能更好的语言上，如 Java、C++、Go。&lt;/li&gt;
  &lt;li&gt;方案2，通过 RPC 将功能分离出来用其它语言实现，让 PHP 做更少的事情，比如 Twitter 就将大量业务逻辑放到了 Scala 中，前端的 Rails 只负责展现。&lt;/li&gt;
  &lt;li&gt;方案3，写 PHP 扩展，在性能瓶颈地方换 C/C++。&lt;/li&gt;
  &lt;li&gt;方案4，优化 PHP 的性能。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;方案1几乎不可行，十年前 Joel 就  &lt;a href="http://www.joelonsoftware.com/articles/fog0000000069.html"&gt;拿 Netscape 的例子警告过&lt;/a&gt;，你将放弃多年的经验积累。尤其是像 Facebook 这种业务逻辑复杂的产品，PHP 代码实在太多了，据称有2千万行（引用自 [PHP on the Metal with HHVM]），修改起来的成本恐怕比写个虚拟机还大，而且对于一个上千人的团队，从头开始学习也是不可接受的。&lt;/p&gt;
 &lt;p&gt;方案2是最保险的方案，可以逐步迁移，事实上 Facebook 也在朝这方面努力了，而且还开发了 Thrift 这样的 RPC 解决方案。Facebook 内部主要使用的另一个语言是 C++，从早期的 Thrift 代码就能看出来，因为其它语言的实现都很简陋，没法在生产环境下使用。&lt;/p&gt;
 &lt;p&gt;目前在 Facebook 中据称 PHP:C++ 已经从 9:1   &lt;a href="http://zh.reddit.com/r/IAmA/comments/1nl9at/i_am_a_member_of_facebooks_hhvm_team_a_c_and_d/ccjlvoq"&gt;增加到 7:3 了&lt;/a&gt;，加上有 Andrei Alexandrescu 的存在，C++ 在 Facebook 中越来越流行。但这只能解决部分问题，毕竟 C++ 开发成本比 PHP 高得多，不适合用在经常修改的地方，而且太多 RPC 的调用也会严重影响性能。&lt;/p&gt;
 &lt;p&gt;方案3看起来美好，实际执行起来却很难，一般来说性能瓶颈并不会很显著，大多是不断累加的结果，加上 PHP 扩展开发成本高，这种方案一般只用在公共且变化不大的基础库上，所以这种方案解决不了多少问题。&lt;/p&gt;
 &lt;p&gt;可以看到，前面3个方案并不能很好地解决问题，所以 Facebook 其实没有选择的余地，只能去考虑 PHP 本身的优化了。&lt;/p&gt;
 &lt;h2&gt;更快的 PHP&lt;/h2&gt;
 &lt;p&gt;既然要优化 PHP，那如何去优化呢？在我看来可以有以下几种方法：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;方案1，PHP 语言层面的优化。&lt;/li&gt;
  &lt;li&gt;方案2，优化 PHP 的官方实现（也就是 Zend）。&lt;/li&gt;
  &lt;li&gt;方案3，将 PHP 编译成其它语言的 bytecode（字节码），借助其它语言的虚拟机（如 JVM）来运行。&lt;/li&gt;
  &lt;li&gt;方案4，将 PHP 转成 C/C++，然后编译成本地代码。&lt;/li&gt;
  &lt;li&gt;方案5，开发更快的 PHP 虚拟机。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;PHP 语言层面的优化是最简单可行的，Facebook 当然想到了，而且还开发了   &lt;a href="http://pecl.php.net/package/xhprof"&gt;XHProf&lt;/a&gt; 这样的性能分析工具，对于定位性能瓶颈是很有帮助的。&lt;/p&gt;
 &lt;p&gt;不过 XHProf 还是没能很好解决 Facebook 的问题，所以我们继续看，接下来是方案2。简单来看，Zend 的执行过程可以分为两部分：将 PHP 编译为 opcode、执行 opcode，所以优化 Zend 可以从这两方面来考虑。&lt;/p&gt;
 &lt;p&gt;优化 opcode 是一种常见的做法，可以避免重复解析 PHP，而且还能做一些静态的编译优化，比如   &lt;a href="https://github.com/zendtech/ZendOptimizerPlus"&gt;Zend Optimizer Plus&lt;/a&gt;，但由于 PHP 语言的动态性，这种优化方法是有局限性的，乐观估计也只能提升20%的性能。另一种考虑是优化 opcode 架构本身，如基于寄存器的方式，但这种做法修改起来工作量太大，性能提升也不会特别明显（可能30%？），所以投入产出比不高。&lt;/p&gt;
 &lt;p&gt;另一个方法是优化 opcode 的执行，首先简单提一下 Zend 是如何执行的，Zend 的 interpreter（也叫解释器）在读到 opcode 后，会根据不同的 opcode 调用不同函数（其实有些是 switch，不过为了描述方便我简化了），然后在这个函数中执行各种语言相关的操作（感兴趣的话可看看  &lt;a href="http://www.php-internals.com/book/?p=chapt02/02-03-02-opcode"&gt;深入理解 PHP 内核&lt;/a&gt;这本书），所以 Zend 中并没有什么复杂封装和间接调用，作为一个解释器来说已经做得很好了。&lt;/p&gt;
 &lt;p&gt;想要提升 Zend 的执行性能，就需要对程序的底层执行有所解，比如函数调用其实是有开销的，所以能通过   &lt;a href="http://dl.acm.org/citation.cfm?id=277743"&gt;Inline threading&lt;/a&gt; 来优化掉。它的原理就像 C 语言中的 inline 关键字那样，但它是在运行时将相关的函数展开，然后依次执行（只是打个比方，实际实现不太一样），同时还避免了 CPU 流水线预测失败导致的浪费。&lt;/p&gt;
 &lt;p&gt;另外还可以像   &lt;a href="http://trac.webkit.org/browser/trunk/Source/JavaScriptCore/llint/LowLevelInterpreter.asm"&gt;JavaScriptCore&lt;/a&gt; 和   &lt;a href="http://repo.or.cz/w/luajit-2.0.git/blob_plain/HEAD:/src/vm_x86.dasc"&gt;LuaJIT&lt;/a&gt; 那样使用汇编来实现 interpreter，具体细节建议看看   &lt;a href="http://www.reddit.com/r/programming/comments/badl2/luajit_2_beta_3_is_out_support_both_x32_x64/c0lrus0"&gt;Mike 的解释&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;但这两种做法修改代价太大，甚至比重写一个还难，尤其是要保证向下兼容，后面提到 PHP 的特点时你就知道了。&lt;/p&gt;
 &lt;p&gt;开发一个高性能的虚拟机不是件简单的事情，JVM 花了10多年才达到现在的性能，那是否能直接利用这些高性能的虚拟机来优化 PHP 的性能呢？这就是方案3的思路。&lt;/p&gt;
 &lt;p&gt;其实这种方案早就有人尝试过了，比如   &lt;a href="http://quercus.caucho.com/"&gt;Quercus&lt;/a&gt; 和 IBM 的 P8，Quercus 几乎没见有人使用，而 P8   &lt;a href="https://www.ibm.com/developerworks/community/forums/html/topic?id=77777777-0000-0000-0000-000014910522&amp;ps=25"&gt;也已经死掉了&lt;/a&gt;。Facebook 也曾经调研过这种方式，甚至还出现过不靠谱的  &lt;a href="http://nerds-central.blogspot.ie/2012/08/facebook-moving-to-jvm.html"&gt;传闻&lt;/a&gt; ，但其实 Facebook 在 2011 年就放弃了。&lt;/p&gt;
 &lt;p&gt;因为方案3看起来美好，但实际效果却不理想，按照很多大牛的说法（比如   &lt;a href="http://lambda-the-ultimate.org/node/3851#comment-57805"&gt;Mike&lt;/a&gt;），VM 总是为某个语言优化的，其它语言在上面实现会遇到很多瓶颈，比如动态的方法调用。关于这点在   &lt;a href="https://www.dartlang.org/articles/why-not-bytecode/"&gt;Dart 的文档中有过介绍&lt;/a&gt;，而且据说 Quercus 的性能与 Zend+APC 比差不了太多（[来自The HipHop Compiler for PHP]），所以没太大意义。&lt;/p&gt;
 &lt;p&gt;不过 OpenJDK 这几年也在努力，最近的   &lt;a href="http://openjdk.java.net/projects/graal/"&gt;Grall&lt;/a&gt; 项目看起来还不错，也有语言在上面取得了  &lt;a href="http://mail.openjdk.java.net/pipermail/graal-dev/2013-December/001250.html"&gt;显著的效果&lt;/a&gt;，但我还没空研究 Grall，所以这里无法判断。&lt;/p&gt;
 &lt;p&gt;接下来是方案4，它正是 HPHPc（HHVM 的前身）的做法，原理是将 PHP 代码转成 C++，然后编译为本地文件，可以认为是一种 AOT（ahead of time）的方式，关于其中代码转换的技术细节可以参考   &lt;a href="http://dl.acm.org/citation.cfm?id=2384658"&gt;The HipHop Compiler for PHP&lt;/a&gt; 这篇论文，以下是该论文中的一个截图，可以通过它来大概了解：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="172" src="http://static.codeceo.com/images/2015/02/6325de4f733e15545a5a49752ea184f3.png" width="630"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这种做法的最大优点是实现简单（相对于一个 VM 来说），而且能做很多编译优化（因为是离线的，慢点也没事），比如上面的例子就将“  &lt;code&gt;- 1”&lt;/code&gt;优化掉了。但它很难支持 PHP 中的很多动态的方法，如   &lt;code&gt;eval()&lt;/code&gt;、  &lt;code&gt;create_function()&lt;/code&gt;，因为这就得再内嵌一个 interpreter，成本不小，所以 HPHPc 干脆就直接不支持这些语法。&lt;/p&gt;
 &lt;p&gt;除了 HPHPc，还有两个类似的项目，一个是   &lt;a href="http://www.roadsend.com/"&gt;Roadsend&lt;/a&gt;，另一个是   &lt;a href="http://phpcompiler.org/"&gt;phc&lt;/a&gt; ，phc 的做法是将 PHP 转成了 C 再编译，以下是它将   &lt;code&gt;file_get_contents($f)&lt;/code&gt; 转成 C 代码的例子：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;static php_fcall_info fgc_info;
php_fcall_info_init (&amp;quot;file_get_contents&amp;quot;, &amp;amp;fgc_info);
php_hash_find (LOCAL_ST, &amp;quot;f&amp;quot;, 5863275, &amp;amp;fgc_info.params);
php_call_function (&amp;amp;fgc_info);&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;话说   &lt;a href="http://blog.paulbiggar.com/archive/a-rant-about-php-compilers-in-general-and-hiphop-in-particular/#bottom"&gt;phc 作者曾经在博客上哭诉&lt;/a&gt;，说他两年前就去 Facebook 演示过 phc 了，还和那里的工程师交流过，结果人家一发布就火了，而自己忙活了4年却默默无闻，现在前途渺茫。。。&lt;/p&gt;
 &lt;p&gt;Roadsend 也已经不维护了，对于 PHP 这样的动态语言来说，这种做法有很多的局限性，由于无法动态 include，Facebook 将所有文件都编译到了一起，上线时的文件部署居然达到了 1G，越来越不可接受了。&lt;/p&gt;
 &lt;p&gt;另外有还有一个叫   &lt;a href="https://github.com/chung-leong/qb"&gt;PHP QB&lt;/a&gt; 的项目，由于时间关系我没有看，感觉可能是类似的东东。&lt;/p&gt;
 &lt;p&gt;所以就只剩下一条路了，那就是写一个更快的 PHP 虚拟机，将一条黑路走到底。或许你和我一样，一开始听到 Facebook 要做一个虚拟机是觉得太离谱，但如果仔细分析就会发现其实也只有这样了。&lt;/p&gt;
 &lt;h2&gt;更快的虚拟机&lt;/h2&gt;
 &lt;p&gt;HHVM 为什么更快？在各种新闻报道中都提到了 JIT 这个关键技术，但其实远没有那么简单，JIT 不是什么神奇的魔法棒——用它轻轻一挥就能提升性能，而且 JIT 这个操作本身也是会耗时的，对于简单的程序没准还比 interpreter 慢，最极端的例子是   &lt;a href="http://lua-users.org/lists/lua-l/2010-03/msg00305.html"&gt;LuaJIT 2&lt;/a&gt; 的 Interpreter 就稍微比 V8 的 JIT 快。所以并不存在绝对的事情，更多还是在细节问题的处理上，HHVM 的发展历史就是不断优化的历史，你可以从下图看到它是如何一点点超过 HPHPc 的：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://static.codeceo.com/images/2015/02/47826f5af95a209680a600f14eaa7d74.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;值得一提的是在 Android 4.4 中新的虚拟机 ART 就采用的是 AOT 方案（还记得么？前面提到的 HPHPc 就是这种），结果比之前使用 JIT 的 Dalvik 快了一倍，所以说 JIT 也不一定比 AOT 快。&lt;/p&gt;
 &lt;p&gt;因此这个项目是有很大风险的，如果没有强大的内心和毅力，极有可能半途而废。  &lt;a href="https://code.google.com/p/unladen-swallow/"&gt;Google 就曾经想用 JIT 提升 Python 的性能&lt;/a&gt;，但  &lt;a href="http://qinsb.blogspot.jp/2011/03/unladen-swallow-retrospective.html"&gt;最终失败了&lt;/a&gt;，对于 Google 来说用到 Python 的地方其实并没什么性能问题（好吧，以前 Google 是用 Python 写过 crawl [参考 In The Plex]，但那都是1996年的事情了）。&lt;/p&gt;
 &lt;p&gt;比起 Google，Facebook 显然有更大的动力和决心，PHP 是 Facebook 最重要的语言，我们来看看 Facebook 都投入了哪些大牛到这个项目中（不全）：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Andrei Alexandrescu，『Modern C++ Design』和『C++ Coding Standards』的作者，C++ 领域无可争议的大神&lt;/li&gt;
  &lt;li&gt;Keith Adams，负责过 VMware 核心架构，当年 VMware 就派他一人去和 Intel 进行技术合作，足以证明在    &lt;a href="http://en.wikipedia.org/wiki/Virtual_Machine_Manager"&gt;VMM&lt;/a&gt; 领域他有多了解了&lt;/li&gt;
  &lt;li&gt;Drew Paroski，在微软参与过 .NET 虚拟机开发，改进了其中的 JIT。&lt;/li&gt;
  &lt;li&gt;Jason Evans，开发了 jemalloc，减少了 Firefox 一半的内存消耗。&lt;/li&gt;
  &lt;li&gt;Sara Golemon，『Extending and Embedding PHP』的作者，PHP 内核专家，这本书估计所有 PHP 高手都看过吧，或许你不知道其实她是女的&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;虽然没有像 Lars Bak、Mike Pall 这样在虚拟机领域的顶级专家，但如果这些大牛能齐心协力，写个虚拟机还是问题不大的，那么他们将面临什么样的挑战呢？接下来我们一一讨论。&lt;/p&gt;
 &lt;h3&gt;规范是什么？&lt;/h3&gt;
 &lt;p&gt;自己写 PHP 虚拟机要面临的第一个问题就是 PHP 没有语言规范，很多版本间的语法还会不兼容（甚至是小版本号，比如 5.2.1 和 5.2.3），PHP 语言规范究竟如何定义呢？来看一篇来自   &lt;a href="http://grouper.ieee.org/groups/plv/DocLog/000-099/060-thru-079/22-OWGV-N-0060/n0060.pdf"&gt;IEEE&lt;/a&gt; 的说法：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;The PHP group claim that they have the ﬁnal say in the speciﬁcation of (the language) PHP. This groups speciﬁcation is an implementation, and there is no prose speciﬁcation or agreed validation suite.&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;所以唯一的途径就是老老实实去看 Zend 的实现，好在 HPHPc 中已经痛苦过一次了，所以 HHVM 能直接利用现成，因此这个问题并不算太大。&lt;/p&gt;
 &lt;h3&gt;语言还是扩展？&lt;/h3&gt;
 &lt;p&gt;实现 PHP 语言不仅仅只是实现一个虚拟机那么简单，PHP 语言本身还包括了各种扩展，这些扩展和语言是一体的，Zend 不辞辛劳地实现了各种你可能会用到的功能。如果分析过 PHP 的代码，就会发现它的 C 代码除去空行注释后居然还有80+万行，而你猜其中 Zend 引擎部分有多少？只有不到10万行。&lt;/p&gt;
 &lt;p&gt;对于开发者来说这不是什么坏事，但对于引擎实现者来说就很悲剧了。我们可以拿 Java 来进行对比，写个 Java 的虚拟机只需实现字节码解释及一些基础的 JNI 调用，Java 绝大部分内置库都是用 Java 实现的。所以如果不考虑性能优化，单从工作量看，实现 PHP 虚拟机比 JVM 要难得多，比如就有人用8千行的 TypeScript 实现了一个   &lt;a href="https://github.com/int3/doppio"&gt;JVM Doppio&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;而对于这个问题，HHVM 的解决办法很简单，那就是只实现 Facebook 中用到的，而且同样可以先用 HPHPc 中之前写过的，所以问题也不大。&lt;/p&gt;
 &lt;h3&gt;实现 Interpreter&lt;/h3&gt;
 &lt;p&gt;接下来是 Interpreter 的实现，在解析完 PHP 后会生成 HHVM 自己设计的一种 Bytecode，存储在  &lt;code&gt;~/.hhvm.hhbc&lt;/code&gt;（SQLite 文件） 中以便重用，在执行 Bytecode 时和 Zend 类似，也是将不同的字节码放到不同的函数中去实现（这种方式在虚拟机中有个专门的称呼：  &lt;a href="http://en.wikipedia.org/wiki/Threaded_code#Subroutine_threading"&gt;Subroutine threading&lt;/a&gt;）&lt;/p&gt;
 &lt;p&gt;Interpreter 的主体实现在   &lt;a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/vm/bytecode.cpp"&gt;bytecode.cpp&lt;/a&gt; 中，比如   &lt;code&gt;VMExecutionContext::iopAdd&lt;/code&gt; 这样的方法，最终执行会根据不同类型来区分，比如 add 操作的实现是在   &lt;a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/base/tv-arith.cpp"&gt;tv-arith.cpp&lt;/a&gt; 中，下面摘抄其中的一小段：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;if (c2.m_type == KindOfInt64)  return o(c1.m_data.num, c2.m_data.num);
if (c2.m_type == KindOfDouble) return o(c1.m_data.num, c2.m_data.dbl);&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;正是因为有了 Interpreter，HHVM 在对于 PHP 语法的支持上比 HPHPc 有明显改进，理论上做到完全兼容官方 PHP。但仅这么做在性能并不会比 Zend 好多少，由于无法确定变量类型，所以需要加上类似上面的条件判断语句，但这样的代码不利于现代 CPU 的执行优化。另一个问题是数据都是 boxed 的，每次读取都需要通过类似   &lt;code&gt;m_data.num&lt;/code&gt; 和  &lt;code&gt;m_data.dbl&lt;/code&gt; 的方法来间接获取。&lt;/p&gt;
 &lt;p&gt;对于这样的问题，就得靠 JIT 来优化了。&lt;/p&gt;
 &lt;h3&gt;实现 JIT 及优化&lt;/h3&gt;
 &lt;p&gt;首先值得一提的是 PHP 的 JIT 之前并非没人尝试过：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;2008 年就有人   &lt;a href="http://llvm.org/devmtg/2008-08/Lopes_PHP-JIT-InTwoDays.pdf"&gt;用 LLVM 实验过&lt;/a&gt;，结果还比原来慢了 21 倍。。。&lt;/li&gt;
  &lt;li&gt;2010 年 IBM 日本研究院基于他们的 JVM 虚拟机代码开发了 P9，性能是官方 PHP 的 2.5 到 9.5 倍，可以看他们的论文   &lt;a href="http://dl.acm.org/citation.cfm?id=1736015"&gt;Evaluation of a just-in-time compiler retrofitted for PHP&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;2011 年 Andrei Homescu 基于 RPython 开发过，还写了篇论文    &lt;a href="http://www.ics.uci.edu/~ahomescu/happyjit_paper.pdf"&gt;HappyJIT: a tracing JIT compiler for PHP&lt;/a&gt;，但测试结果有好有坏，并不理想。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;那么究竟什么是 JIT？如何实现一个 JIT？&lt;/p&gt;
 &lt;p&gt;在动态语言中基本上都会有个 eval 方法，可以传给它一段字符串来执行，JIT 做的就是类似的事情，只不过它要拼接不是字符串，而是不同平台下的机器码，然后进行执行，但如何用 C 来实现呢？可以参考 Eli 写的  &lt;a href="http://eli.thegreenplace.net/2013/11/05/how-to-jit-an-introduction/"&gt;这个入门例子&lt;/a&gt;，以下是文中的一段代码：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;unsigned char code[] = {
  0x48, 0x89, 0xf8,                   // mov %rdi, %rax
  0x48, 0x83, 0xc0, 0x04,             // add $4, %rax
  0xc3                                // ret
};
memcpy(m, code, sizeof(code));&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;然而手工编写机器码很容易出错，所以最好的有一个辅助的库，比如的 Mozilla 的   &lt;a href="https://developer.mozilla.org/en-US/docs/Nanojit"&gt;Nanojit&lt;/a&gt; 以及 LuaJIT 的   &lt;a href="http://luajit.org/dynasm.html"&gt;DynASM&lt;/a&gt;，但 HHVM 并没有使用这些，而是自己实现了一个只支持 x64 的（另外还在尝试用   &lt;a href="https://github.com/armvixl/vixl"&gt;VIXL&lt;/a&gt; 来生成 ARM 64 位的），通过 mprotect 的方式来让代码可执行。&lt;/p&gt;
 &lt;p&gt;但为什么 JIT 代码会更快？你可以想想其实用 C++ 编写的代码最终编译出来也是机器码，如果只是将同样的代码手动转成了机器码，那和 GCC 生成出来的有什么区别呢？虽然前面我们提到了一些针对 CPU 实现原理来优化的技巧，但在 JIT 中更重要的优化是根据类型来生成特定的指令，从而大幅减少指令数和条件判断，下面这张来自   &lt;a href="https://hacks.mozilla.org/2009/07/tracemonkey-overview/"&gt;TraceMonkey&lt;/a&gt; 的图对此进行了很直观的对比，后面我们将看到 HHVM 中的具体例子：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://static.codeceo.com/images/2015/02/5ad415949a0e6994b1693b2a796f2964.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;HHVM 首先通过 interpeter 来执行，那它会在什么时候使用 JIT 呢？常见的 JIT 触发条件有 2 种：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;trace：记录循环执行次数，如果超过一定数量就对这段代码进行 JIT。&lt;/li&gt;
  &lt;li&gt;method：记录函数执行次数，如果超过一定数量就对整个函数进行 JIT，甚至直接 inline。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;关于这两种方法哪种更好在 Lambada 上  &lt;a href="http://lambda-the-ultimate.org/node/3851"&gt;有个帖子&lt;/a&gt;引来了各路大神的讨论，尤其是 Mike Pall（LuaJIT 作者） 、Andreas Gal（Mozilla VP） 和 Brendan Eich（Mozilla CTO）都发表了很多自己的观点，推荐大家围观，我这里就不献丑了。&lt;/p&gt;
 &lt;p&gt;它们之间的区别不仅仅是编译范围，还有很多细节问题，比如对局部变量的处理，在这里就不展开了&lt;/p&gt;
 &lt;p&gt;但 HHVM 并没有采用这两种方式，而是自创了一个叫   &lt;a href="https://news.ycombinator.com/item?id=4856099"&gt;tracelet&lt;/a&gt; 的做法，它是根据类型来划分的，看下面这张图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="322" src="http://static.codeceo.com/images/2015/02/59b32b81af50757f7dde5ee16542fe5e.png" width="630"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;可以看到它将一个函数划分为了 3 部分，上面 2 部分是用于处理   &lt;code&gt;$k&lt;/code&gt; 为整数或字符串两种不同情况的，下面的部分是返回值，所以看起来它主要是根据类型的变化情况来划分 JIT 区域的，具体是如何分析和拆解 Tracelet 的细节可以查看  &lt;a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/vm/jit/translator.cpp"&gt;Translator.cpp&lt;/a&gt; 中的   &lt;code&gt;Translator::analyze&lt;/code&gt; 方法，我还没空看，这里就不讨论了。&lt;/p&gt;
 &lt;p&gt;当然，要实现高性能的 JIT 还需进行各种尝试和优化，比如最初 HHVM 新增的 tracelet 会放到前面，也就是将上图的 A 和 C 调换位置，后来尝试了一下放到后面，结果性能提示了 14%，因为测试发现这样更容易提前命中响应的类型&lt;/p&gt;
 &lt;p&gt;JIT 的执行过程是首先将 HHBC 转成 SSA (hhbc-translator.cpp)，然后对 SSA 上做优化（比如 Copy propagation），再生成本地机器码，比如在 X64 下是由   &lt;a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/vm/jit/translator-x64.cpp"&gt;translator-x64.cpp&lt;/a&gt; 实现的。&lt;/p&gt;
 &lt;p&gt;我们用一个简单的例子来看看 HHVM 最终生成的机器码是怎样的，比如下面这个 PHP 函数：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;&amp;lt;?php
function a($b){
  echo $b + 2;
}&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;编译后是这个样子：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;mov rcx,0x7200000
mov rdi,rbp
mov rsi,rbx
mov rdx,0x20
call 0x2651dfb &amp;lt;HPHP::Transl::traceCallback(HPHP::ActRec*, HPHP::TypedValue*, long, void*)&amp;gt;
cmp BYTE PTR [rbp-0x8],0xa
jne 0xae00306
; 前面是检查参数是否有效
mov rcx,QWORD PTR [rbp-0x10]           ; 这里将 %rcx 被赋值为1了
mov edi,0x2                            ; 将 %edi（也就是 %rdi 的低32位）赋值为2
add rdi,rcx                            ; 加上 %rcx
call 0x2131f1b &amp;lt;HPHP::print_int(long)&amp;gt; ; 调用 print_int 函数，这时第一个参数 %rdi 的值已经是3了
; 后面暂不讨论
mov BYTE PTR [rbp+0x28],0x8
lea rbx,[rbp+0x20]
test BYTE PTR [r12],0xff
jne 0xae0032a
push QWORD PTR [rbp+0x8]
mov rbp,QWORD PTR [rbp+0x0]
mov rdi,rbp
mov rsi,rbx
mov rdx,QWORD PTR [rsp]
call 0x236b70e &amp;lt;HPHP::JIT::traceRet(HPHP::ActRec*, HPHP::TypedValue*, void*)&amp;gt;
ret&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;而 HPHP::print_int 函数的实现是这样的：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;void print_int(int64_t i) {
  char buf[256];
  snprintf(buf, 256, &amp;quot;%&amp;quot; PRId64, i);
  echo(buf);
  TRACE(1, &amp;quot;t-x64 output(int): %&amp;quot; PRId64 &amp;quot;\n&amp;quot;, i);
}&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;可以看到 HHVM 编译出来的代码直接使用了   &lt;code&gt;int64_t&lt;/code&gt;，避免了 interpreter 中需要判断参数和间接取数据的问题，从而明显提升了性能，最终甚至做到了和 C 编译出来的代码区别不大。&lt;/p&gt;
 &lt;p&gt;需要注意：HHVM 在 server mode 下，只有超过12个请求就才会触发 JIT，启动过 HHVM 时可以通过加上如下参数来让它首次请求就使用 JIT：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;-v Eval.JitWarmupRequests=0&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;所以在测试性能时需要注意，运行一两次就拿来对比是看不出效果的。&lt;/p&gt;
 &lt;h3&gt;类型推导很麻烦，还是逼迫程序员写清楚吧&lt;/h3&gt;
 &lt;p&gt;JIT 的关键是猜测类型，因此某个变量的类型要是老变就很难优化，于是 HHVM 的工程师开始考虑在 PHP 语法上做手脚，加上类型的支持，推出了一个新语言 – Hack（吐槽一下这名字真不利于 SEO），它的样子如下：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;&amp;lt;?hh
class Point2 {
  public float $x, $y;
  function __construct(float $x, float $y) {
    $this-&amp;gt;x = $x;
    $this-&amp;gt;y = $y;
  }
}
//来自：https://raw.github.com/strangeloop/StrangeLoop2013/master/slides/sessions/Adams-TakingPHPSeriously.pdf&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;注意到   &lt;code&gt;float&lt;/code&gt; 关键字了么？有了静态类型可以让 HHVM 更好地优化性能，但这也意味着和 PHP 语法不兼容，只能使用 HHVM。&lt;/p&gt;
 &lt;p&gt;其实我个人认为这样做最大的优点是让代码更加易懂，减少无意的犯错，就像 Dart 中的可选类型也是这个初衷，同时还方便了 IDE 识别，据说 Facebook 还在开发一个  &lt;a href="https://twitter.com/jpetazzo/status/308294205598474240"&gt;基于 Web 的 IDE&lt;/a&gt;，能协同编辑代码，可以期待一下。&lt;/p&gt;
 &lt;h2&gt;你会使用 HHVM 么？&lt;/h2&gt;
 &lt;p&gt;总的来说，比起之前的 HPHPc，我认为 HHVM 是值得一试的。它是真正的虚拟机，能够更好地支持各种 PHP 的语法，所以改动成本不会更高，而且因为能无缝切换到官方 PHP 版本，所以可以同时启动 FPM 来随时待命，HHVM 还有  &lt;a href="https://github.com/facebook/hhvm/wiki/FastCGI"&gt;FastCGI&lt;/a&gt; 接口方便调用，只要做好应急备案，风险是可控的，从长远来看是很有希望的。&lt;/p&gt;
 &lt;p&gt;性能究竟能提升多少我无法确定，需要拿自己的业务代码来进行真实测试，这样才能真正清楚 HHVM 能带来多少收益，尤其是对整体性能提升到底有多少，只有拿到这个数据才能做决策。&lt;/p&gt;
 &lt;p&gt;最后整理一下可能会遇到的问题，有计划使用的可以参考：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;扩展问题：如果用到了 PHP 扩展，肯定是要重写的，不过 HHVM 扩展写起来比 Zend 要简单的多，具体细节可以看    &lt;a href="https://github.com/facebook/hhvm/wiki/Extension-API"&gt;wiki 上的例子&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;HHVM Server 的稳定性问题：这种多线程的架构运行一段时间可能会出现内存泄露问题，或者某个没写好的 PHP 直接导致整个进程挂掉，所以需要注意这方面的测试和容灾措施。&lt;/li&gt;
  &lt;li&gt;问题修复困难：HHVM 在出现问题时将比 Zend 难修复，尤其是 JIT 的代码，只能期望它比较稳定了。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;P.S. 其实我只了解基本的虚拟机知识，也没写过几行 PHP 代码，很多东西都是写这篇文章时临时去找资料的，由于时间仓促水平有限，必然会有不正确的地方，欢迎大家评论赐教   &lt;img alt=":)" src="http://www.codeceo.com/wp-content/themes/d-simple/img/smilies/icon_smile.gif"&gt;&lt;/img&gt; &lt;/p&gt;
 &lt;p&gt;2014年1月补充：目前 HHVM 在鄙厂的推广势头很不错，推荐大家在 2014年 尝试一下，尤其是现在兼容性测试已经达到98.58%了，修改成本进一步减小。&lt;/p&gt;
 &lt;h2&gt;　　引用&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://zh.reddit.com/r/IAmA/comments/1nl9at/i_am_a_member_of_facebooks_hhvm_team_a_c_and_d/?limit=500"&gt;Andrei Alexandrescu on AMA&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://news.ycombinator.com/threads?id=kmavm"&gt;Keith Adams 在 HN 上的蛛丝马迹&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.wired.com/wiredenterprise/2013/06/facebook-hhvm-saga/all/"&gt;How Three Guys Rebuilt the Foundation of Facebook&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.infoq.com/presentations/PHP-HHVM-Facebook"&gt;PHP on the Metal with HHVM&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.facebook.com/note.php?note_id=10150336948348920"&gt;Making HPHPi Faster&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.hhvm.com/blog/713/hhvm-optimization-tips"&gt;HHVM Optimization Tips&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.oscon.com/oscon2012/public/schedule/detail/25828"&gt;The HipHop Virtual Machine (hhvm) PHP Execution at the Speed of JIT&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://cufp.org/conference/sessions/2013/julien-verlaguet-facebook-analyzing-php-statically"&gt;Julien Verlaguet, Facebook: Analyzing PHP statically&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.facebook.com/notes/facebook-engineering/speeding-up-php-based-development-with-hiphop-vm/10151170460698920"&gt;Speeding up PHP-based development with HHVM&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.hhvm.com/blog/311/adding-an-opcode-to-hhbc"&gt;Adding an opcode to HHBC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>WEB开发 编程开发 HHVM PHP 高性能</category>
      <guid isPermaLink="true">https://itindex.net/detail/52808-hhvm-%E6%8F%90%E5%8D%87-php</guid>
      <pubDate>Fri, 27 Feb 2015 15:09:06 CST</pubDate>
    </item>
    <item>
      <title>让PHP7达到最高性能的几个Tips</title>
      <link>https://itindex.net/detail/54862-php7-%E6%80%A7%E8%83%BD-tips</link>
      <description>&lt;p&gt;  &lt;strong&gt;标签：&lt;/strong&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=PHP7" target="_blank"&gt;PHP7&lt;/a&gt;&lt;/p&gt; &lt;p&gt;   PHP7已经发布了,  作为PHP10年来最大的版本升级, 最大的性能升级, PHP7在多放的测试中都表现出很明显的性能提升, 然而, 为了让它能发挥出最大的性能, 我还是有几件事想提醒下.&lt;/p&gt; &lt;div&gt;  &lt;a href="http://laruence-wordpress.stor.sinaapp.com/uploads/MYVQSTIBYX6KHZQ9XH72U.jpg"&gt;   &lt;img src="http://laruence-wordpress.stor.sinaapp.com/uploads/MYVQSTIBYX6KHZQ9XH72U-1024x573.jpg" title="PHP7 VS PHP5.6" width="500"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;PHP7 VS PHP5.6&lt;/p&gt;&lt;/div&gt; &lt;h3&gt;1. Opcache&lt;/h3&gt; &lt;p&gt;记得启用Zend Opcache, 因为PHP7即使不启用Opcache速度也比PHP-5.6启用了Opcache快, 所以之前测试时期就发生了有人一直没有启用Opcache的事情. 启用Opcache非常简单, 在php.ini配置文件中加入:&lt;/p&gt; &lt;pre&gt;zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1&amp;quot;

&lt;/pre&gt; &lt;h3&gt;2. 使用新的编译器 &lt;/h3&gt; &lt;p&gt;   使用新一点的编译器, 推荐GCC 4.8以上, 因为只有GCC 4.8以上PHP才会开启Global Register for opline and execute_data支持, 这个会带来5%左右的性能提升(Wordpres的QPS角度衡量)&lt;/p&gt; &lt;p&gt;   其实GCC 4.8以前的版本也支持, 但是我们发现它支持的有Bug, 所以必须是4.8以上的版本才会开启这个特性.&lt;/p&gt; &lt;h3&gt; 3.  HugePage &lt;/h3&gt; &lt;p&gt;   我之前的文章也介绍过:   &lt;a href="http://www.laruence.com/2015/10/02/3069.html"&gt;让你的PHP7更快之Hugepage&lt;/a&gt; , 首先在系统中开启HugePages, 然后开启Opcache的huge_code_pages.&lt;/p&gt; &lt;p&gt;   以我的CentOS 6.5为例, 通过:&lt;/p&gt; &lt;pre&gt;$sudo sysctl vm.nr_hugepages=512

&lt;/pre&gt; &lt;p&gt; 分配512个预留的大页内存:&lt;/p&gt; &lt;pre&gt;$ cat /proc/meminfo  | grep Huge
AnonHugePages:    106496 kB
HugePages_Total:     512
HugePages_Free:      504
HugePages_Rsvd:       27
HugePages_Surp:        0
Hugepagesize:       2048 kB

&lt;/pre&gt; &lt;p&gt;   然后在php.ini中加入:&lt;/p&gt; &lt;pre&gt; opcache.huge_code_pages=1

&lt;/pre&gt; &lt;p&gt;   这样一来, PHP会把自身的text段, 以及内存分配中的huge都采用大内存页来保存, 减少TLB miss, 从而提高性能.&lt;/p&gt; &lt;h3&gt;4. Opcache file cache&lt;/h3&gt; &lt;p&gt;开启Opcache File Cache(实验性),  通过开启这个, 我们可以让Opcache把opcode缓存缓存到外部文件中, 对于一些脚本, 会有很明显的性能提升.  &lt;br /&gt;   在php.ini中加入:&lt;/p&gt; &lt;pre&gt;opcache.file_cache=/tmp

&lt;/pre&gt; &lt;p&gt;   这样PHP就会在/tmp目录下Cache一些Opcode的二进制导出文件, 可以跨PHP生命周期存在.&lt;/p&gt; &lt;h3&gt;5. PGO &lt;/h3&gt; &lt;p&gt;我之前的文章:   &lt;a href="http://www.laruence.com/2015/06/19/3063.html"&gt;让你的PHP7更快(GCC PGO)&lt;/a&gt; 也介绍过, 如果你的PHP是专门为一个项目服务, 比如只是为你的Wordpress, 或者drupal, 或者其他什么, 那么你就可以尝试通过PGO, 来提升PHP, 专门为你的这个项目提高性能.&lt;/p&gt; &lt;p&gt;具体的,  以wordpress 4.1为优化场景.. 首先在编译PHP的时候首先:&lt;/p&gt; &lt;pre&gt;$ make prof-gen

&lt;/pre&gt; &lt;p&gt;然后用你的项目训练PHP, 比如对于Wordpress:&lt;/p&gt; &lt;pre&gt;$ sapi/cgi/php-cgi -T 100 /home/huixinchen/local/www/htdocs/wordpress/index.php &amp;gt;/dev/null

&lt;/pre&gt; &lt;p&gt; 也就是让php-cgi跑100遍wordpress的首页, 从而生成一些在这个过程中的profile信息.&lt;/p&gt; &lt;p&gt; 最后:&lt;/p&gt; &lt;pre&gt;$ make prof-clean
$ make prof-use &amp;amp;&amp;amp; make install

&lt;/pre&gt; &lt;p&gt; 这个时候你编译得到的PHP7就是为你的项目量身打造的最高性能的编译版本.&lt;/p&gt; &lt;p&gt; 暂时就这么多吧, 以后想起来再加, 欢迎大家尝试, thanks&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;Comments&lt;/h2&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;a href="http://www.laruence.com/2015/12/04/3086.html"&gt;2015/12/04&lt;/a&gt;,     &lt;a href="http://www.epooll.com" rel="external nofollow"&gt;seatle&lt;/a&gt; writes: 有点吊的，支持鸟哥&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;a href="http://www.laruence.com/2015/12/04/3086.html"&gt;2015/12/04&lt;/a&gt;, xLight writes: pgo 之后的wordpress性能也应该show出来啊&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;a href="http://www.laruence.com/2015/12/04/3086.html"&gt;2015/12/04&lt;/a&gt;,     &lt;a href="http://www.laruence.com" rel="external nofollow"&gt;Laruence&lt;/a&gt; writes: @xLight 懒死你算了&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;a href="http://www.laruence.com/2015/12/04/3086.html"&gt;2015/12/04&lt;/a&gt;,     &lt;a href="http://www.stutostu.com" rel="external nofollow"&gt;barbery&lt;/a&gt; writes: 还可以机器学习，生成个性的编译版本，PGO太屌了。。。&lt;/p&gt;&lt;/li&gt;  &lt;hr&gt;&lt;/hr&gt;&lt;/ul&gt; &lt;p&gt;Copyright © 2010   &lt;a href="http://www.laruence.com" target="_blank"&gt;风雪之隅&lt;/a&gt; 版权所有, 转载务必注明. 该Feed只供个人使用, 禁止未注明的转载或商业应用. 非法应用的, 一切法律后果自负. 如有问题, 可发E-mail至my at laruence.com.(Digital Fingerprint: 73540ba0a1738d7d07d4b6038d5615e2)&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
				 &lt;p&gt;  &lt;strong&gt;您可能还对下面的文章感兴趣：&lt;/strong&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=7183" target="_blank"&gt;PHP7 VS HHVM (WordPress)&lt;/a&gt; [2014-12-29 00:02:16]&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt; &lt;img alt="" height="1" src="http://feeds.feedburner.com/~r/blogreadIT/~4/uqsma9OLBb0" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>PHP</category>
      <guid isPermaLink="true">https://itindex.net/detail/54862-php7-%E6%80%A7%E8%83%BD-tips</guid>
      <pubDate>Mon, 14 Dec 2015 06:13:09 CST</pubDate>
    </item>
    <item>
      <title>PHP代码优化24条真经</title>
      <link>https://itindex.net/detail/52487-php-%E4%BB%A3%E7%A0%81-%E4%BC%98%E5%8C%96</link>
      <description>&lt;ol&gt;
  &lt;li&gt;echo比print快。&lt;/li&gt;
  &lt;li&gt;使用echo的多重参数代替字符串连接。&lt;/li&gt;
  &lt;li&gt;在执行for循环之前确定最大循环数，不要每循环一次都计算最大值，最好运用foreach代替。&lt;/li&gt;
  &lt;li&gt;对global变量，应该用完就unset()掉。&lt;/li&gt;
  &lt;li&gt;用单引号代替双引号来包含字符串，这样做会更快一些。因为PHP会在双引号包围的字符串中搜寻变量，单引号则不会。&lt;/li&gt;
  &lt;li&gt;函数代替正则表达式完成相同功能。&lt;/li&gt;
  &lt;li&gt;当执行变量$i的递增或递减时，$i++会比++$i慢一些。这种差异是PHP特有的，并不适用于其他语言，++$i更快是因为它只需要3条指令(opcodes)，$i++则需要4条指令。后置递增实际上会产生一个临时变量，这个临时变量随后被递增。而前置递增直接在原值上递增。&lt;/li&gt;
  &lt;li&gt;使用选择分支语句（switch case）好于使用多个if，else if语句。&lt;/li&gt;
  &lt;li&gt;利用var_dump进行PHP代码调试。如果你在寻找php调试技术，我必须说var_dump应该是你要找的目标，在显示php信息方面这个命令可以满足你的所有需要，而调试代码的多数情况与得到PHP中的数值有关。&lt;/li&gt;
  &lt;li&gt;在包含文件时使用完整路径，解析操作系统路径所需的时间会更少。&lt;/li&gt;
  &lt;li&gt;动辄创建全局数值是一种糟糕的做法，不过有时候实际情况的确又需要这么做。对于数据库表或数据库连接信息使用全局数值是一个不错的想法，但不要在你的PHP代码中频繁使用全局数值。另外，更好的一种做法是把你的全局变量存放在一个config.php文件中。&lt;/li&gt;
  &lt;li&gt;如果你想知道脚本开始执行的时刻，使用$_SERVER[‘REQUEST_TIME’]要好于time()。&lt;/li&gt;
  &lt;li&gt;打开apache的mod_deflate模块。&lt;/li&gt;
  &lt;li&gt;用@屏蔽错误消息的做法非常低效。&lt;/li&gt;
  &lt;li&gt;尽量采用大量的PHP内置函数。&lt;/li&gt;
  &lt;li&gt;递增一个未预定义的局部变量要比递增一个预定义的局部变量慢9至10倍。&lt;/li&gt;
  &lt;li&gt;派生类中的方法运行起来要快于在基类中定义的同样的方法。&lt;/li&gt;
  &lt;li&gt;仅定义一个局部变量而没在函数中调用它，同样会减慢速度（其程度相当于递增一个局部变量）&lt;/li&gt;
  &lt;li&gt;Apache解析一个PHP脚本的时间要比解析一个静态HTML页面慢2至10倍。尽量多用静态HTML页面，少用脚本。&lt;/li&gt;
  &lt;li&gt;正如之前提到的，任何php网站中最重要的部分有99%的可能是数据库。因此，你需要非常熟悉如何正确的使用sql，学会关联表和更多高级的数据库技术。&lt;/li&gt;
  &lt;li&gt;调用带有一个参数的空函数，其花费的时间相当于执行7至8次的局部变量递增操作。&lt;/li&gt;
  &lt;li&gt;当操作字符串并需要检验其长度是否满足某种要求时，你想当然地会使用strlen()函数。此函数执行起来相当快，因为它不做任何计算，只返回zval结构（C的内置数据结构，用于存储PHP变量）中存储的已知字符串长度。&lt;/li&gt;
  &lt;li&gt;并不是所有情况都必须使用面向对象开发，面向对象往往开销很大，每个方法和对象调用都会消耗很多内存。&lt;/li&gt;
  &lt;li&gt;除非脚本可以缓存，否则每次调用时都会重新编译一次。引入一套PHP缓存机制通常可以提升25%至100%的性能，以免除编译开销。&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>编程技术 php 优化</category>
      <guid isPermaLink="true">https://itindex.net/detail/52487-php-%E4%BB%A3%E7%A0%81-%E4%BC%98%E5%8C%96</guid>
      <pubDate>Tue, 13 Jan 2015 09:50:58 CST</pubDate>
    </item>
    <item>
      <title>PHP项目性能优化</title>
      <link>https://itindex.net/detail/52561-php-%E9%A1%B9%E7%9B%AE-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96</link>
      <description>&lt;h2&gt;PHP项目性能优化的三个层次&lt;/h2&gt;

 &lt;ul&gt;
  &lt;li&gt;PHP语言层级&lt;/li&gt;
  &lt;li&gt;PHP周边（服务器，数据库，webserver)&lt;/li&gt;
  &lt;li&gt;PHP底层&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;语言层级&lt;/h2&gt;

 &lt;ul&gt;
  &lt;li&gt;尽量使用PHP原生函数和常量，类   &lt;br /&gt;
如果要实现的功能有原生PHP函数，则不要自己用PHP实现&lt;/li&gt;
  &lt;li&gt;尽量使用性能更高的内置函数   &lt;br /&gt;
比如isset和array_key_exists都可以使用，则使用isset&lt;/li&gt;
  &lt;li&gt;尽量不要使用错误抑制符@&lt;/li&gt;
  &lt;li&gt;不要使用PHP处理cpu密集的业务，交给适合的语言去处理&lt;/li&gt;
  &lt;li&gt;减少io操作,比如在一次请求中要生成多条日志，则尽量缓存一次写入&lt;/li&gt;
  &lt;li&gt;代码尽量向上兼容，即尽可能的使用PHP最新版本，比如generaotr实现的range等，数组短语法&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;周边(展开则相当庞大了)&lt;/h2&gt;

 &lt;ul&gt;
  &lt;li&gt;Linux内核优化，硬件提升(ssd硬盘，加内存)&lt;/li&gt;
  &lt;li&gt;减少跨网络请求&lt;/li&gt;
  &lt;li&gt;MySQL 索引使用，NoSQL+MySQL的配合使用，MySQL主从等&lt;/li&gt;
  &lt;li&gt;Nginx 的配置优化&lt;/li&gt;
  &lt;li&gt;PHP-FPM配置优化&lt;/li&gt;
  &lt;li&gt;使用PHP的最新版本，目前PHP 5.6，今年即将会来的PHPNG(PHP7）&lt;/li&gt;
  &lt;li&gt;使用xhprof分析项目源码，找出瓶颈进行优化&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这部分内容需要继续学习研究&lt;/p&gt;

 &lt;h2&gt;PHP底层&lt;/h2&gt;

 &lt;ul&gt;
  &lt;li&gt;使用opcode扩展 缓存PHP的opcode代码，减少PHP的编译过程&lt;/li&gt;
  &lt;li&gt;CPU密集或者复杂功能使用PHP的pecl扩展（swoole等优秀扩展的使用）&lt;/li&gt;
  &lt;li&gt;HHVM，百度再用，不过还是让我们坐等PHPNG的问世，有PHPNG，HHVM就不是必须的选择了&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;当然良好的编码风格（目前PSR很流行   &lt;a href="http://www.php-fig.org/" rel="nofollow"&gt;http://www.php-fig.org/&lt;/a&gt;），优雅的代码实现也非常重要  &lt;br /&gt;
写代码，不要仅仅为了实现目的和功能，还要有诗人的情怀，尽量打磨代码，精炼，如贾岛之推敲，追求自己代码的卓越&lt;/p&gt;

 &lt;p&gt;随着项目的发展，就会有架构方面的变更，来应对更大的并发和请求&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>php 性能优化</category>
      <guid isPermaLink="true">https://itindex.net/detail/52561-php-%E9%A1%B9%E7%9B%AE-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96</guid>
      <pubDate>Wed, 21 Jan 2015 20:20:49 CST</pubDate>
    </item>
    <item>
      <title>如何自动化完成SQL审核</title>
      <link>https://itindex.net/detail/52556-%E8%87%AA%E5%8A%A8%E5%8C%96-sql</link>
      <description>&lt;p&gt;  &lt;strong&gt;sql审核主要完成两方面的目的.&lt;/strong&gt;  &lt;br /&gt;
1、避免性能太差的sql进入生产系统,导致整体性能降低  &lt;br /&gt;
2、检查开发设计的索引是否合理,是否需要添加索引&lt;/p&gt;
 &lt;p&gt;第一点是SQL审核最核心的地方,避免乱七八糟的sql影响线上性能,甚至导致线上系统崩溃.  &lt;br /&gt;
第二点是属于建模的范畴,要解决建模的最好办法是DBA参与项目前期审核,由DBA建模,如果DBA人力资源不足,那么就定期由DBA对开发人员进行培训.然后发现建模太烂的就扣KPI.&lt;/p&gt;
 &lt;p&gt;现在很多公司都是人肉来完成SQL审核的,人肉审核对dba的要求较高,需要懂一些代码,另外是费时费力,毕竟一般公司几十个开发,对应一个DBA，而且DBA还要干很多其他的事情.  &lt;br /&gt;
如何将DBA从人肉SQL审核中解放出来呢?&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;思路其实很简单:&lt;/strong&gt;  &lt;br /&gt;
1、获取程序要执行的SQL  &lt;br /&gt;
2、对要执行的SQL做分析,可以加各种分析条件来判断这个SQL是否可以自动审核通过,未通过审核的需要人工处理.  &lt;br /&gt;
3、配合后期的慢查询日志分析系统完成长期的监控.&lt;/p&gt;
 &lt;p&gt;开源的解决方案主要有淘宝丹臣sqlautoreview系统.可以在github上搜索到.  &lt;br /&gt;
但是这个系统主要是基于java sqlmapfile.xml解决自动创建索引的问题,对源数据有要求,并且是通过解析SQL结构来假设SQL的执行计划,不是特别准确,并且不能够很好的区分新sql还是老sql.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;所以产生了一个新的方案:&lt;/strong&gt;  &lt;br /&gt;
1、为所有的执行过的sql产生一个figerprint  &lt;br /&gt;
2、基于慢查询提供的数据,加上explain 提供的数据来判断这个sql的性能是否可接受,或者可优化.  &lt;br /&gt;
3、自动审核通过性能可接受的部分,给DBA展示性能较差的sql,然后进行优化.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;方案的优点在于:&lt;/strong&gt;  &lt;br /&gt;
基于用户真正执行的SQL,并且可以观察SQL执行频率.  &lt;br /&gt;
基于MySQL真正的执行计划和执行结果,分析更准确.  &lt;br /&gt;
每个SQL都有一个fingerprint,只需要增量处理新加的SQL,效率和性能提高.  &lt;br /&gt;
基于Box anemometer二次开发,让慢查询和sql审核同平台,增加工具集成性,提高用户体验(DBA和开发人员)。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;方案实施:&lt;/strong&gt;  &lt;br /&gt;
既然咱是DBA，肯定会有更DBA的思维方式.基于现有软件二次开发完成,减少开发成本,整合管理平台.  &lt;br /&gt;
基于Box anemometer.  &lt;a href="http://isadba.com/?p=655" target="_blank"&gt;安装Box anemometer&lt;/a&gt;  &lt;br /&gt;
Box anemometer是一款B/S架构,图形化的MySQL慢查询分析工具.功能强大易用,设计简单直接.anemometer是基于pt-query-digest的二次封装得来.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;核心处理流程:&lt;/strong&gt;  &lt;br /&gt;
mysql node–&amp;gt;计划任务通过pt-query-digest收集慢查询信息–&amp;gt;结果写入到数据库中–&amp;gt;anemometer按条件去展示慢查询的结果,并且提供了图形化和趋势分布图等功能.  &lt;br /&gt;
所以anemometer已经帮我们完成了数据收集,包括每个sql的fingerprint信息,以及相关的信息,我们在测试环境,基于anemometer,将long_query_time设置为0,就可以收集到所以的SQL及相关信息.&lt;/p&gt;
 &lt;p&gt;在我们收集到所有SQL以后,我们就要来分析这个SQL是否可以自动审核通过.这里开始我们就要定制了.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;定制内容如下:&lt;/strong&gt;  &lt;br /&gt;
一、  &lt;br /&gt;
设置一个单独的datasources,可以命名为audit_sql.  &lt;br /&gt;
这个datasources里面只放置开发环境或者测试环境的慢查询(你要做sql审核基于哪个环境),将此环境的long_query_time设置为0,接收所有的sql查询.&lt;/p&gt;
 &lt;p&gt;二、修改anemometer  &lt;br /&gt;
ALTER TABLE `global_query_review` ADD  audit_status VARCHAR(255) NOT NULL DEFAULT ‘refuse’ comment ‘sql审计的状态 refuse未通过 pass审核通过’;&lt;/p&gt;
 &lt;p&gt;修改PHP代码.  &lt;br /&gt;
在report模块的where条件中增加一个Aduit Status的选项框,可以过滤audit_status的状态  &lt;br /&gt;
在show_query模块中增加一个Audit Status的选项框,可以人工设置audit_status的状态&lt;/p&gt;
 &lt;p&gt;三、增加两个额外的脚本,准实时的分析audit_status为refuse的sql,如果sql的满足自动审核通过的条件,那么就设置audit_status为pass,表示自动审核通过.  &lt;br /&gt;
自动审核未通过的sql,由DBA人工在anemometer上检索和处理.  &lt;br /&gt;
这里就涉及到一个自动审核通过的算法:  &lt;br /&gt;
算法分两种.  &lt;br /&gt;
第一种是准实时,也就是可以几分钟或者一个小时运行一次,主要是根据每个sql的执行效率判断是否pass.  &lt;br /&gt;
对应的脚本名字叫做:audit_sql.py&lt;/p&gt;
 &lt;p&gt;第二种是一天一次,弱化执行效率判断,增加一天执行的频率判断.  &lt;br /&gt;
对应的脚本名字叫做:audit_sql_day.py&lt;/p&gt;
 &lt;p&gt;各家根据自己的实际情况调整或者优化这两个脚本.  &lt;br /&gt;
至此,你已经可以让99%以上的代码自动审核通过了,审核不通过的代码你可以让开发自己来tracking也可以主动推给开发.  &lt;br /&gt;
对于才搭建的环境,可能会有一些乱七八糟的sql,不过使用一段时间稳定以后,异常的sql指纹都有了,那么每天产生的sql指纹就比较少了,而这部分SQL指纹也就是程序员编写新的代码产生的.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://github.com/ISADBA/anemometerAudit_SQL" target="_blank"&gt;二次修改过的Box anemometer代码和对应的python脚本都在我的GITHUB上.&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;https://github.com/ISADBA/anemometerAudit_SQL&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://isadba.com/wp-content/uploads/2015/01/1.jpg"&gt;   &lt;img alt="" height="615" src="http://isadba.com/wp-content/uploads/2015/01/1.jpg" title="1" width="1350"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://isadba.com/wp-content/uploads/2015/01/2.jpg"&gt;   &lt;img alt="" height="615" src="http://isadba.com/wp-content/uploads/2015/01/2.jpg" title="2" width="1350"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://isadba.com/wp-content/uploads/2015/01/2.jpg"&gt;   &lt;img alt="" height="615" src="http://isadba.com/wp-content/uploads/2015/01/3.jpg" title="3" width="1350"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://isadba.com/wp-content/uploads/2015/01/2.jpg"&gt;   &lt;img alt="" height="615" src="http://isadba.com/wp-content/uploads/2015/01/4.jpg" title="4" width="1350"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://isadba.com/wp-content/uploads/2015/01/2.jpg"&gt;   &lt;img alt="" height="615" src="http://isadba.com/wp-content/uploads/2015/01/5.jpg" title="5" width="1350"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://isadba.com/wp-content/uploads/2015/01/2.jpg"&gt;   &lt;img alt="" height="615" src="http://isadba.com/wp-content/uploads/2015/01/6.jpg" title="6" width="1350"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>CODE DB MYSQL PHP PYTHON</category>
      <guid isPermaLink="true">https://itindex.net/detail/52556-%E8%87%AA%E5%8A%A8%E5%8C%96-sql</guid>
      <pubDate>Wed, 21 Jan 2015 17:22:28 CST</pubDate>
    </item>
    <item>
      <title>谈谈服务端缓存的几种用法</title>
      <link>https://itindex.net/detail/53260-%E6%9C%8D%E5%8A%A1-%E7%BC%93%E5%AD%98</link>
      <description>&lt;p&gt;缓存是一个常谈常新的话题，作为一名服务端的技术，如果你入行一年都还没用过memcached类产品，那只能说你的公司实在太小了，或者你干的活实在太边缘了。&lt;/p&gt;

 &lt;p&gt;说起缓存，可能大家最直接想到的就是：“在数据库前面挡一层”。这是缓存最原始的意义，同时也引申出了缓存最普遍的用法。&lt;/p&gt;

 &lt;h2&gt;原始模式&lt;/h2&gt;

 &lt;h4&gt;代码示例1（原始模式）：&lt;/h4&gt;

 &lt;pre&gt;  &lt;code&gt;//从缓存中获取数据[较快的方式]
data = getfromcache(id)
if data == null then
    //从数据库中获取数据[较慢的方式]
    data = getfromdb(id)
    //缓存1天
    setintocache(id, data, 86400)
    return data
end

return data
&lt;/code&gt;&lt;/pre&gt;

 &lt;h2&gt;缓存加锁&lt;/h2&gt;

 &lt;p&gt;上面这种情况下，当同时有N个请求到达，都同时执行getfromcache，那么都会发现data在缓存中不存在，然后都会去调用getfromdb，以及setintocache。这是不必要的，那么我们有没有办法减少这些并发呢。&lt;/p&gt;

 &lt;p&gt;最直接的想法是加锁，当进入if条件中时，加一把锁，让其他进程不再执行下面的逻辑，而是等第一个进程的setintocache执行完成后，再重新执行一次getfromcache。&lt;/p&gt;

 &lt;p&gt;那这个锁如何加呢？这里推荐一种省时省力的方法。通过直接在缓存value中设置过期时间来实现。&lt;/p&gt;

 &lt;p&gt;比如缓存的value值为data，那我们修改一下，把它放到一个json中，改成&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;{data:data,atime:1429618765}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;我们增加了一个atime来记录缓存生成的时间。而逻辑就变成下面这样。&lt;/p&gt;

 &lt;h4&gt;代码示例2（缓存加锁）：&lt;/h4&gt;

 &lt;pre&gt;  &lt;code&gt;//从缓存中获取数据[较快的方式]
data = getfromcache(id)
data = json.decode(data)
//如果通过检查缓存生成时间，发现缓存已经过于陈旧，那么就将缓存过期时间设置为现在开始的5分钟以后（这样其他并发进程就会以为此缓存还未过期，还会继续使用5分钟，只让当前这一个请求去重建缓存）
if data != null &amp;amp;&amp;amp; data.atime+86400 &amp;lt; now then
    data.atime = now+300-86400
    data = json.encode(data)
    //对真正的cache来说，缓存10天或者更长时间
    setintocache(id, data, 864000)
    //这里把data设置成null是为了走到下面的if中去重建缓存
    data = null
end

if data == null then
    //从数据库中获取数据[较慢的方式]
    data = getfromdb(id)
    data = {data:data, atime:now}
    data = json.encode(data)
    //对真正的cache来说，缓存10天或者更长时间
    setintocache(id, data, 864000)
    return data
end

return data
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;你可以会发现，这里也会存在并发啊，和上面例1一样，第一个getfromcache到setintocache之间，如果同时有N个请求到来，不还是都会执行这段操作，都会去查库吗。&lt;/p&gt;

 &lt;p&gt;没错，是这样的。但是我们仔细看一下，例1中，从getfromcache到setintocache之间，经历了一次漫长的getfromdb操作，这个时间耗费可能是上百毫秒的。而我们例2中，并没有进行什么操作，这个时间耗费只在毫秒甚至微秒级的。&lt;/p&gt;

 &lt;p&gt;所以例1中getfromcache到setintocache之间的并发是远大于例2中的。例2中通过减小时间窗口，有效的模拟了锁机制。同时还没有增强额外的存储复杂度。所以是推荐的一种方式。&lt;/p&gt;

 &lt;p&gt;可以说，我们所有的缓存都应该是例2的方式，他在各方面都优于例1（多保存的一个atime字段耗费的内存基本可以忽略不计。且atime很多时候对于调试程序还很有用）。&lt;/p&gt;

 &lt;h2&gt;主动更新缓存&lt;/h2&gt;

 &lt;p&gt;那这样就够了吗？对于被动过期型的缓存，这样基本就可以了。但是现实中还有一种缓存，是主动更新的。试想有一种缓存，我们要求必须和数据库中的数据一致，不能出现陈旧数据。那么上面的缓存方式就不合适了。&lt;/p&gt;

 &lt;p&gt;我们必然会添加一个流程：即当数据库有更新时，同时更新缓存，因为缓存会自己重建，也可以修改为当数据库有更新时，同时删除缓存。&lt;/p&gt;

 &lt;p&gt;这里提到删除或者更新缓存，就有点意思了。我们上面讲到的都是非常简单的缓存，即一个id对应一个key。那么试想，如果我们有一个分页缓存，缓存了某一个文章最新的前10页数据。分别的key是page_1,page_2...page10。&lt;/p&gt;

 &lt;p&gt;那么当我们有一条新数据产生，这10页就都失效了，需要更新或者删除10次。这显然是不太科学的做法。&lt;/p&gt;

 &lt;p&gt;那么我们应该怎么做呢。我们可以借用上面例2中的方法，例2中，我们在缓存中增加了一个atime字段，标识为缓存的生成时间。我们既然知道缓存什么时候生成的，那问题就好解决了。我们在每次有新数据产生时，都去更新一个updatetime字段。然后获取分页缓存的时候，看一下这个updatetime字段是不是在atime之后，如果是，那么说明这份缓存太旧了，需要走更新流程。&lt;/p&gt;

 &lt;h4&gt;代码示例3（避免批量更新）：&lt;/h4&gt;

 &lt;pre&gt;  &lt;code&gt;//从缓存中获取数据[较快的方式][这里的两次get普通的缓存系统都支持一个请求完成]
data = getfromcache(id)
updatetime = getupdatetime(id)
data = json.decode(data)
//如果通过检查缓存生成时间，发现缓存已经过于陈旧，那么就将缓存过期时间设置为现在开始的5分钟以后（这样其他并发进程就会以为此缓存还未过期，还会继续使用5分钟，只让当前这一个请求去重建缓存）
if data != null &amp;amp;&amp;amp; (data.atime+86400 &amp;lt; now || date.atime &amp;lt; updatetime) then
    data.atime = now+300-86400
    data = json.encode(data)
    //对真正的cache来说，缓存10天或者更长时间
    setintocache(id, data, 864000)
    //这里把data设置成null是为了走到下面的if中去重建缓存
    data = null
end

if data == null then
    //从数据库中获取数据[较慢的方式]
    data = getfromdb(id)
    data = {data:data, atime:now}
    data = json.encode(data)
    //对真正的cache来说，缓存10天或者更长时间
    setintocache(id, data, 864000)
    return data
end

return data
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;这仅仅是在代码示例2的基础上增加了下面这一个条件判断而已&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;date.atime &amp;lt; updatetime
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;这样，无论是缓存保存时间过期了，还是缓存本身有更新，都会触发带锁机制的缓存更新。&lt;/p&gt;

 &lt;p&gt;好了，先说到这里，回头有想起来的再做更新。  &lt;a href="http://iammutex.com/html/y2015/2235.html" rel="nofollow"&gt;原文地址&lt;/a&gt;&lt;/p&gt;

 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;顺便插播一则招聘广告。(码字不易，求别删招聘广告，谢！)&lt;/p&gt;

 &lt;p&gt;题主现在供职于乐视网，负责用户互动类的产品研发，诚招PHP服务端开发（发展机会待遇绝对牛X）。&lt;/p&gt;

 &lt;p&gt;有兴趣的同学请私信或简历发邮箱：ligang1#letv.com&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>缓存 php</category>
      <guid isPermaLink="true">https://itindex.net/detail/53260-%E6%9C%8D%E5%8A%A1-%E7%BC%93%E5%AD%98</guid>
      <pubDate>Wed, 22 Apr 2015 11:44:59 CST</pubDate>
    </item>
    <item>
      <title>PHP程序员最常犯的11个MySQL错误</title>
      <link>https://itindex.net/detail/50971-php-%E7%A8%8B%E5%BA%8F%E5%91%98-mysql</link>
      <description>&lt;p&gt;对于大多数web应用来说，数据库都是一个十分基础性的部分。如果你在使用  &lt;a rel="nofollow" target="_blank"&gt;PHP&lt;/a&gt;，那么你很可能也在使用  &lt;a rel="nofollow" target="_blank"&gt;MySQL&lt;/a&gt;—LAMP系列中举足轻重的一份子。&lt;/p&gt;
 &lt;p&gt;对于很多新手们来说，使用PHP可以在短短几个小时之内轻松地写出具有特定功能的代码。但是，构建一个稳定可靠的数据库却需要花上一些时日和相关技能。下面列举了我曾经犯过的最严重的11个MySQL相关的错误（有些同样也反映在其他语言/数据库的使用上）。&lt;/p&gt;
 &lt;h2&gt;1.使用MyISAM而不是InnoDB&lt;/h2&gt;
 &lt;p&gt;MySQL有很多数据库引擎，但是你最可能碰到的就是MyISAM和InnoDB。&lt;/p&gt;
 &lt;p&gt;MySQL默认使用的是MyISAM。但是，很多情况下这都是一个很糟糕的选择，除非你在创建一个非常简单抑或实验性的数据库。外键约束或者事务处理对于数据完整性是非常重要的，但MyISAM都不支持这些。另外，当有一条记录在插入或者更新时，整个数据表都被锁定了，当使用量增加的时候这会产生非常差的运行效率。&lt;/p&gt;
 &lt;p&gt;结论很简单：使用InnoDB。&lt;/p&gt;
 &lt;h2&gt;2.使用PHP的mysql函数&lt;/h2&gt;
 &lt;p&gt;PHP自产生之日就提供了MySQL库函数（or near as makes no difference）。很多应用仍然在使用类似mysql_connect、mysql_query、mysql_fetch_assoc等的函数，尽管PHP手册上说：&lt;/p&gt;
 &lt;p&gt;如果你在使用MySQL v4.1.3或者更新版本，强烈推荐使用您使用mysqli扩展。&lt;/p&gt;
 &lt;p&gt;mysqli（MySQL的加强版扩展）有以下几个优点：&lt;/p&gt;
 &lt;p&gt;可选的面向对象接口&lt;/p&gt;
 &lt;p&gt;prepared表达式，这有利于阻止SQL注入攻击，还能提高性能&lt;/p&gt;
 &lt;p&gt;支持更多的表达式和事务处理&lt;/p&gt;
 &lt;p&gt;另外，如果你想支持多种数据库系统，你还可以考虑PDO。&lt;/p&gt;
 &lt;h2&gt;3.没有处理用户输入&lt;/h2&gt;
 &lt;p&gt;这或者可以这样说#1：永远不要相信用户的输入。用服务器端的PHP验证每个字符串，不要寄希望与JavaScript。最简单的SQL注入攻击会利用如下的代码：&lt;/p&gt;
 &lt;p&gt;$username = $_POST[&amp;quot;name&amp;quot;];&lt;/p&gt;
 &lt;p&gt;$password = $_POST[&amp;quot;password&amp;quot;];&lt;/p&gt;
 &lt;p&gt;$sql = “SELECT userid FROM usertable WHERE username=’$username’ AND password=’$password’;”;&lt;/p&gt;
 &lt;p&gt;// run query…&lt;/p&gt;
 &lt;p&gt;只要在username字段输入”admin’;–”，这样就会被黑到，相应的SQL语句如下：&lt;/p&gt;
 &lt;p&gt;SELECT userid FROM usertable WHERE username=’admin’;&lt;/p&gt;
 &lt;p&gt;狡猾的黑客可以以admin登录，他们不需要知道密码，因为密码段被注释掉了。&lt;/p&gt;
 &lt;h2&gt;4.没有使用UTF-8&lt;/h2&gt;
 &lt;p&gt;美国、英国和澳大利亚的我们很少考虑除英语之外的其他语言。我们很得意地完成了自己的”杰作”却发现它们并不能在其他地方正常运行。&lt;/p&gt;
 &lt;p&gt;UTF-8解决了很多国际化问题。虽然在PHP v6.0之前它还不能很好地被支持，但这并不影响你把MySQL字符集设为UTF-8。&lt;/p&gt;
 &lt;h2&gt;5.相对于SQL，偏爱PHP&lt;/h2&gt;
 &lt;p&gt;如果你接触MySQL不久，那么你会偏向于使用你已经掌握的语言来解决问题，这样会导致写出一些冗余、低效率的代码。比如，你不会使用MySQL自带的AVG()函数，却会先对记录集中的值求和然后用PHP循环来计算平均值。&lt;/p&gt;
 &lt;p&gt;此外，请注意PHP循环中的SQL查询。通常来说，执行一个查询比在结果中迭代更有效率。&lt;/p&gt;
 &lt;p&gt;所以，在分析数据的时候请利用数据库系统的优势，懂一些SQL的知识将大有裨益。&lt;/p&gt;
 &lt;h2&gt;6.没有优化数据库查询&lt;/h2&gt;
 &lt;p&gt;99%的PHP性能问题都是由数据库引起的，仅仅一个糟糕的SQL查询就能让你的web应用彻底瘫痪。MySQL的EXPLAIN statement、Query Profiler，还有很多其他的工具将会帮助你找出这些万恶的SELECT。&lt;/p&gt;
 &lt;h2&gt;7.不能正确使用数据类型&lt;/h2&gt;
 &lt;p&gt;MySQL提供了诸如numeric、string和date等的数据类型。如果你想存储一个时间，那么使用DATE或者DATETIME类型。如果这个时候用INTEGER或者STRING类型的话，那么将会使得SQL查询非常复杂，前提是你能使用INTEGER或者STRING来定义那个类型。&lt;/p&gt;
 &lt;p&gt;很多人倾向于擅自自定义一些数据的格式，比如，使用string来存储序列化的PHP对象。这样的话数据库管理起来可能会变得简单些，但会使得MySQL成为一个糟糕的数据存储而且之后很可能会引起故障。&lt;/p&gt;
 &lt;h2&gt;8.在查询中使用*&lt;/h2&gt;
 &lt;p&gt;永远不要使用*来返回一个数据表所有列的数据。这是懒惰：你应该提取你需要的数据。就算你需要所有字段，你的数据表也不可避免的会产生变化。&lt;/p&gt;
 &lt;h2&gt;9.不使用索引或者过度使用索引&lt;/h2&gt;
 &lt;p&gt;一般性原则是这样的：select语句中的任何一个where子句表示的字段都应该使用索引。&lt;/p&gt;
 &lt;p&gt;举个例子，假设我们有一个user表，包括numeric ID（主键）和email address。登录的时候，MySQL必须以一个email为依据查找正确的ID。如果使用了索引的话（这里指email），那么MySQL就能够使用更快的搜索算法来定位email，甚至可以说是即时实现。否则，MySQL就只能顺序地检查每一条记录直到找到正确的email address。&lt;/p&gt;
 &lt;p&gt;有的人会在每个字段上都添加索引，遗憾的是，执行了INSERT或者UPDATE之后这些索引都需要重新生成，这样就会影响性能。所以，只在需要的时候添加索引。&lt;/p&gt;
 &lt;h2&gt;10.忘记备份&lt;/h2&gt;
 &lt;p&gt;虽然比较罕见，但是数据库还是有崩溃的危险。硬盘有可能损坏，服务器有可能崩溃，web主机提供商有可能会破产！丢失MySQL数据将会是灾难性的，所以请确保你已经使用了自动备份或者已经复制到位。&lt;/p&gt;
 &lt;h2&gt;11.Bonus mistake-不考虑使用其他数据库&lt;/h2&gt;
 &lt;p&gt;对于PHP开发人员来说，MySQL可能是使用最广泛的数据库系统，但并不是唯一的选择。PostgreSQL和Firebird是最强有力的竞争者：这个两者都是开源的，而且都没有被公司收购。微软提供了sql server Express，甲骨文提供了10g Express，这两者都是企业级数据库的免费版本。有时候，对于一个较小的web应用或者嵌入式应用，SQLite也不失为一个可行的替代方案。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>编程技术 mysql php</category>
      <guid isPermaLink="true">https://itindex.net/detail/50971-php-%E7%A8%8B%E5%BA%8F%E5%91%98-mysql</guid>
      <pubDate>Fri, 05 Sep 2014 10:24:41 CST</pubDate>
    </item>
    <item>
      <title>PHP优化杂烩</title>
      <link>https://itindex.net/detail/52321-php-%E4%BC%98%E5%8C%96</link>
      <description>&lt;p&gt;讲 PHP 优化的文章往往都是教大家如何编写高效的代码，本文打算从另一个角度来讨论问题，教大家如何配置高效的环境，如此同样能够达到优化的目的。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;pool&lt;/h2&gt;
 &lt;p&gt;一个让人沮丧的消息是绝大多数 PHP 程序员都忽视了池的价值。这里所说的池可不是指数据库连接池之类的东西，而是指进程池，PHP 允许同时启动多个池，每个池使用不同的配置，各个池之间尊重彼此的主权领土完整，互不干涉内政。&lt;/p&gt;
 &lt;div&gt;  &lt;a href="http://huoding.com/wp-content/uploads/2014/12/pool.png"&gt;   &lt;img alt="pool" height="129" src="http://huoding.com/wp-content/uploads/2014/12/pool.png" width="544"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;pool&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;有什么好处呢？默认情况下，PHP 只启用了一个池，所有请求均在这个池中执行。一旦某些请求出现拥堵之类的情况，那么很可能会连累整个池出现火烧赤壁的结局；如果启用多个池，那么可以把请求分门别类放到不同的池中执行，此时如果某些请求出现拥堵之类的情况，那么只会影响自己所在的池，从而控制故障的波及范围。&lt;/p&gt;
 &lt;h2&gt;listen&lt;/h2&gt;
 &lt;p&gt;虽然 Nginx 和 PHP 可以部署在不同的服务器上，但是实际应用中，多数人都习惯把它们部署在同一台服务器上，如此就有两个选择：一个是 TCP，另一个是 Unix Socket。&lt;/p&gt;
 &lt;div&gt;  &lt;a href="http://huoding.com/wp-content/uploads/2014/12/listen.jpg"&gt;   &lt;img alt="listen" height="129" src="http://huoding.com/wp-content/uploads/2014/12/listen.jpg" width="559"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;listen&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;和 TCP 比较，Unix Socket 省略了一些诸如 TCP 三次握手之类的环节，所以相对更高效，不过需要注意的是，在使用 Unix Socket 时，因为没有 TCP 对应的可靠性保证机制，所以最好把 backlog 和 somaxconn 设置大些，否则面对高并发时会不稳定。&lt;/p&gt;
 &lt;h2&gt;pm&lt;/h2&gt;
 &lt;p&gt;进程管理有动态和静态之分。动态模式一般先启动少量进程，再按照请求数的多少实时调整进程数。如此的优点很明显：节省资源；当然它的缺点也很明显：一旦出现高并发请求，系统将不得不忙着 FORK 新进程，必然会影响性能。相对应的，静态模式一次性 FORK 足量的进程，之后不管请求量如何均保持不变。和动态模式相比，静态模式虽然消耗了更多的资源，但是面对高并发请求，它不需要执行高昂的 FORK。&lt;/p&gt;
 &lt;div&gt;  &lt;a href="http://huoding.com/wp-content/uploads/2014/12/pm.png"&gt;   &lt;img alt="pm" height="369" src="http://huoding.com/wp-content/uploads/2014/12/pm.png" width="559"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;pm&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;对大流量网站而言，除非服务器资源紧张，否则静态模式无疑是最佳选择。&lt;/p&gt;
 &lt;h2&gt;pm.max_children&lt;/h2&gt;
 &lt;p&gt;启动多少个 PHP 进程合适？在你给出自己的答案之前，不妨看看下面的文章：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.guangla.com/post/2014-03-14/40061238121" target="_blank"&gt;php-fpm的max_chindren的一些误区&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://forum.nginx.org/read.php?3,222702" target="_blank"&gt;Should PHP Workers Always Equal Number Of CPUs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;一个 CPU 在某一个时刻只能处理一个请求。当请求数大于 CPU 个数时，CPU 会划分时间片，轮流执行各个请求，既然涉及多个任务的调度，那么上下文切换必然会消耗一部分性能，从这个意义上讲，进程数应该等于 CPU 个数，如此一来每个进程都对应一个专属的 CPU，可以把上下文切换损失的效率降到最低。不过这个结论仅在请求是 CPU 密集型时才是正确的，而对于一般的 Web 请求而言，多半是 IO 密集型的，此时这个结论就值得商榷了，因为数据库查询等 IO 的存在，必然会导致 CPU 有相当一部分时间处于 WAIT 状态，也就是被浪费的状态。此时如果进程数多于 CPU 个数的话，那么当发生 IO 时，CPU 就有机会切换到别的请求继续执行，虽然这会带来一定上下文切换的开销，但是总比卡在 WAIT 状态好多了。&lt;/p&gt;
 &lt;p&gt;那多少合适呢？要理清这个问题，我们除了要关注 CPU 之外，还要关注内存情况：&lt;/p&gt;
 &lt;div&gt;  &lt;a href="http://huoding.com/wp-content/uploads/2014/12/php_memory.png"&gt;   &lt;img alt="PHP Memory" height="294" src="http://huoding.com/wp-content/uploads/2014/12/php_memory.png" width="489"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;PHP Memory&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;如上所示 top 命令的结果中和内存相关的列分别是 VIRT，RES，SHR。其中 VIRT 表示的是内存占用的理论值，通常不用在意它，RES 表示的是内存占用的实际值，看到这里大家可能会有点恐惧：一个 PHP 进程要占用这么多内存？虽然 RES 显示的数值看上去很大，但是这里面有很多是共享内存，也就是 SHR 显示的值，所以单个 PHP 进程实际占用的内存大小等于「RES – SHR」，一般就是 10M 上下。以此推算，理论上 1G 内存能支撑大概一百个 PHP 进程，10G 内存能大概支撑一千个 PHP 进程。当然并不能粗暴认为越多越好，最好结合 PHP 的 status 接口，通过监控活跃连接数的数量来调整。&lt;/p&gt;
 &lt;p&gt;说明：关于 Web 并发模型方面的知识建议参考  &lt;a href="http://robbinfan.com/" target="_blank"&gt;范凯&lt;/a&gt;的「  &lt;a href="http://robbin.iteye.com/blog/1744725" target="_blank"&gt;Web并发模型粗浅探讨&lt;/a&gt;」。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Technical PHP</category>
      <guid isPermaLink="true">https://itindex.net/detail/52321-php-%E4%BC%98%E5%8C%96</guid>
      <pubDate>Thu, 25 Dec 2014 19:06:20 CST</pubDate>
    </item>
    <item>
      <title>PHP，CURL和你的安全！</title>
      <link>https://itindex.net/detail/49890-php-curl-%E5%AE%89%E5%85%A8</link>
      <description>&lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.webhek.com/php-curl/#intro"&gt;简介&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.webhek.com/php-curl/#not_to_do"&gt;不应该做的事情&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.webhek.com/php-curl/#to_do"&gt;应该怎样做&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;a name="intro"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;简介&lt;/h2&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;如果最近你在美国看电视，你会经常看到一个广告——一个和蔼友善的家伙说“我希望我的电脑被病毒感染”，“我希望所有我家的照片都被人删除，找不回来。”或“我希望我的笔记本运转的声音听起来像打雷。”&lt;/p&gt;
 &lt;p&gt;当然，没有一个正常人希望遇到这样的痛苦，但如果你不对自己的电脑采取保护措施，结果就是让黑客得逞。你需要理解，这就像在你家里，车或钱袋子，你不能让它们都敞着口放在外面，你不能认为陌生路人都是可信的。大部分的陌生人并不像你想象的那样友好。&lt;/p&gt;
 &lt;p&gt;如果没有人告诉你应该怎么做，你很容会犯错误。置之不理是愚蠢的，幸好你读了这篇文章。我要首先假设你不是那么愚蠢的人。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="not_to_do"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;不应该做的事情&lt;/h2&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;下面是一个列表，解释了什么不该做，以及为什么。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;&amp;lt;?php include(&amp;apos;http://www.webhek.com&amp;apos;); ?&amp;gt;&lt;/code&gt;这是外表美味可口巧克力，里面却藏着恶魔。它的意思是“去http://www.webhek.com网站，取回页面内容，运行这些内容，不论是什么内容。”如果是像下面的这些内容到无所谓：
   &lt;pre&gt;    &lt;code&gt;&amp;lt;b&amp;gt;Hello World&amp;lt;/b&amp;gt;&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;但是，如果你不那么幸运，这个网站被人动过手脚，它的内容被替换成：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;Evil ruuLzzzzorz!!! &amp;lt;?php system(&amp;quot;rm -rf /*&amp;quot;); ?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;这句代码会删除你的电脑上的所有东西。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;&amp;lt;?php print read_file(&amp;apos;http://www.webhek.com&amp;apos;); ?&amp;gt;&lt;/code&gt;这样会稍微安全一些，因为这句代码的做法是读取远程页面的内容，然后打印它们。即使有人在内容里插入了恶意的PHP代码，这些代码也没有机会被执行。但是，黑客仍然可以在内容里注入恶意的JavaScript，你会发现你的页面上突然间被植入了无数的弹出式广告窗口页面。这会让你的网站的浏览者非常恼怒。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这里面有很多的学问，但上面这些是最大的问题。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="to_do"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;应该如何做&lt;/h2&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;PHP里面有一个非常强大的函数库，它们的目的就是让你安全的从远程网站上取回内容。这些函数被称作  &lt;a href="http://www.php.net/manual/en/ref.curl.php" rel="nofollow"&gt;CURL&lt;/a&gt;。现在，你不要被CURL官方页面上大量的东西吓阻，它实际上非常的简单。&lt;/p&gt;
 &lt;p&gt;下面是一个简单的替换上面  &lt;code&gt;read_file()&lt;/code&gt;命令的做法：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;?php

$curl_handle=curl_init();
curl_setopt($curl_handle,CURLOPT_URL,&amp;apos;http://www.webhek.com&amp;apos;);
curl_exec($curl_handle);
curl_close($curl_handle);

?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;就是这样，这才是你应该做的，最后一句  &lt;code&gt;curl_close()&lt;/code&gt;不是必要的。&lt;/p&gt;
 &lt;p&gt;小心，你仍然有被远程网站上的恶意JavaScript和cookie盗取者袭击的风险。防范这些攻击需要牵涉到更多的内容。如果你想做这些，我建议你使用PHP正则表达式函数里的  &lt;code&gt;   &lt;a href="http://www.php.net/manual/en/function.preg-replace.php" rel="nofollow"&gt;preg_replace()&lt;/a&gt;&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;假设我们确实要用CURL来做一些事情。假设www.webhek.com这个网站不是那么稳定。它有时候会没有响应，一个页面需要30秒才能拉取成功。对于这种情况，我们的办法是：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;?php

$curl_handle=curl_init();
curl_setopt($curl_handle,CURLOPT_URL,&amp;apos;http://www.webhek.com&amp;apos;);
   &lt;strong&gt;curl_setopt($curl_handle,CURLOPT_CONNECTTIMEOUT,2);&lt;/strong&gt;
curl_exec($curl_handle);
curl_close($curl_handle);

?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这种写法是说，2秒钟内如果不能抓取完数据就做超时处理。是的，也许你更愿意设定为1秒就算超时，因为它妨碍你的页面的速度。(注意，不要设置为0，这是告诉  &lt;code&gt;curl&lt;/code&gt;没有超时限制。)&lt;/p&gt;
 &lt;p&gt;但是，如果是什么东西都没有取回了，你想显示一个提示信息，这该怎么办？哈哈，简单！&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;?php

$curl_handle=curl_init();
curl_setopt($curl_handle,CURLOPT_URL,&amp;apos;http://www.webhek.com&amp;apos;);
curl_setopt($curl_handle,CURLOPT_CONNECTTIMEOUT,2);
   &lt;strong&gt;curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER,1);&lt;/strong&gt;
   &lt;strong&gt;$buffer = &lt;/strong&gt;curl_exec($curl_handle);
curl_close($curl_handle);

   &lt;strong&gt;if (empty($buffer))
{
    print &amp;quot;抱歉，webhek.com 这个网站又无响应了。&amp;lt;p&amp;gt;&amp;quot;;
}
else
{
    print $buffer;
}&lt;/strong&gt;
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;你有没有开始感觉到CURL的强大之处？&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;(英文：   &lt;a href="http://blog.unitedheroes.net/curl/" rel="nofollow"&gt;PHP, CURL, and YOU!&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>技术技巧 curl php</category>
      <guid isPermaLink="true">https://itindex.net/detail/49890-php-curl-%E5%AE%89%E5%85%A8</guid>
      <pubDate>Thu, 05 Jun 2014 00:13:47 CST</pubDate>
    </item>
    <item>
      <title>PHP里10个鲜为人知但却非常有用的函数</title>
      <link>https://itindex.net/detail/47827-php-%E4%BA%BA%E7%9F%A5-%E5%87%BD%E6%95%B0</link>
      <description>&lt;p&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="PHP" height="155" src="http://www.aqee.net/wordpress/wp-content/uploads/2014/01/PHP-247x155.jpg" width="247"&gt;&lt;/img&gt;  &lt;br /&gt;
PHP里有非常丰富的内置函数，很多我们都用过，但仍有很多的函数我们大部分人都不熟悉，可它们却十分的有用。这篇文章里，我列举了一些鲜为人知但会让你眼睛一亮的PHP函数。&lt;/p&gt;
 &lt;h3&gt;levenshtein()&lt;/h3&gt;
 &lt;p&gt;你有没有经历过需要知道两个单词有多大的不同的时候，这个函数就是来帮你解决这个问题的。它能比较出两个字符串的不同程度。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt; &amp;lt;?php
$str1 = &amp;quot;carrot&amp;quot;;
$str2 = &amp;quot;carrrott&amp;quot;;
echo levenshtein($str1, $str2); //Outputs 2

?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;Source:   &lt;a href="http://php.net/manual/en/function.levenshtein.php"&gt;http://php.net/manual/en/function.levenshtein.php&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;get_defined_vars()&lt;/h3&gt;
 &lt;p&gt;这是一个在debug调试时非常有用的函数。这个函数返回一个多维数组，里面包含了所有定义过的变量。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;&amp;lt;?php
print_r(get_defined_vars());
?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;Source:   &lt;a href="http://php.net/manual/en/function.get-defined-vars.php"&gt;http://php.net/manual/en/function.get-defined-vars.php&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;php_check_syntax()&lt;/h3&gt;
 &lt;p&gt;这个函数非常的有用，可以用来检查PHP的语法是否正确。出于技术上的原因，从PHP 5.05开始，这个函数被删除了。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt; &amp;lt;?php
$error_message = &amp;quot;&amp;quot;;
$filename = &amp;quot;./php_script.php&amp;quot;;
if(!php_check_syntax($filename, &amp;amp;$error_message)) {
   echo &amp;quot;Errors were found in the file $filename: $error_message&amp;quot;;
} else {
   echo &amp;quot;The file $filename contained no syntax errors&amp;quot;;
}
?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;Source:   &lt;a href="http://www.php.net/manual/en/function.php-check-syntax.php"&gt;http://www.php.net/manual/en/function.php-check-syntax.php&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;ignore_user_abort()&lt;/h3&gt;
 &lt;p&gt;这个函数用来拒绝浏览器端用户终止执行脚本的请求。正常情况下客户端的退出会导致服务器端脚本停止运行。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;&amp;lt;?php
ignore_user_abort();
?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;Source:   &lt;a href="http://www.php.net/manual/en/function.ignore-user-abort.php"&gt;http://www.php.net/manual/en/function.ignore-user-abort.php&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;highlight_string()&lt;/h3&gt;
 &lt;p&gt;当你想把PHP代码显示到页面上时，  &lt;code&gt;highlight_string()&lt;/code&gt;函数就会显得非常有用。这个函数会把你提供的PHP代码用内置的PHP语法突出显示定义的颜色高亮显示。这个函数有两个参数，第一个参数是一个字符串，表示这个字符串需要被突出显示。第二个参数如果设置成TRUE，这个函数就会把高亮后的代码当成返回值返回。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt; &amp;lt;?php
highlight_string(&amp;apos; &amp;lt;?php phpinfo(); ?&amp;gt;&amp;apos;);
?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;Source:   &lt;a href="http://php.net/manual/en/function.highlight-string.php"&gt;http://php.net/manual/en/function.highlight-string.php&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;highlight_file&lt;/h3&gt;
 &lt;p&gt;这是一个非常有用的PHP函数，它能返回指定的PHP文件，并按照语法语义用高亮颜色突出显示文件内容。其中的突出显示的代码都是用HTML标记处理过的。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;&amp;lt;?php
highlight_file(&amp;quot;php_script.php&amp;quot;);
?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;Source:   &lt;a href="http://www.php.net/manual/en/function.highlight-file.php"&gt;http://www.php.net/manual/en/function.highlight-file.php&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;php_strip_whitespace&lt;/h3&gt;
 &lt;p&gt;这个函数也跟前面的  &lt;code&gt;show_source()&lt;/code&gt;函数相似，但它会删除文件里的注释和空格符。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;&amp;lt;?php
echo php_strip_whitespace(&amp;quot;php_script.php&amp;quot;);
?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;Source:   &lt;a href="http://www.php.net/manual/en/function.php-strip-whitespace.php"&gt;http://www.php.net/manual/en/function.php-strip-whitespace.php&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;get_browser&lt;/h3&gt;
 &lt;p&gt;这个函数会读取  &lt;code&gt;browscap.ini&lt;/code&gt;文件，返回浏览器兼容信息。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;&amp;lt;?php
echo $_SERVER[&amp;apos;HTTP_USER_AGENT&amp;apos;];
$browser = get_browser();
print_r($browser);
?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;Source:   &lt;a href="http://www.php.net/manual/en/function.get-browser.php"&gt;http://www.php.net/manual/en/function.get-browser.php&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;memory_get_usage(),memory_get_peak_usage(),getrusage()&lt;/h3&gt;
 &lt;p&gt;这些函数用来获取内存和CPU使用情况，  &lt;code&gt;memory_get_usage()&lt;/code&gt;函数返回内存使用量，  &lt;code&gt;memory_get_peak_usage()&lt;/code&gt;函数返回内存使用峰值，getrusage()返回CUP使用情况，在调试PHP代码性能时，这些函数会给你提供一些有用信息。但有一点请注意，在这些函数中Window上无效。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt; &amp;lt;?php
echo &amp;quot;Initial: &amp;quot;.memory_get_usage().&amp;quot; bytes \n&amp;quot;;
echo &amp;quot;Peak: &amp;quot;.memory_get_peak_usage().&amp;quot; bytes \n&amp;quot;;
$data = getrusage();
echo &amp;quot;User time: &amp;quot;.
	($data[&amp;apos;ru_utime.tv_sec&amp;apos;] +
	$data[&amp;apos;ru_utime.tv_usec&amp;apos;] / 1000000);
echo &amp;quot;System time: &amp;quot;.
	($data[&amp;apos;ru_stime.tv_sec&amp;apos;] +
	$data[&amp;apos;ru_stime.tv_usec&amp;apos;] / 1000000);

?&amp;gt;&lt;/pre&gt;
 &lt;h3&gt;gzcompress(), gzuncompress()&lt;/h3&gt;
 &lt;p&gt;这两个函数用来压缩和解压字符串数据。它们的压缩率能达到50% 左右。另外的函数 gzencode() 和 gzdecode() 也能达到类似结果，但使用了不同的压缩算法。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt; &amp;lt;?php
$string =
&amp;quot;Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Nunc ut elit id mi ultricies
adipiscing. Nulla facilisi. Praesent pulvinar,
sapien vel feugiat vestibulum, nulla dui pretium orci,
non ultricies elit lacus quis ante. Lorem ipsum dolor
sit amet, consectetur adipiscing elit. Aliquam
pretium ullamcorper urna quis iaculis. Etiam ac massa
sed turpis tempor luctus. Curabitur sed nibh eu elit
mollis congue. Praesent ipsum diam, consectetur vitae
ornare a, aliquam a nunc. In id magna pellentesque
tellus posuere adipiscing. Sed non mi metus, at lacinia
augue. Sed magna nisi, ornare in mollis in, mollis
sed nunc. Etiam at justo in leo congue mollis.
Nullam in neque eget metus hendrerit scelerisque
eu non enim. Ut malesuada lacus eu nulla bibendum
id euismod urna sodales. &amp;quot;;

$compressed = gzcompress($string);
$original = gzuncompress($compressed);

?&amp;gt;&lt;/pre&gt;
 &lt;p&gt;你是否也想到了还有其它很有用的函数？请在评论里分享出来！&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://t.cn/zTcmcnC"&gt;   &lt;img src="http://www.aqee.net/7777.png"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;本文由  &lt;a href="http://www.aqee.net"&gt;外刊IT评论网&lt;/a&gt;(  &lt;a href="http://www.aqee.net"&gt;www.aqee.net&lt;/a&gt;)原创发表，文章地址：  &lt;a href="http://www.aqee.net/10-little-known-but-useful-php-functions/"&gt;PHP里10个鲜为人知但却非常有用的函数&lt;/a&gt;，[英文原文：  &lt;a href="http://www.phpzag.com/10-little-known-but-useful-php-functions/"&gt;10 little known but useful PHP functions&lt;/a&gt; ]&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;h3&gt;你也许会喜欢这些文章：&lt;/h3&gt; &lt;ol&gt;  &lt;li&gt;   &lt;a href="http://www.aqee.net/im-retiring-from-php/"&gt;我的PHP退役了&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://www.aqee.net/why-php-was-a-ghetto/"&gt;为什么说PHP是个集中营&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://www.aqee.net/variable-variables/"&gt;PHP的可变变量名&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://www.aqee.net/php-needs-to-die-what-will-replace-it/"&gt;PHP将死。何以为继？&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://www.aqee.net/php-5-4-built-in-web-server/"&gt;PHP 5.4 内置web服务器&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&lt;/p&gt; &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>技术技巧 php</category>
      <guid isPermaLink="true">https://itindex.net/detail/47827-php-%E4%BA%BA%E7%9F%A5-%E5%87%BD%E6%95%B0</guid>
      <pubDate>Mon, 20 Jan 2014 00:06:28 CST</pubDate>
    </item>
    <item>
      <title>PHP 真正多线程的使用</title>
      <link>https://itindex.net/detail/47091-php-%E5%A4%9A%E7%BA%BF%E7%A8%8B</link>
      <description>　　PHP 5.3 以上版本，使用pthreads PHP扩展，可以使PHP真正地支持多线程。多线程在处理重复性的循环任务，能够大大缩短程序执行时间。 &lt;br /&gt; &lt;br /&gt;　　我之前的文章中说过，大多数网站的性能瓶颈不在PHP服务器上，因为它可以简单地通过横向增加服务器或CPU核数来轻松应对（对于各种云主机，增加VPS或CPU核数就更方便了，直接以备份镜像增加VPS，连操作系统、环境都不用安装配置），而是在于MySQL数据库。如果用 MySQL 数据库，一条联合查询的SQL，也许就可以处理完业务逻辑，但是，遇到大量并发请求，就歇菜了。如果用 NoSQL 数据库，也许需要十次查询，才能处理完同样地业务逻辑，但每次查询都比 MySQL 要快，十次循环NoSQL查询也许比一次MySQL联合查询更快，应对几万次/秒的查询完全没问题。如果加上PHP多线程，通过十个线程同时查询NoSQL，返回结果汇总输出，速度就要更快了。我们实际的APP产品中，调用一个通过用户喜好实时推荐商品的PHP接口，PHP需要对BigSea NoSQL数据库发起500~1000次查询，来实时算出用户的个性喜好商品数据，PHP多线程的作用非常明显。 &lt;br /&gt; &lt;br /&gt;　　PHP扩展下载： &lt;a href="https://github.com/krakjoe/pthreads" target="_blank"&gt;https://github.com/krakjoe/pthreads&lt;/a&gt; &lt;br /&gt;　　PHP手册文档： &lt;a href="http://php.net/manual/zh/book.pthreads.php" target="_blank"&gt;http://php.net/manual/zh/book.pthreads.php&lt;/a&gt; &lt;br /&gt; &lt;br /&gt;　　1、扩展的编译安装(Linux），编辑参数 --enable-maintainer-zts 是必选项： &lt;br /&gt; &lt;div&gt;cd /Data/tgz/php-5.5.1  &lt;br /&gt;./configure --prefix=/Data/apps/php --with-config-file-path=/Data/apps/php/etc --with-mysql=/Data/apps/mysql --with-mysqli=/Data/apps/mysql/bin/mysql_config --with-iconv-dir --with-freetype-dir=/Data/apps/libs --with-jpeg-dir=/Data/apps/libs --with-png-dir=/Data/apps/libs --with-zlib --with-libxml-dir=/usr --enable-xml --disable-rpath --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curl --enable-mbregex --enable-fpm --enable-mbstring --with-mcrypt=/Data/apps/libs --with-gd --enable-gd-native-ttf --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --enable-opcache --with-pdo-mysql --enable-maintainer-zts  &lt;br /&gt;make clean  &lt;br /&gt;make  &lt;br /&gt;make install          &lt;br /&gt;  &lt;br /&gt;unzip pthreads-master.zip  &lt;br /&gt;cd pthreads-master  &lt;br /&gt;/Data/apps/php/bin/phpize  &lt;br /&gt;./configure --with-php-config=/Data/apps/php/bin/php-config  &lt;br /&gt;make  &lt;br /&gt;make install  &lt;br /&gt;&lt;/div&gt; &lt;br /&gt; &lt;br /&gt; &lt;div&gt;vi /Data/apps/php/etc/php.ini&lt;/div&gt; &lt;br /&gt;添加： &lt;br /&gt; &lt;div&gt;extension = &amp;quot;pthreads.so&amp;quot;&lt;/div&gt; &lt;br /&gt; &lt;br /&gt;　　2、给出一段PHP多线程、与For循环，抓取百度搜索页面的PHP代码示例： &lt;br /&gt; &lt;textarea cols="100" name="code" rows="15"&gt;
&amp;lt;?php
  class test_thread_run extends Thread 
  {
      public $url;
      public $data;

      public function __construct($url)
      {
          $this-&amp;gt;url = $url;
      }

      public function run()
      {
          if(($url = $this-&amp;gt;url))
          {
              $this-&amp;gt;data = model_http_curl_get($url);
          }
      }
  }

  function model_thread_result_get($urls_array) 
  {
      foreach ($urls_array as $key =&amp;gt; $value) 
      {
          $thread_array[$key] = new test_thread_run($value[&amp;quot;url&amp;quot;]);
          $thread_array[$key]-&amp;gt;start();
      }

      foreach ($thread_array as $thread_array_key =&amp;gt; $thread_array_value) 
      {
          while($thread_array[$thread_array_key]-&amp;gt;isRunning())
          {
              usleep(10);
          }
          if($thread_array[$thread_array_key]-&amp;gt;join())
          {
              $variable_data[$thread_array_key] = $thread_array[$thread_array_key]-&amp;gt;data;
          }
      }
      return $variable_data;
  }

  function model_http_curl_get($url,$userAgent=&amp;quot;&amp;quot;) 
  {
      $userAgent = $userAgent ? $userAgent : &amp;apos;Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)&amp;apos;; 
      $curl = curl_init();
      curl_setopt($curl, CURLOPT_URL, $url);
      curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($curl, CURLOPT_TIMEOUT, 5);
      curl_setopt($curl, CURLOPT_USERAGENT, $userAgent);
      $result = curl_exec($curl);
      curl_close($curl);
      return $result;
  }

  for ($i=0; $i &amp;lt; 100; $i++) 
  { 
      $urls_array[] = array(&amp;quot;name&amp;quot; =&amp;gt; &amp;quot;baidu&amp;quot;, &amp;quot;url&amp;quot; =&amp;gt; &amp;quot;http://www.baidu.com/s?wd=&amp;quot;.mt_rand(10000,20000));
  }

  $t = microtime(true);
  $result = model_thread_result_get($urls_array);
  $e = microtime(true);
  echo &amp;quot;多线程：&amp;quot;.($e-$t).&amp;quot;\n&amp;quot;;

  $t = microtime(true);
  foreach ($urls_array as $key =&amp;gt; $value) 
  {
      $result_new[$key] = model_http_curl_get($value[&amp;quot;url&amp;quot;]);
  }
  $e = microtime(true);
  echo &amp;quot;For循环：&amp;quot;.($e-$t).&amp;quot;\n&amp;quot;;
?&amp;gt;
&lt;/textarea&gt; &lt;br /&gt; &lt;br /&gt;Tags -  &lt;a href="http://blog.s135.com/tags/php%25E5%25A4%259A%25E7%25BA%25BF%25E7%25A8%258B/" rel="tag"&gt;php多线程&lt;/a&gt; ,  &lt;a href="http://blog.s135.com/tags/php/" rel="tag"&gt;php&lt;/a&gt; ,  &lt;a href="http://blog.s135.com/tags/%25E5%25A4%259A%25E7%25BA%25BF%25E7%25A8%258B/" rel="tag"&gt;多线程&lt;/a&gt; ,  &lt;a href="http://blog.s135.com/tags/pthreads/" rel="tag"&gt;pthreads&lt;/a&gt;

&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>PHP/JS/Shell</category>
      <guid isPermaLink="true">https://itindex.net/detail/47091-php-%E5%A4%9A%E7%BA%BF%E7%A8%8B</guid>
      <pubDate>Tue, 17 Dec 2013 11:17:53 CST</pubDate>
    </item>
    <item>
      <title>成为一个PHP专家：缺失的环节</title>
      <link>https://itindex.net/detail/48286-php-%E4%B8%93%E5%AE%B6</link>
      <description>&lt;p&gt;  &lt;strong&gt;这一篇文章是“Becoming a PHP Professional”系列 4 篇博文中的第 1 篇。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;当浏览各类与PHP相关的博客时，比如Quora上的问题，谷歌群组，简讯和杂志，我经常注意到技能的等级分化。问题都类似于“我如何连接到MySQL数据库？”或者“我该如何扩展邮件系统才能在每小时发送超过一万封邮件，而不需要引入新的服务器？”&lt;/p&gt;
 &lt;p&gt;我将PHP能力水平分为4个等级（可能适用于任何  &lt;a href="http://blog.jobbole.com/tag/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/" title="&amp;#22914;&amp;#20309;&amp;#36873;&amp;#25321;&amp;#35821;&amp;#35328;&amp;#21644;&amp;#32534;&amp;#31243;&amp;#35821;&amp;#35328;&amp;#25490;&amp;#21517;&amp;#30456;&amp;#20851;&amp;#25991;&amp;#31456;"&gt;编程语言&lt;/a&gt;或专业）：初级、中级、专家和精英。&lt;/p&gt;
 &lt;h3&gt;等级&lt;/h3&gt;
 &lt;p&gt;对于PHP初级者，他们学习如何使用变量，包含文件，表单处理等。他们学习简单的逻辑结构。在教程的指导下，实现了用PHP发送邮件，甚至触及了面向对象编程但却没有完全理解它。他们还能修改WordPress的几个CSS文件。有了这些知识，他们就开始找工作，但不幸的是通常都会失败。&lt;/p&gt;
 &lt;p&gt;专家是指那些经历过许多项目并有了丰富经验的人。他们已经开发了许多商业应用，但没有完全使用框架来做。他们能够使用PHP与不同的数据库进行高效地开发，以及通过会议来讨论解决问题的方案。他们熟悉  &lt;a href="http://www.amazon.cn/gp/product/B001130JN8/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=vastwork-23&amp;linkCode=as2&amp;camp=536&amp;creative=3200&amp;creativeASIN=B001130JN8" rel="nofollow" target="_blank" title="1"&gt;设计模式&lt;/a&gt;，能够轻松地将项目的工程图用代码实现。他们远离过程式地编程。&lt;/p&gt;
 &lt;p&gt;精英  &lt;a href="http://blog.jobbole.com/821/" target="_blank" title="&amp;#31243;&amp;#24207;&amp;#21592;&amp;#30340;&amp;#26412;&amp;#36136;"&gt;程序员&lt;/a&gt;是那些努力了  &lt;a href="http://blog.jobbole.com/22905/" target="_blank" title="Peter Norvig&amp;#65306;&amp;#33258;&amp;#23398;&amp;#32534;&amp;#31243;&amp;#65292;&amp;#21313;&amp;#24180;&amp;#30952;&amp;#19968;&amp;#21073;"&gt;10000+小时&lt;/a&gt;磨练自己专业技能的人。他们能够根据自己的需要编写PHP的扩展，只是瞧一下源代码文件就能发现BUG，并且非常了解自己的代码布局。他们只做最复杂的项目，并且能够找到可选的和富有创造力的方案来解决问题。他们已经写了一些深受欢迎的关于编程语言的书籍，开过几十次讲座，甚至可能拥有自己的PHP语言版本或者非常成功的框架，或两者都拥有。&lt;/p&gt;
 &lt;p&gt;那么，谁属于中级呢？&lt;/p&gt;
 &lt;h3&gt;缺失的环节&lt;/h3&gt;
 &lt;p&gt;初学者怎样才能成为和超越专业人士？如果一个人不知道超出基本知识的东西，他如何能提高自己的技能，摒弃错误的开发方式和学习更先进的开发方法。这个问题是许多初学者向我提问过的。为了成为一个专家，他必须先成为中级者。&lt;/p&gt;
 &lt;p&gt;下面的列表中指出了一个人成为中级者所需经历的PHP学习路程：&lt;/p&gt;
 &lt;h3&gt;放弃意大利面条式的代码&lt;/h3&gt;
 &lt;p&gt;很多人认为使用了类就意味着在编写面向对象的代码，而使用了函数则意味着编写过程式的代码。然而这是  &lt;a href="http://blog.ircmaxell.com/2012/07/oop-vs-procedural-code.html" target="_blank"&gt;错误的&lt;/a&gt;，为了支撑这一观点，我们假设一个广为流传的定义：过程式代码是没有使用到类和对象的代码，而OOP代码是尽可能地使用类和对象的代码。&lt;/p&gt;
 &lt;p&gt;我的建议是完全放弃过程式代码。尽可能地使用面向对象的风格编程-编写类，封装逻辑，考虑使用真实世界中的术语。相比适当的OOP代码所带给你的可重用性和未来开发者能方便地在你的项目继续开发的好处，过程式代码的性能优势显得微不足道。针对这个观点的反对声音是“但是，WordPress是过程式的！”。坦白地说，这听起来可能有点刺耳，“WordPress的开发者”并不是真正的PHP开发者，好比拥有了Instagram 就会是摄影师一样。请不要认为这意味着WP是没用的—当你不希望花太多时间开发一个博客，简单的站点和为期一天的小项目时，使用WP开发会让你感到惊奇。它非常适合于急于求成或者没有太多技术的人，但掌握WP绝对无法使你成为一个专业的PHP开发者—它使用意大利面条式的编码，教你的是不合适的设计原则。&lt;/p&gt;
 &lt;p&gt;从小事做起。想想  &lt;a href="http://www.bitfalls.com/2013/08/autofight-php-job-interview-task-part-1.html" target="_blank"&gt;现实世界的概念&lt;/a&gt;，并尝试以OOP代码表示它。通过一些  &lt;a href="http://www.sitepoint.com/object-oriented-php-lesson-1/" target="_blank"&gt;基本的教程&lt;/a&gt;，并慢慢地熟练掌握OOP。在过渡到合适的框架和朴所迷离的术语例如“模型”，“视图”和“控制器”之前，坚持用OOP思想编写代码直到你大体上理解了类-所有这些都是云里雾里，抽象术语在OOP中并没有坚实的基础。&lt;/p&gt;
 &lt;h3&gt;剖析现有项目&lt;/h3&gt;
 &lt;p&gt;深入到现有的你所能寻找到的源代码中。例如，查看  &lt;a href="https://github.com/search?nwo=bcosca%2Ffatfree&amp;q=php&amp;ref=cmdform&amp;search_target=global&amp;type=Repositories" target="_blank"&gt;PHP projects on Github&lt;/a&gt;，克隆它们，部署到自己的主机上并且试着去  &lt;a href="http://blog.jobbole.com/438/" target="_blank" title="Eric Lippert&amp;#65306;&amp;#38405;&amp;#35835;&amp;#20195;&amp;#30721;&amp;#30495;&amp;#30340;&amp;#24456;&amp;#38590;"&gt;阅读代码&lt;/a&gt;。每一个文件，每一行，直到你理解它们是做什么的。&lt;/p&gt;
 &lt;p&gt;寻找具备规范的注释，结构良好，而且还在不断开发的项目。在2008年之前更新的项目并不是太好，如果你要开始使用PHP5.5的话—否则你可能会错过能使你在已经人口过剩的领域里脱颖而出的PHP最新和最强大的特性。&lt;/p&gt;
 &lt;h3&gt;学会搭建自己的PHP开发环境&lt;/h3&gt;
 &lt;p&gt;如果能够建立自己的环境是非常不错的能力。自己搭建环境时不仅允许你根据实际情况进行一些微小的调整，还能让你熟悉如何从源代码构建扩展。&lt;/p&gt;
 &lt;p&gt;放弃在Windows上开发—如果你主要的桌面环境是Windows，那么安装个  &lt;a href="https://www.virtualbox.org/wiki/Downloads" target="_blank"&gt;虚拟软件&lt;/a&gt;和运行一个Linux虚拟机—Windows中不区分大小写，它的行结束符，以及其他的一些东西与大多数服务器环境并不相符，在Windows上开发只会出现许多麻烦，所以最好在一个你最终运行项目的系统上进行开发。&lt;/p&gt;
 &lt;p&gt;虚拟机还可以帮助你进行一些实验-如果出现错误，你可以重新开始或者进行回滚。你可以尽可能多低去尝试，只要你想，而不必担心把事情搞乱了。掌握工具固然重要，但有一个良好的工作平台也是很重要的。&lt;/p&gt;
 &lt;p&gt;自己进行实验也能让你熟悉不同的服务器—是否使用Apache或者Nginx，还是使用  &lt;a href="http://www.sitepoint.com/interview-appserver-io-crew/" target="_blank"&gt;Appserver&lt;/a&gt;等。&lt;/p&gt;
 &lt;h3&gt;尽早地进行最佳实践&lt;/h3&gt;
 &lt;p&gt;当编写代码的时候，请确保你有  &lt;a href="http://pear.php.net/manual/en/standards.sample.php" target="_blank"&gt;充足的文档注释，精美的缩进和良好的结构&lt;/a&gt;。当你构建一个类、项目或库的时候，使用众所周知的文档工具（  &lt;a href="http://www.phpdoc.org/" target="_blank"&gt;PHPDocumentor&lt;/a&gt;,   &lt;a href="http://apigen.org/" target="_blank"&gt;ApiGen&lt;/a&gt;）来提取你的文档注释并加以改进。&lt;/p&gt;
 &lt;p&gt;一个好的IDE也是非常值得的—使用一个跨平台的编辑器能帮助你在任何时间建立一个新的开发环境时，保证你将注意力集中在编写代码而不是把时间浪费在修改键盘快捷键和主题上。确保你备份了IDE的配置文件到谷歌云服务等地方，这样你就可以随时导入你的配置文件进行全新安装。一个好的IDE是  &lt;a href="http://www.sitepoint.com/phpstorm-review-and-give-away/" target="_blank"&gt;PHPStorm&lt;/a&gt;，或者如果你买不起，或者没有需要免费许可的开源项目，  &lt;a href="http://www.sitepoint.com/netbeans-73-html5-support/" target="_blank"&gt;Netbeans&lt;/a&gt;是一个好的选择，并且两者都是跨平台的。&lt;/p&gt;
 &lt;p&gt;尽早的进行最佳实践能帮助你的代码保持一致，以及其他人能够更加流畅的阅读你的代码。找到你的风格，并坚持下去-你帮助到的将不只是你自己，还有他人。试着遵循PSR标准（  &lt;a href="http://www.sitepoint.com/autoloading-and-the-psr-0-standard/" target="_blank"&gt;PSR-0&lt;/a&gt;,   &lt;a href="http://www.sitepoint.com/psr-1-and-psr-2-to-be-approved-as-standards/" target="_blank"&gt;PSR-1&lt;/a&gt;,   &lt;a href="http://www.sitepoint.com/psr-1-and-psr-2-to-be-approved-as-standards/" target="_blank"&gt;PSR-2&lt;/a&gt;,   &lt;a href="http://www.sitepoint.com/logging-with-psr-3-to-improve-reusability/" target="_blank"&gt;PSR-3&lt;/a&gt;）—它们能成为标准是有原因的。我们大多数人都使用并且喜爱这些标准，它让每个人的代码都具有良好的可重用性和可读性。&lt;/p&gt;
 &lt;p&gt;对于一个初级者非常好的且不断更新的资源是  &lt;a href="http://www.phptherightway.com/" target="_blank"&gt;PHP the right way&lt;/a&gt;—学习它将能熟悉最新的实践，基本的OOP，安全，部署，编码标准等我所提到的，甚至更多。&lt;/p&gt;
 &lt;h3&gt;尝试不同的框架，然后选择一个&lt;/h3&gt;
 &lt;p&gt;长期以来，PHP是大多数框架使用的语言（最近JavaScript成为了最多者）。这是否说明我们的社区或者语言的流行程度不一致，我也说不清，但事实仍是，选择一个框架是一项艰巨的任务，特别是第一次开始选择。&lt;/p&gt;
 &lt;p&gt;尝试过了其中的大部分框架，我可以全心全意推荐  &lt;a href="http://www.sitepoint.com/google-app-engine-and-a-plea-for-phalcon/" target="_blank"&gt;Phalcon&lt;/a&gt;框架，因为它具有不错的鲁棒性和质量，而事实上，它是用C编写的并作为PHP的扩展被安装（因此比现在的任何框架都要快）。然而，亲自尝试不同的框架是很有必要的。&lt;/p&gt;
 &lt;p&gt;当你尝试不同框架的时候，你会发现对于同样的问题可以用新的方法解决。每个框架都有你所喜欢的优点和你所厌恶的缺点，但更重要的是，你将能了解到他人的心态（尤其是框架的开发者）。你会看到新的用法和方法，并且最好的实践是使用尽可能多的框架  &lt;a href="http://www.amazon.cn/gp/product/B003BY6PLK/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=vastwork-23&amp;linkCode=as2&amp;camp=536&amp;creative=3200&amp;creativeASIN=B003BY6PLK" rel="nofollow" target="_blank" title="&amp;#37325;&amp;#26500;:&amp;#25913;&amp;#21892;&amp;#26082;&amp;#26377;&amp;#20195;&amp;#30721;&amp;#30340;&amp;#35774;&amp;#35745;"&gt;重构&lt;/a&gt;相同的实验项目。这将帮助你有效地衡量一个特定框架的功效：使用它进行开发的速度和它的性能。&lt;/p&gt;
 &lt;p&gt;别低估别人的提示和技巧。尽可能多地阅读—如果你一直坚持着，它并不会花费你所想象的那么多时间。找一个好的博客并跟随它，阅读本站的指南，遍历  &lt;a href="http://stackoverflow.com/questions/tagged/php" target="_blank"&gt;StackOverflow的问题和答案&lt;/a&gt;，访问  &lt;a href="http://www.sitepoint.com/forums/" target="_blank"&gt;SitePoint论坛&lt;/a&gt;，订阅简讯，跟随在  &lt;a href="https://plus.google.com/u/0/113214561650167033983/posts" target="_blank"&gt;Google+上的良好资源&lt;/a&gt;。避开基本的PHP教程书籍—因为在它们一出版的时候就过时了—相反，专注于你所能在网上找到的含有最新代码的片段和指南。即使遇到你已经学习过的话题，试着阅读下—通过阅读他人对于同一个问题的观点，你经常会发现一些新的东西。&lt;/p&gt;
 &lt;h3&gt;如果没有什么事可以做，试着创造一些&lt;/h3&gt;
 &lt;p&gt;总是有事可做。永远不要说“我没有项目可做”，或者更糟的“我很无聊”。如果你没有一个正在进行的项目可以做—那就创造一个。你每天使用的工具是否让你感到受挫因为它不完善的功能？自己做出一个更好的！对新产品没有想法？那就复制一个现有的—试着重建一个  &lt;a href="http://www.sitepoint.com/social-network-style-posting-php-mongodb-jquery-part-1/" target="_blank"&gt;基本的FaceBook&lt;/a&gt;，重建一些你已经知道了的，为了能够实践一下。&lt;/p&gt;
 &lt;p&gt;最重要的是永不停止—如果不珍惜每一个小时，你将不可能积累到10000小时！继续努力，保持自己的兴趣和参与热情。做一个简单的地址簿应用。然后用另外一个框架重建它。并且使用不同的数据库（例如使用Mongo代替MariaDB）。保持忙碌！&lt;/p&gt;
 &lt;h3&gt;找到一个搭档/导师&lt;/h3&gt;
 &lt;p&gt;如果有人能和你一起的话，学习会变得更加容易。找到能够与你共享激情的搭档。也许你就是那幸运的少数人之一，已经拥有一个搭档共享你的独特兴趣。也许你在学校或者大学里有一个同行愿意和你一起开始并努力学习。你甚至可以  &lt;a href="http://blog.jobbole.com/8125/" target="_blank" title="&amp;#23548;&amp;#24072;&amp;#30340;&amp;#21147;&amp;#37327;&amp;#65306;&amp;#23547;&amp;#25214;&amp;#23548;&amp;#24072;&amp;#65292;&amp;#25104;&amp;#20026;&amp;#23548;&amp;#24072;"&gt;找到一个导师&lt;/a&gt;，并接受专家的指导。&lt;/p&gt;
 &lt;p&gt;不要低估搭档的力量—三人行必有我师焉！&lt;/p&gt;
 &lt;h3&gt;总结&lt;/h3&gt;
 &lt;p&gt;当你专注于所有这些条目并且尽可能地掌握它们时，当你意识到这就是你所想要的，那么请坚持下去—你正走在成为一个高级PHP开发者的路上。维持原则，绝不放弃（即使你身边有人放弃了）并且坚持实践。&lt;/p&gt;
 &lt;p&gt;如果你有一些有用的资源并且想要与我们分享你是如何跨越（或正在跨越）中级开发者这道坎，请在下面留言，让我们知道！&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;
   &lt;h3&gt;相关文章&lt;/h3&gt;
   &lt;ul&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/18563/"&gt;如何为 PHP 贡献代码&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/25010/"&gt;PHP超时处理全面总结&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/12371/"&gt;10个实用的PHP代码片段推荐&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/18304/"&gt;史上最糟糕的两个变量名&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/8792/"&gt;趣文：如果PHP是用英式英语编写的&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/17661/"&gt;全面解析PHP的糟糕设计 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/7033/"&gt;20个PHP开源内容管理系统&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/17814/"&gt;简化PHP开发的11个工具 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="http://blog.jobbole.com/17211/"&gt;提高PHP代码质量的36个技巧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;  &lt;a href="http://blog.jobbole.com/56560/"&gt;成为一个PHP专家：缺失的环节&lt;/a&gt;，首发于  &lt;a href="http://blog.jobbole.com"&gt;博客 - 伯乐在线&lt;/a&gt;。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>IT职场 PHP php</category>
      <guid isPermaLink="true">https://itindex.net/detail/48286-php-%E4%B8%93%E5%AE%B6</guid>
      <pubDate>Thu, 27 Feb 2014 01:26:28 CST</pubDate>
    </item>
    <item>
      <title>Php session内部执行流程的再次剖析</title>
      <link>https://itindex.net/detail/45086-php-session-%E5%86%85%E9%83%A8</link>
      <description>&lt;p&gt;  &lt;strong&gt;标签：&lt;/strong&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=session" target="_blank"&gt;session&lt;/a&gt;&lt;/p&gt; &lt;p&gt;    近期再次分析了php session内部的执行流程，我将在这篇文章中简要地概括出php内部关于session的执行步骤。  &lt;br /&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    首先php中的session其实就是作为一个扩展载入到php内核中的。我们可以将它理解成一个扩展就可以了。当session扩展被载入时，php会调用内部核心函数来获取处理session的save_handler - 也就是存储读取session数据的接口类或者函数。 Php默认地是通过写或者读取文件来处理session数据的。但是，php也提供了user自定义的方式 - 也就是自定义处理session数据的接口，可以通过session_set_save_handler函数来注册。关于这方面，后续我会详细写一篇文章。同时，php会判断session.auto_start是否已经在配置中默认开启。如果开启了session.auto_start，PHP便会调用内部函数自动开启Session功能。以上，就是session扩展被载入时php内部所处理的两件事情。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    接下来，php在启动session的时候，如果发现请求的Cookies,Get,Post中不存在session id,说明这是客户端的第一次访问，php会自动调用php_session_create_id函数创建一个唯一的session id,并且在http response中通过set-cookie头部发送给客户端保存(在客户端cookie被禁用的情况下，php也可以自动将session id添加到url参数中以及form的hidden 字段中,这需要将php.ini中的session.use_trans_sid设为开启，也可以在运行时调用ini_set来设置这个配置项)。相反，如果请求中已经携带了session id,那么php会做以下几件事情:&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    从cookie中获取Session ID&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    调用save_handler的open接口打开存储上下文&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    如果读取不到对应的session id, 生成新的Session Id&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    注册$_SESSION和$_HTTP_SESSION_VARS全局变量，$_SESSION和$_HTTP_SESSION_VARS会被注册为同一个数组&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    接着调用save_handler的read接口读取Session数据，如果是使用files方式存储的话，就从文件中读取Session数据，数据库方式的话，就从数据库中读取。 读取完毕后会把读到的数据写入到$_SESSION数组中&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    最后，当一个请求执行完毕时，php会调用内部函数获取$_SESSION数组中的值，然后调用php_session_encode将其系列化后，通过调用save_handler的write接口将session系列化数据存储起来。&lt;/p&gt;&lt;/li&gt;  &lt;p&gt;    以上大致按照顺序列出了php session的内部执行流程。至于php的源代码，我想就不贴出来了。&lt;/p&gt;&lt;/ul&gt;
				 &lt;p&gt;  &lt;strong&gt;您可能还对下面的文章感兴趣：&lt;/strong&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6085" target="_blank"&gt;浅析http协议、cookies和session机制、浏览器缓存&lt;/a&gt; [2012-12-11 21:57:16]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6004" target="_blank"&gt;你必须了解的Session的本质&lt;/a&gt; [2012-11-13 13:52:34]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=4788" target="_blank"&gt;如何设置一个严格30分钟过期的Session&lt;/a&gt; [2012-01-16 00:02:50]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=4484" target="_blank"&gt;在Express和Socket.IO中使用session&lt;/a&gt; [2011-10-14 13:52:02]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=4319" target="_blank"&gt;PHP Session学习笔记&lt;/a&gt; [2011-09-13 23:30:00]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=3684" target="_blank"&gt;IFrame带来的Session问题&lt;/a&gt; [2011-06-01 23:34:12]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=3520" target="_blank"&gt;深入理解PHP原理之Session Gc的一个小概率Notice&lt;/a&gt; [2011-04-02 14:13:20]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=477" target="_blank"&gt;(oracle)11g与10g中alter session权限差异&lt;/a&gt; [2009-11-08 17:07:07]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=375" target="_blank"&gt;PHP Session的一个警告&lt;/a&gt; [2009-10-29 21:31:40]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=268" target="_blank"&gt;关于session和memcache的若干问题&lt;/a&gt; [2009-10-21 22:15:16]&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt; &lt;img height="1" src="http://feeds.feedburner.com/~r/blogreadIT/~4/d4W6YyrRgIw" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>PHP</category>
      <guid isPermaLink="true">https://itindex.net/detail/45086-php-session-%E5%86%85%E9%83%A8</guid>
      <pubDate>Wed, 31 Jul 2013 13:33:06 CST</pubDate>
    </item>
    <item>
      <title>PHP比你想象的好得多</title>
      <link>https://itindex.net/detail/39775-php-%E6%83%B3%E8%B1%A1</link>
      <description>&lt;p&gt;英文原文：  &lt;a href="http://fabien.potencier.org/article/64/php-is-much-better-than-you-think"&gt;PHP is much better than you think&lt;/a&gt;  ，翻译：  &lt;a href="http://alexzhan.com/php/2012/09/11/php-is-much-better-than-you-think/"&gt; alex zhan&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;有很多对于PHP的抱怨，甚至这些抱怨也出自很多聪明的人。当Jeff Atwood写下对于PHP的另一篇抱怨  &lt;a href="http://www.codinghorror.com/blog/2012/06/the-php-singularity.html"&gt;文章&lt;/a&gt;之后，我思考了下PHP的好的方面。&lt;/p&gt;
 &lt;p&gt;这些抱怨最大的问题是他们出自很多仍在使用旧版本PHP的人。他们或许是不愿意关心或许是不愿意承认PHP不管在语言层面还是在社区层面都在以很快的速度演变。实际上它比任何其他语言或者web平台都演变的快。尽管并不总是如此，但是过去的五年PHP经历了一个惊人的历程。&lt;/p&gt;
 &lt;p&gt;在说最近PHP社区取得的惊人成就之前，我们先来看看一些有趣的数字：PHP被77.9%的服务端  &lt;a href="http://blog.jobbole.com/tag/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/" title="&amp;#22914;&amp;#20309;&amp;#36873;&amp;#25321;&amp;#35821;&amp;#35328;&amp;#21644;&amp;#32534;&amp;#31243;&amp;#35821;&amp;#35328;&amp;#25490;&amp;#21517;&amp;#30456;&amp;#20851;&amp;#25991;&amp;#31456;"&gt;编程语言&lt;/a&gt;已知的网站使用。Wordpress被全世界16.6%的网站使用。使用率最高的三个CMS建站系统是：第一的Wordpress份额为54.3%，第二的Joomla份额为9.2%，第三的Drupal份额为6.8%。这三个产品都是用PHP写的。&lt;/p&gt;
 &lt;p&gt;PHP一定做了一些正确的事，不是吗？&lt;/p&gt;
 &lt;p&gt;现在，我来告诉你吧，PHP的绝技在于：尽管经过了这么多年的变化，PHP对于非技术人员依然是最容易学习的语言，它让人可以比其他技术更快地建立动态网站，也让人没有麻烦地托管网站。PHP可能不是这个世界上设计最好的语言，但是它能让你完成事情(get things done)，这一点是毋庸置疑的。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;PHP语言&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;PHP5.0（2004年发布）带来了很实用的对象模型…等等，我在说8年前发布的东西。快进到现在的PHP5.4，即PHP最近的版本，带来了对于现代web语言你梦寐以求的东西：是的，PHP支持了命名空间(namespaces)；是的，PHP支持闭包(closure)；是的，PHP支持traits。&lt;/p&gt;
 &lt;p&gt;尽管需要花费一些时间，但是PHP5.4带来了一些语法糖使得整体体验比以往更好：是的，PHP支持用[ ]定义数组；是的，PHP支持新创建的对象这样调用函数：(new Foo())-&amp;gt;bar()；是的，PHP支持数组这样获取元素：$foo-&amp;gt;bar()[1]。&lt;/p&gt;
 &lt;p&gt;PHP甚至向它自己曾犯过的错误学习：register_globals 和 magic_quotes被彻底删除了。&lt;/p&gt;
 &lt;p&gt;PHP有了内置web服务器以方便本地测试，它能以微秒级的速度启动。&lt;/p&gt;
 &lt;p&gt;接下来的挑战:我们怎样更新在网络上的讲解PHP的教程？在PHP程序中最好的支持WebSocket的技术是什么？&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;PHP生态系统&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;拥有一个好的语言是很好的，但是拥有一个好的生态系统更棒。在过去的几年PHP生态系统演变了很多。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Git&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;对于Git我不想讨论太多，Git被到处使用，PHP很快拥抱了Git。几乎所有PHP类库、框架和产品都在使用Git，包括PHP本身。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Composer&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;两年前，我想去掉我在symfony 1中hack的丑陋PEAR代码以支持插件。我想替换成能管理项目依赖的东西，而不是一个像PEAR一样的整体的安装，所以我试着寻找能管理软件依赖的最佳的算法。我几乎尝试了所有可能：从Perl到Ruby，从Debian到Redhat。结果没有让我满意的，只有我自己的解决方案恰巧能工作…当然这只是我的经验只谈。之后我偶然发现了  &lt;a href="http://en.wikipedia.org/wiki/ZYpp"&gt;ZYpp&lt;/a&gt;，就是它了。ZYpp使用  &lt;a href="http://en.wikipedia.org/wiki/Boolean_satisfiability_problem"&gt;布尔可满足性问题&lt;/a&gt;解来管理依赖。多亏了  &lt;a href="http://www.naderman.de/"&gt;Nils Adermann&lt;/a&gt;和  &lt;a href="http://seld.be/"&gt;Jordi Boggiano&lt;/a&gt;的辛苦工作，PHP现在有了做好的管理依赖的工具–  &lt;a href="http://getcomposer.org/"&gt;Composer&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;是的，PHP比其他语言有了更好的依赖管理工具。&lt;/p&gt;
 &lt;p&gt;由于有了Git，Composer，和PHP内置web服务器，我们更容易下载/测试/安装一个PHP项目。&lt;/p&gt;
 &lt;p&gt;想测试Symfony（使用PHP5.4）？&lt;/p&gt;
 &lt;pre&gt;$ composer.phar create-project symfony/framework-standard-edition
$ cd framework-standard-edition
$ ./app/console server:run&lt;/pre&gt;
 &lt;p&gt;想测试Silex？&lt;/p&gt;
 &lt;pre&gt;$ composer.phar create-project fabpot/silex-skeleton
$ cd silex-skeleton
$ php -S localhost:8888 -t web/&lt;/pre&gt;
 &lt;p&gt;还不知道Composer？你应该了解下它了。&lt;/p&gt;
 &lt;p&gt;浏览下主要的Composer仓库  &lt;a href="http://packagist.org/packages/"&gt;Packagist&lt;/a&gt;，它已经拥有1900多个包，且它们在不到三个月的时间里被安装了上百万次。&lt;/p&gt;
 &lt;p&gt;接下来的挑战：在下一个PHP版本里内置Composer？&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;合作&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;社区合作是本文说的重点，也是我最引以为豪的地方。我们开始看到PHP项目中更好的合作，甚至大项目也是如此，大到你可以忽略其他项目了。&lt;/p&gt;
 &lt;p&gt;phpBB，Drupal，ez Publish，Symfony，和很多其他项目（比如phpDocumentor, PHPUnit, Behat, Zikula, Propel, Doctrine, Midgard等等）都在共享代码。是的，他们彼此是竞争者，但是他们都理解彼此合作是很重要的。Composer能很好地促进这种合作。&lt;/p&gt;
 &lt;p&gt;接下来的挑战：说服更多的项目加入这个趋势中来。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;结论&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;让我再重申一次，PHP可能不是最好的编程语言，我也是第一个说出它的怪处的，但是PHP是迄今为止最好的web平台。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h2&gt;相关文章&lt;/h2&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/26519/" title="&amp;#21016;&amp;#26133;&amp;#26126;&amp;#65306;&amp;#36865;&amp;#32473;&amp;#21644;&amp;#25105;&amp;#19968;&amp;#26679;&amp;#26366;&amp;#32463;&amp;#28014;&amp;#36481;&amp;#36807;&amp;#30340;PHP&amp;#31243;&amp;#24207;&amp;#21592;"&gt;刘昕明：送给和我一样曾经浮躁过的PHP程序员&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/25276/" title="EvaCloudImage&amp;#65306;&amp;#22522;&amp;#20110;URL&amp;#29983;&amp;#25104;&amp;#32553;&amp;#30053;&amp;#22270;&amp;#30340;&amp;#36731;&amp;#37327;&amp;#32423;PHP&amp;#24211;"&gt;EvaCloudImage：基于URL生成缩略图的轻量级PHP库&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/25010/" title="PHP&amp;#36229;&amp;#26102;&amp;#22788;&amp;#29702;&amp;#20840;&amp;#38754;&amp;#24635;&amp;#32467;"&gt;PHP超时处理全面总结&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/23773/" title="&amp;#38472;&amp;#30355;&amp;#65306;&amp;#20195;&amp;#30721;&amp;#25191;&amp;#34892;&amp;#30340;&amp;#25928;&amp;#29575;"&gt;陈皓：代码执行的效率&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/23559/" title="&amp;#21019;&amp;#24314;&amp;#23433;&amp;#20840;PHP&amp;#24212;&amp;#29992;&amp;#31243;&amp;#24207;&amp;#30340;&amp;#23454;&amp;#29992;&amp;#24314;&amp;#35758;"&gt;创建安全PHP应用程序的实用建议&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/18304/" title="&amp;#21490;&amp;#19978;&amp;#26368;&amp;#31967;&amp;#31957;&amp;#30340;&amp;#20004;&amp;#20010;&amp;#21464;&amp;#37327;&amp;#21517;"&gt;史上最糟糕的两个变量名&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/19262/" title="&amp;#27973;&amp;#35848;PHP&amp;#20195;&amp;#30721;&amp;#35774;&amp;#35745;&amp;#32467;&amp;#26500;"&gt;浅谈PHP代码设计结构&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/18563/" title="&amp;#22914;&amp;#20309;&amp;#20026; PHP &amp;#36129;&amp;#29486;&amp;#20195;&amp;#30721;"&gt;如何为 PHP 贡献代码&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/14213/" title="&amp;#24448;&amp;#36820;&amp;#35835;&amp;#21462;&amp;#21518;&amp;#21488;&amp;#25968;&amp;#25454;&amp;#30340;&amp;#20195;&amp;#20215;"&gt;往返读取后台数据的代价&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://blog.jobbole.com/16390/" title="&amp;#21578;&amp;#21035;&amp;#32534;&amp;#31243;5&amp;#24180;&amp;#20877;&amp;#27425;&amp;#22238;&amp;#24402;&amp;#65292;&amp;#25105;&amp;#27880;&amp;#24847;&amp;#21040;&amp;#24456;&amp;#22810;&amp;#21464;&amp;#21270;"&gt;告别编程5年再次回归，我注意到很多变化&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序员 php</category>
      <guid isPermaLink="true">https://itindex.net/detail/39775-php-%E6%83%B3%E8%B1%A1</guid>
      <pubDate>Wed, 19 Sep 2012 20:24:23 CST</pubDate>
    </item>
    <item>
      <title>【外刊IT评论网】PHP 5.4 内置web服务器</title>
      <link>https://itindex.net/detail/39689-it-php-web</link>
      <description>&lt;br /&gt; &lt;p&gt;  &lt;a href="http://wkee.net/qee/wordpress/wp-content/uploads/2012/09/120912085422890854918.png"&gt;   &lt;img alt="php" height="157" src="http://wkee.net/qee/wordpress/wp-content/uploads/2012/09/120912085422890854918-300x157.png" title="php" width="300"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;PHP是一种脚本语言，它需要PHP解释器来分析运行PHP文件。当把PHP做为CGI服务Web请求时，它需要被嵌入到某种Web服务器里，最常见的是集成到Apache或ISS里，这就是说，在使用PHP前，你需要安装Apache或ISS，并且正确的配置它们和PHP集成的参数。虽然这种配置已经很规范，文档非常丰富，但我们还是经常在安装Apache和PHP集成时遇到问题，而且，有时候我们只想测试一个简单的PHP特征，不想就为此安装、启动Apache服务。&lt;/p&gt;  &lt;p&gt;但据官方文档上说，这个内置的Web服务器只是提供开发测试使用，不推荐使用中生产环境中。因为这个服务器接受处理请求时顺序执行的，不能并发处理。&lt;/p&gt;  &lt;p&gt;这个内置的web服务器使用起来非常的方便，你只需要执行下面的命令：&lt;/p&gt;  &lt;pre&gt;$ php -S localhost:8000&lt;/pre&gt;  &lt;p&gt;然后就可以访问了。这样启动后，默认的web服务目录是执行命令的当前目录，如果不想使用当前目录，你需要使用 -t 参数来指定。&lt;/p&gt;  &lt;h3&gt;例 #1 启动Web服务器&lt;/h3&gt;  &lt;pre&gt;$ cd ~/public_html $ php -S localhost:8000&lt;/pre&gt;  &lt;p&gt;终端输出信息：&lt;/p&gt;  &lt;pre&gt;PHP 5.4.0 Development Server started at Thu Jul 21 10:43:28 2011 Listening on localhost:8000 Document root is /home/me/public_html Press Ctrl-C to quit&lt;/pre&gt;  &lt;p&gt;当请求了 http://localhost:8000/ 和 http://localhost:8000/myscript.html 地址后，终端输出类似如下的信息：&lt;/p&gt;  &lt;pre&gt;PHP 5.4.0 Development Server started at Thu Jul 21 10:43:28 2011 Listening on localhost:8000 Document root is /home/me/public_html Press Ctrl-C to quit. [Thu Jul 21 10:48:48 2011] ::1:39144 GET /favicon.ico - Request read [Thu Jul 21 10:48:50 2011] ::1:39146 GET / - Request read [Thu Jul 21 10:48:50 2011] ::1:39147 GET /favicon.ico - Request read [Thu Jul 21 10:48:52 2011] ::1:39148 GET /myscript.html - Request read [Thu Jul 21 10:48:52 2011] ::1:39149 GET /favicon.ico - Request read&lt;/pre&gt;  &lt;h3&gt;例 #2 启动web服务器时指定文档的根目录&lt;/h3&gt;  &lt;pre&gt;$ cd ~/public_html $ php -S localhost:8000 -t foo/&lt;/pre&gt;  &lt;p&gt;终端显示信息：&lt;/p&gt;  &lt;pre&gt;PHP 5.4.0 Development Server started at Thu Jul 21 10:50:26 2011 Listening on localhost:8000 Document root is /home/me/public_html/foo Press Ctrl-C to quit&lt;/pre&gt;  &lt;p&gt;如果你在启动命令行后面附加一个php脚本文件，那这个文件将会被当成一个“路由器”脚本。这个脚本将负责所有的HTTP请求，如果这个脚本执行时返回FALSE，则被请求的资源会正常的返回。如果不是FALSE，浏览里显示的将会是这个脚本产生的内容。&lt;/p&gt;  &lt;h3&gt;例 #3 使用路由器脚本&lt;/h3&gt;  &lt;p&gt;在这个例子中，对图片的请求会返回相应的图片，但对HTML文件的请求会显示“Welcome to PHP”：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;   &lt;code&gt;    &lt;br /&gt; &amp;lt;?php    &lt;br /&gt; // router.php    &lt;br /&gt; if (preg_match(&amp;apos;/\.(?:png|jpg|jpeg|gif)$/&amp;apos;, $_SERVER[&amp;quot;REQUEST_URI&amp;quot;])) {    &lt;br /&gt; return false;    // serve the requested resource as-is.    &lt;br /&gt; } else {    &lt;br /&gt; echo &amp;quot;&amp;lt;p&amp;gt;Welcome to PHP&amp;lt;/p&amp;gt;&amp;quot;;    &lt;br /&gt; }    &lt;br /&gt; ?&amp;gt;    &lt;br /&gt;     &lt;br /&gt; &lt;/code&gt;&lt;/div&gt; &lt;/div&gt;  &lt;pre&gt;$ php -S localhost:8000 router.php&lt;/pre&gt;  &lt;h3&gt;例 #4 判断是否是在使用内置web服务器&lt;/h3&gt;  &lt;p&gt;通过程序判断来调整同一个PHP路由器脚本在内置Web服务器中和在生产服务器中的不同行为：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;   &lt;code&gt;    &lt;br /&gt; &amp;lt;?php    &lt;br /&gt; // router.php    &lt;br /&gt; if (php_sapi_name() == &amp;apos;cli-server&amp;apos;) {    &lt;br /&gt; /* route static assets and return false */    &lt;br /&gt; }    &lt;br /&gt; /* go on with normal index.php operations */    &lt;br /&gt; ?&amp;gt;    &lt;br /&gt;     &lt;br /&gt; &lt;/code&gt;&lt;/div&gt; &lt;/div&gt;  &lt;pre&gt;$ php -S localhost:8000 router.php&lt;/pre&gt;  &lt;p&gt;这个内置的web服务器能识别一些标准的MIME类型资源，它们的扩展有：.css, .gif, .htm, .html, .jpe, .jpeg, .jpg, .js, .png, .svg, and .txt。对.htm 和 .svg 扩展到支持是在PHP 5.4.4之后才支持的。&lt;/p&gt;  &lt;h3&gt;例 #5 处理不支持的文件类型&lt;/h3&gt;  &lt;p&gt;如果你希望这个Web服务器能够正确的处理不被支持的MIME文件类型，这样做：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;   &lt;code&gt;    &lt;br /&gt; &amp;lt;?php    &lt;br /&gt; // router.php    &lt;br /&gt; $path = pathinfo($_SERVER[&amp;quot;SCRIPT_FILENAME&amp;quot;]);    &lt;br /&gt; if ($path[&amp;quot;extension&amp;quot;] == &amp;quot;ogg&amp;quot;) {    &lt;br /&gt; header(&amp;quot;Content-Type: video/ogg&amp;quot;);    &lt;br /&gt; readfile($_SERVER[&amp;quot;SCRIPT_FILENAME&amp;quot;]);    &lt;br /&gt; }    &lt;br /&gt; else {    &lt;br /&gt; return FALSE;    &lt;br /&gt; }    &lt;br /&gt; ?&amp;gt;    &lt;br /&gt;     &lt;br /&gt; &lt;/code&gt;&lt;/div&gt; &lt;/div&gt;  &lt;pre&gt;$ php -S localhost:8000 router.php&lt;/pre&gt;  &lt;p&gt;如果你希望能远程的访问这个内置的web服务器，你的启动命令需要改成下面这样：&lt;/p&gt;  &lt;h3&gt;例 #6 远程访问这个内置Web服务器&lt;/h3&gt;  &lt;pre&gt;$ php -S 0.0.0.0:8000&lt;/pre&gt;  &lt;p&gt;这样你就可以通过 8000 端口远程的访问这个内置的web服务器了&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;本文来自 &lt;a href="http://www.aqee.net"&gt;外刊IT评论网&lt;/a&gt;( &lt;a href="http://www.aqee.net"&gt;www.aqee.net&lt;/a&gt;)，原始地址： &lt;a href="http://www.aqee.net/php-5-4-built-in-web-server/" rel="bookmark"&gt;PHP 5.4 内置web服务器&lt;/a&gt; &lt;br /&gt; &lt;img border="0" height="1" src="http://aqee.feedsportal.com/c/34519/f/631690/s/236428e4/mf.gif" width="1"&gt;&lt;/img&gt; &lt;div&gt;  &lt;table border="0"&gt;   &lt;tr&gt;    &lt;td valign="middle"&gt;     &lt;a href="http://share.feedsportal.com/viral/sendEmail.cfm?lang=en&amp;title=%E3%80%90%E5%A4%96%E5%88%8AIT%E8%AF%84%E8%AE%BA%E7%BD%91%E3%80%91PHP+5.4+%E5%86%85%E7%BD%AEweb%E6%9C%8D%E5%8A%A1%E5%99%A8&amp;link=http%3A%2F%2Fwww.aqee.net%2Fphp-5-4-built-in-web-server%2F" target="_blank"&gt;      &lt;img border="0" src="http://res3.feedsportal.com/images/emailthis2.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/td&gt;    &lt;td valign="middle"&gt;     &lt;a href="http://res.feedsportal.com/viral/bookmark.cfm?title=%E3%80%90%E5%A4%96%E5%88%8AIT%E8%AF%84%E8%AE%BA%E7%BD%91%E3%80%91PHP+5.4+%E5%86%85%E7%BD%AEweb%E6%9C%8D%E5%8A%A1%E5%99%A8&amp;link=http%3A%2F%2Fwww.aqee.net%2Fphp-5-4-built-in-web-server%2F" target="_blank"&gt;      &lt;img border="0" src="http://res3.feedsportal.com/images/bookmark.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt; &lt;br /&gt; &lt;br /&gt; &lt;a href="http://da.feedsportal.com/r/144540381289/u/0/f/631690/c/34519/s/236428e4/a2.htm"&gt;  &lt;img border="0" src="http://da.feedsportal.com/r/144540381289/u/0/f/631690/c/34519/s/236428e4/a2.img"&gt;&lt;/img&gt;&lt;/a&gt; &lt;img border="0" height="1" src="http://pi.feedsportal.com/r/144540381289/u/0/f/631690/c/34519/s/236428e4/a2t.img" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>文档手册 web服务器 php</category>
      <guid isPermaLink="true">https://itindex.net/detail/39689-it-php-web</guid>
      <pubDate>Fri, 14 Sep 2012 00:06:18 CST</pubDate>
    </item>
    <item>
      <title>百万级的架构</title>
      <link>https://itindex.net/detail/39521-%E7%99%BE%E4%B8%87-%E6%9E%B6%E6%9E%84</link>
      <description>&lt;p&gt;在了解过世界最大的PHP站点，Facebook的后台技术后，今天我们来了解一个百万级PHP站点的网站架构：Poppen.de。Poppen.de是德国的一个社交网站，相对Facebook、Flickr来说是一个很小的网站，但它有一个很好的架构，融合了很多技术，如 Nigix、MySql、CouchDB、Erlang、Memcached、RabbitMQ、PHP、Graphite、Red5以及Tsung。&lt;/p&gt;
 &lt;p&gt;Poppen.de目前有200万注册用户数、2万并发用户数、每天20万条私有消息、每天25万登录次数。而项目团队有11个开发人员，两个设计，两个系统管理员。该站点的商业模式采用免费增值模式，用户可以使用搜索用户、给好友发送消息、上载图片和视频等功能。&lt;/p&gt;
 &lt;p&gt;如果用户想享受不受限制发送消息和上载图片，那么就得根据需要支付不同类型的会员服务，视频聊天及网站其他服务也采用同样的策略。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/p/nginx" rel="nofollow" target="_blank"&gt;Nginx&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;Poppen.de 所有的服务都是基于Nginx服务上的。前端有两台Nginx服务器在高峰期提供每分钟15万次请求的负载，每个机器已经有四年寿命，并且只有一个CPU 和3GB RAM。Poppen.de拥有三台独立的图像服务器，由三台Nginx服务器为*.bilder.poppen.de提供每分钟8万次请求服务。&lt;/p&gt;
 &lt;p&gt;Nginx 架构中一个很酷的设计就是有很多请求是由Memcached处理的，因此请求从缓存中获取内容而不需要直接访问PHP机器。比如，用户信息页(user profile)是网站需要密集处理的内容，如果把用户信息页全部缓存到Memcached上，那么请求直接从Memcached上获取内容。 Poppen.de的Memcached每分钟可以处理8000次请求。&lt;/p&gt;
 &lt;p&gt;架构中有三个Nginx图像服务器提供本地图像缓存，用户上载图 像到一个中央文件服务器。当向这三个Nginx之一中请求图像时，如果服务器本地中没有存在该图像，则从中央文件服务器下载到该服务器上作缓存并提供服 务。这种负载均衡的分布式图像服务器架构设计可以减轻主要存储设备的负载。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/p/php-fpm" rel="nofollow" target="_blank"&gt;PHP-FPM&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;该网站运行在PHP- FPM上。共有28台双CPU、6GB内存的PHP机器，每个机器上运行100个PHP-FPM的工作线程。使用启用了APC的PHP5.3.x。 PHP5.3可以降低CPU和内存使用率的30%以上。&lt;/p&gt;
 &lt;p&gt;程序代码是基于Symfony1.2框架之上开发的。一是可以使用外部资源，二是 能够提高项目开发进度，同时在一个著名的框架上可以让新开发人员更容易加入到团队中来。虽然没有任何事情都是十全十美的，但可以从Symfony框架中得 到很多好处，让团队可以更多的精力放在Poppen.de的业务开发上去。&lt;/p&gt;
 &lt;p&gt;网站性能优化使用XHProf，这是Facebook开源出来的一个类库。这个框架非常容易个性化和配置，能够可以缓存大部分高代价的服务器计算。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/p/mysql" rel="nofollow" target="_blank"&gt;MySQL&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;MySQL是网站主要的RDBMS。网站又几个MySql服务器：一台4CPU、32GB的服务器存储用户相关信息，如基本信息、照片描述信息等。这台机器已经使用了4 年，下一步计划会使用共享集群来替换它。目前仍基于这个系统上进行设计，以简化数据访问代码。根据用户ID进行数据分区，因为网站中大部分信息都是以用户 为中心的，如照片、视频、消息等。&lt;/p&gt;
 &lt;p&gt;有三台服务器按主-从-从配置架构提供用户论坛服务。一台从服务器负责网站自定义消息存储，到现在有 2.5亿条消息。另外四台机器为主-从配置关系。另外由4台机器配置成NDB族群专门服务于密集型写操作数据，如用户访问统计信息。&lt;/p&gt;
 &lt;p&gt;数据表设计尽量避免关联操作，尽可能缓存最多的数据。当然，数据库的结构化规范已经完全被破坏掉了。因此，为了更容易搜索，数据库设计创建了数据挖掘表。大部分表是MyISAM型表，可以提供快速查找。现在的问题是越来越多的表已经全表锁住了。Poppen.de正考虑往XtraDB存储引擎上迁移。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/p/memcached" rel="nofollow" target="_blank"&gt;Memcached&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;网站架构中Memcached应用相当多，超过45GB的高速缓存和51个节点。缓存了Session会话、视图缓存以及函数执行缓存等。架构中有一个系统 当记录被修改时可以自动地把数据更新到缓存中去。未来改善缓存更新的可能方案是使用新的Redis Hash API或者MongoDB。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/p/rabbitmq" rel="nofollow" target="_blank"&gt;RabbitMQ&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在 2009年中开始在架构中使用RabbitMQ。这是一个很好的消息解决方案，便于部署和集中到这个架构中去，在LVS后运行了两台RabbitMQ服务 器。在上个月，已经把更多的东西集成到该队列中，意味着同一时刻有28台PHP服务器每天要处理50万次请求。发送日志、邮件通知、系统消息、图像上载等 更多的东西到这个队列中。&lt;/p&gt;
 &lt;p&gt;应用PHP-FPM中的fastcgi_finish_request()函数集成队列消息，可以把消息异步发 送到队列中。当系统需要给用户发送HTML或JSON格式响应时，就调用这个函数，这样用户就没有必要等到PHP脚本清理。&lt;/p&gt;
 &lt;p&gt;这个系统可以改善架构资源管理。例如，在高峰期服务每分钟可以处理1000次登录请求。这表示有1000并发更新用户表保存用户的登录时间。由于使用了队列机制，可以 按相反的顺序来运行这些查询。如果需要提高处理速度，只需要增加更多的队列处理者即可，甚至可以增加更多的服务器到这集群中去，而不需要修改任何配置和部 署新节点。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/p/couchdb" rel="nofollow" target="_blank"&gt;CouchDB&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;日志存储CouchDB运行在一台机器上。在这台机器上可以根据模块/行为进行日志查询 /分组，或者根据错误类型等等。这对定位问题非常有用。在使用日志聚合服务CouchDB之前，不得不逐台登录到PHP服务器上设法日志分析定位问题，这 是非常麻烦的。而现在把所有的日志集中到队列中保存到CouchDB中，可以集中进行问题检查和分析。&lt;/p&gt;
 &lt;p&gt;Graphite&lt;/p&gt;
 &lt;p&gt;网站使用Graphite采集网站实时信息并统计。从请求每个模块/行为到Memcached的命中和未命中、RabbitMQ状态监控以及Unix负载等等。Graphite服务平均每分钟有4800次更新操作。实践已经证实要监测网站发发生什么是非常有用的，它的简单文本协议和绘图功能可以方便地即插即 用的方式用于任何需要监控的系统上。&lt;/p&gt;
 &lt;p&gt;一件很酷的事情是使用Graphite同时监控了网站的两个版本。一月份部署了Symfony框架新 版本，以前代码作为一个备份部署。这就意味着网站可能会面临性能问题。因此可以使用Graphite来对两个版本在线进行对比。&lt;/p&gt;
 &lt;p&gt;发现新版本上的Unix负载表较高，于是使用  &lt;a href="http://www.oschina.net/p/xhprof" rel="nofollow" target="_blank"&gt;XHProf&lt;/a&gt;对两个版本进行性能分析，找出问题所在。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/p/red5" rel="nofollow" target="_blank"&gt;Red5&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;网站为用户也提供了两种类型的视频服务，一种是用户自己上载的视频，另外一种是视频聊天，用户视频互动和分享。到2009年年中，每月为用户提供17TB的流量服务。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/p/tsung" rel="nofollow" target="_blank"&gt;Tsung&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;Tsung 是一个  &lt;a href="http://www.oschina.net/p/erlang" rel="nofollow" target="_blank"&gt;Erlang&lt;/a&gt;编写的分布式基准分析工具。在Poppen.de网站中主要用于HTTP基准分析、MySQL与其他存储系统(XtraDB)的对比分 析。用一个系统记录了主要的MySQL服务器的流量，再转换成Tsung的基准会话。然后对该流量进行回放，由Tsung产生数以千计的并发用户访问实验 室的服务器。这样就可以在实验环境中与真实场景非常接近。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.oschina.net/question/54100_35417"&gt;http://www.oschina.net/question/54100_35417&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>PHP技术 Web技术 转载 运维学习</category>
      <guid isPermaLink="true">https://itindex.net/detail/39521-%E7%99%BE%E4%B8%87-%E6%9E%B6%E6%9E%84</guid>
      <pubDate>Wed, 22 Aug 2012 15:43:07 CST</pubDate>
    </item>
    <item>
      <title>PHP最佳实践：MySQL的连接</title>
      <link>https://itindex.net/detail/51933-php-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5-mysql</link>
      <description>&lt;p&gt;  &lt;strong&gt;标签：&lt;/strong&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=MySQL%E8%BF%9E%E6%8E%A5" target="_blank"&gt;MySQL连接&lt;/a&gt;&lt;/p&gt; &lt;p&gt;从PHP 5.5版本开始，  &lt;a href="http://www.php.net/manual/zh/ref.mysql.php"&gt;mysql函数&lt;/a&gt;将被官方废弃，即所有 mysql_* 格式函数 将在5.5版本后当产生一个   &lt;strong&gt;   &lt;code&gt;E_DEPRECATED&lt;/code&gt;&lt;/strong&gt; 错误。废弃mysql函数的主要原因为：此函数为 的MySQL  3.23版本开发的，而目前的MySQL版本已经到了 5.6，中间产生了非常多的特性没有被函数所支持。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;1、预处理语句（Prepared statements）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;许多成熟的数据库都支持预处理语句（Prepared Statements)的概念。你可以把它们想成是一种编译过的要执行的SQL语句模板，可以使用不同的变量参数定制它。预处理语句具有两个主要的优点：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;查询只需要被解析（或准备）一次，但可以使用相同或不同的参数执行多次。当查询准备好（Prepared）之后，数据库就会分析，编译并优化它要执行查询的计划。对于复杂查询来说，如果你要重复执行许多次有不同参数的但结构相同的查询，这个过程会占用大量的时间，使得你的应用变慢。通过使用一个预处理语句你就可以避免重复分析、编译、优化的环节。简单来说，预处理语句使用更少的资源，执行速度也就更快。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;Prepared Statements通过sql逻辑与数据的分离来增加安全，sql逻辑与数据的分离能防止普通类型的sql注入攻击。传给预处理语句的参数不需要使用引号，底层驱动会为你处理这个。如果你的应用独占地使用预处理语句，你就可以确信没有SQL注入会发生。（如果你仍然在用基于不受信任的输入来构建查询的其他部分，这仍然是具有风险的）。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;从 5.1开始，mysql支持服务器端的Prepared Statements，MySQL prepare语法：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;PREPARE &lt;/strong&gt;statement_name     &lt;strong&gt;FROM&lt;/strong&gt; preparable_SQL_statement; /*定义*/&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;EXECUTE&lt;/strong&gt; statement_name [    &lt;strong&gt;USING&lt;/strong&gt;     &lt;em&gt;@var_name [, @var_name] …];&lt;/em&gt; /*执行预处理语句*/&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;{DEALLOCATE | DROP} PREPARE&lt;/strong&gt; statement_name /*删除定义*/ ;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;strong&gt;2、事务（Transactions）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;事务是操作数据的一个单元，是恢复和并发控制的基本单位。例如，用户A给用户B通过ATM机转账1000元，那么A账户上就会少1000元，而B用户会多1000元。在这个过程中，两个环节是关联的。第一个账户划出款项必须保证正确的存入第二个账户，如果第二个环节没有完成，整个的过程都应该取消，否则就会发生丢失款项的问题。整个交易过程，可以看作是一个事物，成功则全部成功，失败则需要全部撤消，这样可以避免当操作的中间环节出现问题时，产生数据不一致的问题。使用数据库事务可以确保除事务性单元内的所有操作都成功完成。MySQL中的InnoDB引擎的表才支持transaction，如果是MyISAM引擎的表暂不支持。&lt;/p&gt; &lt;p&gt;数据库事务的特性：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;原子性：事务必须是原子工作单元；对于其数据修改，要么全都执行，要么全都不执行。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;一致性：事务在完成时，必须使所有的数据都保持一致状态。在相关数据库中，所有规则都必须应用于事务的修改，以保持所有数据的完整性。事务结束时，所有的内部数据结构（如B树索引或双向链表）都必须是正确的。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;隔离性：由发并事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态，要么是另一并发事务修改它之前的状态，要么是另一事务修改它之后的状态，事务不会查看中间状态的数据。这称为可串行性，因为它能够重新装载起始数据，并且重播一系列事务，以使数据结束时的状态与原始事务执行的状态相同。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;持久性：事务完成之后，它对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;3、存储过程（Stored procedures）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;储存过程是一组为了完成特定功能的SQL语句集，经过编译之后存储在数据库中，当需要使用该组SQL语句时用户只需要通过指定储存过程的名字并给定参数就可以调用执行它了，简而言之就是一组已经写好的命令，需要使用的时候拿出来用就可以了。&lt;/p&gt; &lt;p&gt;储存过程是一个可编程的函数，它在数据库中创建并保存。它可以有SQL语句和一些特殊的控制结构组成。当希望在不同的应用程序或平台上执行相同的函数，或者封装特定功能时，存储过程是非常有用的。数据库中的存储过程可以看做是对编程中面向对象方法的模拟。它允许控制数据的访问方式。&lt;/p&gt; &lt;p&gt;存储过程的优点：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;存储过程能实现较快的执行速度。如果某一操作包含大量的Transaction-SQL代码或分别被多次执行，那么存储过程要比批处理的执行速度快很多。因为存储过程是预编译的。在首次运行一个存储过程时查询，优化器对其进行分析优化，并且给出最终被存储在系统表中的执行计划。而批处理的Transaction-SQL语句在每次运行时都要进行编译和优化，速度相对要慢一些。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;存储过程允许标准组件是编程。存储过程被创建后，可以在程序中被多次调用，而不必重新编写该存储过程的SQL语句。而且数据库专业人员可以随时对存储过程进行修改，对应用程序源代码毫无影响。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;存储过程可以用流控制语句编写，有很强的灵活性，可以完成复杂的判断和较复杂的运算。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;存储过程可被作为一种安全机制来充分利用。系统管理员通过执行某一存储过程的权限进行限制，能够实现对相应的数据的访问权限的限制，避免了非授权用户对数据的访问，保证了数据的安全。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;存储过程能过减少网络流量。针对同一个数据库对象的操作（如查询、修改），如果这一操作所涉及的Transaction-SQL语句被组织程存储过程，那么当在客户计算机上调用该存储过程时，网络中传送的只是该调用语句，从而大大增加了网络流量并降低了网络负载。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;存储过程的缺点&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;预存程序的性能调校与撰写，受限于各种数据库系统。MySQL没有提供很好的开发和调试工具，这点SQL Server做的更好；&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;可用函数有限，很难写出非常复杂的查询；&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;预存程序，往往定制化于特定的数据库上，因为支持的编程语言不同。当切换到其他厂商的数据库系统时，需要重写原有的预存程序。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;目前还无法确定在高并发(百万或者千万级)下存储过程是否会严重影响数据库性能。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;strong&gt;4、异步查询（Asynchronous queries）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;对于服务器来说，最好的情况就是IO不被阻塞（non-blocking），这样才能充分利用带宽。缺省情况下，mysql使用的是同步操作，如果一个查询在服务器上需要花费10秒钟，你调用函数就会阻塞10秒钟。为了使用异步的功能，我们需要在建立链接或者在prepare的时候加上async选项。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;5、多语句执行（Multiple statements）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;通常情况MySQL出于安全考虑不允许一次执行多条语句。MySQL 5.1支持在单个查询字符串中指定的多语句的执行。要想与给定的连接一起使用该功能，打开连接时，必须将标志参数中的选项指定。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;使用PDO_MYSQL链接数据库&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;要实现以上的特性，就需要用到新的  &lt;a href="http://us3.php.net/manual/zh/book.mysqli.php"&gt;MySQLi&lt;/a&gt; 或   &lt;a href="http://us3.php.net/manual/zh/ref.pdo-mysql.php"&gt;PDO_MySQL&lt;/a&gt; 扩展作为替代。在这里要推荐的是PDO_MySQL。&lt;/p&gt; &lt;p&gt;PDO - PHP Data Objects - 是一个对多种数据库提供统一操作方法的数据库访问层。它并不具备数据库特有的语法，但它将使切换数据库和平台更加容易，多数情况下，只需要简单修改链接字符串。此扩展可以使用 PDO 驱动编写过的所有数据库。下面的数据库支持已经实现：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;PDO_DBLIB ( FreeTDS / Microsoft SQL Server / Sybase )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_FIREBIRD ( Firebird/Interbase 6 )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_IBM ( IBM DB2 )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_INFORMIX ( IBM Informix Dynamic Server )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_MYSQL ( MySQL 3.x/4.x/5.x )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_OCI ( Oracle Call Interface )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_ODBC ( ODBC v3 (IBM DB2, unixODBC and win32 ODBC) )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_PGSQL ( PostgreSQL )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_SQLITE ( SQLite 3 and SQLite 2 )&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PDO_4D ( 4D )&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;PHP 数据对象 （PDO） 扩展为PHP访问数据库定义了一个轻量级的一致接口。实现 PDO 接口的每个数据库驱动可以公开具体数据库的特性作为标准扩展功能。 PDO 提供了一个 数据访问 抽象层，这意味着，不管使用哪种数据库，都可以用相同的函数（方法）来查询和获取数据。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;PHP PDO的目标是：&lt;/strong&gt;提供一种轻型、清晰、方便的 API ，统一各种不同 RDBMS 库的共有特性，但不排除更高级的特性。 通过 PHP 脚本提供可选的较大程度的抽象/兼容性。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;PHP PDO类的特点：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;性能。PDO 从一开始就吸取了现有数据库扩展成功和失败的经验教训。因为 PDO 的代码是全新的，所以我们有机会重新开始设计性能，以利用 PHP 5 的最新特性。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;能力。PDO 旨在将常见的数据库功能作为基础提供，同时提供对于 RDBMS 独特功能的方便访问。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;简单。PDO 旨在使您能够轻松使用数据库。API 不会强行介入您的代码，同时会清楚地表明每个函数调用的过程。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;运行时可扩展。PDO 扩展是模块化的，使您能够在运行时为您的数据库后端加载驱动程序，而不必重新编译或重新安装整个 PHP 程序。例如，PDO_OCI 扩展会替代 PDO 扩展实现 oracle 数据库 API。还有一些用于 MySQL、PostgreSQL、ODBC 和 Firebird 的驱动程序，更多的驱动程序尚在开发。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;参考资料：  &lt;a href="http://www.php.net/manual/zh/book.pdo.php"&gt;http://www.php.net/manual/zh/book.pdo.php&lt;/a&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;strong&gt;您可能还对下面的文章感兴趣：&lt;/strong&gt;&lt;/p&gt;
				 &lt;p&gt;很抱歉，暂时没有...... &lt;/p&gt; &lt;img height="1" src="http://feeds.feedburner.com/~r/blogreadIT/~4/79-hbwlJiIw" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>PHP</category>
      <guid isPermaLink="true">https://itindex.net/detail/51933-php-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5-mysql</guid>
      <pubDate>Wed, 26 Nov 2014 07:16:00 CST</pubDate>
    </item>
    <item>
      <title>LogicalDOC 6.8.4 发布，文档管理系统</title>
      <link>https://itindex.net/detail/49566-logicaldoc-%E6%96%87%E6%A1%A3-%E7%AE%A1%E7%90%86</link>
      <description>&lt;p&gt;  &lt;div&gt;    &lt;img alt="LogicalDOC 6.8.4 &amp;#21457;&amp;#24067;&amp;#65292;&amp;#25991;&amp;#26723;&amp;#31649;&amp;#29702;&amp;#31995;&amp;#32479;" src="http://shellsec.qiniudn.com/wp-content/uploads/2014/05/20140513_53716fa4d7560.gif"&gt;&lt;/img&gt; &lt;/div&gt;&lt;/p&gt; &lt;p&gt;LogicalDOC 6.8.4 发布，此版本更新内容如下：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;启用了 Vietnamese 的 GUI 本地化；&lt;/p&gt; &lt;/li&gt;   &lt;li&gt;    &lt;p&gt;记录文件夹的  &amp;quot;zip export&amp;quot; 事件；&lt;/p&gt; &lt;/li&gt;   &lt;li&gt;    &lt;p&gt;文件夹搜索现在 case-insenstive；&lt;/p&gt; &lt;/li&gt;   &lt;li&gt;    &lt;p&gt;修复了 Ticket 下载（文件名，编码 IE 11）；&lt;/p&gt; &lt;/li&gt;   &lt;li&gt;    &lt;p&gt;修复了移动文档到文件夹，不需要读权限；&lt;/p&gt; &lt;/li&gt;   &lt;li&gt;    &lt;p&gt;修复了文档预览只能看到第一页的问题。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;     LogicalDOC是一个采用Java开发的基于网页的文档管理系统，           易于使用和学习。它利用最佳的Java技术，实现了强大而灵活的解决方案。为用户提供了强大的搜索引擎（基于Lucene），（通过CXF的JAX-WS）Web服务接口。提供 .NET和PHP版本，附带论坛和WebDAV的接口，支持文档导入导出到ZIP文件和邮箱文件。文件夹可分层次组织，搜索使用集成搜索引擎，或通过标签浏览。通过插件体系可方便进行功能扩展。     &lt;/p&gt;  &lt;p&gt;   &lt;img alt="LogicalDOC 6.8.4 &amp;#21457;&amp;#24067;&amp;#65292;&amp;#25991;&amp;#26723;&amp;#31649;&amp;#29702;&amp;#31995;&amp;#32479;" src="http://shellsec.qiniudn.com/wp-content/uploads/2014/05/20140513_53716fa669534.jpg"&gt;&lt;/img&gt; &lt;/p&gt;  &lt;table border="0" cellpadding="3" cellspacing="0"&gt;
    
      &lt;tr&gt;
           &lt;td colspan="5"&gt;    &lt;strong&gt;您可能也喜欢：&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    
          &lt;tr&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F62592.html&amp;from=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F65537.html" target="_blank" title="Drupal 7.28 &amp;#21457;&amp;#24067;&amp;#65292;&amp;#20869;&amp;#23481;&amp;#31649;&amp;#29702;&amp;#31995;&amp;#32479;"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/resources/images/related_item_default/63.jpg" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        Drupal 7.28 发布，内容管理系统
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F44098.html&amp;from=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F65537.html" target="_blank" title="Drupal 7.27 &amp;#21457;&amp;#24067;&amp;#65292;&amp;#20869;&amp;#23481;&amp;#31649;&amp;#29702;&amp;#31995;&amp;#32479;"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/site_images/ti/v4tbq763.jpg?i=iTeOWw5W" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        Drupal 7.27 发布，内容管理系统
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F1560.html&amp;from=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F65537.html" target="_blank" title="&amp;#38647;&amp;#39536;&amp;#26032;&amp;#38395;&amp;#21457;&amp;#24067;&amp;#31649;&amp;#29702;&amp;#31995;&amp;#32479;v3.0&amp;#28431;&amp;#27934;"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/site_images/ti/o3G6d6I2.jpg?i=sKwnGHtm" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        雷驰新闻发布管理系统v3.0漏洞
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F43593.html&amp;from=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F65537.html" target="_blank" title="&amp;#20961;&amp;#35834;&amp;#20225;&amp;#19994;&amp;#32593;&amp;#31449;&amp;#31649;&amp;#29702;&amp;#31995;&amp;#32479;XSS&amp;#28431;&amp;#27934;"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/site_images/ti/1gJL0HRDm.jpg?i=NU2b0U1u" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        凡诺企业网站管理系统XSS漏洞
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F41674.html&amp;from=http%3A%2F%2Fwww.shellsec.com%2Ftech%2F65537.html" target="_blank" title="[&amp;#21407;&amp;#21019;]&amp;#21033;&amp;#29992;fluentd&amp;#26500;&amp;#24314;&amp;#20998;&amp;#24067;&amp;#24335;&amp;#26085;&amp;#24535;&amp;#31649;&amp;#29702;&amp;#31995;&amp;#32479; - Hevienz"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/site_images/ti/xNsdEGyi.png?i=9qjZgYbi" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        [原创]利用fluentd构建分布式日志管理系统 - Hevienz
                    &lt;/a&gt;
                &lt;/td&gt;
        &lt;/tr&gt;
    
      &lt;tr&gt;
           &lt;td align="right" colspan="5"&gt;
                &lt;a href="http://www.wumii.com/widget/relatedItems" target="_blank" title="&amp;#26080;&amp;#35269;&amp;#20851;&amp;#32852;&amp;#25512;&amp;#33616;"&gt;
                无觅
            &lt;/a&gt;
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;The post   &lt;a href="http://www.shellsec.com/tech/65537.html" rel="nofollow"&gt;LogicalDOC 6.8.4 发布，文档管理系统&lt;/a&gt; appeared first on   &lt;a href="http://www.shellsec.com" rel="nofollow"&gt;神刀安全网&lt;/a&gt;.&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>LSIP-黑客语言 PERL高效编程语言 PHP-快速WEB2.0 PYTHON-脚本解释语言 ROR-动态语言</category>
      <guid isPermaLink="true">https://itindex.net/detail/49566-logicaldoc-%E6%96%87%E6%A1%A3-%E7%AE%A1%E7%90%86</guid>
      <pubDate>Tue, 13 May 2014 09:04:35 CST</pubDate>
    </item>
    <item>
      <title>如何正确配置Nginx+PHP</title>
      <link>https://itindex.net/detail/46156-%E6%AD%A3%E7%A1%AE-nginx-php</link>
      <description>&lt;p&gt;对很多人而言，配置Nginx+PHP无外乎就是搜索一篇教程，然后拷贝粘贴。听上去似乎也没什么问题，可惜实际上网络上很多资料本身年久失修，漏洞百出，如果大家不求甚解，一味的拷贝粘贴，早晚有一天会为此付出代价。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;假设我们用PHP实现了一个前端控制器，或者直白点说就是统一入口：把PHP请求都发送到同一个文件上，然后在此文件里通过解析「REQUEST_URI」实现路由。&lt;/p&gt;
 &lt;p&gt;此时很多教程会教大家这样配置Nginx+PHP：&lt;/p&gt;
 &lt;pre&gt;server {
    listen 80;
    server_name foo.com;

    root /path;

    location / {
        index index.html index.htm index.php;

        if (!-e $request_filename) {
            rewrite . /index.php last;
        }
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /path$fastcgi_script_name;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
    }
}&lt;/pre&gt;
 &lt;p&gt;这里面有很多错误，或者说至少是坏味道的地方，大家看看能发现几个。&lt;/p&gt;
 &lt;p&gt;…&lt;/p&gt;
 &lt;p&gt;我们有必要先了解一下Nginx配置文件里指令的继承关系：Nginx配置文件分为好多块，常见的从外到内依次是「http」、「server」、「location」等等，缺省的继承关系是从外到内，也就是说内层块会自动获取外层块的值作为缺省值（有例外，详见参考）。&lt;/p&gt;
 &lt;p&gt;参考：  &lt;a href="http://blog.martinfjordvald.com/2012/08/understanding-the-nginx-configuration-inheritance-model/" target="_blank"&gt;UNDERSTANDING THE NGINX CONFIGURATION INHERITANCE MODEL&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;…&lt;/p&gt;
 &lt;p&gt;让我们先从「  &lt;a href="http://nginx.org/en/docs/http/ngx_http_index_module.html#index" target="_blank"&gt;index&lt;/a&gt;」指令入手吧，在问题配置中它是在「location」中定义的：&lt;/p&gt;
 &lt;pre&gt;location / {
    index index.html index.htm index.php;
}&lt;/pre&gt;
 &lt;p&gt;一旦未来需要加入新的「location」，必然会出现重复定义的「index」指令，这是因为多个「location」是平级的关系，不存在继承，此时应该在「server」里定义「index」，借助继承关系，「index」指令在所有的「location」中都能生效。&lt;/p&gt;
 &lt;p&gt;参考：  &lt;a href="http://wiki.nginx.org/Pitfalls" target="_blank"&gt;Nginx Pitfalls&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;…&lt;/p&gt;
 &lt;p&gt;接下来看看「  &lt;a href="http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if" target="_blank"&gt;if&lt;/a&gt;」指令，说它是大家误解最深的Nginx指令毫不为过：&lt;/p&gt;
 &lt;pre&gt;if (!-e $request_filename) {
    rewrite . /index.php last;
}&lt;/pre&gt;
 &lt;p&gt;很多人喜欢用「  &lt;a href="http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if" target="_blank"&gt;if&lt;/a&gt;」指令做一系列的检查，不过这实际上是「  &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files" target="_blank"&gt;try_files&lt;/a&gt;」指令的职责：&lt;/p&gt;
 &lt;pre&gt;try_files $uri $uri/ /index.php;&lt;/pre&gt;
 &lt;p&gt;除此以外，初学者往往会认为「  &lt;a href="http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if" target="_blank"&gt;if&lt;/a&gt;」指令是内核级的指令，但是实际上它是rewrite模块的一部分，加上Nginx配置实际上是声明式的，而非过程式的，所以当其和非rewrite模块的指令混用时，结果可能会非你所愿。&lt;/p&gt;
 &lt;p&gt;参考：  &lt;a href="http://wiki.nginx.org/IfIsEvil" target="_blank"&gt;IfIsEvil&lt;/a&gt; and   &lt;a href="http://agentzh.blogspot.com/2011/03/how-nginx-location-if-works.html" target="_blank"&gt;How nginx “location if” works&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;…&lt;/p&gt;
 &lt;p&gt;下面看看「fastcgi_params」配置文件：&lt;/p&gt;
 &lt;pre&gt;include fastcgi_params;&lt;/pre&gt;
 &lt;p&gt;Nginx有两份fastcgi配置文件，分别是「fastcgi_params」和「fastcgi.conf」，它们没有太大的差异，唯一的区别是后者比前者多了一行「SCRIPT_FILENAME」的定义：&lt;/p&gt;
 &lt;pre&gt;fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;&lt;/pre&gt;
 &lt;p&gt;注意：$document_root 和 $fastcgi_script_name 之间没有 /。&lt;/p&gt;
 &lt;p&gt;原本Nginx只有「fastcgi_params」，后来发现很多人在定义「SCRIPT_FILENAME」时使用了硬编码的方式，于是为了规范用法便引入了「fastcgi.conf」。&lt;/p&gt;
 &lt;p&gt;不过这样的话就产生一个疑问：为什么一定要引入一个新的配置文件，而不是修改旧的配置文件？这是因为「fastcgi_param」指令是数组型的，和普通指令相同的是：内层替换外层；和普通指令不同的是：当在同级多次使用的时候，是新增而不是替换。换句话说，如果在同级定义两次「SCRIPT_FILENAME」，那么它们都会被发送到后端，这可能会导致一些潜在的问题，为了避免此类情况，便引入了一个新的配置文件。&lt;/p&gt;
 &lt;p&gt;参考：  &lt;a href="http://blog.martinfjordvald.com/2013/04/nginx-config-history-fastcgi_params-versus-fastcgi-conf/" target="_blank"&gt;FASTCGI_PARAMS VERSUS FASTCGI.CONF – NGINX CONFIG HISTORY&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;…&lt;/p&gt;
 &lt;p&gt;此外，我们还需要考虑一个安全问题：在PHP开启「cgi.fix_pathinfo」的情况下，PHP可能会把错误的文件类型当作PHP文件来解析。如果Nginx和PHP安装在同一台服务器上的话，那么最简单的解决方法是用「  &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files" target="_blank"&gt;try_files&lt;/a&gt;」指令做一次过滤：&lt;/p&gt;
 &lt;pre&gt;try_files $uri =404;&lt;/pre&gt;
 &lt;p&gt;参考：  &lt;a href="http://www.80sec.com/nginx-securit.html" target="_blank"&gt;Nginx文件类型错误解析漏洞&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;…&lt;/p&gt;
 &lt;p&gt;依照前面的分析，给出一份改良后的版本，是不是比开始的版本清爽了很多：&lt;/p&gt;
 &lt;pre&gt;server {
    listen 80;
    server_name foo.com;

    root /path;
    index index.html index.htm index.php;

    location / {
        try_files $uri $uri/ /index.php;
    }

    location ~ \.php$ {
        try_files $uri =404;

        include fastcgi.conf;
        fastcgi_pass 127.0.0.1:9000;
    }
}&lt;/pre&gt;
 &lt;p&gt;实际上还有一些瑕疵，主要是「  &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files"&gt;try_files&lt;/a&gt;」和「  &lt;a href="http://wiki.nginx.org/HttpFastcgiModule#fastcgi_split_path_info" target="_blank"&gt;fastcgi_split_path_info&lt;/a&gt;」不够兼容，虽然能够解决，但方案比较丑陋，具体就不多说了，有兴趣的可以参考  &lt;a href="http://trac.nginx.org/nginx/ticket/321" target="_blank"&gt;问题描述&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;补充：因为「location」已经做了限定，所以「fastcgi_index」其实也没有必要。&lt;/p&gt;
 &lt;p&gt;…&lt;/p&gt;
 &lt;p&gt;希望大家以后不要在拷贝粘贴了，如果实在改不了，那么就请拷贝粘贴本文。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Technical Nginx PHP</category>
      <guid isPermaLink="true">https://itindex.net/detail/46156-%E6%AD%A3%E7%A1%AE-nginx-php</guid>
      <pubDate>Wed, 23 Oct 2013 20:11:50 CST</pubDate>
    </item>
    <item>
      <title>正确处理浏览器在下载文件时HTTP头的编码问题（Content-Disposition）</title>
      <link>https://itindex.net/detail/49229-%E6%AD%A3%E7%A1%AE-%E6%B5%8F%E8%A7%88%E5%99%A8-%E4%B8%8B%E8%BD%BD</link>
      <description>&lt;p&gt;最近在做项目时遇到了一个 case ：需要实现一个强制在浏览器中的下载功能（即强制让浏览器弹出下载对话框），并且文件名必须保持和用户之前上传时相同（可能包含非 ASCII 字符）。&lt;/p&gt;
 &lt;p&gt;前一个需求很容易实现：使用 HTTP Header 的 Content-Disposition: attachment 即可，还可以配合 Content-Type: application/octet-stream 来确保万无一失。而后一个需求就比较蛋疼了，牵扯到 Header 的编码问题（文件名是作为 filename 参数放在 Content-Disposition 里面的）。众所周知， HTTP Header 中的 Content-Type 可以指定内容的编码，可 Header 本身的编码又该如何制定？甚至， Header 究竟是否允许非 ASCII 编码呢？&lt;/p&gt;
 &lt;p&gt;如果放任编码问题不管，那么恭喜你，你一定会遇到在某个系统及浏览器下下载文件时文件名乱码的情况。如果你尝试搜索解决，那么再一次恭喜你，你会找到一堆自相矛盾的解决方案（我可以负责任地告诉你，其中的99%都是不符合标准的 trick 罢了）。让我们来看看到底应该如何优雅完美地解决这个问题吧！&lt;/p&gt;
 &lt;p&gt;为了探索这个问题，我走了不少弯路。从自己尝试，到 Google 、百度（分别尝试过中英文搜索），再到阅读 Discuz 等经典项目的源码，众说纷纭、莫衷一是。最后我才想到回归 RFC ，从标准文档中找办法，果然有所收获。由于探究过程实在太曲折，我就先把标准做法写下来。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;应该这样设置 Content-Disposition ：&lt;/p&gt;

  &lt;div&gt;   &lt;div&gt;    &lt;pre&gt;Content-Disposition: attachment;
                      filename=&amp;quot;$encoded_fname&amp;quot;;
                      filename*=utf-8&amp;apos;&amp;apos;$encoded_fname&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

  &lt;p&gt;其中，$encoded_fname指的是将 UTF-8 编码的原始文件名按照    &lt;a href="http://tools.ietf.org/html/rfc3986#section-2.1" target="_blank"&gt;RFC 3986&lt;/a&gt; 进行百分号 urlencode 后得到的（ PHP 中使用 rawurlencode() 函数）。这几行也可以合并为一行，推荐使用一个空格隔开。&lt;/p&gt;
  &lt;p&gt;另外，为了兼容 IE6 ，请保证原始文件名必须包含英文扩展名！&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;好了，接下来我们来看看为什么要这么做以及为什么能这么做。&lt;/p&gt;
 &lt;p&gt;首先，根据 HTTP 1.1 协议规范（ RFC 2616 Section 4 ）， HTTP 消息格式其实是基于古老的 ARPA INTERNET TEXT MESSAGES （ RFC 822 Section 3 ），根据其规定，消息只能是 ASCII 编码的。 RFC 2616 Section 2.2 又一次强调， TEXT 中若要使用其他字符集，必须使用 RFC 2047 的规则将字符串编码为 ASCII 码（事实上这个规则原本是针对 MIME 的扩展，使用的是 base64 编码，格式与百分号编码有很大不同）。总而言之，按照标准， HTTP Header 中的文本数据必须是 ASCII 编码的。&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;pre&gt;filename=&amp;quot;TEXT&amp;quot;
 ;这是 RFC 2616 标准，TEXT必须是 ASCII 字符且被认为就是“原文”
filename*=charset&amp;apos;lang&amp;apos;encoded-text
 ;这是按照 RFC 2047 扩展后的，注意格式上的细微区别，采用 base64 编码（编码结果也是 ASCII 字符）&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

 &lt;p&gt;然而，事实上在1999年 HTTP 1.1 标准推出之时， Content-Dispostion 这个 Header 尚不是正式标准的一部分，只不过是因为被广泛使用而从 MIME 标准中直接借用过来了而已（   &lt;a href="http://tools.ietf.org/html/rfc2616#section-19.5.1" target="_blank"&gt;RFC 2616 Section 19.5.1&lt;/a&gt; ）。因而几乎没有浏览器去支持 Content-Disposition 的多语言编码特性这样一个“扩展特性的扩展特性”（事实上， HTTP 1.1 草案中建议的使用 RFC 2047 来进行多语言编码的特性从未被主流浏览器支持过）。&lt;/p&gt;
 &lt;p&gt;可是这个问题却的确是现实需要的，所以浏览器就各自想出了一些办法：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;IE支持两种格式的混合版：filename=&amp;quot;encoded_text&amp;quot; （这里采用的是百分号编码）。本来按照 RFC 2616 ，引号内的部分应当直接被当作内容，就算它“看起来像是编码后的字符串”；可是IE却会“自动”对这样的文件名进行解码——   &lt;strong&gt;前提是该文件名必须有一个不会被编码的后缀名（即正常的英文字母后缀名）！&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;其他一些浏览器则支持一种更为粗暴的方式——允许在 filename=&amp;quot;TEXT&amp;quot; 中直接使用 UTF-8 编码的字符串！&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这两类浏览器的行为是彼此互不兼容的。所以你可以判断 UA 然后对IE使用前一种办法，其他浏览器使用后一种，这样便可以达到一般情况下能够 just work 的效果（ Discuz 就是这么做的）。不过对于 Opera 和 Safari ，这样做可能不一定有效。&lt;/p&gt;
 &lt;p&gt;时代在进步，2010年   &lt;a href="http://tools.ietf.org/html/rfc5987" target="_blank"&gt;RFC 5987&lt;/a&gt; 发布，正式规定了 HTTP Header 中多语言编码的处理方式，应当采用类似 MIME 扩展的 parameter*=charset&amp;apos;lang&amp;apos;value 的格式，但是其中 value 应根据   &lt;a href="http://tools.ietf.org/html/rfc3986#section-2.1" target="_blank"&gt;RFC 3986 Section 2.1&lt;/a&gt; 使用百分号进行编码，并且规定浏览器至少应该支持 ASCII 和 UTF-8 。随后，2011年   &lt;a href="http://tools.ietf.org/html/rfc6266" target="_blank"&gt;RFC 6266&lt;/a&gt; 发布，正式将 Content-Disposition 纳入 HTTP 标准，并再次强调了 RFC 5987 中多语言编码的方法，还给出了一个  &lt;a href="http://tools.ietf.org/html/rfc6266#section-5" target="_blank"&gt;范例&lt;/a&gt;用于解决向后兼容的问题——就是我在一开始给出的例子：&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;pre&gt;Content-Disposition: attachment;
                      filename=&amp;quot;encoded_text&amp;quot;;
                      filename*=utf-8&amp;apos;&amp;apos;encoded_text&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

 &lt;p&gt;在这个例子中，对于较新的 Firefox 、 Chrome 、 Opera 、 Safari 等浏览器，都支持新标准规定的 filename* ，并且会优先使用，所以尽管 filename=”encoded_text” 不被它们支持，仍然不会有问题；至于使用 UTF-8 只是因为它是标准中强制要求必须支持的。而对于旧版本的IE浏览器，它们无法识别后面的 filename* ，会自动忽略并使用旧的 filename 。这样一来就完美解决了多浏览器的多语言兼容问题，既不需要 UA 判断，也符合标准。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;P.S.&lt;/strong&gt; 为什么 PHP 要使用 rawurlencode() 函数呢？因为这才是真正符合 RFC 3986 的“百分号URL编码”，只是由于历史原因，之前先有了一个 urlencode() 函数用于实现 HTTP POST 中的类似的编码规则，故而只好用这么一个奇怪的名字。两者的区别在于前者会把空格编码为%20，而后者则会编码为+号。如果使用后者，那么IE6在下载带有空格的文件名时空格会变为加号。一般情况下，你是不会用到 urlencode() 这个函数的（ Discuz 某些版本中错误地使用它来进行文件名编码，从而导致空格变加号的BUG）。&lt;/p&gt;
 &lt;p&gt;via:Robot Shell&lt;/p&gt;

 &lt;p&gt;This article addresses:  &lt;a href="http://www.iefans.net/xiazai-wenjian-http-bianma-content-disposition/"&gt;http://www.iefans.net/xiazai-wenjian-http-bianma-content-disposition/&lt;/a&gt;&lt;/p&gt;Here is no comments yet by the time  your rss reader get this, Do you want to be the first commentor? Hurry up &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>IE Web PHP web</category>
      <guid isPermaLink="true">https://itindex.net/detail/49229-%E6%AD%A3%E7%A1%AE-%E6%B5%8F%E8%A7%88%E5%99%A8-%E4%B8%8B%E8%BD%BD</guid>
      <pubDate>Mon, 21 Apr 2014 23:08:09 CST</pubDate>
    </item>
    <item>
      <title>当cpu飙升时，找出php中可能有问题的代码行</title>
      <link>https://itindex.net/detail/49106-cpu-php-%E6%9C%89%E9%97%AE%E9%A2%98</link>
      <description>&lt;p&gt;  &lt;strong&gt;标签：&lt;/strong&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=cpu" target="_blank"&gt;cpu&lt;/a&gt;&lt;/p&gt; &lt;p&gt;    当你发现一个平时占用cpu比较少的进程突然间占用cpu接近100%时，你如何找到导致cpu飙升的原因？我的思路是，首先找到进程正在执行的代码行，从而确定可能有问题的代码段。然后，再仔细分析有问题的代码段，从而找出原因。&lt;/p&gt; &lt;p&gt;    如果你的程序使用的是c、c++编写，那么你可以很容易的找到正在执行的代码行。但是，程序是php编写的，如何找到可能有问题的代码行呢？这个问题就是本文要解决的问题。&lt;/p&gt; &lt;p&gt;      &lt;strong&gt;背景知识:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;    大家都知道php是一个解释性语言。用户编写的php代码会生成opcode，由解释器引擎去解释执行。在解释执行过程中，有一个全局变量包含了执行过程中用到的各种数据。它就是executor_globals。在源码的Zend/zend_globals.h 文件中可以找到他的类型定义。&lt;/p&gt; &lt;pre&gt;struct _zend_executor_globals {
    zval **return_value_ptr_ptr;
 
    zval uninitialized_zval;
    zval *uninitialized_zval_ptr;
 
    zval error_zval;
    zval *error_zval_ptr;
 
    zend_ptr_stack arg_types_stack;
 
    /* symbol table cache */
    HashTable *symtable_cache[SYMTABLE_CACHE_SIZE];
    HashTable **symtable_cache_limit;
    HashTable **symtable_cache_ptr;
 
    zend_op **opline_ptr;
 
    HashTable *active_symbol_table;
    HashTable symbol_table;     /* main symbol table */
 
    HashTable included_files;   /* files already included */
 
    JMP_BUF *bailout;
 
    int error_reporting;
    int orig_error_reporting;
    int exit_status;
 
    zend_op_array *active_op_array;
 
    HashTable *function_table;  /* function symbol table */
    HashTable *class_table;     /* class table */
    HashTable *zend_constants;  /* constants table */
 
    zend_class_entry *scope;
    zend_class_entry *called_scope; /* Scope of the calling class */
 
    zval *This;
 
    long precision;
 
    int ticks_count;
 
    zend_bool in_execution;
    HashTable *in_autoload;
    zend_function *autoload_func;
    zend_bool full_tables_cleanup;
 
    /* for extended information support */
    zend_bool no_extensions;
 
#ifdef ZEND_WIN32
    zend_bool timed_out;
    OSVERSIONINFOEX windows_version_info;
#endif
 
    HashTable regular_list;
    HashTable persistent_list;
 
    zend_vm_stack argument_stack;
 
    int user_error_handler_error_reporting;
    zval *user_error_handler;
    zval *user_exception_handler;
    zend_stack user_error_handlers_error_reporting;
    zend_ptr_stack user_error_handlers;
    zend_ptr_stack user_exception_handlers;
 
    zend_error_handling_t  error_handling;
    zend_class_entry      *exception_class;
 
    /* timeout support */
    int timeout_seconds;
 
    int lambda_count;
 
    HashTable *ini_directives;
    HashTable *modified_ini_directives;
 
    zend_objects_store objects_store;
    zval *exception, *prev_exception;
    zend_op *opline_before_exception;
    zend_op exception_op[3];
 
    struct _zend_execute_data *current_execute_data;
 
    struct _zend_module_entry *current_module;
 
    zend_property_info std_property_info;
 
    zend_bool active; 
 
    void *saved_fpu_cw;
 
    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};&lt;/pre&gt; &lt;p&gt;    这里我们只说两个对我们比较重要的变量，active_op_array 和 current_execute_data。&lt;/p&gt; &lt;p&gt;      &lt;strong&gt;active_op_array&lt;/strong&gt;变量中保存了引擎正在执行的op_array(  &lt;a href="http://www.bo56.com/php%E5%86%85%E6%A0%B8%E6%8E%A2%E7%B4%A2%E4%B9%8Bzend_execute%E7%9A%84%E5%85%B7%E4%BD%93%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B/#op_array" target="_blank"&gt;想了解什么是op_array请点击查看&lt;/a&gt;)。在Zend/zend_compile.h中有关于op_array的数据类型的定义。&lt;/p&gt; &lt;pre&gt;struct _zend_op_array {
    /* Common elements */
    zend_uchar type;
    char *function_name;        
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;
    /* END of common elements */
 
    zend_bool done_pass_two;
 
    zend_uint *refcount;
 
    zend_op *opcodes;
    zend_uint last, size;
 
    zend_compiled_variable *vars;
    int last_var, size_var;
 
    zend_uint T;
 
    zend_brk_cont_element *brk_cont_array;
    int last_brk_cont;
    int current_brk_cont;
 
    zend_try_catch_element *try_catch_array;
    int last_try_catch;
 
    /* static variables support */
    HashTable *static_variables;
 
    zend_op *start_op;
    int backpatch_count;
 
    zend_uint this_var;
 
    char *filename;
    zend_uint line_start;
    zend_uint line_end;
    char *doc_comment;
    zend_uint doc_comment_len;
    zend_uint early_binding; /* the linked list of delayed declarations */
 
    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};&lt;/pre&gt; &lt;p&gt;    看完定义，就不用我多说了把。定义中，filename和 function_name分别保存了正在执行的文件名和方法名。&lt;/p&gt; &lt;p&gt;      &lt;strong&gt;current_execute_data&lt;/strong&gt;保存了正在执行的op_array的execute_data。execute_data保存了每个op_array执行过程中的一些数据。其定义在，Zend/zend_compile.h：&lt;/p&gt; &lt;pre&gt;struct _zend_execute_data {  
    struct _zend_op *opline;  
    zend_function_state function_state;  
    zend_function *fbc; /* Function Being Called */ 
    zend_class_entry *called_scope;  
    zend_op_array *op_array;  
    zval *object;  
    union _temp_variable *Ts;  
    zval ***CVs;  
    HashTable *symbol_table;  
    struct _zend_execute_data *prev_execute_data;  
    zval *old_error_reporting;  
    zend_bool nested;  
    zval **original_return_value;  
    zend_class_entry *current_scope;  
    zend_class_entry *current_called_scope;  
    zval *current_this;  
    zval *current_object;  
    struct _zend_op *call_opline;  
};  &lt;/pre&gt; &lt;p&gt;    定义中的opline就是正在执行的opcode。opcode的结构定义如下：&lt;/p&gt; &lt;pre&gt;struct _zend_op {
    opcode_handler_t handler;
    znode result;
    znode op1;
    znode op2;
    ulong extended_value;
    uint lineno;
    zend_uchar opcode;
};&lt;/pre&gt; &lt;p&gt;    其中lineno就是opcode所对应的行号。&lt;/p&gt; &lt;p&gt;      &lt;strong&gt;示例说明:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;    看完上面的数据结构定义，你是否已经知道如何找php正在执行的文件名，方法名和行号呢?如果还有疑问的话，那就接着看下面的例子。创建一个文件test.php,代码如下：&lt;/p&gt; &lt;pre&gt;&amp;lt;?php
function test1(){
        while(true){
              sleep(1);
        }
}
test1();
?&amp;gt;&lt;/pre&gt; &lt;p&gt;    cli方式执行php脚本，加入执行的进程号为14973。我们使用gdb命令来调试进程。&lt;/p&gt; &lt;pre&gt;$sudo gdb -p 14973
(gdb) print (char *)executor_globals.active_op_array-&amp;gt;filename
$1 = 0x9853a34 &amp;quot;/home/xinhailong/test/php/test.php&amp;quot;
(gdb) print (char *)executor_globals.active_op_array-&amp;gt;function_name
$2 = 0x9854db8 &amp;quot;test1&amp;quot;
(gdb) print executor_globals-&amp;gt;current_execute_data-&amp;gt;opline-&amp;gt;lineno
$3 = 4&lt;/pre&gt; &lt;p&gt;    很显然，他正在执行第四行的sleep方法。&lt;/p&gt; &lt;p&gt;    如果上面的方法你感觉麻烦，那你可以使用.gdbinit文件。这个文件在php源码的根目录下。使用方法如下： &lt;/p&gt; &lt;pre&gt;$sudo gdb -p 14973
(gdb) source /home/xinhailong/.gdbinit 
(gdb) zbacktrace
[0xa453f34] sleep(1) /home/xinhailong/test/php/test.php:4 
[0xa453ed0] test1() /home/xinhailong/test/php/test.php:7 
(gdb) &lt;/pre&gt; &lt;p&gt;      &lt;strong&gt;题外话：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;    从php5.6开始，php中集成了一个phpdbg的工具。可以像gdb调试c语言程序一样，调试php程序。感兴趣的话，可以打开下面的连接看看。&lt;/p&gt; &lt;p&gt;    https://wiki.php.net/rfc/phpdbg&lt;/p&gt; &lt;p&gt;    http://phpdbg.com/docs&lt;/p&gt;
				 &lt;p&gt;  &lt;strong&gt;您可能还对下面的文章感兴趣：&lt;/strong&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6844" target="_blank"&gt;写Java也得了解CPU缓存&lt;/a&gt; [2014-04-13 22:39:18]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6813" target="_blank"&gt;大量小包的CPU密集型系统调优案例一则&lt;/a&gt; [2014-03-20 23:00:07]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6774" target="_blank"&gt;Linux如何统计进程的CPU利用率&lt;/a&gt; [2013-11-01 13:59:43]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6558" target="_blank"&gt;7个示例科普CPU Cache&lt;/a&gt; [2013-07-30 13:36:38]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6522" target="_blank"&gt;Linux下CPU的利用率&lt;/a&gt; [2013-07-15 13:25:03]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6047" target="_blank"&gt;玩转CPU Topology&lt;/a&gt; [2012-12-07 13:54:53]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=5985" target="_blank"&gt;进程运行于不同的 CPU 核&lt;/a&gt; [2012-11-02 13:14:28]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=5293" target="_blank"&gt;从Java视角理解CPU上下文切换(Context Switch)&lt;/a&gt; [2012-05-08 00:02:08]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=3908" target="_blank"&gt;查看 CPU, Memory, I/O and NetFlow&lt;/a&gt; [2011-06-23 13:49:53]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=3154" target="_blank"&gt;ubuntu10.10 使用mrtg监控服务器的cpu、内存、网络等等情况&lt;/a&gt; [2011-01-30 19:36:22]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=3074" target="_blank"&gt;Linux 查看机器配置信息&lt;/a&gt; [2011-01-23 23:09:13]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=2691" target="_blank"&gt;解剖CPU&lt;/a&gt; [2010-11-14 09:01:00]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=2659" target="_blank"&gt;oracle数据库的CPU/IO信息采集&lt;/a&gt; [2010-11-07 22:42:09]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=1239" target="_blank"&gt;如何查看Linux 硬件配置信息&lt;/a&gt; [2010-03-24 22:28:11]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=837" target="_blank"&gt;Linux下进程绑定多CPU运行&lt;/a&gt; [2009-12-18 09:33:13]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=547" target="_blank"&gt;利用taskset有效控制cpu资源&lt;/a&gt; [2009-11-11 23:48:48]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=428" target="_blank"&gt;新型高性能服务器CPU酷睿i5和酷睿i7&lt;/a&gt; [2009-11-03 11:57:32]&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt; &lt;img height="1" src="http://feeds.feedburner.com/~r/blogreadIT/~4/AzetPaA9dso" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>PHP</category>
      <guid isPermaLink="true">https://itindex.net/detail/49106-cpu-php-%E6%9C%89%E9%97%AE%E9%A2%98</guid>
      <pubDate>Wed, 16 Apr 2014 06:41:31 CST</pubDate>
    </item>
  </channel>
</rss>

