2016年9月

typecho代码高亮插件

这是很久之前写的一个typecho代码高亮插件了,由于是自己用的,所以高亮theme之类的都没有弄成可配置的;

自定义修改

需要更改theme的话,可以直接修改 HighLight/Plugin.php 文件里面引用css的代码行

echo '<link rel="stylesheet" href="' . $url . '/styles/solarized_dark.css">' . "\n";

@疯狂的杰作 同学提出是否要分享一下,所以就拿出来了。

安装方法

下载 HighLight.zip,解压

上传到 /path/to/your/typecho/usr/plugins/ 目录下,使用 admin 帐号登录你的 typecho 后台,在插件管理里面启用他就可以啦。

HighLight.zip

正则-编程语言调用代码的生成

演示: 正则表达式测试工具

功能介绍

给定一个正则,生成php的正则调用代码。

一般正则中都会或多或少的包含具体编程语言中的特殊字符,还需要考虑到具体语言中字符串的转义;

下面列出了一些常见的要考虑的情况:

1. 语言是否支持多行字符串?
2. 是否有定界符?
3. 语言的正则引擎是否支持逆序环视?
4. 正则修正符?

具体场景

我们拿PHP做个例子:

写一个特殊字符覆盖比较全的正则,并写出他的PHP匹配代码:

要匹配的内容

<!-- 模拟一个要匹配的字符串, 我们要取出其中的<div/>区块的内容 -->
<article>
  <div class="quote">'引用\/'</div>
</article>

正则

<div class="quote">'(.+?\\/)'</div>

PHP匹配代码

$contents = <<<EOT
<article>
  <div class="quote">'引用\/'</div>
</article>
EOT;

if (preg_match('/<div class="quote">\'(.+?)\\\\\/\'<\/div>/', $contents, $match)) {
    var_dump($match);
}

注意: 上面的正则写到PHP代码中的时候,以下字符进行了转义:

1. ' 单引号
2. / 定界符
3. \ 转义字符,这是个比较特殊的字符

实现思路

要实现一个可用的代码生成,无非就是要把上面提到的特殊字符给转义了;

本想要用正则再对正则的字符串转义,最后发现不太靠谱,越写越复杂;

记起之前看过的redis conf分析相关的代码之后,遂选择逐字判断字符,并对字符转义,效果不错;注意优先级!

public function escape($value)
{
    $chars = str_split($value);
    $len = count($chars);
    $result = '';
    for ($i = 0; $i < $len; $i++) {
        if ($chars[$i] === '\\' && $i < $len - 1) {
            // 转义字符的判断优先级最高,可以避免 \' 正则的重复转义
            $i++; // 跳过后面一个字符
            if ($chars[$i] === '\\') {
                $result .= '\\\\\\\\';
            } else {
                $result .= '\\' . $chars[$i];
            }
        } else if ($chars[$i] === '\'') {
            // 单引号,php字符串边界
            $result .= '\\\'';
        } else if ($chars[$i] === '/') {
            // / php正则定界符
            $result .= '\\/';
        } else {
            $result .= $chars[$i];
        }
    }

    return $result;
}

根据magnet下载torrent

整体流程

magnet2torrent.png

requirement

# 安装virtualenv
pip install virtualenv
# 创建虚拟环境
virtualenv env
# source
. ./env/bin/activate

安装依赖包
pip install beanstalkc

Client

实现往beanstalkd里面塞hash

import beanstalkc
import sys

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("usage: %s <hash>" % sys.argv[0])
        sys.exit(1)
    if len(sys.argv[1]) != 40:
        print("hash code is error.")
        sys.exit(1)
    beanstalk = beanstalkc.Connection(host = "localhost", port = 11300)
    beanstalk.put(sys.argv[1])
    print("hash has put")

server

依赖libtorrent下载torrent

wget https://github.com/arvidn/libtorrent/releases/download/libtorrent-1_0_10/libtorrent-rasterbar-1.0.10.tar.gz
tar zxf libtorrent-rasterbar-1.0.10.tar.gz
cd libtorrent-rasterbar-1.0.10
./configure --enable-python-binding PYTHON=`which python` --prefix=$VIRTUAL_ENV LIBS='-liconv'
make -j2 && make install

依赖beanstalkd队列

mkdir /usr/local/beanstalkd/bin
wget https://github.com/kr/beanstalkd/archive/v1.10.tar.gz
tar zxf v1.10.tar.gz
cd beanstalkd-1.10/
make
mv beanstalkd /usr/local/beanstalkd/bin

mkdir /data/beanstalkd/
# /usr/local/beanstalkd/bin/beanstalkd -b /data/beanstalkd/

使用supervisor管理beanstalkd

[program:beanstalkd]
directory = /data/beanstalkd/
command = /usr/local/beanstalkd/bin/beanstalkd -b /data/beanstalkd/

Python2.7 支持 ThreadPoolExecutor 的话还需要 futures 包

pip install futures

上Server端代码

import time
import tempfile
import libtorrent
import os
import os.path as path
import shutil
from concurrent import futures
import beanstalkc

