Zoom.Quiet's PyBlosxom blogging

¶ 然何买书不读?

2010-08-06 11:01

1. 问

  • 发觉近来自个儿买的书被读的越来越少
  • 好容易得空也是积极看网络中可以免费获得的

    何也?

2. 答

  • 买了就是自个儿的了,什么时候读就不重要了
  • 网络中免费得的也都是有网络费用的,但是相对低很多,很占便宜的!在没有和谐前,多看哪...

    然也?

3. 解

时间的投入产出,是自个儿的控制和选择...

  • 但是,读书的体验和氛围是有技巧营造的,就看自个儿是否愿意了...

动力源自::txt2tags


§ 写于: Fri, 06 Aug 2010 | 链接: permanent /rdf /rss /raw | 分类: /mind §
[Comment] [Print] Creative Commons License
作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ MoinMoin插件hacking

2010-07-30 15:00

1. 需求

GraphVizForMoin 插件部署到MoinMoin 中之后,很爽直!

参考: 在维基中使用 Graphviz~ 啄木鸟中的效果

可以说,解决了在维基中无法快速表达思维导图的问题:

  • 以往都是使用 FreeMind 绘制后截屏附件上来
  • 或是使用插件 ParserMarket/FreeMind - MoinMoin 将文件使用Flash 控件就地发布出来~中文一直是个问题

    但是,使用 Graphviz 的dot 图形脚本在维基中书写图谱一直以来残念的问题是无法输出可点击的有热区的导图!

1.1. Hacking

思路:
  • hacked MoinGraphViz 令其使用-Tcmapx -o **.mp命令,输出热区定义
  • hacked MoinMoin 相关脚本令输出到HTML 的图片认识可能的热区定义
fxied:
  • path/2/moin运行实例/data/plugin/parser/MoinGraphViz/main.py 是插件的主体
  • 很直白,快速就定位了具体代码进行了修订

diff:


Index: tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py
===================================================================
--- tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 16946)
+++ tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 16975)
@@ -56,4 +56,5 @@
         p = request.formatter.page
         self.renderer = Renderer(tool, targetdir=p.getPagePath('attachments'), encoding=config.charset)
+        self.attapath = p.getPagePath('attachments')

     def format(self, formatter):
@@ -61,5 +62,10 @@
         ##w('<div style="border:3px ridge gray; padding:5px; width:95%; overflow:auto">')
         s = self.renderer.render(self.raw)
+        imgname = os.path.basename(s)
+        #s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
         s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
+        #   100728 Zoom.Quiet fixed for include URL hotarea map define
+        pfImgMap = "%s/%s.map"%(self.attapath,imgname)
+        s += fread(pfImgMap)
         print '[TRACE] attachment URL:', s
         w(s)
@@ -182,5 +193,7 @@

 def renderGraphImage(tool, format, imagefilename, dotfilename):
