Yourmoonlight's Blog
2020-05-07T04:18:50+00:00
http://yourmoonlight.com
yourmoonlight
Golang服务的性能调优与问题定位
2017-07-15T00:00:00+00:00
http://yourmoonlight.com/golang/2017/07/15/How-To-Profile-Golang-Programs
<p>前段时间做了一个技术分享,关于服务性能调优的,再简要总结一下。</p>
<p>大家用Golang写服务还是比较顺手的,语法简洁,开发速度快,又天然并发。</p>
<p>但是仅仅这样,还不足以让Golang进入编程语言Top10。</p>
<p>写“bug”跟定位bug是俩码事,好像后者要更难一点。不过不用担心,Golang给我们提供了很多实用工具,来帮助我们定位问题。</p>
<p>今天要介绍的就是<strong>pprof</strong>。</p>
<h4 id="原理">原理</h4>
<p>市面上有几款Golang的性能调优工具,但是核心都是Golang自带的这个pprof。</p>
<p>它主要分成两部分:</p>
<p> 1.runtime/pprof</p>
<p> 2.go tool pprof</p>
<p>pprof的原理,其实就是对运行时的程序进行<strong>取样</strong>,然后<strong>统计</strong>绘图</p>
<p>当前运行的系统线程,每隔段时间就会收到来自操作系统的中断信号,这时候pprof就会进行取样,然后记录在文件里,取样完成,我们使用go tool pprof工具对样本进行统计分析就OK了。</p>
<h4 id="使用场景">使用场景</h4>
<p>我们需要使用pprof的场景:</p>
<p> 1.定位内存泄漏</p>
<p> 2.程序效率瓶颈</p>
<p> 3.或者就是为了看一下程序的调用图?</p>
<p>经常要做的就两类:内存分析、CPU分析,</p>
<p>照着文档先看一遍,我们常用的命令其实就这么几个:top、peek、list、web。</p>
<p>其中web命令会生成一张调用图,</p>
<p><img src="https://github.com/yourmoonlight/yourmoonlight.github.com/blob/master/img/pprof-web.png?raw=true" alt="pprof-web" /></p>
<p>这张图是CPU profile,我们可以很清楚的看到程序运行时的调用状态,当然主要是看各个方法的耗时。</p>
<p>为什么有的地方是虚线?因为有些耗时特别少的节点没在图上体现出来,要把图连起来,有的地方就使用虚线了。</p>
<h4 id="go-torch">Go-Torch</h4>
<p>这张图其实看着有点乱,那有没有能让我们看着舒服一点的工具?</p>
<p>当然有! Uber开源的<a href="https://github.com/uber/go-torch">火焰图</a>绝对让你high~
话不多说,我们上图:</p>
<p><img src="https://github.com/yourmoonlight/yourmoonlight.github.com/blob/master/img/flame.png?raw=true" alt="flame" />简要说一下,图上的X轴代表时间,Y轴代表程序的调用顺序。</p>
<p>所以小方块越长耗时越多,这种瘦瘦长长的就没啥问题,相反,比较扁平的就稍微注意一下~</p>
<p>至于pprof内存分析,也非常有用,之前写的服务有内存泄漏(因为thrift-0.9.1的一个bug,升级到0.9.2就OK了,有空写写thrift)就靠这个搞定的,用起来还挺方便。</p>
<p>呐,有问题欢迎讨论,就酱。</p>
分享使用redigo连到redis proxy踩到的坑
2017-01-15T00:00:00+00:00
http://yourmoonlight.com/golang/2017/01/15/分享使用redigo连到redis proxy踩到的坑
<p>最近在开发项目的时候,踩了一个redis相关的坑,现分享给大家。</p>
<p>使用的第三方库是 redigo,</p>
<p>连的redis地址是一个proxy,</p>
<p>首先要说的是,redigo 和 redis proxy 都是好东西,redigo的源码看着很清晰,redis proxy也很棒。刀是好刀,但是用的姿势不对就会伤到自己。</p>
<p>这个坑就是关于redigo连接池的:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RedisPool</span> <span class="o">=</span> <span class="o">&</span><span class="n">redis</span><span class="o">.</span><span class="n">Pool</span><span class="p">{</span>
<span class="n">MaxIdle</span><span class="o">:</span> <span class="m">5</span><span class="p">,</span>
<span class="n">IdleTimeout</span><span class="o">:</span> <span class="m">240</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">,</span>
<span class="n">Dial</span><span class="o">:</span> <span class="k">func</span><span class="p">()</span> <span class="p">(</span><span class="n">redis</span><span class="o">.</span><span class="n">Conn</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">c</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">redis</span><span class="o">.</span><span class="n">Dial</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="n">address</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="nb">println</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
<span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="s">"SELECT"</span><span class="p">,</span> <span class="n">db</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="nb">println</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">c</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">},</span>
<span class="n">TestOnBorrow</span><span class="o">:</span> <span class="k">func</span><span class="p">(</span><span class="n">c</span> <span class="n">redis</span><span class="o">.</span><span class="n">Conn</span><span class="p">,</span> <span class="n">t</span> <span class="n">time</span><span class="o">.</span><span class="n">Time</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="s">"PING"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="nb">println</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">err</span>
<span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>
<p>上图所示为老项目中的redigo redis pool,咋一看没啥问题,</p>
<p>MaxIdle: 池子里的最大空闲连接</p>
<p>IdleTimeout: 超过这个duration的空闲连接,会被关闭</p>
<p>TestOnBorrow: 用之前检查这个连接是不是健康的</p>
<p>问题就在于这个Dial,</p>
<blockquote>
<p>Dial is an application supplied function for creating and configuring a connection.</p>
</blockquote>
<p>pool结构体里的这个Dial 就是用来创建,<strong>并配置</strong>一个redis连接的。</p>
<p><strong>坑就在这,</strong>我们执行redis 命令是通过connection对象的Do方法,</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">conn</span><span class="p">)</span> <span class="n">Do</span><span class="p">(</span><span class="n">cmd</span> <span class="kt">string</span><span class="p">,</span> <span class="n">args</span> <span class="o">...</span><span class="k">interface</span><span class="p">{})</span> <span class="p">(</span><span class="k">interface</span><span class="p">{},</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">mu</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
<span class="n">pending</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">pending</span>
<span class="n">c</span><span class="o">.</span><span class="n">pending</span> <span class="o">=</span> <span class="m">0</span>
<span class="n">c</span><span class="o">.</span><span class="n">mu</span><span class="o">.</span><span class="n">Unlock</span><span class="p">()</span>
<span class="k">if</span> <span class="n">cmd</span> <span class="o">==</span> <span class="s">""</span> <span class="o">&&</span> <span class="n">pending</span> <span class="o">==</span> <span class="m">0</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">c</span><span class="o">.</span><span class="n">writeTimeout</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">conn</span><span class="o">.</span><span class="n">SetWriteDeadline</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">()</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">writeTimeout</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">cmd</span> <span class="o">!=</span> <span class="s">""</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">writeCommand</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">args</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">bw</span><span class="o">.</span><span class="n">Flush</span><span class="p">();</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">c</span><span class="o">.</span><span class="n">readTimeout</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">conn</span><span class="o">.</span><span class="n">SetReadDeadline</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">()</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">readTimeout</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">cmd</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
<span class="n">reply</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="k">interface</span><span class="p">{},</span> <span class="n">pending</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">reply</span> <span class="p">{</span>
<span class="n">r</span><span class="p">,</span> <span class="n">e</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">readReply</span><span class="p">()</span>
<span class="k">if</span> <span class="n">e</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">fatal</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">reply</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">r</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">reply</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="k">var</span> <span class="n">err</span> <span class="kt">error</span>
<span class="k">var</span> <span class="n">reply</span> <span class="k">interface</span><span class="p">{}</span>
<span class="k">for</span> <span class="n">i</span> <span class="o">:=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">pending</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span> <span class="p">{</span>
<span class="k">var</span> <span class="n">e</span> <span class="kt">error</span>
<span class="k">if</span> <span class="n">reply</span><span class="p">,</span> <span class="n">e</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">readReply</span><span class="p">();</span> <span class="n">e</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">fatal</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">e</span><span class="p">,</span> <span class="n">ok</span> <span class="o">:=</span> <span class="n">reply</span><span class="o">.</span><span class="p">(</span><span class="n">Error</span><span class="p">);</span> <span class="n">ok</span> <span class="o">&&</span> <span class="n">err</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">err</span> <span class="o">=</span> <span class="n">e</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">reply</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
</code></pre></div></div>
<p>如上述源码中,如果你不传入timeout的值,那么默认0值的话,这两个set deadline的逻辑就跳过了。。。</p>
<p>如果不设置read/write timeout 会导致什么问题呢?假如网络有波动,执行一个redis 命令的时候,一直没收到服务器的响应,会导致这次请求一直没有返回,晾在那。直到redis服务器设置的超时时间到了,关闭连接,然后就会读到一个EOF的错误。</p>
<ol>
<li>单点redis的情况,如果不设置MaxActive,redis pool的连接数是没有上限的,问题就不会暴露出来,这对我们的服务来说,影响也不大,就是在错误日志中,会多几条redis相关的EOF日志,但是这样真的没问题么?当然有问题,如果是从redis读消息,没有设置read timeout,一直读不到,这个协程就卡在那,迟迟不给响应,对用户来说很不好。</li>
<li>使用集群模式,一般redis_proxy 会限制连接数,所以redis pool 就应该用MaxActive限制池子里的最大连接数,这时候如果不设置read/write timeout,问题就来了,池子里的连接会越来越少直到没有。</li>
</ol>
<p>因此,不管那种情况,我们都应该给redis.Dial这个方法,传入三个超时时间,DialConnectTimeout, DialReadTimeout,DialWriteTimeout。</p>
<p>附上现在在用的 redigo的redis pool。</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">redis</span><span class="o">.</span><span class="n">Pool</span><span class="p">{</span>
<span class="n">MaxIdle</span><span class="o">:</span> <span class="n">cf</span><span class="o">.</span><span class="n">StatsRedis</span><span class="o">.</span><span class="n">MaxIdle</span><span class="p">,</span>
<span class="n">MaxActive</span><span class="o">:</span> <span class="n">cf</span><span class="o">.</span><span class="n">StatsRedis</span><span class="o">.</span><span class="n">MaxActive</span><span class="p">,</span>
<span class="n">Dial</span><span class="o">:</span> <span class="k">func</span><span class="p">()</span> <span class="p">(</span><span class="n">redis</span><span class="o">.</span><span class="n">Conn</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">c</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">redis</span><span class="o">.</span><span class="n">Dial</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="n">cf</span><span class="o">.</span><span class="n">StatsRedis</span><span class="o">.</span><span class="n">ProxyAddress</span><span class="p">,</span>
<span class="n">redis</span><span class="o">.</span><span class="n">DialConnectTimeout</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">Duration</span><span class="p">(</span><span class="n">cf</span><span class="o">.</span><span class="n">StatsRedis</span><span class="o">.</span><span class="n">ConnectTimeout</span><span class="p">)</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Millisecond</span><span class="p">),</span>
<span class="n">redis</span><span class="o">.</span><span class="n">DialReadTimeout</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">Duration</span><span class="p">(</span><span class="n">cf</span><span class="o">.</span><span class="n">StatsRedis</span><span class="o">.</span><span class="n">ReadTimeout</span><span class="p">)</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Millisecond</span><span class="p">),</span>
<span class="n">redis</span><span class="o">.</span><span class="n">DialWriteTimeout</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">Duration</span><span class="p">(</span><span class="n">cf</span><span class="o">.</span><span class="n">StatsRedis</span><span class="o">.</span><span class="n">WriteTimeout</span><span class="p">)</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Millisecond</span><span class="p">))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">l4g</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">c</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">},</span>
<span class="c">// Use the TestOnBorrow function to check the health of an idle connection</span>
<span class="c">// before the connection is returned to the application.</span>
<span class="n">TestOnBorrow</span><span class="o">:</span> <span class="k">func</span><span class="p">(</span><span class="n">c</span> <span class="n">redis</span><span class="o">.</span><span class="n">Conn</span><span class="p">,</span> <span class="n">t</span> <span class="n">time</span><span class="o">.</span><span class="n">Time</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">time</span><span class="o">.</span><span class="n">Since</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="o"><</span> <span class="n">time</span><span class="o">.</span><span class="n">Minute</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="s">"PING"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">err</span>
<span class="p">},</span>
<span class="n">IdleTimeout</span><span class="o">:</span> <span class="m">300</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">,</span>
<span class="c">// If Wait is true and the pool is at the MaxActive limit,</span>
<span class="c">// then Get() waits for a connection to be returned to the pool before returning</span>
<span class="n">Wait</span><span class="o">:</span> <span class="no">true</span><span class="p">,</span>
<span class="p">},</span>
</code></pre></div></div>
<p>之前一直以为redis.Dial这个方法会有默认的超时时间,结果事实证明它没有啊。。</p>
<p>写代码一定不能用“以为”。</p>
<p>其实也不能怪redigo,这个库底层用的是golang的net库,default timeout 就是0啊,which is interpreted as “no timeout”…</p>
用Django的Signal方便地把自定义日志存到数据库
2016-05-18T00:00:00+00:00
http://yourmoonlight.com/learning/2016/05/18/用Django的Signal方便地把自定义日志存到数据库
<p>有时候需要根据业务,制定log系统,比如需要把一个对象的状态变换给记录下来,留着以后分析。刚看了篇文章,可以用django的signal来做自定义日志记录。</p>
<p>让我们先来看看一个可能的应用场景,比如我们的应用使用有限状态机来处理用户的状态变换,可以是invited,active,locked 或者deactivated。从一个状态变换到另一个状态,有可能是用户自己触发的,比如输错太多次密码,也有可能是从管理后台由管理员触发的。</p>
<p>我们想做的事,就是把每一次用户状态的变化,包括是谁发起的,受影响的是谁,原状态,变化后的状态,都记录到数据库里。</p>
<p>首先建个记录状态变化的model:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="k">class</span> <span class="nc">UserStateAudit</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="n">username</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">null</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">initiated_by</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">null</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">start_state</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">null</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">end_state</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">null</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">datetime</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateTimeField</span><span class="p">(</span><span class="n">auto_now_add</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">ip_address</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">()</span>
</code></pre></div></div>
<p>这个表记录用户的哪些状态,一目了然。</p>
<p>现在我们建个signal,用来发送想要记下的东西,状态改变的用户,改变之前的状态,改变之后的状态。在signals.py里,我们添加:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.dispatch</span> <span class="kn">import</span> <span class="n">Signal</span>
<span class="n">state_audit_signal</span> <span class="o">=</span> <span class="n">Signal</span><span class="p">(</span><span class="n">providing_args</span><span class="o">=</span><span class="p">[</span><span class="s">'user'</span><span class="p">,</span> <span class="s">'old_state'</span><span class="p">,</span> <span class="s">'new_state'</span><span class="p">])</span>
</code></pre></div></div>
<p>比如,你的用户有限状体机model叫FSM,你可以定义这个FSM的一个实例方法state_change,一有状态变换,就在这个实例方法里发送state_audit_signal,具体操作如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">play_signal.signals</span> <span class="kn">import</span> <span class="n">state_audit_signal</span>
<span class="k">class</span> <span class="nc">UserFSM</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">OneToOneField</span><span class="p">(</span><span class="n">User</span><span class="p">)</span>
<span class="n">current_state</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">null</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">32</span><span class="p">)</span>
<span class="p">[</span><span class="o">.....</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">state_change</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">e</span><span class="p">):</span>
<span class="n">state_audit_signal</span><span class="o">.</span><span class="n">send</span><span class="p">(</span>
<span class="n">sender</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">__class__</span><span class="p">,</span>
<span class="n">user</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">user</span><span class="p">,</span>
<span class="n">old_state</span><span class="o">=</span><span class="n">e</span><span class="p">[</span><span class="s">'src'</span><span class="p">],</span>
<span class="n">new_state</span><span class="o">=</span><span class="n">e</span><span class="p">[</span><span class="s">'dst'</span><span class="p">]</span>
<span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">current_state</span> <span class="o">=</span> <span class="n">e</span><span class="p">[</span><span class="s">'dst'</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
</code></pre></div></div>
<p>好了,现在信号可以发了,在哪接收呢?我们在signals_processing.py里添加处理信号的逻辑:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.dispatch</span> <span class="kn">import</span> <span class="n">receiver</span>
<span class="kn">from</span> <span class="nn">crequest.middleware</span> <span class="kn">import</span> <span class="n">CrequestMiddleware</span>
<span class="kn">from</span> <span class="nn">play_signals.models</span> <span class="kn">import</span> <span class="n">UserStateAudit</span><span class="p">,</span> <span class="n">User</span>
<span class="kn">from</span> <span class="nn">play_signals.signals</span> <span class="kn">import</span> <span class="n">state_audit_signal</span>
<span class="o">@</span><span class="n">receiver</span><span class="p">(</span><span class="n">state_audit_signal</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">user_change_state_signal</span><span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">current_request</span> <span class="o">=</span> <span class="n">CrequestMiddleware</span><span class="o">.</span><span class="n">get_request</span><span class="p">()</span>
<span class="n">user_id</span> <span class="o">=</span> <span class="n">kwargs</span><span class="p">[</span><span class="s">'user'</span><span class="p">]</span>
<span class="n">old_state</span> <span class="o">=</span> <span class="n">kwargs</span><span class="p">[</span><span class="s">'old_state'</span><span class="p">]</span>
<span class="n">new_state</span> <span class="o">=</span> <span class="n">kwargs</span><span class="p">[</span><span class="s">'new_state'</span><span class="p">]</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="n">user_id</span><span class="p">)</span>
<span class="n">username</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="n">username</span>
<span class="n">initiated_by</span> <span class="o">=</span> <span class="n">current_request</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">username</span> <span class="k">if</span> <span class="n">current_request</span> <span class="k">else</span> <span class="s">'CLI'</span>
<span class="n">start_state</span> <span class="o">=</span> <span class="n">old_state</span>
<span class="n">end_state</span> <span class="o">=</span> <span class="n">new_state</span>
<span class="n">ip</span> <span class="o">=</span> <span class="n">get_client_ip</span><span class="p">(</span><span class="n">current_request</span><span class="p">)</span> <span class="k">if</span> <span class="n">current_request</span> <span class="k">else</span> <span class="s">'CLI'</span>
<span class="n">audit</span> <span class="o">=</span> <span class="n">UserStateAudit</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
<span class="n">username</span><span class="o">=</span><span class="n">username</span><span class="p">,</span>
<span class="n">initiated_by</span><span class="o">=</span><span class="n">initiated_by</span><span class="p">,</span>
<span class="n">start_state</span><span class="o">=</span><span class="n">start_state</span><span class="p">,</span>
<span class="n">end_state</span><span class="o">=</span><span class="n">end_state</span><span class="p">,</span>
<span class="n">ip_address</span><span class="o">=</span><span class="n">ip</span>
<span class="p">)</span>
<span class="n">audit</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
</code></pre></div></div>
<p>这里我们用<a href="https://pypi.python.org/pypi/django-crequest">Django Crequest</a>来获取是谁发起的请求。</p>
<p>django的signal就是这么方便。</p>
<p><a href="https://www.algotech.solutions/blog/python/using-django-signals-for-database-logging/">原文链接</a></p>
DECO让Python并发变得超级简单
2016-05-17T00:00:00+00:00
http://yourmoonlight.com/learning/2016/05/17/DECO让Python并发变得超级简单
<p>最近发现了个好玩的库deco,对Python multiprocessing进行了封装,由美国威斯康星大学的Alex Sherman和 Peter Den Hartog共同开发,对应的论文地址<a href="https://drive.google.com/a/mozilla.com/file/d/0B_olmC0u8E3gWTBmN3pydGxHdEE/view">paper</a>。</p>
<p><a href=" https://github.com/alex-sherman/deco">deco</a>使用装饰器,让Python并发变得超级简单,有一点需要说明的是,如果你要收集子进程的执行结果,那你需要传一个基于key索引的mutable对象(比如,dict)。Python list 也是mutable,但是不是基于key索引,所以传进去,会引起竞争。</p>
<p>我们来看个简单的小例子:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># before.py
</span>
<span class="k">def</span> <span class="nf">slow</span><span class="p">(</span><span class="n">index</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="c1"># 模拟耗时操作
</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">():</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="s">'123'</span><span class="p">):</span>
<span class="n">slow</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
<span class="n">run</span><span class="p">()</span>
</code></pre></div></div>
<p>运行上面这个例子,会发现理所应当的耗时15秒:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">time</span> <span class="n">python</span> <span class="n">before</span><span class="o">.</span><span class="n">py</span>
<span class="n">python</span> <span class="n">before</span><span class="o">.</span><span class="n">py</span> <span class="mf">0.02</span><span class="n">s</span> <span class="n">user</span> <span class="mf">0.01</span><span class="n">s</span> <span class="n">system</span> <span class="mi">0</span><span class="o">%</span> <span class="n">cpu</span> <span class="mf">15.048</span> <span class="n">total</span>
</code></pre></div></div>
<p>现在我们装上deco,pip install deco,</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># after.py
</span>
<span class="kn">from</span> <span class="nn">deco</span> <span class="kn">import</span> <span class="n">concurrent</span><span class="p">,</span> <span class="n">synchronized</span>
<span class="o">@</span><span class="n">concurrent</span>
<span class="k">def</span> <span class="nf">slow</span><span class="p">(</span><span class="n">index</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="o">@</span><span class="n">synchronized</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">():</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="s">'123'</span><span class="p">):</span>
<span class="n">slow</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
<span class="n">run</span><span class="p">()</span>
</code></pre></div></div>
<p>这次我们运行after.py,肯定是少于15秒的:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">time</span> <span class="n">python</span> <span class="n">after</span><span class="o">.</span><span class="n">py</span>
<span class="n">python</span> <span class="n">after</span><span class="o">.</span><span class="n">py</span> <span class="mf">0.09</span><span class="n">s</span> <span class="n">user</span> <span class="mf">0.04</span><span class="n">s</span> <span class="n">system</span> <span class="mi">2</span><span class="o">%</span> <span class="n">cpu</span> <span class="mf">5.242</span> <span class="n">total</span>
</code></pre></div></div>
<p>我们简单加点log 看看它的执行顺序:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">slow</span><span class="p">(</span><span class="n">index</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'done with {}'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="n">index</span><span class="p">))</span>
</code></pre></div></div>
<p>然后运行两次,看一下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">python</span> <span class="n">after</span><span class="o">.</span><span class="n">py</span>
<span class="n">done</span> <span class="k">with</span> <span class="mi">1</span>
<span class="n">done</span> <span class="k">with</span> <span class="mi">3</span>
<span class="n">done</span> <span class="k">with</span> <span class="mi">2</span>
<span class="err">$</span> <span class="n">python</span> <span class="n">after</span><span class="o">.</span><span class="n">py</span>
<span class="n">done</span> <span class="k">with</span> <span class="mi">3</span>
<span class="n">done</span> <span class="k">with</span> <span class="mi">1</span>
<span class="n">done</span> <span class="k">with</span> <span class="mi">2</span>
</code></pre></div></div>
<p>同时有几个进程并发的,是由multiprocessing.Pool决定的,对应到deco,可以用concurrent里的processes参数设置。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">deco</span> <span class="kn">import</span> <span class="n">concurrent</span>
<span class="o">@</span><span class="n">concurrent</span><span class="p">(</span><span class="n">processes</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">slow_func</span><span class="p">():</span>
<span class="o">...</span>
</code></pre></div></div>
<p>这里的process数量最好等于cpu核数。
原文<a href="https://www.peterbe.com/plog/deco">地址</a>。</p>
2016 Write something different
2016-05-04T00:00:00+00:00
http://yourmoonlight.com/learning/2016/05/04/write-something-different-2016
<p>工作已经10个月了,</p>
<p> 1. 最大的感悟是,了解越多,越能发现自己的无知,越需要学习。</p>
<p> 2. 最大的<strong>收获</strong>是, 不再害怕问题,相信问题总有解决的方法。</p>
<p> 3. 最大的庆幸是,选择来了北京,周围的同事都很nice。</p>
<p>在学校的时候,用Python做完那个小搜索引擎,狂妄地觉得,编程不过如此嘛,现在想想,如果不是Python语法简洁易懂,并且有众多第三方库的支持,编程基础基本为零的我,根本不可能写出什么搜索引擎,当时的Python水平连入门都算不上。。</p>
<p>工作的这几个月,偶尔也会想写写所学所思,写写装饰器?写写生成器,迭代器?这些东西还是不写了吧。。毕竟网上随便一搜都是答案,我学这些东西也是从谷歌,stackoverflow上找来的。</p>
<p>之前大致读过一本私以为很好的Python书,<a href="http://www.amazon.com/Fluent-Python-Luciano-Ramalho/dp/1491946008">Fluent Python</a>, 第一次读到的时候还是Early Release,当时就觉得这书,跟<a href="http://www.amazon.com/Learn-Python-Hard-Way-Introduction/dp/0321884914/">Learn Python The Hard Way</a>相比,“画风”很不一样啊,按照作者的说法:</p>
<blockquote>
<p>“If you are just learning Python, this book is going to be hard to follow. Not only that, if you read it too early in your Python journey, it may give you the impression that every Python script should leverage special methods and metaprogramming tricks. Premature abstraction is as bad as premature optimization.”</p>
<p>摘录来自: Luciano Ramalho. “Fluent Python”。</p>
</blockquote>
<p>作者觉得这本书不太适合刚学Python的同学。。</p>
<p>但是,我以为这是本不可多得的好书!后来零零碎碎又看过一些,每次读都能加深对Python的理解。</p>
<p>所以,现在要开启个计划,<strong>翻译Fluent Python</strong>,</p>
<p>让每个想学Python,但是又觉得英文难啃的同学,都能“说”一口流利的Python。</p>
If You Want The Rainbow, You Must Have The Rain
2014-10-28T00:00:00+00:00
http://yourmoonlight.com/%E6%9D%82%E8%AE%B0/2014/10/28/want-the-rainbow
<p>大西洋帝国是我一直在追的美剧,昨天完结了。</p>
<p>故事本身,可以算得上是美国黑暗历史教科书了吧,从美国禁酒令开始,围绕主角nucky,讲述一些黑帮大佬的发家史。最后nucky果然被干掉了。。</p>
<p>故事就不多说了,最后一集的片尾曲比较给力:</p>
<blockquote>
<p>take your share of trouble, face it and don’t complain.
if you want the rainbow, you must have the rain.</p>
</blockquote>
<p>这首歌名是<a href="https://www.youtube.com/watch?v=lIiDK4GLPXw">If You Want The Rainbow, You Must Have TheRain</a>, NorahJones的声线也好动人啊。</p>
<p>眼前的困难,算什么? How hard can it be?
这就好像打拳击,对手的拳很快,如果只是闭着眼睛逃跑,最后肯定会输。
Just face it!</p>
批量重命名字幕(二)
2014-07-13T00:00:00+00:00
http://yourmoonlight.com/learning/2014/07/13/rename-srt2
<p>###觉得麻烦,所以要改进。
这次下载的视频分好多章,所以解压出来有好多个子目录,这样上次那个小程序就
需要改进了。既然是批量重命名,就得一键操作才行。
查了查pythondoc,找到需要用的方法名。实现遍历文件夹,得到所有字幕文件,然
后改名。代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kn">import</span> <span class="nn">os</span>
<span class="n">path</span> <span class="o">=</span> <span class="s">r"X:\XXX\XXX"</span>
<span class="k">for</span> <span class="p">(</span><span class="n">dirpath</span><span class="p">,</span> <span class="n">dirnames</span><span class="p">,</span> <span class="n">filenames</span><span class="p">)</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">filenames</span><span class="p">:</span>
<span class="k">if</span> <span class="n">filename</span><span class="p">[</span><span class="o">-</span><span class="mi">4</span><span class="p">:]</span> <span class="o">==</span> <span class="s">".srt"</span><span class="p">:</span>
<span class="n">newname</span> <span class="o">=</span> <span class="n">filename</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s">".en.srt"</span><span class="p">,</span> <span class="s">".srt"</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">rename</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dirpath</span><span class="p">,</span> <span class="n">filename</span><span class="p">),</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dirpath</span><span class="p">,</span> <span class="n">newname</span><span class="p">)</span> <span class="p">)</span>
</code></pre></div></div>
批量重命名字幕文件
2014-07-13T00:00:00+00:00
http://yourmoonlight.com/learning/2014/07/13/rename-srt
<p>###程序员都是”懒”家伙吧
今天从网上下载一些英文视频教程, 虽然文件不大,但是分成了很多小节,一节几分钟的样子,
这样每过几分钟就得手动添加字幕,好麻烦。我就想为什么播放器不能自动加载字幕呢,我都已经把
字幕跟视频放在一个文件夹了,播放器应该有这个功能吧?上网搜索了一下果然有这个功能,只是需
要字幕文件跟视频文件同名才能自动加载。对比了下字幕和视频的文件名,区别只是字幕文件名多了
个”.en”。要手动改么?当然不要,有上百个字幕文件,太麻烦。
那么下面,就稍微动动手吧。</p>
<p>###解决方案</p>
<ol>
<li>
<p>把文件夹里的所有文件名都列出来。</p>
</li>
<li>
<p>把其中.srt结尾的字幕文件找出来。</p>
</li>
<li>
<p>给它重命名。</p>
</li>
</ol>
<p>依着这个步骤,谷歌了一下,每一步需要用到的function,也就找出来了,再看
看Python Doc,就知道怎么用了。 下面是代码:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># -*- coding: utf-8 -*-
#如果让KMPlayer自动加载字幕,则需要字幕文件名跟video文件名一样。
#字幕文件太多了,手动怎么改的过来?所以有了下面的这个小程序。
</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="c1">#这个路径前面要加r, 不然会出现error-123.
</span><span class="n">path</span> <span class="o">=</span> <span class="s">r"x:\xxxxxx\xxxxx\xxxxxx\xxxxx"</span>
<span class="k">for</span> <span class="nb">file</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="k">if</span> <span class="nb">file</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s">".srt"</span><span class="p">):</span>
<span class="c1">#print file
</span> <span class="n">newname</span> <span class="o">=</span> <span class="nb">file</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s">".en.srt"</span><span class="p">,</span> <span class="s">".srt"</span><span class="p">)</span>
<span class="c1">#print newname
</span> <span class="n">os</span><span class="o">.</span><span class="n">rename</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="nb">file</span><span class="p">),</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">newname</span><span class="p">))</span>
</code></pre></div></div>
YouRaiseMeUp
2014-05-30T00:00:00+00:00
http://yourmoonlight.com/%E6%9D%82%E8%AE%B0/2014/05/30/You-Raise-Me-Up
<p>###You Raise Me Up —Westlife
When I am down and, oh my soul</p>
<p>so weary</p>
<p>When troubles come and my heart</p>
<p>burdened be</p>
<p>Then, I am still and wait here in the</p>
<p>silence</p>
<p>Until you come and sit a while with</p>
<p>me</p>
<p>You raise me up, so I can stand on</p>
<p>mountains</p>
<p>You raise me up, to walk on stormy</p>
<p>seas</p>
<p>I am strong, when I am on your</p>
<p>shoulders</p>
<p>You raise me up To more than I</p>
<p>can be</p>
<p>You raise me up, so I can stand on</p>
<p>mountains</p>
<p>You raise me up, to walk on stormy</p>
<p>seas</p>
<p>I am strong, when I am on your</p>
<p>shoulders</p>
<p>You raise me up To more than I</p>
<p>can be</p>
<p>You raise me up so I can stand on</p>
<p>mountains</p>
<p>you raise me up to walk on stormy</p>
<p>seas</p>
<p>I am strong, when I am on your</p>
<p>shoulders</p>
<p>You raise me up To more than I</p>
<p>can be</p>
<p>You raise me up, so I can stand on</p>
<p>mountains</p>
<p>You raise me up, to walk on stormy</p>
<p>seas</p>
<p>I am strong, when I am on your</p>
<p>shoulders</p>
<p>You raise me up To more than I</p>
<p>can be</p>
<p>You raise me up To more than I</p>
<p>can be</p>
<p>END</p>
What Are Words
2014-05-28T00:00:00+00:00
http://yourmoonlight.com/%E6%9D%82%E8%AE%B0/2014/05/28/what-are-words
<p>###What Are Words – chris medina</p>
<p>Anywhere you are,I am near</p>
<p>Anywhere you go, I’ll be there</p>
<p>Anytime you whisper my name</p>
<p>you’ll see</p>
<p>How every single promise I keep</p>
<p>Cause what kind of guy would I be</p>
<p>If I was to leave when you need me</p>
<p>most</p>
<p>What are words</p>
<p>If you really don’t mean them</p>
<p>When you say them</p>
<p>What are words</p>
<p>If they’re only for good times</p>
<p>Then they don’t</p>
<p>When it’s love</p>
<p>Yeah, you say them out-loud those</p>
<p>words</p>
<p>They never go away</p>
<p>They live on , even when we’re gone</p>
<p>And I know an angel was sent just</p>
<p>for me</p>
<p>And I know I’m meant to be where I</p>
<p>am</p>
<p>And I’m gonna be standing right</p>
<p>beside her tonight</p>
<p>And I’m gonna be by your side</p>
<p>I would never leave when she needs</p>
<p>me most</p>
<p>What are words</p>
<p>If you really don’t mean them</p>
<p>When you say them</p>
<p>What are words</p>
<p>If they’re only for good times</p>
<p>Then they don’t</p>
<p>When it’s love</p>
<p>Yeah, you say them out-loud those</p>
<p>words</p>
<p>They never go away</p>
<p>They live on, even when we’re gone</p>
<p>Anywhere you are, I am near</p>
<p>Anywhere you go, I’ll be there</p>
<p>And I’m gonna be here forever</p>
<p>more</p>
<p>Every single promise I keep</p>
<p>Cause what kind of guy would I be</p>
<p>If I was to leave when you need me</p>
<p>most</p>
<p>I’m forever keeping my angel close</p>