def hash2torrent(torrent_hash, timeout = None):
    torrent_hash = torrent_hash.lower()
    print("start download: %s" % (torrent_hash))
    magnet = "magnet:?xt=urn:btih:" + torrent_hash

    directory = path.join("torrents", torrent_hash[0:2], torrent_hash[-2:])
    output = path.join(directory, torrent_hash + ".torrent")

    if not path.exists(directory):
        os.makedirs(directory)

    if path.exists(output):
        print('Already exists.')
        return output

    tempdir = tempfile.mkdtemp()
    session = libtorrent.session()

    session.add_dht_router('router.bittorrent.com', 6881)
    session.add_dht_router('router.utorrent.com', 6881)
    session.add_dht_router('router.bitcomet.com', 6881)
    session.add_dht_router('dht.transmissionbt.com', 6881)
    session.add_dht_router("dht.aelitis.com", 6881)
    session.start_dht()

    params = {
        'save_path': tempdir,
        # 'storage_mode': libtorrent.storage_mode_t(2),
        # 'paused': False,
        # 'auto_managed': True,
        'duplicated_is_error': True
    }

    handle = libtorrent.add_magnet_uri(session, magnet, params)

    cost = 0
    while not handle.has_metadata():
        if timeout is not None and cost > timeout:
            print("Timeout.")
            # session.pause()
            session.remove_torrent(handle)
            shutil.rmtree(tempdir)
            return None
        time.sleep(1)
        cost = cost + 1
    # session.pause()
    print("Downloaded. %d" % (cost))

    # print 'got metadata, starting torrent download...'
    # while handle.status().state != libtorrent.torrent_status.seeding:
    #     s = handle.status()
    #     state_str = ['queued', 'checking', 'downloading metadata', 'downloading', 'finished', 'seeding', 'allocating']
    #     print '%.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s %.3f' % (s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000, s.num_peers, state_str[s.state], s.total_download/1000000)
    #     time.sleep(3)

    torrent_info = handle.get_torrent_info()
    torrent_file = libtorrent.create_torrent(torrent_info)

    torrent_content = libtorrent.bencode(torrent_file.generate())

    with open(output, "wb") as f:
        f.write(torrent_content)
        f.close()

    session.remove_torrent(handle)
    shutil.rmtree(tempdir)
    return output

if __name__ == '__main__':
    beanstalk = beanstalkc.Connection(host='localhost', port=11300)
    timeout = None
    with futures.ProcessPoolExecutor(10) as executor:
        while True:
            job = beanstalk.reserve()
            torrent_hash = job.body
            job.delete()
            executor.submit(hash2torrent, torrent_hash, timeout)

实测,平均每个种子的下载时间需要 15 分钟左右。

分库分表的策略

1. 主键+业务虚拟键分表

这种方式适合查询比较单一的业务,最大的缺点就是业务对分库分表这层是有感知:

  1. 查询返回的时候需要在接口层拼接业务的真实Id
  2. 根据真实业务Id查询,或者更新数据的时候,需要拆分真实业务Id,路由到数据所在表,再根据数据库Id查询

虚拟场景

假设我们分了1024张表,具体表结构如下:

QQ20160904-0@2x.png

插入流程

1. 插入数据,必须带有userId
2. 根据userId计算出xId
xId = userId % 10000;
3. 根据xId定位数据所在表
tNum = xId % 1024; // 最简单的取模hash(具体策略由中间件决定)
4. 插入数据,返回realId
realId = id + xId;

单条查询流程

1. 根据realId查询, 获取单张表中的Id值
id = realId / 10000; // 整除
2. 获取虚拟键xId
xId = realId % 10000;
3. 根据xId定位数据所在表
tNum = xId % 1024; // 最简单的取模hash(具体策略由中间件决定)
4. 根据表和Id获取单条数据

批量查询流程

基本和单条查询一致,可优化的点:

第3步,根据xId进行分组,将查询同一张表的query放在一次sql的查询语句中

2. 主键/业务外键分表

这种策略对业务代码可以无侵略性, 为避免Id冲突, 使用外部Id生成器, 以下步骤全在中间件中执行:
(根据业务外键的处理同理,查询比较复杂的情况一般都是使用冗余表)

虚拟场景

假设我们分了1024张表,具体表结构如下:

QQ20160904-1@2x.png

插入流程

1. 从Id生成器获取Id
2. 根据Id进行hash (简单的可以使用取模), 获取数据应该插入的表名
tNum = id % 1024;
3. 插入数据表

单条查询流程

1. 根据Id进行hash (简单的可以使用取模), 获取数据所在表名
tNum = id % 1024;
2. 查询数据

批量查询流程

基本和单条查询一致,可优化的点:

第1步,根据Id进行hash后进行分组,将查询同一张表的query放在一次sql的查询语句中

数据的聚合

在批量查询的时候,需要 order by group by distinct 的时候,需要在查询出各分表数据之后,在中间件组件中自行实现对数据的处理