-    cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" "%(dotfilename)s"' % locals()
+    #100728 Zoom.Quiet fixed for export URL hotarea map export
+    cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" -Tcmapx -o "%(imagefilename)s.map" "%(dotfilename)s"' % locals()
+    #cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" "%(dotfilename)s"' % locals()
     print '[TRACE] executing:', cmd
     os.system(cmd

html 输出:


<img alt="graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png" 
class="attachment" 
src="/moin/KupHostsMapping/MapLegend?action=AttachFile&amp;do=get&amp;target=graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png" 
title="graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png" /> 
<map id="hostLegendG" name="hostLegendG">
<area shape="rect" href="http://wiki.s.kingsoft.net/moin/KupHosts" title="普配主机" alt="" coords="101,36,173,63"/>
<area shape="rect" href="http://wiki.s.kingsoft.net/moin/KupHosts" title="高配主机" alt="" coords="197,36,269,63"/>
</map>

注意: 发现,插件是直接使用{{attachment:导图图片名}} 标准的图片附件形式来发布的!
  • 然而,HTML 中要想啓用热区图,至少要有专用属性的对应:
    <img usemap="#俺的ImgMap" src="..."/>
    <map id="俺的ImgMap" name="俺的ImgMap">
    <area shape="rect" href="..." title="普配主机" alt="" coords="101,36,173,63"/>
    ...
    </map>
    
    • 在dot 输出的map 数据中,id/name 就是``digraph G { `` 第一行的那个G,可以任意命名,当然最好是E文
  • 所以,就得找到方法来让 MoinMoin 对附件图片追加usemap属性

    找哈找,幸好有 ack-grep 快速从一堆脚本中定位到靠谱的代码段:
path/2/python2.5/site-packages/MoinMoin/formatter/text_html.py
...
    def attachment_image(self, url, **kw):
        ...
        if exists:
            ...
            if not 'alt' in kw:
                kw['alt'] = kw['title']
            #   100729 Zoom.Quiet fixed for support imagemap for Graphviz
            kw['usemap'] = "#%s"%kw['alt']
            return self.image(**kw)
        ...

追加一行就好...

1.2. jQuery

虽然目标完成了,但是心里总感觉不好:

  • MoinMoin 本身的脚本被hacking 了,就等于,以后升級,迁移时,都要维护这一hacking
  • 很不 Pythonic 哪...

    怎么样脱离 MoinMoin 系统本身来给附件图片追加usemap 属性?
  • 答案,自然是的 Ajax 哪
  • jQuery 就是为这类快速夹塞儿式行为诞生的哪...
部署jQuery:
  • 这是样式的事儿,所以:

    path/2/moin实例/
    +-- data
        +-- plugin
            +-- theme
                +-- 你的样式定义脚本
                +-- woodpecker.py ~ 俺用的
            def footer(self, d, **keywords):
                ... # 追加
                u'<!-- Finally, to loading jQuery Ajax Lib. -->',
                u'<script src="/wiki/common/js/jquery-1.4.2.min.js" type="text/javascript"></script>',
                u'<script src="/wiki/common/js/jquery-graphviz-map.js" type="text/javascript"></script>',
    
    +-- htdoc
        +-- common
            +-- js
                +-- jquery-1.4.2.min.js ~ 官方运营用压缩版本
                +-- jquery-graphviz-map.js  ~ 动态行为定义用
    
使用jQuery:
  • 看看文档,就两行搞定..
    
    $(document).ready(function() {
    	$("img[class='attachment']").each(function(){
    	    $(this).attr("usemap","#"+$(this).attr("alt"));
        });
    });
    
    • 特别的:得考虑一页多个导图时的情况,所以是要进行 each() 循环处置
  • 当然的,需要 MoinGraphViz/main.py插件的配合,以便从附图的 alt 中获得正确的图片热区ID

    
    Index: tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py
    ===================================================================
    --- tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 17010)
    +++ tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 17013)
    @@ -65,5 +65,8 @@
             fImgName = os.path.basename(s)
             pfImgMap = "%s/%s.map"%(self.attapath,fImgName)
    -        s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
    +        #s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
    +        s = wiki2html(self.request, '{{attachment:%s|%s}}' % (os.path.basename(s)
    +                        ,fImgName.split("-")[1])
    +                    )
             #   100728 Zoom.Quiet appended <map> data
             if os.path.exists(pfImgMap):
    

1.3. 编码问题

一切表现良好,无意间发现凡是有URL包含的 dot 图谱,被其它页面包含时就出错!

囧rz...:
  • 尝试各种编码,未果
  • 嘗試各种<map>的包装形式:
    1. 使用 <pre>
    2. 使用 <textarea>
  • 都在 Include 时,可怜的出错了...
  • 实在是因为 MoinMoin 不想处理正常的 HTML 标签属性的其它编码内容
  • 好吧,俺就不给出无用的中文内容!
  • path/2/moin运行实例/data/plugin/parser/MoinGraphViz/main.py 追加一小段正则表达式替换

    def format(self, formatter):
        w = self.request.write
        #...
        #   100728 Zoom.Quiet appended <map> data
        if os.path.exists(pfImgMap):
            import re
            p=re.compile( 'title=\".+?\"')
            s += p.sub("title=\"\"",fread(pfImgMap))
            #s += fread(pfImgMap)

HTML 输出:


<img alt="graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png" 
class="attachment" 
src="/moin/KupHostsMapping/MapLegend?action=AttachFile&amp;do=get&amp;target=graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png" 
title="graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png" /> 
<map id="hostLegendG" name="hostLegendG">
<area shape="rect" href="http://wiki.s.kingsoft.net/moin/KupHosts" title="" alt="" coords="101,36,173,63"/>
<area shape="rect" href="http://wiki.s.kingsoft.net/moin/KupHosts" title="" alt="" coords="197,36,269,63"/>
</map>

一切安定了...

2. 小结

  • 思路不乱的情况下,主要问题就是定位代码段!以及测试!
    • 面对一运行中的MoinMoin 进行测试开发时
    • 使用sshfs 可以快速挂接远程服务器的任意目录,非常方便!
    • 使用沙箱页面进行修订插件的测试,可以避免正常文章页面的中间调试失常..
    • 在调试中,直接输出预想数据到 HTML 里看,比看系统日志,使用print 要方便
    • MoinMoin 有完备的缓冲机制,要及时看到修订效果,得重启HTTPD
  • jQuery 真的很好用,也好学!
下载:
diff: MoinMoin_parser_MoinGraphViz_main.py-from-r16946-to-r17013.diff

2.1. 时间清单

  1. 00:05 定目标
  2. 00:15 准备环境
  3. 01:30 探查运行环境,明确修订目标脚本
  4. 01:45 插件修订完成
  5. 00:15 图片应用map jQuery 嘗試
  6. 00:35 图片插入系统修订完成
  7. 01:00 正则表达式+jQuery 解决 Incldue() 时的编码问题
  8. 00:45 整理代码,发布到Blog
  9. 00:35 整理代码,反馈到MoinMoin.in
  10. 00:40 8次中断,回到工作场景的心理浪费

总计: ~ 6小时


动力源自::txt2tags


§ 写于: Fri, 30 Jul 2010 | 链接: permanent /rdf /rss /raw | 分类: /utility/py4web/MoinMoin §
[Comment] [Print] Creative Commons License
作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ [Py4SA]智能关闭触摸板

2010-07-14 10:22

1. 问题

嗯嗯嗯,俺一直使用 WACOM 的数字绘图板替代鼠标的 ;-)

  • 去年生日,老婆升级了俺的 FAVO CTE-430BAMBOO CTH-461
  • 随着 Ubuntu 10.04系统,一同升级了驱动,非常好用:
    1. 小横板 248.2 x 176.1,和屏幕 1:1 对应,不用拖动,有空间感觉了直接就可以移动到对应区域!
    2. 无线压感笔,直接点击就是左击鼠标
    3. 笔有侧键,配置默认就是原先最舒服的:
      • 上键是右击
      • 下键是中击
  • 触摸板的手指点击就不必要了
    • 如何令系统不启动触摸板的 Touch 响应?

2. 尝试

  • Command Line Configuration Interface (xsetwacom)
    • 官方是有命令行级别的配置工具的
    • 但是!真TMD难以看明白哈
    • 广泛的搜索后,大致明白,xsetwacom 提供两种配置输出:
      1. 命令行用的执行字串
      2. 配置文件中使用的配置文本
    • 整个使用流程是:
      1. 使用其它系统监察命令,确认 WACOM 设备USB接入后的设备号
      2. 使用 xsetwacom 进行模拟配置,选择输出合适的配置命令
      3. 然后将输出的配置内容,部署到合适的系统启动脚本或是配置文件中

  • 俺习惯使用命令行配置,这样可以直接测试是否靠谱,但是发现:
    
    ~> xinput --list 
    ⎡ Virtual core pointer                    	id=2	[master pointer  (3)]
    ⎜   ↳ Virtual core XTEST pointer              	id=4	[slave  pointer  (2)]
    ⎜   ↳ Wacom Bamboo Craft Pen eraser           	id=11	[slave  pointer  (2)]
    ⎜   ↳ Wacom Bamboo Craft Pen                  	id=12	[slave  pointer  (2)]
    ⎜   ↳ Wacom Bamboo Craft Finger pad           	id=13	[slave  pointer  (2)]
    ⎜   ↳ Wacom Bamboo Craft Finger               	id=14	[slave  pointer  (2)]
    ⎜   ↳ TPPS/2 IBM TrackPoint                   	id=16	[slave  pointer  (2)]
    ⎜   ↳ Macintosh mouse button emulation        	id=18	[slave  pointer  (2)]
    ⎣ Virtual core keyboard                   	id=3	[master keyboard (2)]
        ↳ Virtual core XTEST keyboard             	id=5	[slave  keyboard (3)]
    ...
    
    • 使用 xinput 探察出来的 Wacom Bamboo Craft Finger 触摸板设备号,每次重启系统时不一定一样!
    • FT! 那就得编程让系统重启时,自个儿探察一下设备号,然后使用固定的配置命令关闭 BAMBOO 对应设备
