Comments

缘起

GFW 早已经是臭名昭著,路人皆知的了,因为它的存在,使得整个大陆的用户都只能在「局域网」里活动。政治敏感的内容就不说了,很多技术性的网站也被墙掉,导致查找问题浏览网页时经常网络被重置。

我是个重度 Google 用户,虽然经常用到的 Google 的产品基本上只有 Google 搜索和 Gmail,但只需要这两项就让我离不开 Google。此外,还有很多网站使用 Google 的 OpenID 登录,引用 Google 的字体文件和其他资源文件,这些网站也都几乎无法正常访问。我曾经使用过一些手段来实现翻墙,在大学时得益于教育网免费的 IPv6,毕业后使用了很久的 GoAgent,手机上用过 fqrouter,然而都不是很稳定和一劳永逸的解决方案。

有很多人使用 VPN,有购买的,也有自己搭建的。在 GoAgent 无法使用后,我开始正式考虑使用 VPN 了,但不想买 VPN,主要原因有:

  1. 很多人使用的 VPN 容易被盯上而面临被干掉的危险(应该是多虑了)
  2. 出于信息安全和隐私的考虑,不希望自己的信息有被第三方获取的风险(所以也不想用 fqrouter 了)
  3. 想自己折腾

所以就选择了国外 VPS + Shadowsocks 的解决方案。

Read on →
Comments

平时需要经常用到 SSH,比如登录远程服务器,用 Git 推送和更新代码等。建立一次 SSH 连接可能并不需要多久长时间,但是如果要频繁登录同一台服务器,就未免显得有些繁琐和浪费时间。如果是用用户名和密码登录,每次都要输入密码就更加让人崩溃。还有使用 Git 的时候,短时间内可能需要经常 git pullgit push,如果每次操作都需要重新建立连接,等待过程就让人心生厌恶了。

实际上,SSH 有个「鲜为人知」的特性可以做到重用连接,只有在第一次登录的时候会创建新的连接,后续的会话都可以重用这个已经存在的连接。这样,后续的登录就会非常快,而且不需要输入密码认证。配置也很简单,直接上代码。

修改 ~/.ssh/config 文件,添加如下配置:

Host *
    ControlMaster auto
    ControlPath /tmp/ssh_mux_%h_%p_%r
    ControlPersist 600

Read on →
Comments

Golang 里面 map 不是并发安全的,这一点是众所周知的,而且官方文档也很早就给了解释:Why are map operations not defined to be atomic?. 也正如这个解释说的一样,要实现一个并发安全的 map 其实非常简单。

并发安全

实际上,大多数情况下,对一个 map 的访问都是读操作多于写操作,而且读的时候,是可以共享的。所以这种场景下,用一个 sync.RWMutex 保护一下就是很好的选择:

type syncMap struct {
    items map[string]interface{}
    sync.RWMutex
}

上面这个结构体定义了一个并发安全的 string map,用一个 map 来保存数据,一个读写锁来保护安全。这个 map 可以被任意多的 goroutine 同时读,但是写的时候,会阻塞其他读写操作。添加上 GetSetDelete 等方法,这个设计是能够工作的,而且大多数时候能表现不错。

但是这种设计会有些性能隐患。主要是两个方面:

  1. 读写锁的粒度太大了,保护了整个 map 的访问。写操作是阻塞的,此时其他任何读操作都无法进行。
  2. 如果内部的 map 存储了很多 key,GC 的时候就需要扫描很久。

Read on →
Comments

Golang 的 defer 语句是个非常有用的语法,可以把一些函数调用放到一个列表里,在函数返回前延迟执行。这个功能可以很方便的在函数结束前处理一些清理操作。比如关闭打开的文件,关闭一个连接,解锁,捕捉 panic 等。

这篇 Go Blog 用例子讲解了 defer 的用途和使用规则。总结一下主要就是三点:

  • 传递给 defer 语句的参数是在添加时就计算好的。比如下面的函数的输出将会是 0.
func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}
  • 多个 defer 语句的执行顺序类似于 stack,即 Last In First Out. 比如下面的函数的输出将会是 3210.
func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}
  • defer 语句可能会读取并修改函数的命名返回值(named return values)。比如下面的函数的返回值将会是 2 ,而不是 1.
