About
RSS

Bit Focus


Redis Cluster 简单配置与动态扩容

    Redis 3.0 就要自带集群功能了, 去看了一下这里还有官方教程之后, 发现似乎必须用命令行来搞着, 而且官方提供的 redis-trib.rb 要求至少 3 个节点才能建立一个集群, 这规格是向党支部看齐么...
    至少 3 个节点这个还是略坑, 而且不能自动添加节点 (难道要我启动个 py 的 subprocess 去掉 ruby?), 于是去看看源代码, 惊讶地发现, 原来限制 3 个节点起步的是 ruby 脚本, 而且调集群加节点平衡负载其实都可以用 redis 命令来完成. 好吧, 那我自己来连 socket 搞总行了吧.
    结果一番折腾还真的可行的样子, 于是有了这篇文章和一个简单的工具. 那么首先说说怎么用 redis-cli 来做这些事情.

    如何在 redis-cli 手动启动集群呢, 请随意连上一个空的支持集群模式的节点, 然后执行
cluster addslots 0 1 2 ... 16383
    千万不要误会了, 中间那个 ... 可是要实打实地从头写到尾的哦. 所以如果可以的话, 手动写个脚本来干这事情吧.
    不过也可以略过这些步骤, 反正下面看看例子就行, 最后会给出一个 Python 工具来做这些.
    接下来的例子中, 假定已经开好了一个集群, 共有 3 个 master 节点. 要在控制台检视这些节点, 请用 redis-cli 随意连上其中一个, 并执行
cluster nodes
    输出
e7f4fcc0dd003fc107333a4132a471ad306d5513 127.0.0.1:8001 master - 0 1414033928009 3 connected 0-2729 8192-10921
bd239f7dbeaba9541586a708484cdce0ca99aba5 127.0.0.1:8000 master - 0 1414033929011 2 connected 2730-8191
787e06e9d96e6a9a3d02c7f3ec14e243882293e9 127.0.0.1:7999 myself,master - 0 0 1 connected 10922-16383
以上每一行是一个节点信息, 按空格分隔的域依次表示
  • 节点 ID
  • 节点地址
  • 节点角色 (master / slave), 如果是当前节点, 还会有个 myself
  • 对于 slave 而言, 其 master 节点的 ID
  • 最后一次 ping 时间戳
  • 最后一次 pong 时间戳
  • 节点顺序号
  • 节点连接状态
  • 之后的所有 : 节点所配给的槽位, 如果槽位连续, 就以 BEGIN-END 表示, 不连续的由空格隔开
    如果要向集群新增一个节点, 需要用 redis-cli 连上这个新节点, 调用一次 cluster meet 命令. 如
cluster meet 127.0.0.1 7999
后面参数是已经在集群中的节点中任意一个节点的地址及端口. 然后再来一次

Permanent Link: /p/524 Load full text

Post tags:

 Python
 Redis
 集群

麻将听牌算法 [下篇]

上篇中分析了听牌可能有关字牌的情形, 具体包括字牌中有一个单张, 而剩下的数牌全能构成面子的单骑醒, 或者字牌中有个对子, 而剩下某数牌含有一个对子的双碰型或一个搭子的边/嵌张听牌. 这篇要讨论字牌全是刻子时的类似情况. 之所以说类似是由于此时数牌只可能有以下两种情况
  • 某一色数牌的牌总数模 3 余 1, 其它两个色都能恰好构成面子
  • 某两色数牌的牌总数摸 3 余 2, 剩下一色能恰好构成面子
体现成代码就是, 需要解决以下两个函数
def _waits_4groups(tiles):
    # 前略
    # 在前面情况不满足时, 调用如下实现
    return (_detect_numeric_suit_with_one_more(tiles) +
            _detect_2_numeric_suits_with_2_more(tiles))

# 找一个花色, 它的数量模 3 余 1
def _detect_numeric_suit_with_one_more(tiles):
    pass

# 找两个花色, 它们各自的牌的数量模 3 都余 2
def _detect_2_numeric_suits_with_2_more(tiles):
    pass
在上一篇代码的支援下, 后一个函数的实现相对容易一些, 如下

Permanent Link: /p/522 Load full text

Post tags:

 Algorithm
 麻将
 Python