如何在Shell 中获取另外系统命令的输出并截获对应数码组合成新的命令?:
嗯嗯嗯,,, 嗯嗯嗯,,,,直接囧掉! 俺没有这种功力!

3. Py之

  • 直接使用模板记录要执行的命令,预留可能变动的设备号:
    
    #!/bin/sh
    ### xinput4bamboo.tpl
    sleep 3 && xsetwacom set %s Touch "off"
    
    

  • 配合xinput4bamboo.py
    
    import sys,os
    if __name__ == '__main__':      # this way the module can be
        """usage:
        $ xinput --list | grep "Wacom Bamboo Craft Finger" | python ./xinput4bamboo.py
        """
        if sys.stdin:
            for l in sys.stdin.readlines():
                if "pad" not in l:
                    bambooid= l.split()[6].split("=")[1]
                    open("xinput4bamboo.sh","w").write(open("xinput4bamboo.tpl").read()%bambooid)
                    os.chmod("xinput4bamboo.sh",0755)
    
    

  • 组合成启动脚本
    
    #!/bin/sh
    VER="main-rcloc.sh v10.7.14"
    DATE=`date "+%y%m%d"`
    MYRC="/home/zoomq/.zoomq/rc.local"
    #=========================================================== path defines
    LOGF="/var/log/0day/$DATE-myrcloc.log"
    #=========================================================== action defines
    cd $MYRC
    xinput --list | grep "Wacom Bamboo Craft Finger" | python ./xinput4bamboo.py  >> $LOGF 2>&1 
    ./xinput4bamboo.sh  >> $LOGF 2>&1 
    

  • 部属成启动应用:
    • 齐活! 没有疑惑,随手写成! 5分钟!
Python在Unix和Linux系统管理中的应用(影印版):
  • 绝对好书! 值得慢慢看,E文非常简洁,Py清明的代码,直接看/尝试就可以体会到本意了...

动力源自::txt2tags


§ 写于: Wed, 14 Jul 2010 | 链接: permanent /rdf /rss /raw | 分类: /utility/py4sys §
[Comment] [Print] Creative Commons License
作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ [Py4SA]批量文件重命名

2010-07-13 22:02

1. 问题

嗯嗯嗯,最近从 六哥的 DUKU 重新激发了评书的记忆,下载了全本 袁阔成(40回)红岩魂的mp3 ;-)

可是解开都是乱码文件名:

  • :;
  • 可恶的中文编码! 一定是在XP 中使用CP936 压出来的
  • 怎么整呢?
Python在Unix和Linux系统管理中的应用(影印版):
  • 绝对好书! 值得慢慢看,E文的也非常简洁,Py 清明的代码,直接看,尝试就可以体会到本意了...

2. 尝试

已知有很多途径...参考:linux下批量修改文件名_乌哥的家常菜_百度空间

  1. 使用find、awk/grep/sed、mv等命令组合来实现
    • 俺都不熟练,而且要串很多步操作,忒不直爽了..

  2. 编写Shell脚本运行~ 不就是命令组合嘛,,.,
  3. rename ... NGU/Linux 中的标准化重命名工具,但是,怎么可以将批量文件中指定前几个字符替换成指定字符?
    
    ~/media/4talking/袁阔诚/try> rename -n 's/^.{6}/红岩魂/' *.mp3
    ...
    ���һ�38.mp3 renamed as 红岩魂38.mp3
    ���һ�39.mp3 renamed as 红岩魂39.mp3
    ���һ�40.mp3 renamed as 红岩魂40.mp3
    
    • 反复尝试半小时才获得满意的...