func c() (i int) {
    defer func() { i++ }()
    return 1
}

Read on →
Comments

严格的讲,应该是在把 intfloat等类型转换为字符串时,不要用 fmt.Sprintf,更好的做法是用标准库函数。fmt.Sprintf 的用途是格式化字符串,接受的类型是 interface{},内部使用了反射。所以,与相应的标准库函数相比,fmt.Sprintf 需要更大的开销。大多数类型转换的函数都可以在 strconv 包里找到。

int to string

整数类型转换为字符串,推荐使用 strconv.FormatIntint64),对于 int 类型,strconv.Itoa 对前者做了一个封装。

比较一下 strconv.FormatIntfmt.Sprintf 的时间开销:

package main

import (
    "fmt"
    "strconv"
    "time"
)

const LOOP = 10000

var num int64 = 10000

func main() {
    startTime := time.Now()
    for i := 0; i < LOOP; i++ {
        fmt.Sprintf("%d", num)
    }
    fmt.Printf("fmt.Sprintf taken: %v\n", time.Since(startTime))

    startTime = time.Now()
    for i := 0; i < LOOP; i++ {
        strconv.FormatInt(num, 10)
    }
    fmt.Printf("strconv.FormatInt taken: %v\n", time.Since(startTime))
}

其中某一次运行结果:

fmt.Sprintf taken: 2.995178ms
strconv.FormatInt taken: 1.057318ms

Read on →
Comments

Interface 接口

Go 语言标准库提供了排序的package sort,也实现了对 intfloat64string 三种基础类型的排序接口。所有排序调用 sort.Sort,内部根据排序数据个数自动切换排序算法(堆排、快排、插排)。下面这段代码出自 Go 标准库 sort/sort.go:

func quickSort(data Interface, a, b, maxDepth int) {
    for b-a > 7 {
        if maxDepth == 0 {
            heapSort(data, a, b)
            return
        }
        maxDepth--
        mlo, mhi := doPivot(data, a, b)
        // Avoiding recursion on the larger subproblem guarantees
        // a stack depth of at most lg(b-a).
        if mlo-a < b-mhi {
            quickSort(data, a, mlo, maxDepth)
            a = mhi // i.e., quickSort(data, mhi, b)
        } else {
            quickSort(data, mhi, b, maxDepth)
            b = mlo // i.e., quickSort(data, a, mlo)
        }
    }
    if b-a > 1 {
        insertionSort(data, a, b)
    }
}

// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
    // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
    n := data.Len()
    maxDepth := 0
    for i := n; i > 0; i >>= 1 {
        maxDepth++
    }
    maxDepth *= 2
    quickSort(data, 0, n, maxDepth)
}

Read on →
Comments

innodb_flush_log_at_trx_commitsync_binlog 是 MySQL 的两个配置参数,前者是 InnoDB 引擎特有的。之所以把这两个参数放在一起讨论,是因为在实际应用中,它们的配置对于 MySQL 的性能有很大影响。

1. innodb_flush_log_at_trx_commit

简而言之,innodb_flush_log_at_trx_commit 参数指定了 InnoDB 在事务提交后的日志写入频率。这么说其实并不严谨,且看其不同取值的意义和表现。

  1. innodb_flush_log_at_trx_commit 取值为 0 的时候,log buffer 会 每秒写入到日志文件并刷写(flush)到磁盘。但每次事务提交不会有任何影响,也就是 log buffer 的刷写操作和事务提交操作没有关系。在这种情况下,MySQL性能最好,但如果 mysqld 进程崩溃,通常会导致最后 1s 的日志丢失。
  2. 当取值为 1 时,每次事务提交时,log buffer 会被写入到日志文件并刷写到磁盘。这也是默认值。这是最安全的配置,但由于每次事务都需要进行磁盘I/O,所以也最慢。
  3. 当取值为 2 时,每次事务提交会写入日志文件,但并不会立即刷写到磁盘,日志文件会每秒刷写一次到磁盘。这时如果 mysqld 进程崩溃,由于日志已经写入到系统缓存,所以并不会丢失数据;在操作系统崩溃的情况下,通常会导致最后 1s 的日志丢失。