麻将听牌算法 [上篇]

    作为一个人类经常在打清一色的时候望着手牌不知道听牌没不知道听了哪几张也不知道切哪一张会让听牌数量最大化是一件不愉快的事情, 除了九莲宝灯之类的定式役给背下来好像没别的有效方法. 或者, 写个程序来搞吧.
    首先是数据结构, 这里用如下类来描述

Permanent Link: /p/521 Load full text

Post tags:

 麻将
 Algorithm
 Python

就算是 Linux 命令行只要有爱就能剪辑 MAD 了吧

起因当然就是, UP 主以前还没当过 UP 主呢, 这会儿想剪个 MAD 啦.

经过是, 写了个简单脚本用 avconv + mencoder 剪出没有任何特效, 只是纯粹拼接原始片段的视频.

教训是 Linux 从来不缺乏图形界面软件, 只是... 缺乏比对应的命令行软件更优秀的图形界面软件.

这次实践, 基本原理是用 avconv (不要吐槽名字啊, 其实这软件很健全的) 对源视频进行分割, 然后用 mencoder 串联起来, 再配上音乐.

Ubuntu 上安装这些东西以及对应的转码器

apt-get install libav-tools libavcodec-extra-53 mencoder

(Windows 上也有 avconv 和 mencoder 和, 理论上也能这么搞)

直接运用上述工具, 跟一般 Linux 命令行软件一样的问题就是, 参数略复杂, 比如用 mencoder 合并视频

mencoder -ovc copy -o OUTPUT_FILE.mp4 INPUT_FILE INPUT_FILE INPUT_FILE ...

而用 avconv 从一个视频源文件中提取一段内容, 转压成 640x360 分辨率的 MP4 文件, 去掉声音, 要这么干

avconv -ss 00:00:10 -i INPUT_VIDEO_FILE -t 00:00:30 -vf "scale=640:360" -f mp4 -vcodec libx264 -an OUTPUT.mp4

上面 -ss 参数后面是开始时间, -t 参数后是剪出的片段长度. (因为视频流压缩的问题, 这两个时间都可能不精确)

对于制 MAD 来说一次要剪出上百个片段, 这么一个个手打再多的爱最后也是死路一条. 所以得换个方式, 选取一些相对容易的工具; 当然如果没有, 就造一个.

这个轮子构想的出发点是尽量简化对视频剪裁参数的编写. 因为制作 MAD 往往是从多个视频中混合剪出片段, 而且顺序不确定, 所以填写文件名还是太麻烦, 可以这么考虑

将所有源视频放在一个目录下, 文件名前面编号 (如果是同一部动画, 那么就是集数了)

由一个文件给出剪取顺序, 信息包括视频顺序编号, 场景开始时间, 场景长度; 为了让这个文件更容易维护, 也允许文件中有注释

剩下的参数只需要指定输入的音频文件路径和输出的最终文件路径即可

中间弄出来的片段都放临时文件目录好了

基于以上指导思想, 实作如下的 Python 函数

Permanent Link: /p/518 Load full text

Post tags:

 Python
 视频剪辑

索引统计与 Python 字典

    最近折腾索引引擎以及数据统计方面的工作比较多, 与 Python 字典频繁打交道, 至此整理一份此方面 API 的用法与坑法备案.

    索引引擎的基本工作原理便是倒排索引, 即将一个文档所包含的文字反过来映射至文档; 这方面算法并没有太多花样可言, 为了增加效率, 索引数据尽可往内存里面搬, 此法可效王献之习书法之势, 只要把十八台机器内存全部塞满, 那么基本也就功成名就了. 而基本思路举个简单例子, 现在有以下文档 (分词已经完成) 以及其包含的关键词
  • doc_a: [word_w, word_x, word_y]
  • doc_b: [word_x, word_z]
  • doc_c: [word_y]
将其变换为
  • word_w -> [doc_a]
  • word_x -> [doc_a, doc_b]
  • word_y -> [doc_a, doc_c]
  • word_z -> [doc_b]
    写成 Python 代码, 便是
doc_a = {'id': 'a', 'words': ['word_w', 'word_x', 'word_y']}
doc_b = {'id': 'b', 'words': ['word_x', 'word_z']}
doc_c = {'id': 'c', 'words': ['word_y']}