3. Py之

想嘗試Py 的思路...


import sys,shutil
if __name__ == '__main__':      # this way the module can be
    '''usage:
        $ ls *.mp3 | python rename-zh.py
    '''
    if sys.stdin:
        for l in sys.stdin.readlines():
            shutil.copyfile(l[:-1], "redStoneSoul-%s"%l[6:-1])
  • 好的,没有尝试,随手写来,3分钟搞掂

Python在Unix和Linux系统管理中的应用(影印版):
  • 绝对好书! 值得慢慢看,E文非常简洁,Py清明的代码,直接看/尝试就可以体会到本意了...

动力源自::txt2tags


§ 写于: Tue, 13 Jul 2010 | 链接: permanent /rdf /rss /raw | 分类: /utility/py4sys §
[Comment] [Print] Creative Commons License
作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ IOP:实践之一

2010-05-13 19:19

1. 背景

什么事儿呢?

  • 在一高压力服务环境中,需要加速系统的响应
  • 现行系统对于数据查询要尝试三种数据库源:
    1. memcache
    2. redis
    3. MySQL
  • 期望查询加速至少 300%
运行环境
客户机:
    - 俺的Laptop HP 520
    - 双核CPU 2G内存
    - Ubuntu 9.10
DB主机:
    - CentOS 5.0
    - 单核CPU 4G 内存

1.1. 预案

这事儿,一想,简单哪,都放内存就好的哪

  • 统计了一下需要用来查询的数据不到2千万条
  • 折算成文件不过1G
  • 加载到内存中,使用 Python 字典结构的话,也最多涨一倍,也完全可以接受
  • 速度?!

2. IOP的加速技巧

没有想到,加速,只要不断将代码住短里面写就好!

不知道什么是 IOP?

  • PyIOP
  • 咔咔咔,沈游侠总结的编程态度>...

2.1. 10万:170+4秒

最直接的实现
  • 从redis 读
  • 生成 dict 对象
  • 以pickle dump 出序列化文件
  • 用pickle load 加载成dict对象

2.1.1. code

代码:


#!/usr/bin/python 
# -*- coding: utf-8 -*-
import struct,sys,time
import cPickle as pickle
import redis
REVERSION = "r2d.py v10.5.7"
def _push2dict(dictall,key,smembers):
    dictall[struct.pack('I',int(key[1:]))]=[s.split("|") for s in rb.smembers(k)]    
    return dictall
if __name__ == '__main__':      # this way the module can be
    if 3 != len(sys.argv):
        print """ %s usage::
$ python r2d.py redistIP limitnumber [like 10000]
        """ % REVERSION
    else:
        hostIP = sys.argv[1]
        limitn = sys.argv[2]
        rb = redis.Redis(host=hostIP, port=6379, db=9)
        rbkeys = rb.keys().split()
        loop = int(limitn)
        s4dict={}
        for k in rbkeys:
            if 0 == loop:
                break
            else:
                loop -=1
                _push2dict(s4dict,k,rb.smembers(k))
        pickle.dump(s4dict, open('r4d.dump', 'wb'))

2.1.2. speed

  • 代码足够简单了,单函式,20行
  • 速度测试::
    • 10W 值对导出 >170秒,导入>4秒;
    • 100W 值对导出 >500秒,导入>25秒;
    • 1000W 值对导出 失败! 内存提前耗光!

2.1.3. improve

这完全无法接受哪...

加速尝试::
  1. 根据 IOP 尽量不用函式,将那个一行函式清除,代码填回循环 ~ 立即获得几秒的加速
  2. 本来用的就是cPickle 了,模块效率没有办法了
  3. 嗯嗯嗯,可以不用 cPickle卟?
    • 直接输出自然 .py 哈?!


