SegmentFault Weber最新的文章
2018-06-08T23:01:40+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
gracehttp: 优雅重启 Go 程序(热启动 - Zero Downtime)
https://segmentfault.com/a/1190000015232528
2018-06-08T23:01:40+08:00
2018-06-08T23:01:40+08:00
fevin
https://segmentfault.com/u/fevin
14
<blockquote>看完此篇你会知道,如何优雅的使用 HTTP Server</blockquote>
<h2>问题背景</h2>
<p>在 <code>http</code> 应用程序重启时,如果我们直接 <code>kill -9</code> 使程序退出,然后在启动,会有以下几个问题:</p>
<ol>
<li>旧的请求未处理完,如果服务端进程直接退出,会造成客户端链接中断(收到 <code>RST</code>);</li>
<li>新请求打过来,服务还没重启完毕,造成 <code>connection refused</code>;</li>
<li>即使是要退出程序,直接 <code>kill -9</code> 仍然会让正在处理的请求中断;</li>
<li>面对海量请求,如何对链接数进行限制,并进行过载保护;</li>
<li>避免 <code>open too many files</code> 错误;</li>
</ol>
<p>这些问题会造成不好的客户体验,严重的甚至影响客户业务。所以,我们需要以一种优雅的方式重启/关闭我们的应用,来达到热启动的效果,即:<code>Zero Downtime</code>。</p>
<blockquote>(Tips:名词解释)<br><code>热启动</code>:新老程序(进程)无缝替换,同时可以保持对client的服务。让client端感觉不到你的服务挂掉了;<br><code>Zero Downtime</code>: 0 宕机时间,即不间断的服务;</blockquote>
<h2>解决问题</h2>
<p>Github: <a href="https://link.segmentfault.com/?enc=XEMEdDWtKh4PgaUqsnDsow%3D%3D.HccmXEpgSGuZ1yEPKJ%2F0DCgF1boBaxjP1kIcndC%2F8Z91pXlxdg8oGqMgSVjJ5JuN" rel="nofollow">gracehttp</a></p>
<h3>平滑启动</h3>
<p>一般情况下,我们是退出旧版本,再启动新版本,总会有时间间隔,时间间隔内的请求怎么办?而且旧版本正在处理请求怎么办?<br>那么,针对这些问题,在升级应用过程中,我们需要达到如下目的:</p>
<ul>
<li>旧版本为退出之前,需要先启动新版本;</li>
<li>旧版本继续处理完已经接受的请求,并且不再接受新请求;</li>
<li>新版本接受并处理新请求的方式;</li>
</ul>
<p>这样,我们就能实现 <code>Zero Downtime</code> 的升级效果。</p>
<h4>实现原理</h4>
<p>首先,我们需要用到以下基本知识:<br>1.<code>linux</code> 信号处理机制:在程序中,通过拦截 <code>signal</code>,并针对 <code>signal</code> 做出不同处理;<br>2.子进程继承父进程的资源:一切皆文件,子进程会继承父进程的资源句柄,网络端口也是文件;<br>3.通过给子进程重启标识(比如:重启时带着 <code>-continue</code> 参数),来实现子进程的初始化处理;</p>
<p>重启时,我们可以在程序中捕获 <code>HUP</code> 信号(通过 <code>kill -HUP pid</code> 可以触发),然后开启新进程,退出旧进程。信号处理代码示例如下:</p>
<pre><code class="go">package gracehttp
import (
"fmt"
"os"
"os/signal"
"syscall"
)
var sig chan os.Signal
var notifySignals []os.Signal
func init() {
sig = make(chan os.Signal)
notifySignals = append(notifySignals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGTSTP, syscall.SIGQUIT)
signal.Notify(sig, notifySignals...) // 注册需要拦截的信号
}
// 捕获系统信号,并处理
func handleSignals() {
capturedSig := <-sig
srvLog.Info(fmt.Sprintf("Received SIG. [PID:%d, SIG:%v]", syscall.Getpid(), capturedSig))
switch capturedSig {
case syscall.SIGHUP: // 重启信号
startNewProcess() // 开启新进程
shutdown() // 退出旧进程
case syscall.SIGINT:
fallthrough
case syscall.SIGTERM:
fallthrough
case syscall.SIGTSTP:
fallthrough
case syscall.SIGQUIT:
shutdown()
}
}</code></pre>
<p><code>startNewProcess</code> <code>shutdown</code> 具体实现可以参考 <a href="https://link.segmentfault.com/?enc=ozsfsefqX4dH2uUHGvwY8Q%3D%3D.TP3LuK2s%2BK0n3UALG5z3I9OLlsc%2BGJ2l7UIycMBFFwt%2BKvFdltATKzihFh6G5zfk" rel="nofollow">Github</a></p>
<h3>过载保护</h3>
<p>通过限制 <code>HTTP Server</code> 的 <code>accept</code> 数量实现链接数的限制,来达到如果并发量达到了最大值,客户端超时时间内可以等待,但不会消耗服务端文件句柄数(我们知道 Linux 系统对用户可以打开的最大文件数有限制,网络请求也是文件操作)</p>
<h4>实现原理</h4>
<ul>
<li>利用 <code>channel</code> 的缓冲机制实现,每个请求都会获取缓冲区的一个单元大小,知道缓冲区满了,后边的请求就会阻塞;</li>
<li>如果客户端请求被阻塞,达到了客户端设置的超时时间,这时候链接会断开,那我们利用 <code>go</code> 的 <code>select</code> 机制,退出阻塞,并返回,不再进行 <code>accept</code>
</li>
</ul>
<p>处理代码如下:</p>
<pre><code class="go">package gracehttp
// about limit @see: "golang.org/x/net/netutil"
import (
"net"
"sync"
"time"
)
type Listener struct {
*net.TCPListener
sem chan struct{}
closeOnce sync.Once // ensures the done chan is only closed once
done chan struct{} // no values sent; closed when Close is called
}
func newListener(tl *net.TCPListener, n int) net.Listener {
return &Listener{
TCPListener: tl,
sem: make(chan struct{}, n),
done: make(chan struct{}),
}
}
func (l *Listener) Fd() (uintptr, error) {
file, err := l.TCPListener.File()
if err != nil {
return 0, err
}
return file.Fd(), nil
}
// override
func (l *Listener) Accept() (net.Conn, error) {
acquired := l.acquire()
tc, err := l.AcceptTCP()
if err != nil {
if acquired {
l.release()
}
return nil, err
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(time.Minute)
return &ListenerConn{Conn: tc, release: l.release}, nil
}
// override
func (l *Listener) Close() error {
err := l.TCPListener.Close()
l.closeOnce.Do(func() { close(l.done) })
return err
}
// acquire acquires the limiting semaphore. Returns true if successfully
// accquired, false if the listener is closed and the semaphore is not
// acquired.
func (l *Listener) acquire() bool {
select {
case <-l.done:
return false
case l.sem <- struct{}{}:
return true
}
}
func (l *Listener) release() { <-l.sem }
type ListenerConn struct {
net.Conn
releaseOnce sync.Once
release func()
}
func (l *ListenerConn) Close() error {
err := l.Conn.Close()
l.releaseOnce.Do(l.release)
return err
}</code></pre>
<p>参考:<a href="https://link.segmentfault.com/?enc=9dRp54n7IAiuJhnED%2F8ziA%3D%3D.GcBjQ%2BtOkFLZRUurcm%2FL%2BLqRTPmtcNl3TPSvwZu6ouxOiLSi4OXp3HmmstIYqo3neW9Ydnsnrab7YG3LX9FL1g%3D%3D" rel="nofollow">grace-http:listener.go</a></p>
<h2>gracehttp</h2>
<p>现在我们把这个功能做得更优美有点,并提供一个开箱即用的代码库。<br>地址:<a href="https://link.segmentfault.com/?enc=UbwnLPXo8Q08tvxqQLrTdQ%3D%3D.FZtSt3XkFWV0ga%2BbxFvWKqBQu4WKP39LhdHOlCbJcM2gQwDRGNpvl0rKszso7n6F" rel="nofollow">Github-gracehttp</a></p>
<h3>支持功能</h3>
<ol>
<li>平滑重启(<code>Zero-Downtime</code>);</li>
<li>平滑关闭;</li>
<li>多 <code>Server</code> 添加(支持 <code>HTTP</code> 、<code>HTTPS</code>);</li>
<li>自定义日志组件;</li>
<li>支持单个端口 server 链接数限流,默认值为:C100K。超过该限制之后,链接阻塞进入等待,但是不消耗系统文件句柄,避免发生雪崩,压坏服务。</li>
</ol>
<h3>使用指南</h3>
<h4>添加服务</h4>
<pre><code class="go"> import "fevin/gracehttp"
....
// http
srv1 := &http.Server{
Addr: ":80",
Handler: sc,
}
gracehttp.AddServer(srv1, false, "", "")
// https
srv2 := &http.Server{
Addr: ":443",
Handler: sc,
}
gracehttp.AddServer(srv2, true, "../config/https.crt", "../config/https.key")
gracehttp.Run() // 此方法会阻塞,直到进程收到退出信号,或者 panic</code></pre>
<p>如上所示,只需创建好 <code>Server</code> 对象,调用 <code>gracehttp.AddServer</code> 添加即可。</p>
<h4>退出或者重启服务</h4>
<ul>
<li>重启:<code>kill -HUP pid</code>
</li>
<li>退出:<code>kill -QUIT pid</code>
</li>
</ul>
<h4>添加自定义日志组件</h4>
<pre><code class="go"> gracehttp.SetErrorLogCallback(logger.LogConfigLoadError)</code></pre>
<p>此处提供了三个 <code>Set*</code> 方法,分别对应不同的日志等级:</p>
<ul>
<li><code>SetInfoLogCallback</code></li>
<li><code>SetNoticeLogCallback</code></li>
<li><code>SetErrorLogCallback</code></li>
</ul>
<h2>最后</h2>
<p>实际中,很多情况会用到这种方式,不妨点个 <a href="https://link.segmentfault.com/?enc=AknEsQY8THO7tDRFQ9AYbQ%3D%3D.37LrNmv302paQB2p%2FERVDUcrV4Eo5YH5CQR8XBTCIHc6esmIOAqpqQ6SWiiiQpAL" rel="nofollow">star</a> 吧!<br>欢迎一起来完善这个小项目,共同贡献代码。</p>
解决 vim 插件 taglist 报错:Error detected while processing function
https://segmentfault.com/a/1190000012425760
2017-12-14T14:48:33+08:00
2017-12-14T14:48:33+08:00
fevin
https://segmentfault.com/u/fevin
1
<h2>触发场景</h2>
<p><img src="/img/bV0iDH?w=253&h=129" alt="clipboard.png" title="clipboard.png"></p>
<p><code>a.php</code> 标签打开 <code>taglist</code>,然后打开 <code>b.php</code> 标签,此时再切回到 <code>a.php</code> 标签,<code>vim</code> 会报出一下错误信息:</p>
<pre><code class="shell">Error detected while processing function <SNR>29_Tlist_Refresh_Folds</code></pre>
<h2>解决办法</h2>
<p>下载 <code>taglist</code> 补丁:<a href="https://link.segmentfault.com/?enc=PFj9JkYe0PrXvmGPzSpHfQ%3D%3D.xe4RYIcBAFtMKZD1B5Ae26TUka2f9qBkgazh27kVbEcx8f0usWUAnHPLCHZsV8qZALu3RDyHN0HdWf9%2Bz2DbySyN0xq9VyMKg%2BkoS%2BKClYE%3D" rel="nofollow">taglist.diff</a><br>补丁内容如下:(若不能下载,可以复制此内容)</p>
<pre><code class="shell">diff --git a/vim/.vim/bundle/taglist/plugin/taglist.vim b/vim/.vim/bundle/taglist/plugin/taglist.vim
index 59901f6..74487a1 100644
--- a/vim/.vim/bundle/taglist/plugin/taglist.vim
+++ b/vim/.vim/bundle/taglist/plugin/taglist.vim
@@ -4097,6 +4097,11 @@ endfunction
" window. Used after entering a tab. If this is not done, then the folds
" are not properly created for taglist windows displayed in multiple tabs.
function! s:Tlist_Refresh_Folds()
+
+ if g:Tlist_Show_One_File
+ return
+ endif
+
let winnum = bufwinnr(g:TagList_title)
if winnum == -1
return</code></pre>
<p>然后,应用此补丁:</p>
<pre><code class="shell">patch -p0 ~/.vim/plugged/taglist.vim/plugin/taglist.vim ~/Downloads/taglist.diff</code></pre>
<p><strong>注意:</strong> 插件位置由你所用的插件管理器决定,可能跟我不一样。</p>
<p>参考:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=akE%2FNqygbPV%2BPlYCKCnN0w%3D%3D.O3OxQFKEWXXP39qHsyi9Q2%2Fi1lf1nZSZ5NSa7EwFIxQUMsOqmLp%2FP8j0n%2BbgoirbSLcibSA9fR1yma69RZNZ8PPeC37r52eJnnWvVBpDAf9CJK2Uvsvx895uvBlMLpM%2FQNw%2BlmLwaoadU9QAi%2Fgt%2BoJY05zFSCbSqYpIuHiMWWptx8T1QzPBW1zJkZjb2GVy" rel="nofollow">Taglist error: Error detected while processing function <SNR>29_Tlist_Refresh_Folds</a></li>
<li><a href="https://link.segmentfault.com/?enc=0GejxbyPkNiPxq8r%2FRlZ0w%3D%3D.Da742u0i8FoJWpQJ%2BjiNhJ7e1Nc%2Bon6Gc5yrCFKFU6s%3D" rel="nofollow">vim 常用功能</a></li>
</ul>
PHP 自动加载 深度总结
https://segmentfault.com/a/1190000012203213
2017-11-28T21:58:42+08:00
2017-11-28T21:58:42+08:00
fevin
https://segmentfault.com/u/fevin
16
<h2>写在前边</h2>
<p>我们将介绍 <code>include*</code> <code>require*</code> 的一些使用细节,<br>以及从 <code>PHP</code> 应用 和 <code>zend</code> 源码角度,来分别分析 <code>__autoload</code> <code>spl_autoload_register</code> 的实现和调用过程。<br>分析的目的更多的是让自己对这些细节加深认识,并进一步深入了解 <code>Zend</code> 源码。</p>
<pre><code>PHP 版本:`php-5.6`
核心方法:` spl_autoload_register`
</code></pre>
<hr>
<h2>类加载方式</h2>
<ul>
<li>手动加载</li>
<li><code>__autoload</code></li>
<li><code>spl_autoload_register</code></li>
</ul>
<h3>手动加载</h3>
<p>包含:<code>include</code> <code>include_once</code> <code>requice</code> <code>requice_one</code></p>
<h4>include</h4>
<blockquote>
<p><strong>以下文档也适用于 require。</strong><br>被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path 指定的目录寻找。如果在 include_path 下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include 结构会发出一条警告;这一点和 require 不同,后者会发出一个致命错误。</p>
<p>如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 开头,在 Unix/Linux 下以 / 开头)还是当前目录的相对路径(以 . 或者 .. 开头)——include_path 都会被完全忽略。例如一个文件以 ../ 开头,则解析器会在当前目录的父目录下寻找该文件。</p>
</blockquote>
<p>代码示例:<br><strong>FILE: <code>run.php</code></strong></p>
<pre><code class="php"><?php
ini_set('display_errors', '1');
// 直接包含
$ret = include 'class.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret));
Foo::getFoo();
// 包含不存在的文件
$ret1 = include './class1.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret1, gettype($ret1));
// 重复包含
$ret2 = include './class.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret));
Foo::getFoo();</code></pre>
<p><strong>FILE: <code>class.php</code></strong></p>
<pre><code class="php"><?php
class Foo
{
static public function getFoo()
{
echo "I am foo!\n";
}
}</code></pre>
<p>结果:<br><img src="/img/bVZhD3?w=2874&h=406" alt="clipboard.png" title="clipboard.png"></p>
<p>结论:</p>
<ol>
<li>
<code>include</code> 成功返回值:<code>1</code>,失败返回值:<code>false</code>
</li>
<li>
<code>include</code> 失败会有 <code>warning</code>,不会中断进程</li>
</ol>
<h4>include_once</h4>
<blockquote><p><code>include_once</code> 行为和 include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。</p></blockquote>
<p>将 <code>run.php</code> 修改如下:</p>
<pre><code class="php"><?php
ini_set('display_errors', '1');
// 直接包含
$ret = include 'class.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret));
Foo::getFoo();
// 重复包含
$ret2 = include_once './class.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret));
Foo::getFoo();</code></pre>
<p>结果:<br><img src="/img/bVZhEz?w=752&h=234" alt="clipboard.png" title="clipboard.png"></p>
<p>结论:</p>
<ol><li>
<code>include_once</code> 重复包含时,会直接返回 <code>1</code>,并忽略此次包含操作,继续执行</li></ol>
<h4>require</h4>
<blockquote><p><code>require</code> 和 <code>include</code> 几乎完全一样,但 <code>require</code> 在出错时产生 <code>E_COMPILE_ERROR</code> 级别的错误。(脚本将会中止运行)</p></blockquote>
<p>将 <code>run.php</code> 修改如下:</p>
<pre><code class="php"><?php
ini_set('display_errors', '1');
// 直接包含
$ret = require 'class.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret));
Foo::getFoo();
// 包含不存在的文件
$ret1 = require './class1.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret1, gettype($ret1));</code></pre>
<p>结果:<br><img src="/img/bVZhFj?w=2860&h=326" alt="clipboard.png" title="clipboard.png"></p>
<p>结论:</p>
<ol>
<li>
<code>require</code> 包含成功,同 <code>include</code> 一样,返回值:<code>1</code>
</li>
<li>
<code>require</code> 包含失败,直接抛出 <code>Fatal error</code>,进程中止</li>
</ol>
<h4>require_once</h4>
<blockquote><p><code>require_once</code> 语句和 <code>require</code> 语句完全相同,唯一区别是 <code>PHP</code> 会检查该文件是否已经被包含过,如果是则不会再次包含。</p></blockquote>
<p>将 <code>run.php</code> 修改如下:</p>
<pre><code class="php">ini_set('display_errors', '1');
// 直接包含
$ret = require_once 'class.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret));
Foo::getFoo();
// 重复包含
$ret2 = require_once './class.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret, gettype($ret));
Foo::getFoo();
// 包含不存在的文件
$ret1 = require_once './class1.php';
echo sprintf("include ret-value:%d,ret-type:%s\n", $ret1, gettype($ret1));</code></pre>
<p>结果:<br><img src="/img/bVZhF3?w=2872&h=404" alt="clipboard.png" title="clipboard.png"></p>
<p>结论:</p>
<ol>
<li>成功,返回值:<code>1</code>
</li>
<li>重复包含,返回 <code>1</code>,并忽略此次包含</li>
</ol>
<h4>总结</h4>
<p><code>include</code> <code>include_once</code> <code>requice</code> <code>requice_one</code> 成功时,都会返回 <code>1</code>,差别在于 包含失败、重复包含 的处理</p>
<h2>__autoload</h2>
<blockquote><p>尝试加载未定义的类。此函数将会在 <code>PHP 7.2.0</code> 中弃用。</p></blockquote>
<h3>PHP 代码解释</h3>
<p>使用示例:<br><strong>FILE:<code>foo.php</code></strong></p>
<pre><code class="php"><?php
class Foo
{
static public function getFoo()
{
echo "I am foo!\n";
}
}</code></pre>
<p><strong>FILE:<code>run.php</code></strong></p>
<pre><code class="php"><?php
ini_set('display_errors', '1');
function __autoload($classname)
{
$filename = "./". lcfirst($classname) .".php";
include_once($filename);
}
Foo::getFoo();</code></pre>
<p>结果:</p>
<pre><code class="php">➜ load git:(master) ✗ php run.php
I am foo!</code></pre>
<p>结论:<br>遇到未包含的类,会触发 <code>__autoload</code> 进行加载,如果所有加载规则中没有此类,则 <code>Fatal error</code>。</p>
<h3>Zend 代码解释</h3>
<p>下面,我们来看一下 <code>Zend</code> 引擎是如何触发 <code>__autoload</code> 调用的。<br>利用 <a href="https://link.segmentfault.com/?enc=%2B%2FQQ%2FIfAS2Oit8eehUeZOA%3D%3D.cDGq%2FSHq8ywWdbIC06FpG1f%2FXSgsvqQxVVJ4p%2FFVcCs%3D" rel="nofollow">vld</a> 来查看刚才执行过程中产生的 <code>opcode</code>,结果如下:<br><img src="/img/bVZjWg?w=821&h=658" alt="clipboard.png" title="clipboard.png"></p>
<p>我们看到,<code>PHP</code> 运行到第 10 行时,所生成的 <code>opcode</code> 为:<code>INIT_STATIC_METHOD_CALL</code>,两个操作数都为常量(<code>CONST</code>)。<br>根据 <a>opcode 的处理函数对应规则</a>,我们利用 <code>命名法</code> 可以确定,<br>处理函数为:<code>ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER</code><br>源码位置为:<code>vim Zend/zend_vm_execute.h +3819</code><br>源码如下:</p>
<pre><code class="c">static int ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *function_name;
zend_class_entry *ce;
call_slot *call = EX(call_slots) + opline->result.num;
SAVE_OPLINE();
if (IS_CONST == IS_CONST) {
/* no function found. try a static method in class */
if (CACHED_PTR(opline->op1.literal->cache_slot)) {
ce = CACHED_PTR(opline->op1.literal->cache_slot);
} else {
ce = zend_fetch_class_by_name(Z_STRVAL_P(opline->op1.zv), Z_STRLEN_P(opline->op1.zv), opline->op1.literal + 1, opline->extended_value TSRMLS_CC);
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
if (UNEXPECTED(ce == NULL)) {
zend_error_noreturn(E_ERROR, "Class '%s' not found", Z_STRVAL_P(opline->op1.zv));
}
CACHE_PTR(opline->op1.literal->cache_slot, ce);
}
call->called_scope = ce;
} else {
ce = EX_T(opline->op1.var).class_entry;
if (opline->extended_value == ZEND_FETCH_CLASS_PARENT || opline->extended_value == ZEND_FETCH_CLASS_SELF) {
call->called_scope = EG(called_scope);
} else {
call->called_scope = ce;
}
}
if (IS_CONST == IS_CONST &&
IS_CONST == IS_CONST &&
CACHED_PTR(opline->op2.literal->cache_slot)) {
call->fbc = CACHED_PTR(opline->op2.literal->cache_slot);
} else if (IS_CONST != IS_CONST &&
IS_CONST == IS_CONST &&
(call->fbc = CACHED_POLYMORPHIC_PTR(opline->op2.literal->cache_slot, ce))) {
/* do nothing */
} else if (IS_CONST != IS_UNUSED) {
char *function_name_strval = NULL;
int function_name_strlen = 0;
if (IS_CONST == IS_CONST) {
function_name_strval = Z_STRVAL_P(opline->op2.zv);
function_name_strlen = Z_STRLEN_P(opline->op2.zv);
} else {
function_name = opline->op2.zv;
if (UNEXPECTED(Z_TYPE_P(function_name) != IS_STRING)) {
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
zend_error_noreturn(E_ERROR, "Function name must be a string");
} else {
function_name_strval = Z_STRVAL_P(function_name);
function_name_strlen = Z_STRLEN_P(function_name);
}
}
if (function_name_strval) {
if (ce->get_static_method) {
call->fbc = ce->get_static_method(ce, function_name_strval, function_name_strlen TSRMLS_CC);
} else {
call->fbc = zend_std_get_static_method(ce, function_name_strval, function_name_strlen, ((IS_CONST == IS_CONST) ? (opline->op2.literal + 1) : NULL) TSRMLS_CC);
}
if (UNEXPECTED(call->fbc == NULL)) {
zend_error_noreturn(E_ERROR, "Call to undefined method %s::%s()", ce->name, function_name_strval);
}
if (IS_CONST == IS_CONST &&
EXPECTED(call->fbc->type <= ZEND_USER_FUNCTION) &&
EXPECTED((call->fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_HANDLER|ZEND_ACC_NEVER_CACHE)) == 0)) {
if (IS_CONST == IS_CONST) {
CACHE_PTR(opline->op2.literal->cache_slot, call->fbc);
} else {
CACHE_POLYMORPHIC_PTR(opline->op2.literal->cache_slot, ce, call->fbc);
}
}
}
if (IS_CONST != IS_CONST) {
}
} else {
if (UNEXPECTED(ce->constructor == NULL)) {
zend_error_noreturn(E_ERROR, "Cannot call constructor");
}
if (EG(This) && Z_OBJCE_P(EG(This)) != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) {
zend_error_noreturn(E_ERROR, "Cannot call private %s::__construct()", ce->name);
}
call->fbc = ce->constructor;
}
if (call->fbc->common.fn_flags & ZEND_ACC_STATIC) {
call->object = NULL;
} else {
if (EG(This) &&
Z_OBJ_HT_P(EG(This))->get_class_entry &&
!instanceof_function(Z_OBJCE_P(EG(This)), ce TSRMLS_CC)) {
/* We are calling method of the other (incompatible) class,
but passing $this. This is done for compatibility with php-4. */
if (call->fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC) {
zend_error(E_DEPRECATED, "Non-static method %s::%s() should not be called statically, assuming $this from incompatible context", call->fbc->common.scope->name, call->fbc->common.function_name);
} else {
/* An internal function assumes $this is present and won't check that. So PHP would crash by allowing the call. */
zend_error_noreturn(E_ERROR, "Non-static method %s::%s() cannot be called statically, assuming $this from incompatible context", call->fbc->common.scope->name, call->fbc->common.function_name);
}
}
if ((call->object = EG(This))) {
Z_ADDREF_P(call->object);
call->called_scope = Z_OBJCE_P(call->object);
}
}
call->num_additional_args = 0;
call->is_ctor_call = 0;
EX(call) = call;
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}</code></pre>
<p>通过以上源码,我们发现关键方法为 <code>zend_fetch_class_by_name</code>,跟进此方法:</p>
<pre><code class="c">zend_class_entry *zend_fetch_class_by_name(const char *class_name, uint class_name_len, const zend_literal *key, int fetch_type TSRMLS_DC) /* {{{ */
{
zend_class_entry **pce;
int use_autoload = (fetch_type & ZEND_FETCH_CLASS_NO_AUTOLOAD) == 0;
if (zend_lookup_class_ex(class_name, class_name_len, key, use_autoload, &pce TSRMLS_CC) == FAILURE) {
if (use_autoload) {
if ((fetch_type & ZEND_FETCH_CLASS_SILENT) == 0 && !EG(exception)) {
if ((fetch_type & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_INTERFACE) {
zend_error(E_ERROR, "Interface '%s' not found", class_name);
} else if ((fetch_type & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_TRAIT) {
zend_error(E_ERROR, "Trait '%s' not found", class_name);
} else {
zend_error(E_ERROR, "Class '%s' not found", class_name);
}
}
}
return NULL;
}
return *pce;
}</code></pre>
<p>我们发现是通过 <code>zend_lookup_class_ex</code> 来获取类,继续跟进:</p>
<pre><code class="c">ZEND_API int zend_lookup_class_ex(const char *name, int name_length, const zend_literal *key, int use_autoload, zend_class_entry ***ce TSRMLS_DC) /* {{{ */
{
...
/* 注意:在 类的符号表 中没有找到示例中调用的类 foo */
if (zend_hash_quick_find(EG(class_table), lc_name, lc_length, hash, (void **) ce) == SUCCESS) {
if (!key) {
free_alloca(lc_free, use_heap);
}
return SUCCESS;
}
...
/*
* ZVAL_STRINGL 为 zval (即 PHP 类型的实现基础 zvalue_value)赋值宏,
* 此处实现了 把 ZEND_AUTOLOAD_FUNC_NAME 值 赋给 autoload_function
* #define ZEND_AUTOLOAD_FUNC_NAME "__autoload"
*/
ZVAL_STRINGL(&autoload_function, ZEND_AUTOLOAD_FUNC_NAME, sizeof(ZEND_AUTOLOAD_FUNC_NAME) - 1, 0);
...
fcall_info.size = sizeof(fcall_info);
fcall_info.function_table = EG(function_table);
fcall_info.function_name = &autoload_function;
fcall_info.symbol_table = NULL;
fcall_info.retval_ptr_ptr = &retval_ptr;
fcall_info.param_count = 1;
fcall_info.params = args;
fcall_info.object_ptr = NULL;
fcall_info.no_separation = 1;
fcall_cache.initialized = EG(autoload_func) ? 1 : 0;
fcall_cache.function_handler = EG(autoload_func); /* 留意此处 */
fcall_cache.calling_scope = NULL;
fcall_cache.called_scope = NULL;
fcall_cache.object_ptr = NULL;
...
retval = zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC); /* 调用自动加载函数 */
...
EG(autoload_func) = fcall_cache.function_handler;
zval_ptr_dtor(&class_name_ptr);
zend_hash_quick_del(EG(in_autoload), lc_name, lc_length, hash);
...
}</code></pre>
<p>我们发现是通过 <code>zend_call_function</code> 出发了自动加载函数,而且看到了加载方法的名字 <code>__autoload</code> (宏:<code>ZEND_AUTOLOAD_FUNC_NAME</code>)</p>
<p><code>zend_call_function</code> 中会做一下检测并调用等,而且我们看到 <code>zend_lookup_class_ex</code> 的返回结果即为 <code>zend_call_function</code> 的返回结果。</p>
<p>接下来我们逐步退出函数调用栈:<br>假设 <code>zend_call_function</code> 调用失败,返回 <code>FALSE</code>,<br>则 <code>zend_lookup_class_ex</code> 返回 <code>FALSE</code>;<br>则 <code>zend_fetch_class_by_name</code> 返回 <code>NULL</code>;<br>则 <code>ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER</code> 抛出异常 <code>Class ** not found</code>,如下图所示:<br><img src="/img/bVZl5O?w=2604&h=596" alt="clipboard.png" title="clipboard.png"></p>
<h3>结论</h3>
<p>至此,我们通过 <code>PHP 代码</code> <code>Zend 源码</code> 了解了 <code>__autoload</code> 的调用过程。<br>我们知道 <code>__autoload</code> 现在已并不推荐使用,<br>它的缺点也很明显,不支持多个自动加载函数。</p>
<h2>spl_autoload_register</h2>
<blockquote><p>将函数注册到SPL __autoload函数队列中。如果该队列中的函数尚未激活,则激活它们。如果在你的程序中已经实现了__autoload()函数,它必须显式注册到__autoload()队列中。</p></blockquote>
<h3>PHP 代码解释</h3>
<p>使用示例:<br><strong>FILE:<code>foo.php</code></strong><br>(同上 <code>__autoload</code>)</p>
<p><strong>FILE:<code>foo2.class.php</code></strong></p>
<pre><code class="php"><?php
class Foo2
{
static public function getFoo2()
{
echo "I am foo2!\n";
}
}</code></pre>
<p><strong>FILE:<code>run.php</code></strong></p>
<pre><code class="php"><?php
ini_set('display_errors', '1');
$my_autoload1 = function ($classname)
{
echo "entry my_autoload1 \n";
$filename = "./". lcfirst($classname) .".php";
include_once($filename);
};
$my_autoload2 = function ($classname)
{
echo "entry my_autoload2 \n";
$filename = "./". lcfirst($classname) .".class.php";
include_once($filename);
};
spl_autoload_register($my_autoload1);
spl_autoload_register($my_autoload2);
Foo::getFoo();
Foo2::getFoo2();</code></pre>
<p>结果如下:<br><img src="/img/bVZmq6?w=1472&h=215" alt="clipboard.png" title="clipboard.png"></p>
<p>我们看到,调用 <code>getFoo2</code> 时,会先调用第一个注册的 <code>autoload</code> 方法,如果没找到对应的类,会产生 <code>warning</code> 并继续调用后边注册的 <code>autoload</code> 方法。<br>说明了 <code>PHP</code> 内核中为通过 <code>spl_autoload_register</code> 注册的 <code>autoload</code> 方法维护了一个队列,当前文件为包含调用类,便会触发此队列,并依次调用,直到队列结束 或者 找到对应类。</p>
<h3>Zend 源码解释</h3>
<p>首先,我们看一下 <code>PHP</code> 文件生成的 <code>opcode</code><br><img src="/img/bVZmNu?w=2218&h=1514" alt="clipboard.png" title="clipboard.png"></p>
<p>我们发现,其方法调用所生成的 <code>opcode</code> 跟 <code>__autoload</code> 一样,<br>但是我们之前调用了 `spl_autoload_register,<br>那么,看一下 <code>spl_autoload_register</code> 的源码:<br><strong>FILE: <code>ext/spl/php_spl.c</code></strong>*</p>
<pre><code class="c">PHP_FUNCTION(spl_autoload_register)
{
char *func_name, *error = NULL;
int func_name_len;
char *lc_name = NULL;
zval *zcallable = NULL;
zend_bool do_throw = 1;
zend_bool prepend = 0;
zend_function *spl_func_ptr;
autoload_func_info alfi;
zval *obj_ptr;
zend_fcall_info_cache fcc;
if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "|zbb", &zcallable, &do_throw, &prepend) == FAILURE) {
return;
}
if (ZEND_NUM_ARGS()) {
if (Z_TYPE_P(zcallable) == IS_STRING) {
if (Z_STRLEN_P(zcallable) == sizeof("spl_autoload_call") - 1) {
if (!zend_binary_strcasecmp(Z_STRVAL_P(zcallable), sizeof("spl_autoload_call"), "spl_autoload_call", sizeof("spl_autoload_call"))) {
if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Function spl_autoload_call() cannot be registered");
}
RETURN_FALSE;
}
}
}
if (!zend_is_callable_ex(zcallable, NULL, IS_CALLABLE_STRICT, &func_name, &func_name_len, &fcc, &error TSRMLS_CC)) {
alfi.ce = fcc.calling_scope;
alfi.func_ptr = fcc.function_handler;
obj_ptr = fcc.object_ptr;
if (Z_TYPE_P(zcallable) == IS_ARRAY) {
if (!obj_ptr && alfi.func_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) {
if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Passed array specifies a non static method but no object (%s)", error);
}
if (error) {
efree(error);
}
efree(func_name);
RETURN_FALSE;
}
else if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Passed array does not specify %s %smethod (%s)", alfi.func_ptr ? "a callable" : "an existing", !obj_ptr ? "static " : "", error);
}
if (error) {
efree(error);
}
efree(func_name);
RETURN_FALSE;
} else if (Z_TYPE_P(zcallable) == IS_STRING) {
if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Function '%s' not %s (%s)", func_name, alfi.func_ptr ? "callable" : "found", error);
}
if (error) {
efree(error);
}
efree(func_name);
RETURN_FALSE;
} else {
if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Illegal value passed (%s)", error);
}
if (error) {
efree(error);
}
efree(func_name);
RETURN_FALSE;
}
}
alfi.closure = NULL;
alfi.ce = fcc.calling_scope;
alfi.func_ptr = fcc.function_handler;
obj_ptr = fcc.object_ptr;
if (error) {
efree(error);
}
lc_name = safe_emalloc(func_name_len, 1, sizeof(long) + 1);
zend_str_tolower_copy(lc_name, func_name, func_name_len);
efree(func_name);
if (Z_TYPE_P(zcallable) == IS_OBJECT) {
alfi.closure = zcallable;
Z_ADDREF_P(zcallable);
lc_name = erealloc(lc_name, func_name_len + 2 + sizeof(zend_object_handle));
memcpy(lc_name + func_name_len, &Z_OBJ_HANDLE_P(zcallable),
sizeof(zend_object_handle));
func_name_len += sizeof(zend_object_handle);
lc_name[func_name_len] = '\0';
}
if (SPL_G(autoload_functions) && zend_hash_exists(SPL_G(autoload_functions), (char*)lc_name, func_name_len+1)) {
if (alfi.closure) {
Z_DELREF_P(zcallable);
}
goto skip;
}
if (obj_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) {
/* add object id to the hash to ensure uniqueness, for more reference look at bug #40091 */
lc_name = erealloc(lc_name, func_name_len + 2 + sizeof(zend_object_handle));
memcpy(lc_name + func_name_len, &Z_OBJ_HANDLE_P(obj_ptr), sizeof(zend_object_handle));
func_name_len += sizeof(zend_object_handle);
lc_name[func_name_len] = '\0';
alfi.obj = obj_ptr;
Z_ADDREF_P(alfi.obj);
} else {
alfi.obj = NULL;
}
if (!SPL_G(autoload_functions)) {
ALLOC_HASHTABLE(SPL_G(autoload_functions));
zend_hash_init(SPL_G(autoload_functions), 1, NULL, (dtor_func_t) autoload_func_info_dtor, 0);
}
zend_hash_find(EG(function_table), "spl_autoload", sizeof("spl_autoload"), (void **) &spl_func_ptr);
if (EG(autoload_func) == spl_func_ptr) { /* registered already, so we insert that first */
autoload_func_info spl_alfi;
spl_alfi.func_ptr = spl_func_ptr;
spl_alfi.obj = NULL;
spl_alfi.ce = NULL;
spl_alfi.closure = NULL;
zend_hash_add(SPL_G(autoload_functions), "spl_autoload", sizeof("spl_autoload"), &spl_alfi, sizeof(autoload_func_info), NULL);
if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) {
/* Move the newly created element to the head of the hashtable */
HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions));
}
}
if (zend_hash_add(SPL_G(autoload_functions), lc_name, func_name_len+1, &alfi.func_ptr, sizeof(autoload_func_info), NULL) == FAILURE) {
if (obj_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) {
Z_DELREF_P(alfi.obj);
}
if (alfi.closure) {
Z_DELREF_P(alfi.closure);
}
}
if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) {
/* Move the newly created element to the head of the hashtable */
HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions));
}
skip:
efree(lc_name);
}
if (SPL_G(autoload_functions)) {
zend_hash_find(EG(function_table), "spl_autoload_call", sizeof("spl_autoload_call"), (void **) &EG(autoload_func)); /* 注意此处 */
} else {
zend_hash_find(EG(function_table), "spl_autoload", sizeof("spl_autoload"), (void **) &EG(autoload_func));
}
RETURN_TRUE;
} /* }}} */</code></pre>
<p>通过分析源码,我们发现 <code>spl_autoload_register </code> 会把注册的自动加载函数添加到 <code>autoload_functions</code> 中,最后将 <code>autoload_functions</code> 赋值给 <code>EG(autoload_func)</code> (上方源码倒数第一个 <code>if</code> 判断逻辑中)。<br>而有印象的同学会发现,<code>EG(autoload_func)</code> 在分析 <code>__autoload</code> 调用源码时出现过(可以划到之前的分析查看),它是执行环境全局结构体中的成员,出现调用大概源码如下:</p>
<pre><code class="c">ZEND_API int zend_lookup_class_ex(const char *name, int name_length, const zend_literal *key, int use_autoload, zend_class_entry ***ce TSRMLS_DC)
{
...
fcall_info.size = sizeof(fcall_info);
fcall_info.function_table = EG(function_table);
fcall_info.function_name = &autoload_function;
fcall_info.symbol_table = NULL;
fcall_info.retval_ptr_ptr = &retval_ptr;
fcall_info.param_count = 1;
fcall_info.params = args;
fcall_info.object_ptr = NULL;
fcall_info.no_separation = 1;
fcall_cache.initialized = EG(autoload_func) ? 1 : 0;
fcall_cache.function_handler = EG(autoload_func); /* 注意这里 */
fcall_cache.calling_scope = NULL;
fcall_cache.called_scope = NULL;
fcall_cache.object_ptr = NULL;
zend_exception_save(TSRMLS_C);
retval = zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC);
zend_exception_restore(TSRMLS_C);
...
return retval;
}</code></pre>
<p>分析到这里,我们已经知道了,<code>spl_autoload_register</code> 注册的函数是如何在 <code>PHP</code> 代码调用时被触发的。<br>感兴趣的同学可以继续查看一下 <code>zend_call_function</code> 的源码,了解具体的调用方式。</p>
<h3>结论</h3>
<p>通过 <code>spl_autoload_register</code> 注册自动加载函数,会在 <code>Zend</code> 引擎中维护一个 <code>autoload</code> 队列,即可添加多个 <code>autoload</code> 函数,并在 <code>PHP</code> 调用当前文件未知的类时,触发 <code>autoload_func</code> 的调用。</p>
<p>同时,细心的同学也会从 <code>spl_autoload_register</code> 源码中发现,当注册时传入的方法不可调用时, 如果有实现 <code>spl_autoload</code>,也其会被注册到 <code>autoload</code> 队列中。</p>
<p>更多使用细节,请参考:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=v1mYVz5StYE9s%2BO4dsxKHA%3D%3D.JUDocsfgG%2BVSiETKdSR30npJFbwPt6wMqYDE45bztKsKhw3gub16aZ2FXUF%2Bh0LY5467S%2FR2%2BOh6rbrkDL5uJQ%3D%3D" rel="nofollow">__autoload</a></li>
<li><a href="https://link.segmentfault.com/?enc=hAY9eqgbz6ti2TZohZ2WHA%3D%3D.qrlPTjPEJr0KC%2BoNxXMlKdWqMa8UGsURUrOSK1XtzH%2FJb%2FhRLIdFxpUlDwE51ZTjki89FhAVjCrjeWGsOT9xSuKk8YwgoT1g6dYEg3YGf%2Fo%3D" rel="nofollow">spl_autoload_register</a></li>
<li><a href="https://link.segmentfault.com/?enc=BcW2x%2FWugFkmXvosR2M5xw%3D%3D.ivKN1oyQ6Rk56%2Fa%2BwinOg1Fqs9fgDI%2BCFzIpC5YP0S4a7Q2rpy%2FsPsDOzbkLTfBM" rel="nofollow">SPL 函数</a></li>
<li><a href="https://link.segmentfault.com/?enc=%2FjhgZuMxYlkIbJHqD2%2F41Q%3D%3D.ViDYfyAZNaTGq2sKbKfWp1UxsC0A46QyeqCUZ5DRXlXPMaPpWlVZSkFBQQbtwb0M" rel="nofollow">深入理解 PHP 内核</a></li>
</ul>
<hr>
<p>以上分析,如有不适的地方,请多多指教!</p>
PHP 的 错误/异常 处理总结
https://segmentfault.com/a/1190000012149712
2017-11-24T17:57:17+08:00
2017-11-24T17:57:17+08:00
fevin
https://segmentfault.com/u/fevin
5
<h2>错误</h2>
<blockquote><p>这里说的错误,可能是由 语法解析、运行时等各种原因产生的信息引起的</p></blockquote>
<h3>常见的错误类型</h3>
<h4>运行时错误</h4>
<ul>
<li>
<p><code>E_ERROR</code> - 致命错误</p>
<ul>
<li>定义:致命的运行时错误</li>
<li>后果:脚本终止不再继续运行</li>
</ul>
</li>
<li>
<p><code>E_WARNING</code> - 警告</p>
<ul>
<li>定义:运行时警告 (非致命错误)</li>
<li>后果:给出提示信息,但是脚本不会终止运行</li>
</ul>
</li>
<li>
<p><code>E_NOTICE</code> - 通知</p>
<ul>
<li>定义:运行时通知</li>
<li>结果:给出通知信息,但是脚本不会终止运行</li>
</ul>
</li>
</ul>
<h4>其他类型错误</h4>
<ul>
<li>编译时错误<br>eg. <code>E_PARSE</code> <code>E_COMPILE_ERROR</code> <code>E_COMPILE_WARNING</code> ...</li>
<li>用户产生的信息<br>eg. <code>E_USER_WARNING</code> <code>E_USER_ERROR</code> <code>E_USER_NOTICE</code>
</li>
<li>... 等</li>
</ul>
<p>具体如下图:</p>
<p><img src="/img/bVY8ot?w=931&h=823" alt="clipboard.png" title="clipboard.png"></p>
<p>参考:<a href="https://link.segmentfault.com/?enc=kx%2BTaHGmMQDfPny95%2BQCjw%3D%3D.yoZHs8%2B2juxrDsWo2%2BYK6JktC2z7kCuNutgE1J0WkQfwNHYkviAFjHq6SAtZZgVWePAD7jotWtXemLTOlvElBg%3D%3D" rel="nofollow">PHP-错误处理-预定义常量</a></p>
<h3>错误处理</h3>
<blockquote><p>这里只针对运行时错误进行处理,其他(如:<code>语法错误</code> <code>Zend 引擎产生的错误</code> 等)不在讨论范围内。</p></blockquote>
<h4>设置一般错误的处理函数</h4>
<p>核心方法:<a href="https://link.segmentfault.com/?enc=kd4DgODidpPI8F1fn%2BYmqQ%3D%3D.IH%2FigO0%2BVVD%2BXPg7lqo3Q4l7eac35zDpzOSMYFMGxEBPeNSbfEv7zBMqumvPcJFFfjKOk%2FMyPVKQ%2B6Ppt84XwQ%3D%3D" rel="nofollow">set_error_handler</a></p>
<p>测试代码如下:</p>
<pre><code class="php"><?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");
/**
* 回调函数原型 : bool handler ( int $errno , string $errstr [, string $errfile [, int $errline [, array $errcontext ]]] )
*/
set_error_handler(function ($errno, $errstr) {
$err_type = '';
$return = true;
if (E_WARNING === $errno) {
$err_type = 'warning';
$return = false;
} elseif (E_NOTICE === $errno) {
$err_type = 'notice';
} elseif (E_ERROR === $errno) {
$err_type = 'error';
}
echo sprintf("This is error callback, err_type:%s, err_no:%d, err_str:%s \n", $err_type, $errno, $errstr);
return $return;
});
function sayHere($line)
{
echo sprintf("I am here.Line:%d \n", $line);
}
/* warning */
function test($a) {}
test();
sayHere(__LINE__);
/* notice */
echo $notice_msg;
sayHere(__LINE__);
/* fatal */
$i = '';
while(1) {
$i .= 'a';
}
sayHere(__LINE__);</code></pre>
<p>结果如下:</p>
<p><img src="/img/bVY8vS?w=1534&h=212" alt="clipboard.png" title="clipboard.png"></p>
<p>这里我们看到,<code>set_error_handler</code> <strong>只对</strong> <code>E_WARNING</code> <code>E_NOTICE</code> 进行了捕获,并且当回调函数遇到<br><code>E_NOTICE</code> 返回 <code>true</code> 的时候,我们看到底层对标准错误的输出,但是遇到 <code>E_WARNING</code> 返回 <code>false</code>,我们并没有看到底层对标准错误的输出。</p>
<p>总结,来自于官方手册:</p>
<ol>
<li>
<code>set_error_handler</code> 第二个参数指定的错误类型都会绕过 PHP 标准错误处理程序</li>
<li>以下级别的错误不能由用户定义的函数来处理: <code>E_ERROR</code>、 <code>E_PARSE</code>、 <code>E_CORE_ERROR</code>、 <code>E_CORE_WARNING</code>、 <code>E_COMPILE_ERROR</code>、 <code>E_COMPILE_WARNING</code>
</li>
</ol>
<p><strong>备注:此方法可有针对性的对服务产生的消息进行收集,处理。比如:在框架初始化时,注册一个定制化的错误回调。</strong></p>
<p>那致命错误有没有办法处理呢?接着看。</p>
<h4>设置致命错误处理函数</h4>
<p>我们知道致命错误会引起:脚本终止不再继续运行。<br>那么,我们就可以利用 <a href="https://link.segmentfault.com/?enc=4HgV64Q5W880mAMN9K1R%2FA%3D%3D.5%2F0iL3GSu33eVNhrqYfozGGSyE2%2FovB%2BFgq9czWfEIwlQOKLTqoHRFA9nvb1UXEyV9sfEb6eFupTd%2BwyEzADm0rIO1E2G0yL5scFWc2z%2BC0%3D" rel="nofollow">register_shutdown_function</a> 方法做一些处理。<br>作用:注册一个会在php中止时执行的函数</p>
<p>测试代码如下:</p>
<pre><code class="php"><?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");
/**
* 回调函数原型 : 参数由 register_shutdown_function 的参数决定
*/
register_shutdown_function(function () {
echo "This will shutdown. \n";
});
function sayHere($line)
{
echo sprintf("I am here.Line:%d \n", $line);
}
function test($a)
{
return;
}
/* warning */
test();
sayHere(__LINE__);
/* notice */
echo $notice_msg;
sayHere(__LINE__);
/* fatal */
$i = '';
while(1) {
$i .= 'a';
}
sayHere(__LINE__);</code></pre>
<p>结果如下:</p>
<p><img src="/img/bVY8C1?w=1653&h=238" alt="clipboard.png" title="clipboard.png"></p>
<p>如前所述,发生致命错误,进程退出,但是中止之前执行了我们注册的回调函数。</p>
<hr>
<h2>异常</h2>
<p>说明:我们这里指用户自定义的异常。</p>
<h3>try-catch 捕获</h3>
<p>测试代码如下:</p>
<pre><code class="php"><?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");
class UserException extends \Exception
{
}
try {
throw new \UserException('This is exception');
} catch (\UserException $e) {
echo 'UserException:' . $e->getMessage() . PHP_EOL;
} catch (\Exception $e) {
echo 'Exception:' . $e->getMessage() . PHP_EOL;
} finally {
echo 'here is finally' . PHP_EOL;
}</code></pre>
<p>结果如下:</p>
<pre><code class="shell">➜ answer git:(master) ✗ php exception.php
UserException:This is exception
here is finally</code></pre>
<p>这是常见的捕获,不做过多说明,参见:<a href="https://link.segmentfault.com/?enc=i07cIQBQboqNbwZmzyCENA%3D%3D.HGeZG9MAy50hJLcEfmvRDnehWYIfKLRnEnuLj6EsmZV8whm1c6SHVm2gQDMvhvPx42PtJoc6%2FqLG2oMSmuqCrw%3D%3D" rel="nofollow">异常处理</a></p>
<h3>未捕获的异常</h3>
<p>那么,如有抛出去的异常未被 <code>catch</code>,怎么办?<br>我们先看一下,未被 <code>catch</code> 会怎么样:</p>
<pre><code class="php"><?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");
throw new \Exception('I am an exception');
echo 'I am here' . PHP_EOL;</code></pre>
<p>结果如下:</p>
<pre><code class="shell">➜ answer git:(master) ✗ php throw.php
Fatal error: Uncaught exception 'Exception' with message 'I am an exception' in /Users/javin/github/answer/throw.php:5
Stack trace:
#0 {main}
thrown in /Users/javin/github/answer/throw.php on line 5</code></pre>
<p>会出现 <code>致命错误</code>,脚本中断,那么,我们当然可以用上边所说的 <code>register_shutdown_function </code> 来处理。<br>这样的话,就没有合其他致命错误区分了,那么,有没有专门处理未捕获的异常呢?<br>答案是有的,它就是:<a href="https://link.segmentfault.com/?enc=v9lZiEUBzcoDnLiNbO9ckA%3D%3D.DXXHbUWPLBAjX8r3CHL2myIKgojZVP%2BHyO8efB%2BDg61SNc74xOWuKoaKzAZ6mmcWAMsLtnydP5axxlO0%2Fpn00w%3D%3D" rel="nofollow">set_exception_handler</a></p>
<p>测试代码如下:</p>
<pre><code class="php"><?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");
/**
* 回调函数签名:void handler ( Exception $ex )
*/
set_exception_handler(function ($e) {
echo sprintf("This is exception, msg:%s\n", $e->getMessage());
});
throw new \Exception('I am an exception');
echo 'I am here' . PHP_EOL;</code></pre>
<p>结果如下:</p>
<pre><code class="shell">➜ answer git:(master) ✗ php throw.php
This is exception, msg:I am an exception</code></pre>
<p>结论:<code>set_exception_handler</code> 可以对未捕获的异常进行处理,但是脚本仍然会因为致命错误而中断。</p>
<hr>
<h2>结尾</h2>
<p>本文对 <code>异常处理</code> 做了简要的总结,其中涉及到三个核心方法 <code>set_error_handler</code> <code>register_shutdown_function</code> <code>set_exception_handler</code>,其详细说明,请参见 <a href="https://link.segmentfault.com/?enc=FW1zKNkQGPmL7vpI1oEeSA%3D%3D.NqfyjarvFtX6fDlmuqAAZiFS4za25999F1gFIlw%2BiimMnEKcYFqFmuN500ZEFiZu" rel="nofollow">官方手册</a> 。<br>同时 <code>PHP-7</code> 中也有一些新的特性,比如:<a href="https://link.segmentfault.com/?enc=9DHQhgS1%2FpCw9X8%2BffGe7A%3D%3D.%2FIJVth%2BlF1oEXXjtx0WvfjE2cOvfy1YoNt7a8dAh7WtlmQ1jNNddKdFCYIKAEuJZ" rel="nofollow">Error 类</a></p>
<p>参考:<a href="https://link.segmentfault.com/?enc=N2SZ1o%2FdT51nv5dd%2Blwx%2Fg%3D%3D.vXcb39huF2DYymCFL5%2Fjkn4FtVDopH2OR9GdVBbD2LpU4GhshtqL5l3bF0vT%2B%2F9S8SJXGHbJqai8msYknEm%2B0w%3D%3D" rel="nofollow">PHP 7 错误处理</a></p>
<p>最后,强烈建议开启编辑器的 <code>语法检查</code> 功能,不管是 <code>IDE</code>,还是 <code>GUI 文本编辑器</code>,还是 <code>vim</code>,这样可以避免很多不必要的错误。如果有使用版本控制,可以给对应的软件加上 <code>语法检查</code> 的钩子。</p>
<p>可以参考:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=SyRa%2By9%2FKyyoc8loUqQhOw%3D%3D.xKQA7r4qxPAjtfgx3499%2FNQKP2VjokG11Mcm%2FgqNuzg%3D" rel="nofollow">我的 vim-配置</a></li>
<li><a href="https://link.segmentfault.com/?enc=lqAKQ805s%2FLbm13gIr12%2Bg%3D%3D.rq%2F6vB8vftZHbsT2e0qDZw1lLT6xTUrjD9QFrnt6VfUbV5icGjYkls2MmyjuV6Hf" rel="nofollow">自动化检测PHP语法和编程规范(Git pre-commit)</a></li>
</ul>
<hr>
<p>以上如有错误,请多多指正。如有遗漏,请多多补充。?</p>
【nginx】 web-server 多文件入口访问
https://segmentfault.com/a/1190000011570070
2017-10-16T11:26:56+08:00
2017-10-16T11:26:56+08:00
fevin
https://segmentfault.com/u/fevin
0
<h3>访问需求示例</h3>
<p>需要访问如下 <code>url</code>:<br><code>localhost/info.php</code><br><code>localhost/detail.php</code></p>
<p>服务端 <code>server-root</code> 目录结构:</p>
<pre><code class="shell">➜ ~ tree public
public
├── detail.php
└── info.php</code></pre>
<h3>问题</h3>
<p>我们习惯配置 <code>nginx</code> 的 <code>web</code> 服务为 <code>单入口</code>,即:</p>
<pre><code>root /opt/pro/public;
index index.php index.html;</code></pre>
<h3>多入口 <code>nginx</code> 配置</h3>
<blockquote><p>利用 <code>nginx</code> 变量 <code>$uri</code> 动态配置 <code>SCRIPT_NAME</code>,实现 <code>web</code> 多入口访问</p></blockquote>
<pre><code class="shell">server {
listen80;
server_name localhost;
index index.php;
root /opt/pro/public;
location ~* \.php {
try_files $uri $uri/ /$uri?$query_string;
set $php_script $uri;
include fastcgi_params;
fastcgi_pass unix:/tmp/php-fpm.socket;
fastcgi_param SCRIPT_FILENAME $document_root/$php_script;
fastcgi_param SCRIPT_NAME /$php_script;
}
}</code></pre>
【python】[转载]UnicodeEncodeError: 'ascii' codec
https://segmentfault.com/a/1190000007403227
2016-11-07T11:59:43+08:00
2016-11-07T11:59:43+08:00
fevin
https://segmentfault.com/u/fevin
0
<h3>1、原因</h3>
<p>python2.7在安装时,默认的编码是ascii,当程序中出现非ascii编码时,python的处理常常会报这样的错,不过在python3就不会有这样的问题。</p>
<h3>2、解决办法</h3>
<h4>临时解决方法:</h4>
<p>代码中加入如下三行<br>import sys <br>reload(sys) <br>sys.setdefaultencoding('utf8')</p>
<h4>永久解决方法:</h4>
<p>如果不想在每个文件中都加这三行,就在python的Lib\site-packages文件夹下新建一个sitecustomize.py<br>内容如下:</p>
<pre><code class="python">#encoding=utf8
import sys
reload(sys)
sys.setdefaultencoding('utf8')</code></pre>
<p>这样的话,系统在python启动的时候,自行调用该文件,设置系统的默认编码</p>
<p>或者:</p>
<p>重新编译安装python,将默认编码改为utf8</p>
<hr>
<p>以上参考:<a href="https://link.segmentfault.com/?enc=gipKMjVDa6GBcmsd7jhSbA%3D%3D.HNs7TISADZQUM7QzMd9UDY5Z00rd80x6shmKHmEoP7K%2FTozryhN55MZLkXXmKvkX" rel="nofollow">解决UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-11</a></p>
【python】python2.7源码安装遇到的问题&&解决
https://segmentfault.com/a/1190000007345825
2016-11-01T16:15:45+08:00
2016-11-01T16:15:45+08:00
fevin
https://segmentfault.com/u/fevin
0
<h3>1、Error_Info 1: INFO: Can't locate Tcl/Tk libs and/or headers</h3>
<h4>1.1 解决</h4>
<pre><code class="shell">yum install tcl-devel
yum install tk-devel</code></pre>
<h4>1.2 原因</h4>
<blockquote><p>参考:<br>You may have to install Tcl and Tk(when using RPM, install the –devel RPM as well) and /or edit the setup.py script to point to the right locations where Tcl/Tk is installed. If you install Tcl/Tk in the default locations, simply rerunning “make” should build the _tkinter extension.</p></blockquote>
<h3>2、Error_Info 2: Python build finished, but the necessary bits to build these modules were not found</h3>
<p>错误信息如下示:<br>Python build finished, but the necessary bits to build these modules were not found:<br>_bsddb _tkinter bsddb185<br>dl imageop sunaudiodev</p>
<p>解决:<br>在CentOS下,可以安装这些依赖包:readline-devel,sqlite-devel,bzip2-devel.i686,openssl-devel.i686,gdbm-devel.i686,libdbi-devel.i686,ncurses-libs,zlib-devel.i686。完成这些安装之后,可以再次编译。</p>
<pre><code class="shell">$ yum install readline-devel sqlite-devel bzip2-devel.i686 openssl-devel.i686 gdbm-devel.i686 libdbi-devel.i686 ncurses-libs,zlib-devel.i686
$ make && make install</code></pre>
<p>此解决办法参考:<a href="https://link.segmentfault.com/?enc=ODO%2BbqDn03P%2FnQsfiKOfVQ%3D%3D.4kOgUhz9wNRARqnr5XeYFFzkZykbPX%2Fus9cza%2Fl1v1j3r0F0M6x1CCNPhu5nZ30NeeKBeJAdrI5y2szylKXtZQ%3D%3D" rel="nofollow">Python编译安装遇到的问题</a></p>
<h3>3、多版本共存解决:安装完python2.7之后,还是显示旧版(2.6)</h3>
<p>问题如图:<br><img src="/img/bVEY6c?w=306&h=42" alt="clipboard.png" title="clipboard.png"></p>
<p><strong>解决:</strong></p>
<pre><code class="shell"># 把原来的python重命名成python_old,注意不要删除它
$ mv /usr/bin/python /usr/bin/python_old
# 建立新的python的软链接,/usr/local/python27为你刚才的安装目录
$ ln -s /usr/local/python27/bin/python /usr/bin/</code></pre>
<h3>4、yum错误:解决因python版本变化,引起的yum问题</h3>
<p>问题如图:<br><img src="/img/bVEY7L?w=466&h=284" alt="clipboard.png" title="clipboard.png"></p>
<p><strong>解决:</strong><br>修改yum脚本(因为yum脚本使用python解析的)</p>
<pre><code class="shell">vim /usr/bin/yum
# 进入文件,修改首行#!/usr/bin/python 为旧版的python2.6(这个是我本机的)
#!/usr/bin/python2.6</code></pre>
<p>参考:<a href="https://link.segmentfault.com/?enc=s6znYbenpR7gpIX5T0r%2Bag%3D%3D.8ZH8g5C5c75MqRF4rt3DhFC6JN1vIe%2BZ8rp2P1s%2Fv5TagaeXXvN2ejXA6cvAIOzsRxfUcO%2B%2BnWg6Miuintq%2BUA%3D%3D" rel="nofollow">升级python到最新2.7.x -- linux</a><br>success!<br>over~</p>
【Laravel】[错误解决] 'Class App\Console\Kernel does not exist'
https://segmentfault.com/a/1190000007314915
2016-10-28T19:31:17+08:00
2016-10-28T19:31:17+08:00
fevin
https://segmentfault.com/u/fevin
0
<blockquote><p>今天用php artisan app:name 更改了app的命名空间名字,然后又用git<br>恢复到了原来的App命名空间名。 结果直接导致以下错误:<br>Fatal error: Uncaught exception 'ReflectionException' with message 'Class AppConsoleKernel does not exist'</p></blockquote>
<h3>1、问题</h3>
<p><img src="/img/bVEQ46?w=1537&h=142" alt="err_img.png" title="err_img.png"></p>
<h3>2、解决</h3>
<pre><code># 1. 确定你的引导程序没问题
bootstrap/app.php
# 2. 在项目根目录执行命令
composer dump-autoload</code></pre>
<p>解决方案来自:<a href="https://link.segmentfault.com/?enc=RGloHcgRrkK4ZP%2FKiTE6RA%3D%3D.tSyskiTpLg698u%2FXjz492PfP1qBsrLna2vmF1eGaQRB9lWDSgraqUwthVYw4cKwbLrwZaUsqyOOtVd3sR5NMdczi5qKa8MgIr8FVWkO%2BwH6DH0ny9oRCeZco%2F%2BIHb%2BtB" rel="nofollow">传送门</a></p>
<p><strong>为什么要执行 composer dump-autoload ,参考:<a href="https://link.segmentfault.com/?enc=F0IFImH1fFct%2F%2B%2FSNq1JCg%3D%3D.j01iI0503bLcbMnjp6SgQwhNzinbKthyNVSnaA0UYZLer3D8TTUG%2BSK%2Beh7jO0E9" rel="nofollow">深入 Composer autoload</a></strong></p>
【web开发】php服务端提交post请求
https://segmentfault.com/a/1190000007076141
2016-10-05T15:58:35+08:00
2016-10-05T15:58:35+08:00
fevin
https://segmentfault.com/u/fevin
0
<blockquote><p>服务端常见的post提交有三种方式,这里主要记录curl方式</p></blockquote>
<h2>1、服务端进行http-post的三种方法</h2>
<h3>1.1 通过<a href="https://link.segmentfault.com/?enc=jcVbpm4cWHLu92ZDxsy%2BJw%3D%3D.Y%2F0f7RTM8jsyZ3xM0Zsh4rvjrZEThPYOPVxHLzh%2B030tpGaGwlpSR9ltlV9wvq4g" rel="nofollow">curl函数</a>
</h3>
<pre><code class="php">function post($url, $post_data = '', $timeout = 5){//curl
$ch = curl_init();
curl_setopt ($ch, CURLOPT_URL, $url);
curl_setopt ($ch, CURLOPT_POST, 1);
if($post_data != ''){
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
}
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_HEADER, false);
$file_contents = curl_exec($ch);
curl_close($ch);
return $file_contents;
}</code></pre>
<h3>1.2 通过<a href="https://link.segmentfault.com/?enc=Txmko1fagB10b7OshQrYzA%3D%3D.E7T09LJ7Cvd%2F7g5hmp3V9GnbitI3FSRAJY6CFP7d%2Bfk3VZt%2FpqE88wP1jlmvOMl6" rel="nofollow">Filesystem函数</a>
</h3>
<pre><code class="php">function post2($url, $data)
{
$postdata = http_build_query(
$data
);
$opts = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
),
);
$context = stream_context_create($opts);
$result = file_get_contents($url, false, $context);
return $result;
}</code></pre>
<h3>1.3 借助<a href="https://link.segmentfault.com/?enc=zBph%2BWmZnyhlUWw1011nvA%3D%3D.BI1vkYyGl5zcBpBcpFkb%2BkzBYK2vU2gAyfzTfOjNu8x0OTk1SXkr%2BUpvAVQf%2F3u%2B" rel="nofollow">网络函数</a>
</h3>
<pre><code class="php">fsockopen();
fwrite();
fread();
fclose();</code></pre>
<p><strong>以上三种方法源码参考自:<a href="https://link.segmentfault.com/?enc=Vb4frNil56Atr2nC3YnqrQ%3D%3D.Jm5FJcUi9p1OgxY%2F9yHtJVKgwxntDod%2BauoqB6tDg6BYOyVGJAPvI94b667dMNeG3BPNjDgQydQE1%2FJ%2Fwp3kjQ%3D%3D" rel="nofollow">原文</a></strong></p>
<h2>2、我本地构造的curl成功提交post</h2>
<h3>2.1 curl提交post源码</h3>
<pre><code class="php"> /**
* [sendPostHttp]
* @param string $url 提交地址,[schema://host:port]
* @param array $params 需要通过post提交的数据
* @param integer $timeout 连接超时
* @return mixed 提交状态
*/
static public function sendPostHttp($url, $params, $timeout = 5)
{
// 构造post提交
$ch = curl_init();
$option = array(
CURLOPT_URL => $url,
CURLOPT_CONNECTTIMEOUT => $timeout,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HEADER => false,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_RETURNTRANSFER => true,
);
curl_setopt_array($ch, $option);
// 提交post
$ret = curl_exec($ch);
if (false == $ret) {
echo curl_error($ch);
}
curl_close($ch);
return $ret;
}</code></pre>
<h3>2.2 构造请求中遇到的坑</h3>
<p>1、构造过程中参考了php手册中的<a href="https://link.segmentfault.com/?enc=MXnLBbYAIMyA2duN%2FQyeoA%3D%3D.RuAWHzOJaPLKkbthJcRvdYTeXMwElLAfXvyLvDjOLvgaMJuDPNp5p8i2hPAOrmHa%2FF%2F2IJ2X3BkDr0xlc6MfEw%3D%3D" rel="nofollow">curl_setopt</a></p>
<p><img src="/img/bVDQYA?w=794&h=284" alt="clipboard.png" title="clipboard.png"></p>
<p>所以首次尝试时,CURLOPT_POSTFIELDS的值用了数组:<br><strong>CURLOPT_POSTFIELDS => $params</strong><br>结果总是返回错误信息:<br><strong>Recv failure: Connection was reset</strong><br>2、为什么会出现这样的错误呢?<br>我们注意到CURLOPT_POSTFIELDS说明中如果value是数组,<br><strong>Content-Type头将会被设置成multipart/form-data</strong><br>猜测原因就出在CURLOPT_POSTFIELDS的值上边,所以将其传值改为<strong>CURLOPT_POSTFIELDS => http_build_query($params)</strong>,果然success!<br>参考:<a href="https://link.segmentfault.com/?enc=bCeAN4G%2Fq45bi03e50gxnQ%3D%3D.YIKtcvU1yU%2Ba8QG0Fch9lobvQElAw9g3VJxbErgUe1%2Bkr%2FAwV09m24nC8ktiPswI7J0vfXXPSwjXxI%2FCQhhbKQ%3D%3D" rel="nofollow">http_build_query构造请求字符串</a></p>
<h3>2.3 未解的疑问</h3>
<p>但是,为什么CURLOPT_POSTFIELDS会产生这样影响呢?查了一下multipart/form-data,但是没有得到具体原因。参见<a href="https://link.segmentfault.com/?enc=sbEps6RJZOanLFgqfb9atw%3D%3D.dKf7IMuha0%2Bc7MTUgPSdB8WmPQ1igQ%2FprCsXw27cyy1P44EWHyl3lRxAIAAablkPjPIItHbSXNYqQbyzzBHEjg%3D%3D" rel="nofollow">multipart/form-data请求分析</a></p>
<p>推测可能是对方服务器不接受这样的Content-Type吧?<br>或者对multipart/form-data的不兼容?</p>
【windows】win10正式版分享WiFi热点
https://segmentfault.com/a/1190000006949074
2016-09-20T10:47:43+08:00
2016-09-20T10:47:43+08:00
fevin
https://segmentfault.com/u/fevin
0
<blockquote><p>前提:本机WiFi功能处于打开状态</p></blockquote>
<h3>1、更改并开启windows自带网络承载设置</h3>
<pre><code class="shell">#以管理员身份打开命令提示符,执行以下命令
netsh wlan set hostednetwork mode=allow ssid=WiFiName key=password
netsh wlan start hostednetwork</code></pre>
<h3>2、设置网络中心共享</h3>
<p><strong>step1</strong>执行完之后,会在网络连接面板出现<strong>Microsoft Hosted Network Virtual Adapter</strong><br>下面只需设置本地网络连接对这个虚拟适配器的网络共享即可。</p>
<p><img src="/img/bVDjU8?w=641&h=644" alt="clipboard.png" title="clipboard.png"></p>
<h3>3、关闭承载网络(即WiFi热点)</h3>
<pre><code class="shell">#以管理员身份打开命令提示符,执行以下命令
netsh wlan set hostednetwork mode=disallow</code></pre>
【mysql】[error]group_concat造成的sql语法错误
https://segmentfault.com/a/1190000006854669
2016-09-08T17:57:14+08:00
2016-09-08T17:57:14+08:00
fevin
https://segmentfault.com/u/fevin
0
<h3>sql错误实例</h3>
<pre><code class="php">$sql_id = "select group_concat(`table_a_id`) from `table_b`";
$id_str = $db->execute($sql_id);
$sql = "select * from `table_a` where `id` in ( {$id_str} ) and `is_effect` = 1`";
$res = $db->execute($sql);
/*
error:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right
syntax to use near ') and `is_effect` = 1'
*/
// 完整sql: select * from `table_a` where `id` in ( 1,2,3,4, ) and `is_effect` = 1`</code></pre>
<h3>错误原因</h3>
<blockquote><p>sql中多了一个逗号<br><strong>group_concat</strong> 执行结构有大小限制,会根据byte大小进行截取</p></blockquote>
<p><img src="/img/bVCVnB" alt="图片描述" title="图片描述"></p>
<p>参考:<a href="https://link.segmentfault.com/?enc=03Wgw3Kj1DKCG4H4Q7XMBA%3D%3D.ViVeEpSsbo4PdW%2FpqwayjtO24hHg2UOUhzMchXwZW6bQlaqbW3bCd4A3VublPVu1AO2Nje%2BNwW8ZFRhlByHMpaK0HvigKc8YQQ5DKYM63MBxaZnUE4YryKrrmZcuHg5c" rel="nofollow">mysql手册-group_concat</a></p>
【工具】[github]上传本地代码仓库
https://segmentfault.com/a/1190000006805931
2016-09-03T11:28:51+08:00
2016-09-03T11:28:51+08:00
fevin
https://segmentfault.com/u/fevin
0
<blockquote><p>linux环境,这里假设本地已安装git</p></blockquote>
<h3>1、生成ssh,添加到github</h3>
<blockquote><p>由于本地git与github是通过ssh进行通信,所以需要本地生成ssh-key<br><a href="https://link.segmentfault.com/?enc=fxtSTSclKg7v1hX31PMIBA%3D%3D.kDk80a7Cg7b5E4%2BjHI0QZIUIOuPKrFtN9FtMlKrE7lRHa3GXWM9hbvelyxAITA%2FTHtdssCtIoHTFkyNh8R9S1yRXQM5Z0RVgvAp5x%2FznMBHY5Tvafc4qZm39Tg4EWEjzpMl%2Fi5PngKuforLUbtsCHeMk0haWha0TvO9a4%2B0SR2WsvKQZmwA4j9%2FK3S6rZJO%2B" rel="nofollow">此处参考,阮一峰老师一篇文章</a></p></blockquote>
<h4>1、生成ssh-key</h4>
<pre><code class="shell">ssh-keygen -t rsa -C 'your-email-address'</code></pre>
<p>你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。</p>
<p>如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,此处就是需要放在github上的key,可以放心地告诉任何人。</p>
<h4>2、将pub-key添加到github上</h4>
<p>复制~/.ssh/id_rsa.pub中的所有内容,并在github中新建ssh-key。</p>
<p><img src="/img/bVDj1y?w=999&h=741" alt="clipboard.png" title="clipboard.png"></p>
<h3>2、添加仓库</h3>
<ul><li><p>在命令行创建一个新的仓库</p></li></ul>
<pre><code class="shell">echo "# test" >> README.md
git init
git add README.md
git commit -m "first commit"
#下边要将git地址替换成自己的
git remote add origin git@github.com:JavinFan/test.git
git push -u origin master</code></pre>
<ul><li><p>直接从命令行条件一个现有的仓库</p></li></ul>
<pre><code class="shell">#下边要将git地址替换成自己的
git remote add origin git@github.com:JavinFan/test.git
git push -u origin master</code></pre>
<h3>3、再看github,就有了这个仓库了~</h3>
【踩过的坑】[web]1.生产与灰度数据缓存;2.Mysql主从不同步;
https://segmentfault.com/a/1190000006805840
2016-09-03T11:13:48+08:00
2016-09-03T11:13:48+08:00
fevin
https://segmentfault.com/u/fevin
1
<blockquote><p>背景:php做web开发,MVC,phalcon</p></blockquote>
<h3>1.生产与灰度数据缓存</h3>
<ul>
<li>
<p>原因:</p>
<ol>
<li><p>service层获取数据,有新增数据字段;</p></li>
<li><p>controller层是通过redisCache调用service接口;</p></li>
<li><p>redisCache采用redis-file双缓存结构,可能存在情况:redis-cache有效;file-cache有效;直接本地调用service,再写进redis和file-cache中;</p></li>
<li><p>线上有个脚本会每隔1秒通过redisCache调用一次此service接口,并且强制刷新缓存(redis-file);</p></li>
<li><p>灰度环境和生产环境用的是同一套redis,而且必须这样;<br><strong>所以,这就造成线上的脚本不断的从线上的service中取得数据,并刷新的redis-file缓存中,从而造成灰度环境直接读了线上缓存,导致灰度代码的service变更没有生效</strong></p></li>
</ol>
</li>
<li>
<p>尝试解决:</p>
<ol>
<li><p>灰度代码:问题controller调用redisCache接口,有强制刷新参数,将其置为false;<br><strong>存在问题:这样是恨不正确的做法,会把灰度的service数据强制刷新到redis-file缓存中,从而导致线上缓存出现脏数据,这样后果很严重!!</strong></p></li>
<li><p>灰度代码:问题controller中,直接调用本地的service,不走缓存;<br><strong>存在问题:导致灰度环境的所有(此controller)请求直接打在mysql上,从而增加了mysql本身的风险。</strong></p></li>
</ol>
</li>
</ul>
<p>(方法1、2,如图)<br><img src="/img/bVCIFK" alt="solve.png" title="solve.png"></p>
<blockquote>
<p><strong>总结:因为灰度环境在公司内网,访问量较小,相比方法1,方法2可以暂时解决灰度测试时的缓存问题。但是仍然存在风险。</strong></p>
<pre><code>(各位看官,有木有更好的解决方案?)
</code></pre>
</blockquote>
<hr>
<h3>2.Mysql主从不同步</h3>
<ul><li>
<p>原因:</p>
<ol>
<li><p>环境:php+mysql+phalcon,生产环境,mysql存在主从;</p></li>
<li><p>通过接口传入A、B两组数据并在一个事务中分别插入到A-table、B-table中,提交事务,再更新A刚插入的一个字段;</p></li>
<li><p>更新通过phalcon的findFrist找到数据 刚才插入的数据,更新字段,调用save;</p></li>
</ol>
</li></ul>
<pre><code class="php"> // 示例代码 ATable,BTable都是继承phalcon的model
$a = array('id' => 1, 'testa' => 'data');
$b = array('id' => 1, 'testb' => 'data');
// 插入数据
$db->startTrascation();
$a_obj = new ATable();
$a_obj->id = $a['id'];
$a_obj->testa = $a['testa'];
$a_obj->save();
$b_obj = new BTable();
$b_obj->id = $b['id'];
$b_obj->testb = $b['testb'];
$b_obj->save();
$db->commit();
// 更新数据,findFirst
$update_a_obj = ATable::findFirst(array('id=:a_id:', 'bind' => array('id' => $a['id'])));
$update_a_obj->testa = 'new_data';
$update_a_obj->save();
// 这里就会出错,因为这里findFirst走了从库
// -----------------说明----------------------
// findFirst走从库是项目本身在model层做的初始化
public function initialize() {
parent::initialize();
$this->setReadConnectionService('db_r');
$this->setWriteConnectionService('db');
}
// setReadConnectionService由phalcon底层提供</code></pre>
<p><a href="https://link.segmentfault.com/?enc=RLA7%2FhBgRpZug27VoH%2BLIQ%3D%3D.VPuZejNSBe5BiUO8aFjZPK1fwqH3f%2BXgEHKyicEW86a7cyjv1YdUmNg1L0t0rx4R2B4GkoZPZ0Sif1ArJ1CaSx12JooZSGaYidDXwoJLQBU%3D" rel="nofollow">可参考phalcon-model源码</a></p>
<hr>
<blockquote><p><strong>总结:1. 永远不要认为主从同步;2.同一个mysql连接,不要出现既用主库、又用从库;</strong></p></blockquote>