docs = [doc_a, doc_b, doc_c]
indices = dict()

for doc in docs:
    for word in doc['words']:
        if word not in indices:
            indices[word] = []
        indices[word].append(doc['id'])

print indices
    不过这里有个小技巧, 就是对于判断当前词是否已经在索引字典里的分支
if word not in indices:
    indices[word] = []
可以被 dictsetdefault(key, default=None) 接口替换. 此接口的作用是, 如果 key 在字典里, 那么好说, 拿出对应的值来; 否则, 新建此 key, 且设置默认对应值为 default. 但从设计上来说, 我不明白为何 default 有个默认值 None, 看起来并无多大意义, 如果确要使用此接口, 大体都会自带默认值吧, 如下
for doc in docs:
    for word in doc['words']:
        indices.setdefault(word, []).append(doc['id'])
    这样就省掉分支了, 代码看起来少很多.
    不过在某些情况下, setdefault 用起来并不顺手: 当 default 值构造很复杂时, 或产生 default 值有副作用时, 以及一个之后会说到的情况; 前两种情况一言以蔽之, 就是 setdefault 不适用于 default 需要惰性求值的场景. 换言之, 为了兼顾这种需求, setdefault 可能会设计成
def setdefault(self, key, default_factory):
    if key not in self:
        self[key] = default_factory()
    return self[key]
倘若真如此, 那么上面的代码应改成
for doc in docs:
    for word in doc['words']:
        indices.setdefault(word, list).append(doc['id'])

Permanent Link: /p/517 Load full text

Post tags:

 Data Structure
 Python

简易配置 gunicorn

引子

    单纯 gevent 跟 nodejs 一样有个问题是如果服务器有大的同步计算 (比如压缩一张图片什么的) 需求时, 服务器会很卡. 这也不能怪它们, 因为本来它们的长处是 IO 异步化, 同步计算卡住是缺陷特性之一.
    然, 或荐基独搅受 gunicorn 以解此困. 只是其首页上例子意味不明, 各种文档文章都说要编写一些离奇复杂的配置文件, 然后跑个语焉不详的 hello world, 并没能明示重点问题.

正文

    嘛, 一番探索之后配了下面一个用例 (Flask)
import time
import flask

app = flask.Flask(__name__)

@app.route('/<int:n>')
def root(n):
    time.sleep(2)
    i = n / 2
    while 1 < i:
        if n % i == 0:
            return 'not prime'
        i -= 1
    return 'prime'

if __name__ == '__main__':
    app.run(port=8000)
    这个例子里面兼顾了长 IO (用睡眠去模拟) 跟大计算 (算请求的数是不是个素数). 把这货在控制台裸着启动起来, 然后用 apache benchmark 来一发 (如果觉得后面请求参数里那个素数不够大, 可以自行算一个大的替换)
ab -n 500 -c 50 localhost:8000/16785407
    当然了, -c 50 这个参数纯是卖萌的, 因为上面这代码自身根本异步不起来. 结果自然是惨不忍睹, 重点两行在测试机上表现如下
Time per request:       131417.472 [ms] (mean)
Time per request:       2628.349 [ms] (mean, across all concurrent requests)
    平均单个请求耗时 2.6 秒以上, 其中 2 秒是睡过去的, 剩下 0.6 秒是计算. 也就是说 IO 时间与计算时间大概的比例是 3:1.

    安装 gunicorn 可以直接通过 pip 安装, 简单容易, 就不废话了. 下面上 gunicorn 平装版, 把上面的文件保存为 test.py, 在控制台中执行
gunicorn -w 4 test:app
    这个是说, 开 4 个进程跑 test 模块下的 app (就是文件里全局定义的 app 变量啦). 现在再开 ab 来一炮 (参数完全相同), 结果是
Time per request:       33150.026 [ms] (mean)
Time per request:       663.001 [ms] (mean, across all concurrent requests)
    从结果上来看差不多就是裸跑的 1/4 了, 因为开了 4 个进程一起搅嘛.

    虽然有 4 个进程睡睡醒醒轮番搞, 但没有异步 IO 的支持, 进程睡着就不干事了. 作为要榨干 worker 进程以及 CPU 使用率的系统管理员来说这可不能忍, 于是继续折腾个 gevent 进去好了, 两者互补, 相得益彰.
    不过用 gunicorn 就不需要在文件最开始打猴子补丁了, gunicorn 有个参数直接让 gevent 嵌入进程