# 使用 str() 将字典对象用文本的方式记入 .py 
vdf = open("r2d.define.py","wa")
vdf.write("s4dict=")
vdf.write(str(s4dict))
vdf.close()
# 使用时直接 import 就好

  • 改进后测试::
    • 10W 值对导出 >160秒,导入>3秒;
    • 100W 值对导出 >400秒,导入>19秒;
    • 1000W 值对导出 依然杯具
  • 加速不明显:
    • 导出时速度变化很小
    • 载入时速度有提升
  • 进一步观察到,导出时内存飞速增长:
    • 100W级别,要食掉1.6G左右的内存
    • redis 本身也很占内存,千万级别时,也要占上G (当然这和条目数量/内容有关)
    • 这对于服务器是个不可接受的方式

2.2. 10万:150+10秒

改进中间数据格式:
  • 从redis 读
  • 生成 中间log文件
  • 导入成 字典对象

2.2.1. thinking

因为有这些现实:

  1. 想输出不论 pickle 或是 .py 的字典对象,都得先在内存中构造出这一对象
  2. 随着字典对象的规模增加,必然导致这一构建过程的时间加长

经沈游侠提醒,发现字典对象其实是可以线性输出的:

  • 比如说,字典结构如:
    {key:[(v1,v2,v3),..]
    ,...
    }
    
    • K:[list] 形式的两层结构
  • 那么,就可以通过中间数据文本:
    ('key', [(v1, 'v2', v3)])
    ...
    
  • 进行线性加载,e.g:
    
    for l in open("r2d.define.py.log","r").readlines():
        dd = eval(l)
        if dd[0] in s4d:
            s4d[dd[0]].append((dd[1],dd[2],dd[3]))
        else:
            s4d[dd[0]]=[(dd[1],dd[2],dd[3])]
    
    

2.2.2. code

快速修订对应行动代码:


#!/usr/bin/python 
# -*- coding: utf-8 -*-
import struct,sys,time
import redis
REVERSION = "r2d.py v10.5.8"
if __name__ == '__main__':      # this way the module can be
    if 3 != len(sys.argv):
        print """ %s usage::
$ python r2d.py redistIP limitnumber [like 10000] > mid-data.log
        """ % REVERSION
    else:
        hostIP = sys.argv[1]
        limitn = sys.argv[2]
        rb = redis.Redis(host=hostIP, port=6379, db=9)
        rbkeys = rb.keys().split()
        loop = int(limitn)
        for k in rbkeys:
            if 0 == loop:
                break
            else:
                loop -=1
                dictkey = struct.pack('I',int(k[1:]))
                sli = []
                for s in rb.smembers(k):
                    rli = s.split("|")[:3]
                    if rli:
                        rli[0] = int(rli[0])
                        rli[1] = struct.pack('I',int(rli[1]))
                        rli[2] = int(rli[2])
                        sli.append(tuple(rli))
                print >> d2f,`dictkey,sli`
                #注意: `obj` 等同 repr(obj)  

2.2.3. improve

代码依然简单了,无函式,30行

  • 速度测试::
    • 10W 值对导出 >150秒,导入>10秒;
    • 100W 值对导出 >500秒,导入>20秒;
    • 1000W 值对导出 >1300秒,导入>90秒;
  • 速度看起来没有什么明显的提高
  • 不过!
    1. 千万级别的数据可以在低配置环境中跑完了!
    2. 内存占用很稳定永远90M左右,不会随字典对象的增长而增长!

嗯嗯嗯,这算是可用了...

2.3. 10万:100+200秒?!

继续改进:
  • 放弃 redis 直接从MySQL 读
  • 生成 中间日志
  • 导入成 dict 文件

2.3.1. thinking

虽然redis 是号称最快的 K/V 数据库产品,但是,明显就是它将整个业务响应速度拖慢了..