上面说到的「最后 1s」并不是绝对的,有的时候会丢失更多数据。有时候由于调度的问题,每秒刷写(once-per-second flushing)并不能保证 100% 执行。对于一些数据一致性和完整性要求不高的应用,配置为 2 就足够了;如果为了最高性能,可以设置为 0。有些应用,如支付服务,对一致性和完整性要求很高,所以即使最慢,也最好设置为 1.

Read on →
Comments

这其实是我之前在 StackOverflow 上回答过的一道题,令我感到意外的是,这个问题只有我一个人回答,而且我也获得了 8 个赞同。小小的成就感。

1. What

原题在这里:How to validate integer range in Flask routing (Werkzeug)?

简单翻译一下,大致如下:

Flask 应用里面有一个这样的路由

from foo import get_foo

@app.route("/foo/<int:id>")
def foo_id(id):
    return render_template('foo.html', foo = get_foo(id))

其中 id 的取值是 1~300,如何在路由层级做这个验证?也就是一个类似于这样的东西 @app.route("/foo/<int:id(1-300)").

Read on →
Comments

首先来一句装X的话:

less is more

1. How

less 是一个很方便的命令行工具,但不足的是不能语法高亮,查看的都是黑白的纯文本。幸运的是,source-highlight 可以弥补这一点。在 Ubuntu 安装 source-highlight 非常方便:

sudo apt-get install source-highlight

安装完成后需要做一些简单的配置。编辑 .bashrc,加上以下配置项:

# less hightlight
export LESSOPEN="| /usr/share/source-highlight/src-hilite-lesspipe.sh %s"
export LESS=" -R "

要注意的是 /usr/share/source-highlight/src-hilite-lesspipe.shsrc-hilite-lesspipe.sh 脚本的路径,不同的系统可能不一样,可以查找一下(find / -name src-hilite-lesspipe.sh)。

使配置生效:

source ~/.bashrc

这样就可以在之后使用 less filename 查看文件内容时,支持语法高亮。

Read on →
Comments

Note

前段时间在 stack overflow 上看到一个关于 python decorator(装饰器)的问题,有一个人很耐心的写了一篇很长的教程。我也很耐心的看完了,获益匪浅。现在尝试翻译过来,尽量追求准确和尊重原文。不明白的地方,或翻译不好的地方,请参照原文,地址:

Understanding Python decorators


1. python的函数是对象(Python’s functions are objects)

要理解装饰器,就必须先知道,在python里,函数也是对象(functions are objects)。明白这一点非常重要,让我们通过一个例子来看看为什么。

def shout(word="yes"):
    return word.capitalize()+"!"
 
print shout()
# outputs : 'Yes!'
 
# 作为一个对象,你可以像其他对象一样把函数赋值给其他变量
 
scream = shout
 
# 注意我们没有用括号:我们不是在调用函数,
# 而是把函数'shout'的值绑定到'scream'这个变量上
# 这也意味着你可以通过'scream'这个变量来调用'shout'函数
 
print scream()
# outputs : 'Yes!'
 
# 不仅如此,这也还意味着你可以把原来的名字'shout'删掉,
# 而这个函数仍然可以通过'scream'来访问
del shout
try:
    print shout()
except NameError, e:
    print e
    #outputs: "name 'shout' is not defined"
 
print scream()
outputs: 'Yes!'

OK,先记住这点,我们马上会用到。python 函数的另一个有趣的特性是,它们可以在另一个函数体内定义。

def talk():
 
    # 你可以在 'talk' 里动态的(on the fly)定义一个函数...
    def whisper(word="yes"):
        return word.lower()+"..."
 
    # ... 然后马上调用它!
 
    print whisper()
 
# 每当调用'talk',都会定义一次'whisper',然后'whisper'在'talk'里被调用
talk()
# outputs:
# "yes..."
 
# 但是"whisper" 在 "talk"外并不存在:
 
try:
    print whisper()
except NameError, e:
    print e
    #outputs : "name 'whisper' is not defined"*

Read on →
getElementsByTagName('BODY')[0]).appendChild(s); }()); getElementsByTagName('BODY')[0]).appendChild(s); }()); getElementsByTagName('BODY')[0]).appendChild(s); }());