gunicorn -w 4 -k gevent test:app
    再来一发 ab, 结果是
Time per request:       9724.214 [ms] (mean)
Time per request:       194.484 [ms] (mean, across all concurrent requests)
    嘛, 算是还看得过去的数据了.

补充说明

绑定其它端口

Permanent Link: /p/516 Load full text

Post tags:

 Python
 Flask
 Web Server
 Gevent
 Gunicorn

记一些 (没) 有意义的 reduce 用法

    在 Python 或 Javascript 等许多语言中都有 reduce 函数. 其中 Python 中 reduce 作为全局函数出现, 而 Javascript 中则是 Array 的成员函数. 大量的用 reduce 来做累加累乘之类的例子就不说了, 这里探讨一个特殊的用例.
    前端经常会需要将页面中用户填写的一些内容打包成 JSON 字典, 比如一个注册页面片段
<div>
    <input id='email' placeholder='Email'>
    <input id='password' placeholder='Password'>
    <input id='conform_password' placeholder='Confirm Password'>
    <input id='address' placeholder='Address'>
    <input id='phonenum' placeholder='Phone Number'>
    <button id='subm'>Submit</button>
</div>

<script>
document.getElementById('subm').onclick = function() {
    var inputValues = {
        email: document.getElementById('email').value,
        password: document.getElementById('password').value,
        address: document.getElementById('address').value,
        phonenum: document.getElementById('phonenum').value
    };
    /* process inputValues */
};
</script>
    以后每次这个表单多一项时, 构造 inputValues 时就会多一项, 代码维护会很烦.
    如果能这样写的话可能会好一些
var inputValues = {k: document.getElementById(k).value
                   for k in ['email', 'password', 'address', 'phonenum']};
    可惜 Javascript 里面没有温暖人心的 dict comprehension... 于是, 就有了下面这种 reduce 替代品 (终于正题了)
var inputValues = ['email', 'password', 'address', 'phonenum'].reduce(
    function(obj, item) {
        obj[item] = document.getElementById(item).value;
        return obj;
    }, {}));

Permanent Link: /p/515 Load full text

Post tags:

 Javascript
 函数式程序设计
 Python

Flask / MongoDB 搭建简易图片服务器

前期准备

通过 pip 或 easy_install 安装了 pymongo 之后, 就能通过 Python 调教 mongodb 了.
接着安装个 flask 用来当 web 服务器.
当然 mongo 也是得安装的. 对于 Ubuntu 用户, 特别是使用 Server 12.04 的同学, 安装最新版要略费些周折, 具体说是

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb-10gen

如果你跟我一样觉得让通过上传文件名的后缀判别用户上传的什么文件完全是捏着山药当小黄瓜一样欺骗自己, 那么最好还准备个 Pillow 库

pip install Pillow

或 (更适合 Windows 用户)

easy_install Pillow

正片

Flask 文件上传

    Flask 官网上那个例子居然分了两截让人无从吐槽. 这里先弄个最简单的, 无论什么文件都先弄上来
import flask

app = flask.Flask(__name__)
app.debug = True

@app.route('/upload', methods=['POST'])
def upload():
    f = flask.request.files['uploaded_file']
    print f.read()
    return flask.redirect('/')

@app.route('/')
def index():
    return '''
    <!doctype html>
    <html>
    <body>
    <form action='/upload' method='post' enctype='multipart/form-data'>
         <input type='file' name='uploaded_file'>
         <input type='submit' value='Upload'>
    </form>
    '''

if __name__ == '__main__':
    app.run(port=7777)
  • 注: 在 upload 函数中, 使用 flask.request.files[KEY] 获取上传文件对象, KEY 为页面 form 中 input 的 name 值
    因为是在后台输出内容, 所以测试最好拿纯文本文件来测.

保存到 mongodb

    如果不那么讲究的话, 最快速基本的存储方案里只需要

Permanent Link: /p/514 Load full text

Post tags:

 Python
 Web Server
 Flask
 Tutorial
 MongoDB

Page 0 1 2 3 4


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site