为什么呢?
  • 服务器程序和本地程序面对的环境是不同的
  • 高压力服务器程序和小压力服务器程序也是不同的
  • 简单来说:
    1. 小型服务~=每秒<C60
    2. 中型服务~=每秒<C600
    3. 大型服务~=每秒>C1000
  • 面对的矛盾是完全不同的:
    1. 小型服务->语言执行效率
    2. 中型服务->框架执行效率
    3. 大型服务->I/O 响应速度
  • 所以,对于面向Web 的查询服务,不论 Redis/MySQL 对于业务系统,都是进程间通讯!
  • 每次跨进程通讯,都意味着至少四次I/O操作!
  • 所以,当前 Redis->log->内存字典的转换流程,其实包含了 MySQL->Redis 的进程操作
  • 另外:
    • 遍查 Redis 文档,居然没有 iterkeys() 类似的操作!
    • 每次不论转换多少 Redis 的值对,都得使用keys() 将键先取出来然后再逐一匹配处理
    • 怪不得使用 Redis 测试用小仓库(包含20万值对)时,脚本运行速度和使用全数据Redis(千万值对)时速度要相差5倍以上!内存也占用多几倍!

所以!要直接从 MySQL 相关表中读取

2.3.2. code

配合一SQL 模板:


-- _tpl/all_black.tpl
SELECT v1,v2,v3,v4,id FROM t_black LIMIT %(limitMAX)s;

核心代码:


#!/usr/bin/python 
# -*- coding: utf-8 -*-
REVERSION = "m2d.py v10.5.9"
import struct,sys,time

if __name__ == '__main__':      # this way the module can be
    """usage:
$ python m2d.py limit [such as 100] |\
  mysql -h xx.xx.xx.xx -u User -D --password=***  |\
  python m2d.py > m2d.log
    """
    if sys.stdin:
        if 1 < len(sys.argv):
            limit = sys.argv[1]
            limitMAX = int(limit)
            print >> sys.stderr, info
            print open("_tpl/all_black.tpl","r").read()%locals()
        else:
            s4dict={}
            virusname={}
            totalN = 0
            for l in sys.stdin.readlines():
                if "id" in l:
                    pass
                else:
                    totalN += 1
                    lkv=l.split()
                    dictkey = struct.pack('I',int(lkv[0]))
                    lkv[1] = int(lkv[1])
                    lkv[2] = struct.pack('I',int(lkv[2]))
                    lkv[3] = int(lkv[3])
                    print >> d2f,`dictkey,tuple(lkv[1:])`
  • 用是否有额外参数来判定是否生成SQL,还是进行MySQL 的输出数据处理
  • 为了不影响标准输出,调试信息,输出到标准错误IO
  • 调试也应该根据管道串的层级,一级级运行尝试
  • 调用的shell:
    
    #!/bin/sh
    #   m2d.sh v10.5.9
    python m2d.py go |\
        mysql -h xx.xx.xx.xx -u User --password=***|\
        python mysql4dict.py $1 |\
        mysql -h xx.xx.xx.xx -u User --password=***|\
        python m2d.py > m2d.log
    

    加载时代码没有怎么变

2.3.3. speed

  • 速度测试::
    • 0.1W 值对导出 <5秒,导入<1秒;
    • 10W 值对导出 >300秒,导入>10秒;
    • 100W 值对导出 杯具鸟
    • 1000W 值对导出 杯具鸟
  • 速度怎么和查询的条目多少有关? 而且一大MySQL 就拒绝服务?

2.3.4. improve

嗯嗯嗯,FT! 当然了,MySQL 请求响应时间是有限制的,大数据传输肯定是有问题的, 利用 LIMIT 的切片!

  • 改造前述m2d.py v10.5.9 SQL生成部分的代码:
    
    step = 5000.0
    limitMAX = int(limit)
    sqltpl = open("_tpl/all_black.tpl","r").read()
    if 1 >= limitMAXb/step:
        print sqltpl%locals()
    else:
        for i in range(int(limitMAX/step)):
            limitMAX = "%d,%d"%(step*i,int(step))
            print sqltpl%locals()
    
  • 以 5000 为界限,生成类似 LIMIT 15000,5000 的限制
  • 速度测试::
    • 100W 值对导出 >290秒,导入>190秒;
    • 1000W 值对导出 杯具鸟

FT!怎么回事儿?速度意外的慢!

2.4. 1000万:1100秒+480秒

冷静后改进:
  • 从MySQL 读使用id 进行限制切分
  • 生成 中间日志,但是重新设计结构
  • 导入成 dict 文件

2.4.1. thinking

MySQL 为什么这么慢?!

2.4.2. code

SQL 模板配合改进:


-- _tpl/all_black.tpl
SELECT v1,v2,v3,v4,id FROM t_black WHERE id>%(LIMbwID)s AND id<=%(MAXbwID)s;

SQL 生成代码:


step = 3000.0
offset = int(step)
# < <gen_sql_with_max> > 使用Leo 时可以定义子节点将成堆代码变成语义标记
for l in sys.stdin.readlines():
    if "max(id)" not in l:
        amount = l.split()
        MAXbwID = int(amount[0]
        MINbwID = int(amount[1]
        MAXbwA = MAXbID-MINbID
MAXbMAX = MAXbwID
lastID = 0
sqltpl = open("_tpl/all_black.tpl","r").read()
for i in range(MAXbwA/offset):
    MAXbwID,LIMbwID = (MAXbMAX-offset*i,MAXbMAX-offset*(i+1))
    print sqltpl%locals()
    lastID = LIMbID
MAXbID,LIMbID = lastID,MINbID   # 将切片限数之内的尾数个ID也查询出来
print sqltpl%locals()
  • 咔咔咔,当然的,要进行基于ID 的精确切分选择,就得先知道最大和最小的ID
  • 使用SQL SELECT max(id),min(id) FROM t_black ;
  • 自然运用系统管道串接成处理过程:
    
    #!/bin/sh
    #   m2d.sh v10.5.9
    DATE=`date "+%y%m%d-%H%M%S"`
    mysql -h xx.xx.xx -u User --password=*** < _tpl/total_black.sql |\
        python m2d.py go |\
        mysql -h xx.xx.xx -u User --password=*** |\
        python m2d.py > m2d_$DATE.log
    

2.4.3. improve

  • 速度测试::
    • 100W 值对导出 >280秒,导入>120秒;
      • m2d.log > 150M
    • 1000W 值对导出 >1300秒,导入>780秒;
      • m2d.log > 570M

进一步的,发现业务其实可以将双层,两次查询优化成一次查询的!

  • 简单的将输出字典的数据结构变成:
    {key:v,...
    }
    
  • 将原先的2个值结合原先的key 变成键,就成为了全局唯一的key
  • 即和MySQL 每行数据完成一一对应
  • 速度再测试::
    • 100W 值对导出 >250秒,导入>100秒;
      • m2d.log > 130M
    • 1000W 值对导出 >1100秒,导入>480秒;
      • m2d.log > 490M
  • 哗! 而且加载完后,字典对象所点内存体积也同样减少了 30% !

2.5. 小记

综上:
  • 应用 IOP 方面的主要招术:
    1. 表用 函式
    2. 表用 模块
    3. 尽量使用 OS 的标准 I/O 进行功能串接
  • DOP~Data Oriented Programming
    • 面向数据编程
    • 归根到底,程序都得操作数据解决问题
    • 服务器端,大并发压力时,最有效的节省I/O 的方式,就是高效方式
    • 压缩输入/出的数据量自然是最好的代码!

3. 提速100倍

嗯嗯嗯?!怎么说到最后好象也没有加速到100倍哪?

  • 是也乎,是也乎,以上分享的是内存化字典数据的转换脚本加速过程
  • 但是最后要加速的是整个查询业务哈?
  • 因为涉及公司核心服务,代码就无法展示了
  • 不过,现实是:
    1. 使用了以上 IOP 中提及的基础技巧
    2. 每查询业务的处理时间从原先的 0.* 秒,加速到 0.00*秒,至少100倍
    3. 而对应的代码从 几千行,精简到几十行

所以,基本上可以这么理解:

  • 将代码每精简一倍体积
  • 运行速度就有望提高10倍

    不相信? 尝试一哈噻...

动力源自::txt2tags


§ 写于: Thu, 13 May 2010 | 链接: permanent /rdf /rss /raw | 分类: /Zen/IOP §
[Comment] [Print] Creative Commons License
作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。