能不能成功取决于什么

Published on:
Tags:

随着年龄的增长,自己对一些事情的认识在改变,一直想总结一个给年轻同学的帖子,但是总是不能总结太好怎么讲,大概从想讲到现在,已经几个月了,我感觉不讲的话可能就讲不出来了,所以乱谈一下。

从全栈讲起

什么是全栈?我感觉肯定不是普通人,我觉得全栈至少得是绝顶聪明的人才可以做到,对于他们来讲,有很扎实的 CS 功底,并且知识面非常广泛,还有很多产出,并且这些产出是各个方面的。

某种观点

上面是李笑来老师对于全栈的看法,这里 有知乎用户对这个的讨论。

我只是关注到了他那句话里面的「不太笨」,如果他的意思是很聪明的意思,那观点就和我一致了,呵呵。

不得不承认,智商和你能掌握的内容是有关系的,智商不够再怎么努力也很难达到某种顶峰。当然这并不是说,智商不够的就没戏了,换句话说,如果智商不够还不努力,你还是就想想老婆孩子热炕头就算了。

学习别人

学习别人的成功经验,似乎是一种很好的办法,至少人家那条路是走通了。

这几年流行健身,是吧。看着别人一个一个健身房也好,路边跑步也好,还有奥森跑步的,是不是看着眼红但是又觉得自己周边不具备环境?健身房贵,路边有觉得没有好环境,奥森还有点远,所以我得找一个满足条件的才能去进行这个事情。

还有比如看到有人拿 kindle 看书,是不是觉得我要是有个 kindle 带着方便,肯定可以看好多书,比如一直想学习的 iOS 开发,还有 Java 编程思想,哎呀呀,编程能力大幅增长啊。所以一定要买个 kindle。

等等类似的事情吧,别的不多讲了。就是我想要干什么事情,但是呢最好满足个什么条件,会让我干的更好更有动力。

最后通常的结果呢?步顶多跑几次就会觉得没意思了,kindle 在家里吃灰,然后开始有其他的想法,比如看着人家的 iphone 不错唉,我要是有个,看个 pdf 什么的,比 kindle 方便啊,手机可是一直带着的,所以。。。。

听别人讲

欧洲人在放难民进来的时候,想的可是我们可怜你们让你们进来,你们应该很满足,不要闹事老实呆着。所以实际上呢?

父母亲戚常见的「我都是为了你好」这种说法,大家估计都听腻了吧。有用么?

微信朋友圈网上各种鸡汤文,比尔盖兹为什么成功,雷军马云的奋斗,这些文章看的时候让人激动人心,看完了貌似就忘记了,是不是?。。

关键在哪里?

我感觉关键就是一个字「懒」。

懒

2004 年左右在 irc 玩的时候,就有一个网站 let me google that for you。这个网站就是鄙视那些连 google 都懒得用的,稍微有点问题就问别人。知识都是别人的,把别人当 google 用。

很多人都有类似习惯。我们学习的时候,别人都是引进大门,如果自己不能养成自己知识持续更新的习惯,等到自己连年轻人都跟不上的时候,就很悲哀了。

这个都无关智商,所谓活到老学到老,别人都给你总结好了,懒字一上来,就混吃等死吧。

想要客服懒字,得自己给自己洗脑,让自己能坚持的下去。老罗的奋斗 里面,老罗讲自己决定要去新东方当老师之前,学习英语的时候,隔段时间就会学不下去,学不下去的时候,看看成功学的书,给自己打打鸡血,就又活蹦乱跳了。

我大概总结几项提升的方向,遇事情想想,应该有好处

  • 主动思考解决问题的最佳思路
  • 主动发现问题,改进承担
  • 主动推进事情进展
  • 不要限定自己的范围,不停挑战难点
  • 积极参与到别人的有激情的项目里面
  • 对技术保持强烈的好奇心

如何不花钱建立一个支持 https 的 blog

Published on:
Tags: blog

早年的时候要搞 blog 还得弄一个空间,现在,免费的东西越来越多了,感觉共产主义的实现还要靠资本家啊,不过羊毛出在羊身上。。。

要想弄一个免费的 blog,首先你的 blog 内容最好是纯静态网页,如果是类似 php 什么的,那就难找了。使用 jeklly, hexo 这些都可以把 markdown 文件渲染成 html。

然后注册一个 github 或者 gitcafe 等等支持 pages 服务的空间,搞定之后就能得到一个类似于 http://wd.github.io 这样的地址。

然后你注册一个域名(发现标题没起好,这个还是要收费的。。),然后注册 cloudflare,把你的域名的 dns 使用 cloudflare 的,然后在 cloudflare 配置一个 cname 到 wd.github.io。然后建立一个 page rule,强制你的域名使用 ssl。

ok 拉,整个过程就是域名花钱了。可以访问下 http://wdicc.com 看看效果,会自动跳转到 https://wdicc.com :D

Custom Netgear r6300v2 wireless router

Published on:

科学上网。买了群晖之后,一直通过群晖上面跑一个 haproxy 来做转发。不过心里总觉得有点不爽,毕竟一方面多转发了一次,另外群晖在不使用的时候,还会休眠,又或多或少担心影响休眠(经过测试应该是不影响的,但是..)。所以买了 r6300v2 之后,就琢磨通过路由器做这个事情。

路由器上面搞就有两个选择,一是从 iptables 入手,直接转发出去,另外一个是从软件层面做。

开始搞了几天的 iptables,发现原有系统 iptables 条目还是挺多的,加上路由器翻墙的功能也需要加一些条目,导致尝试了好几天之后总算能够转发过去链接了,但是数据包过不去,为了调试就开始打算在路由器安装 tcpdump。然后找到了 https://github.com/Entware/entware ,配置好之后可以使用 opkg 来安装包。包列表可以参考这里 http://pkg.entware.net/binaries/armv7/Packages.html ,这个路由器是 armv7 版本的 cpu。

安装 opkg 之前先得了解下,梅林固件分两部分存储,一部分是系统区,一部分是自定义区。系统区应该是你刷的固件所在的地方,是不能修改的,自定义区是可以存放一些自己定义的脚本的。每次系统启动的时候,你的一些自定义的东西都是存在自定义区加载的。自定义区就是 /jffs 分区。想要使用,得在 系统管理 -> 系统设置 里面,打开 JFFS 的配置,允许执行上面的脚本。

因为系统自带的 /jffs 分区只有 60M 左右,而我们装包的时候很容易就超过这个限制了,我现在已经用了 8xM 空间。所以最好还是用一个 u 盘来做这个事情。每次想要自动加载 u 盘,启动 u 盘里面的程序的话,还需要一些自定义的脚本来做这个事情。

先把 opkg 配置好,需要先准备好 /opt 目录。

1
2
mkdir -p /tmp/opt
mount -t ext4 -o rw,noatime /dev/sda1 /opt

上面的 /dev/sda1 是 u 盘,ext4 是文件系统类型,按照自己的修改一下。一般 u 盘插上去就会自动挂载,df 看一下就知道是哪个名字了。系统配置里面有个 dlna 的配置记得关掉,否则他会读 u 盘导致你不能 umount 之类,或者 kill 掉一个叫做 minidlna 的进程也可以。

然后参考 https://github.com/Entware/entware 操作就可以了,可以看到他会在 /opt 给你安装一陀东西。因为这个是 u 盘,所以东西重启也不会丢失。

然后参考梅林的 wiki,它允许用户在 /jffs/scripts 自定义一些启动脚本,来支持我们自动挂载和启动 u 盘上面的程序。

post-mount 内容如下,前面几个注释的是调试用的。最后一个 if 里面内容是执行一些 opkg 安装的程序的启动脚本,这个后面说。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh

#echo $(date) > /tmp/000service-start
#echo "$1" >> /tmp/000service-start
#ls /dev/sda* >> /tmp/000service-start

if [ -b /dev/sda1 ];then
mkdir -p /tmp/opt
mount -t ext4 -o rw,noatime /dev/sda1 /opt
fi

if [ -x /opt/bin/opkg ];then
/opt/etc/init.d/rc.unslung start
fi

要记得 sudo chmod +x post-mount,然后可以重启路由器看看是不是启动之后就能看到 /opt 有了你上次安装的程序了。

上面一阶段搞定之后,就可以装一些软件了,比如我装了 vim, tcpdump,bind-dig, haproxy。opkg 的命令使用可以参考这里 https://wiki.openwrt.org/doc/techref/opkg

接着上面的话题,本来打算装好 tcpdump 来调试的,然后发现可以比较方便的启动 haproxy 之后,就打算用 haproxy 弄了,路由表太多,分析比较麻烦,还是走简单的吧。。

/opt/etc/haproxy.cfg 如下,把 IP 和 PORT 改成你自己的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
global
ulimit-n 331071

defaults
log global
mode tcp
option dontlognull
timeout connect 1000
timeout client 150000
timeout server 150000

frontend ss-in
bind *:本机PORT
default_backend ss-out

backend ss-out
server server1 IP:远端PORT maxconn 20480

/opt/etc/init.d/S001haproxy.sh 如下,sudo chmod +x /etc/init.d/S001haproxy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/sh

haproxy_bin=/opt/sbin/haproxy
haproxy_cfg=/opt/etc/haproxy.cfg
pid=/opt/var/run/haproxy.pid

action=$1

if [ -z "$action" ];then
printf "Usage: $0 {start|stop|restart}\n" >&2
exit 1
fi

case "$action" in
start)
$haproxy_bin -f $haproxy_cfg -p $pid -D
;;
stop)
kill $(cat $pid)
;;
restart)
kill $(cat $pid)
$haproxy_bin -f $haproxy_cfg -p $pid -D
;;
esac

因为前面在 post-mount 最后一个 if 里面的语句,这样启动路由器就会自动启动 haproxy 了。

想使用这个端口转发,还需要在路由器配置界面里面增加一个到路由器 ip 的映射,然后还需要一个 /jffs/scripts/firewall-start 如下

1
2
3
#!/bin/sh

iptables -I INPUT -i ppp0 -p tcp --destination-port PORT -j ACCEPT

我使用的过程中还发现一个问题,pkg.entware.net 貌似被墙了。。虽然配置了翻墙但是不太明白为什么路由器上面时好时坏,而我局域网内的 mac 访问总是 ok 的,很奇怪。路由器上面不能访问有个办法是通过 mac 代理一下。

mac 上面启动一个 ngx,配置如下,使用 nginx -p ./ -c nginx.conf 启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
events {
worker_connections 1024;
}


http {
server {
listen 0.0.0.0:8000;
location / {
proxy_pass http://pkg.entware.net;
}
}
}

然后修改路由器上面 /opt/etc/opkg.conf src/gz packages http://MAC_IP:8000/binaries/armv7,然后就可以了。

写完自己看发现这不是一份操作指南,只能算是一些提示,如果有人照着做能不能成功可能还是得看自己。。。

lua metatable

Published on:
Tags: lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
t = setmetatable({ bar = 4, foo = 7 }, { __index = { foo = 3 } })

print(t.foo) -- 7
print(t.bar) -- 4

t = setmetatable({ }, { __index = { foo = 3} })

print(t.foo) -- 3
print(t.bar) -- nil

fuc = function (t,k)
if k == 'foo' then
return rawget(t, 'bar')
else
return 0
end
end

t = setmetatable({ }, { __index = fuc })

print(t.foo) -- 3
print(t.bar) -- nil
print(t.ff)

Across the Great Wall, we can reach every corner in the world

Published on:
Tags: gfw

科学上网估计是每个搞 IT 的人必须要掌握的知识了。简单讲讲我目前使用的一些方法。

国外服务

首先肯定需要先有个国外的资源,比如买专业的 vpn ss 服务等。数据经过第三方都不一定那么可靠,我的主力邮箱在 gmail,可不想被攻破,所以我选择了自己购买和搭建服务。

我买的是 linode 的服务,最便宜的是 10$ 一个月。也可以买一些美国的 kvm 或者 zen 的虚拟机,有比较便宜的一个月可能才不到 1$,当然这种便宜的一般很快就会卖没,得看运气。我的 linode 是和 3 个基友一起合租的,这样大家每个人一年也就 300 来块钱,也就一顿海底捞(我这两年非常喜欢用饭钱来衡量消费,吃饭可是天天都有的,但是有些消费,比如买软件,买服务这些一般都是一年或者几年才一次,比起吃饭真的便宜多了),一般都能承担的吧。

早期我买过一些 ssh 服务,速度不稳定,因为很难限制超售。后来还买过云梯,他们提供的节点比较多速度还不错。

有了 vps 搭一个 ssserver 就是很简单的了,就不多说了。服务器上面我还配置了 ocserv 这个支持 cisco 客户端的 vpn 服务。当然并不是所有 vps 都支持,有的 vps 没有 tun 设备,跑不起来,买的时候要注意。

ssh 方式

使用 ssh 方式的时候,最早是直接 ssh 链接弄一个 socks 端口给本地,然后本地使用 pac 配合。

后来有段时间被断的不行,就试了 stunnel,可以把 ssh 转为 https 服务,这样就可以跑在 443 端口了,和其他 https 服务比较难区分。可以起点作用。

这个方式没法支持手机。

使用 ngx

有段时间还使用 hosts 文件加 ngx 反向代理还翻墙。ngx 上面配置 google.com 和 twitter.com 的反向代理,然后手机或者电脑上面配置 hosts 指向 ngx。就可以实现翻墙了。不过因为都是 https 的网站,所以服务器上面得配置 https 的服务,证书得弄到电脑或者手机上面信任才行。

这个手机想要支持的话,ios 比较麻烦,必须得越狱。

vpn 方式

早期用 vpn 方式的时候,pptp 可以搭配 chnrouts 来实现国外和国内走不同的路由。用起来也不错,不过问题是全局的有时候切换也不方便,并且有时候还需要连公司的 vpn 路由一乱就麻烦了。

pc 上面选择比较多。手机上面,试过 anyconnect,缺点就是全局翻墙(我试过让 server 只 push fb twitter 的路由,但是维护麻烦,效果也不好)。anyconnect 比较赞的地方是他的链接 cookie 可以设置比较长的有效期,这样网络切换什么的临时断开之后自动重连也不需要输入密码。哦,当然,我的 anyconnect 和后面提到的 openvpn 都是通过自己签的证书来认证的,也不需要输入密码。

后来用过 openvpn,openvpn 是基友维护的。他的思路很赞。

他买了一台 server 放家里,家里是联通 adsl 24h 联机,然后 server 上面跑一个 openvpn 的服务器端给手机连接用,服务器上面再通过 vpn 和 vps 的 vpn 链接,同时这机器配置只有国外路由才走 vps,国内都是直接链接。大概链路就是 手机 -> 家里的 server -> vps。如果是国内的网站,就直接通过家里的 server 访问了,比国外的 vps 访问速度快。

这个方法还有好处是有时候一些公开的场合链接一些 wifi 的时候,很不安全,而通过 vpn 之后,数据都是加密的,就安全多了。我在 surge 之前,在 hosts 不能用之后,基本都是用这个和 anyconnect。

使用 ss

开始使用 ss 的时候,是使用 goagentx (貌似已经比较难找到下载了)的,这个工具异常好用,支持切换全局还是使用 pac 非常方便。pac 推荐使用 genpac 来维护 pac,放到 dropbox 里面就可以四处用了。

我有相当长一段时间都是这么翻墙的。直到后面 ios 9 放开网络权限之后,出来了 surge。surge 现在卖的太贵了,不建议购买,最近好像看到有一些新的软件也在出现,可以考虑。

surge 出来之后,ios 基本就是用这个了。

surge 也有 mac 版本。如果没有,使用 ss mac 版本也可以,搭配 pac 可以做到透明。

应对不稳定的网络情况

家里是联通 adsl,链接我的 linode 一直都比较稳定,速度不错也没有丢包。公司访问 linode 有时候丢包比较严重,不过也将就用了。去年去长沙出差,那边完全访问不能把我搞的很痛苦。回来就开始琢磨怎么搞。

上面基友的思路提醒了我,就是自己家里一台 server 建长链接,然后在外面翻墙先连家里。但是家里的路由器完全不能定制,后来发现我的群晖的 nas 可以装 haproxy,就搞定这个事情了。在路由器上面映射一个端口给群晖,群晖上面跑 haproxy 转发到 linode。给路由器弄了个 ddns,在外面翻墙都连接这个 dns。

群晖上面跑 haproxy 还不影响硬盘休眠,还挺不错。这样就彻底解决了我翻墙的问题了。

但是遇到家里停电断网就虾米了。。

家里的全局翻墙方案

前段时间换了 Netgear R6300v2,才发现我之前错过了好多好玩的东西。刷了个国内论坛定制的梅林 rom,自带了 ss 客户端,并且配置的非常完美,支持多种翻墙策略,具体就不细说了。就说现在的效果吧。

直接映射了端口到 linode 的 ss,并且也支持 ddns(我用的 3322 的),这样 nas 上面的 haproxy 就彻底可以不用了。

路由器跑了 ss 客户端,加 redsocks2 和 dns2proxy,实现了国内网站直连,国外根据域名匹配到列表里面的服务器通过 ss 链接。这样家里所有的终端,不需要跑任何服务,就可以无缝翻墙了。我的 ps4,apple tv,ipad 上面的 yotube 都可以访问了。然后还支持黑名单,我把 nas 加进去了,防止使用 bt 下载的时候跑到国外流量。

这样我目前手机和 mac 都是直接通过 surge,国外流量通过 3322 的 dns 先链接到路由器,然后转发到 linode 实现翻墙。

目前唯一的问题

mac 版本的 surge,还不能自己配置网络,这样临时想关掉代理的时候,就比较麻烦,得去网络配置里面关。也不能很简单的配置让全部流量走 proxy,有时候需要测试下什么的,就比较麻烦。所以我现在有时候还会使用 goagentx 辅助一下。

setup proxy for emacs

Published on:
Tags: emacs osx

我在 mac 上面使用 emacs 都是使用 daemon + emacsclient 模式。使用 paradox 包管理(其实就是比 list-package 稍微多了一点功能’),但是因为那些包什么的信息都在国外的网站,还有 github 什么的,导致速度巨慢甚至连不上,关键 emacs 单线程还得卡着别的操作,所以挺讨厌的(其实 paradox 提供了异步更新的方式,不会阻塞现在进程,但是有时候会不知道进度…)。

思路就是使用 proxychains

新建一个 /Library/LaunchAgents/gnu.emacs.daemon.plist 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">
<dict>
<key>Label</key>
<string>gnu.emacs.daemon</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/proxychains4</string>
<string>-f</string>
<string>/Users/wd/.proxychains/proxychains.conf</string>
<string>/usr/local/opt/emacs-mac/bin/emacs</string>
<string>--daemon</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ServiceDescription</key>
<string>Gnu Emacs Daemon</string>
<key>UserName</key>
<string>wd</string>
</dict>
</plist>

其中 /Users/wd/.proxychains/proxychains.conf 文件的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
localnet 127.0.0.0/255.0.0.0
localnet 10.0.0.0/255.0.0.0
localnet 172.16.0.0/255.240.0.0
localnet 192.168.0.0/255.255.0.0
quiet_mode

[ProxyList]
#socks5 127.0.0.1 1080
http 127.0.0.1 6152

use gearman to distribute you nagios check

Published on:

安装

gearman

需要 boost > 1.39, libevent-devel, libuuid-devel, gperf
需要 gcc >= 4.2

export CC=gcc44
export CC=g++44

mod-gearman

libtool-ltdl-devel ncurses-devel
—with-gearman

配置

gearman 分为几个模块

  • mod_gearman: 负责把 nagios 中的检查任务发给 gearmand job server
  • gearmand: 负责接收任务,分配给 worker 执行。这个是个通用的队列管理服务。
  • gearman_workder: 负责消费 gearmand 中的 job。

其中 mod_gearman 的代码里面包括了上面提到的 mod_gearman 和 gearman_workder。

所以规划好 gearmand 启动的机器,以及你的 worker 机器。其中要注意 worker 机器上面是会执行所有[1] nagios 监控

需要配置的文件有几个

  • nagios.cfg 增加 broker
  • mod-gearman/etc/mod_gearman/mod_gearman_neb.conf broker 的配置文件
  • mod-gearman/etc/mod_gearman/mod_gearman_worker.conf workder 的配置文件

启动

  • 先启动 gearmand 使用 mod-gearman/etc/init.d/gearmand 这个脚本。
  • 启动 worker 使用 mod-gearman/etc/init.d/mod_gearman_worker 这个脚本。启动之后可以用 gearman_top 看到多了一些队列。
  • 重启 nagios

确认 nagios.log 里面正常加载了 gearman,然后看 gearman_top 里面开始有一些 run 的 job 了。

注意事项

基本上使用 gearman 还算是对用户透明,需要配置的东西不多,默认配置就可以跑。

一般使用 gearman 的时候都是现有 nagios 遇到瓶颈了,这个时候扩展的时候需要注意下,第一步可以在 nagios 机器上面(或者弄一台新的机器)做 gearman 的 job server,然后在 nagios 的机器上面跑一个 worker,这样基本就是 0 配置都可以跑起来,不会有任何问题。

第一步完成之后就会需要增加 worker,这个时候就要注意了,新的 worker 机器上面,需要在相同的路径下面包括所有你用到的 nagios plugin(包括自己写的,也包括这些 plugin 依赖的其他内容,比如临时文件路径,配置文件等),否则分发过来的 job 会执行不成功。

这个时候有个办法,就是把原来机器的 nagios 相关目录通过 nfs 共享给其他机器(但是得注意二进制程序是兼容的)。

另外如果需要测试一下新的 worker,也可以通过配置只让 worker 执行某些 servicegroup 或者 hostgroup 的任务。要注意这个时候需要配置 service, eventhandler, host 都为 no,然后配置 servicegroups 或者 hostgroups。

Footnotes

[1] 其实 worker 是可以被配置为只处理某些监控的,这个后面会讲。

use ivy to replace isearch

Published on:
Tags: emacs

之前习惯使用 isearch 来搜索了,最近看别人使用 ivy 看着心痒痒的,就想试试看。其实 ivy 的效果和 swoop 很像,不过区别是 ivy 是在 minibuffer 来显示可选信息的,swoop 是在一个 buffer 显示的。有洁癖的可能稍微计较一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
;; ivy swiper
(defun wd-swiper-at-point ()
"Pull next word from buffer into search string."
(interactive)
(let (query)
(with-ivy-window
(let ((tmp (symbol-at-point)))
(setq query tmp)))

(when query
(insert (format "%s" query))
))
)


(use-package ivy
:config
(ivy-mode 1)
(setq ivy-use-virtual-buffers t)
(set-variable 'ivy-on-del-error-function '(lambda()))
)


(use-package swiper
:config
(global-set-key "\C-s" 'swiper)
(define-key swiper-map (kbd "C-w") 'wd-swiper-at-point)
(define-key swiper-map (kbd "C-f") 'swiper-avy)
)

我大致做了上面几个设定,\C-s 绑定了 swiper,启动 swiper 之后用 \C-w 可以快速把光标位置的 symbol 放到 minibuffer 来搜索。然后 swiper 和 isearch 有个区别是,默认情况下,swiper 如果 minibuffer 没有内容,按 backspace 会退出,这个和 isearch 的习惯不一样,把 ivy-on-del-error-function 重新绑定一下就可以了。

ivy 默认会绑定一个快捷键是在你 minibuffer 输入一些内容之后,会出来很多匹配结果,这个时候可以按 C-' 调用 swiper-avy 方便你快速定位,也挺好用的。不过我这里不好用,不知道怎么回事,只好重新绑定了一下。

还可以在 swiper 查询阶段按 M-q 进入替换模式。

基于 ffi 的 libmcrypt 加密模块

Published on:
Tags:

openresty 提供了 luajit 的支持,luajit 又提供了 ffi 库的支持。通过 ffi 可以很方便的调用 c 库的一些方法。

我在项目里面用到了加解密,因为需要各语言支持,使用了 libmcrypt 库。之前通过 c 模块完成了在 lua 里面加密,在 perl 里面解密。有了 ffi 就可以使用纯 lua 模块搞定了。

代码写的比较丑,有使用加解密的需求的可以参考下,另外计划写其他模块的也可以参考下。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
local ffi = require 'ffi'
local ffi_new = ffi.new
local ffi_str = ffi.string
local ffi_copy = ffi.copy
local setmetatable = setmetatable

local _M = { }
local mt = { __index = _M }

ffi.cdef[[
struct CRYPT_STREAM;
typedef struct CRYPT_STREAM *MCRYPT;

MCRYPT mcrypt_module_open(char *algorithm,
char *a_directory, char *mode,
char *m_directory);

int mcrypt_generic_init(const MCRYPT td, void *key, int lenofkey,
void *IV);

int mcrypt_generic_deinit(const MCRYPT td);
int mcrypt_generic_end(const MCRYPT td);
int mdecrypt_generic(MCRYPT td, void *plaintext, int len);
int mcrypt_generic(MCRYPT td, void *plaintext, int len);
int mcrypt_module_close(MCRYPT td);

]]


local mcrypt = ffi.load('libmcrypt.so.4')

_M.new = function (self)
local cipher = 'blowfish'
local mode = 'cbc'

local c_cipher = ffi_new("char[9]", cipher)
local c_mode = ffi_new("char[4]", mode)

local td = mcrypt.mcrypt_module_open(c_cipher, nil, c_mode, nil)
return setmetatable( { _td = td }, mt )
end

_M.encrypt = function(self, key, raw)
local iv_len = 8
local td = self._td

local c_key = ffi_new("char[?]", #key+1, key)
local c_iv = ffi_new("char[9]", key)
local c_raw = ffi_new("char[?]", #raw+1, raw)

mcrypt.mcrypt_generic_init(td, c_key, #key, c_iv)
mcrypt.mcrypt_generic(td, c_raw, #raw )
mcrypt.mcrypt_generic_deinit(td)

return ffi_str(c_raw, ffi.sizeof(c_raw)-1)
end

_M.decrypt = function(self, key, raw)
local iv_len = 8
local td = self._td

local c_key = ffi_new("char[?]", #key+1, key)
local c_iv = ffi_new("char[9]", key)
local c_raw = ffi_new("char[?]", #raw+1, raw)

mcrypt.mcrypt_generic_init(td, c_key, #key, c_iv)
mcrypt.mdecrypt_generic(td, c_raw, #raw )
mcrypt.mcrypt_generic_deinit(td)

return ffi_str(c_raw, ffi.sizeof(c_raw)-1)
end

_M.close = function(self)
local td = self._td
if td then
mcrypt.mcrypt_module_close(td)
end
end

return _M

使用方法比较简单,代码里面写死了是 blowfishcbc 模式,并且 iv 使用 key 的前 8 个字符

1
2
3
4
5
6
7
8
9
10
local mcrypt = require "mcrypt"
local m = mcrypt:new()

-- 一系列加解密
local en = m:encrypt('xxx','yyyy')
...
local de = m:decrypt('xxx', yyyy')

-- 最后关闭
m:close()

postgresql transaction isolation

Published on:
Tags: postgresql

翻译自 http://www.postgresql.org/docs/current/static/transaction-iso.html, 内容没翻译全,供参考。

并发控制

13.2 事务隔离级别

SQL 标准定义了四个事务隔离级别。最严格的就是串行化(Serializable),根据标准定义,任何并发的串行化事务如果在相同的时间使用相同的顺序执行,那么需要有相同的执行结果。其他的三个隔离级别,都定义了在并发事务互相影响的情况下,在各隔离级别下不允许出现的一些现象。根据标准,这些现象都不允许出现在串行化这个级别。(这并不令人惊讶 — 如果为了事务的结果一致只允许同时运行一个事务,那怎么可能会出现因为事务互相影响产生的现象)。

在各级别禁止的一些现象如下:

脏读(dirty read)

一个事务读取到另一个并发的事务还没有提交的数据。

不可重复读(nonrepeatable read)

一个事务重复读取之前读过的数据的时候,发现数据已经被其他事务修改(就是在第一次读取之后做的提交)。

幻读(phantom read)

一个事务重新执行一个查找符合条件的数据的查询的时候,发现返回的数据因为这期间别的事务做了提交而发生了变化。

四个事务隔离级别和对应的行为在表 13-1 中进行了描述。

表 13-1 SQL 标准定义的事务隔离级别

| 事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
|——————————————+————+——————+————|
| 读未提交(read uncommitted) | 可能 | 可能 | 可能 |
| 读已提交(read committed) | 不可能 | 可能 | 可能 |
| 可重复读(repeatable read) | 不可能 | 不可能 | 可能 |
| 串行化(serializable) | 不可能 | 不可能 | 不可能 |

(译者注:这个表格是个很大的迷惑,要注意他描述的只是标准定义的,而不是 PostgreSQL 里面的情况,在 PostgreSQL 中的实际情况和上面表格标记的不一致,下面的译文里面也多次会提到。)

在 PostgreSQl 中,你可以使用任意的四个隔离级别。但是,在内部其实只有三个,分别对应到读已提交、可重复读和串行化。当使用读未提交这个级别的时候,实际上和读已提交是一样的,而幻读在 PostgresSQL 的可重复读级别是不可能出现的,所以在 PostgreSQL 中实际的隔离级别可能比你选的更加严格一点。这个是 SQL 标准允许的:标准里面的四个隔离级别只定义了哪种现象不能出现,没有定义哪种现象一定会出现。PostgreSQL 只提供了三个隔离级别,是因为这是唯一比较合理的把标准定义的隔离级别映射到多版本并发控制架构上面的办法。在下面的章节里面会详细讲解各个隔离级别。

设置事务隔离级别的语句是 set transaction。

重要提示:有些 PostgreSQL 的数据类型和函数在事务里面有特别的表现。特别的,对于序列(sequence)(以及定义为 serial 类型的列对应的序列)的修改会立刻对所有事务有效,并且在事务回滚的时候也不会被回滚。请参考 9.16 和 8.1.4

13.2.1 读已提交事务隔离级别

读已提交是 PostgreSQL 里面默认的事务隔离级别。当一个事务使用此级别的时候,一个 select 查询(不带 for update/share 字句)只可以查到当前查询开始前已经提交的数据,(在此查询执行的过程中)永远不会查到其他并发事务执行时的未提交数据和已提交的修改。事实上,一个 select 查询可以”看“到执行开始瞬间的数据库的一个快照。不过,select 查询可以”看“到当前事务里面已经完成更新的数据,即使他们还没有被提交。同时也要注意,如果有其他事务在一个事务的第一个 select 执行之后提交了修改,那么那个事务里面的前后成功的两个 select 查询也有可能得到不同的结果。

update, delete, select for update, 和 select for share 这些语句在查找目标数据的时候的时候,表现和 select 是一样的:都是只能查找到在语句执行开始的时候就已经提交的数据。然而,这些目标数据可能就已经被其他并发事务修改(或者删除、加锁(locked))了。在这种情况下,即将执行更新的事务将会等待第一个执行了更新的事务的提交或者回滚(如果他仍然在执行中)。如果第一个更新事务执行了回滚,那么它的执行结果会取消,后续的更新事务会处理所有之前查找到的数据。如果第一个更新事务执行了提交,那么后续的更新事务会忽略第一个事务删除的行,然后针对已经更新过的数据上面执行它自己的操作。语句里面的查询条件(where 语句)会重新被执行来查看已经更新的数据是否还满足条件。如果满足,那后续的更新事务会在已经更新过的数据上面执行他自己的操作。如果执行的是 select for update 和 select for share 语句,那会返回或者锁定更新后的数据给客户端。

基于以上规则,一个更新语句可能会得到一个“不稳定”的快照。它会“看”到其他并发事务对它将要更新的数据的修改,但是“看”不到其他并发事务对其他数据的修改。这个行为会导致读已提交事务隔离级别不适用于一些复杂的查询,只适用于简单的情况。例如,想象一下在事务里面更新银行账户余额:

1
2
3
4
BEGIN;
UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534;
COMMIT;

如果有两个类似的事务要更新 12345 这个帐号的余额,我们显然是希望第二个更新事务是基于第一个的结果来更新。因为每个语句都是更新既定的数据,所以只能“看”到更新的数据不会造成不一致。

在读已提交事务隔离级别里面,复杂一点的情况可能会得到预期之外的结果。例如,想象一下一个 delete 语句操作的数据被其他语句从他的限制条件里面增加或者移除。例如,假设 website 是一个包含两行数据的表,其中 website.hits 字段分别等于 9 和 10。

1
2
3
4
BEGIN;
UPDATE website SET hits = hits + 1;
-- run from another session: DELETE FROM website WHERE hits = 10;
COMMIT;

虽然在执行 update 前后,都有 website.hits = 10 的数据,但是那个 delete 语句将没有任何效果。这是因为未执行成功 update 前,9 这行数据是被 delete 忽略的,当 update 执行完毕,delete 得到锁之后,新的数据的值已经不是 10 而是 11 了,已经不再符合 delete 的条件了。

读已提交事务隔离级别在事务开始的时候会创建一个包含了在那个瞬间所有已提交的事务的数据的快照,同一个事务里面的后续语句会“看”到其他并发事务提交的数据。前面的问题的关键点是,单个语句是否可以得到一个持续一致的数据库。

读已提交事务隔离级别对于很多程序来说就已经足够了,使用起来快速简单。显然,它并不适用于所有情况。对于使用了复杂查询和更新的程序,或许需要对数据一致性要求比读已提交更加严格的事务隔离级别。

13.2.2 可重复读事务隔离级别

可重复读事务隔离级别只能“看”到在事务开始前已经提交的数据,并且永远也“看”不到未提交的或者在事务执行期间被其他并发事务更新的数据。(当然,查询语句是可以“看”到在当前事务里面的已经执行的更新的,即使他们还没有被提交。)这么做比 SQL 标准里面针对这个隔离级别的要求严格,在表 13-1 里面表述的现象都不能发生。如前面所说,这么做标准是允许的,标准只描述了各隔离级别最低的要求。

这个隔离级别不同于读已提交隔离级别,在可重复读事务隔离级别里面一个查询可以“看”到事务开始的时候的一个快照,不是当前事务里面当前查询开始的时候的快照。因此,一个事务里面成功执行的 select 语句会得到相同的结果,也就是说,他们都”看“不到在事务开始之后其他事务提交的修改。

使用这个隔离级别的应用,应该在遇到序列化失败(serialization failures)的时候准备好重试。

update, delete, select for update, 和 select for share 这些语句在查找目标数据的时候的时候,表现和 select 是一样的:都是只能查找到在事务执行开始的时候就已经提交的数据。然而,这些目标数据可能就已经被其他并发事务修改(或者删除、加锁(locked))了。在这种情况下,可重复读事务将会等待第一个执行了更新的事务的提交或者回滚(如果他仍然在执行中)。如果第一个更新事务执行了回滚,那么它的执行结果会取消,后续的更新事务会处理所有之前查找到的数据。如果第一个更新事务执行了提交(真的对数据执行了更新或者删除,不是只是加了锁),那么可重复读事务会执行回滚,并且报错

1
ERROR:  could not serialize access due to concurrent update

因为可重复读事务在事务开始后不能对被其他事务做了修改的数据做修改或者锁定。

当应用程序得到这个错误信息的时候,应该立刻中止当前事务,重新从事务开始再次执行。再次执行的时候,这个事务就可以”看“到之前被其他事务提交的修改了,所以使用新版本数据作为新事务的起点的时候,就不会再有逻辑冲突了。

要注意只有有更新操作的事务才需要重试,只读的事务是永远不会有序列化冲突的。

读已提交事务隔离机制严格保证了每个事务都得到一个完整稳定的数据库快照。However, this view will not necessarily always be consistent with some serial (one at a time) execution of concurrent transactions of the same level. For example, even a read only transaction at this level may see a control record updated to show that a batch has been completed but not see one of the detail records which is logically part of the batch because it read an earlier revision of the control record. Attempts to enforce business rules by transactions running at this isolation level are not likely to work correctly without careful use of explicit locks to block conflicting transactions.

提示:在 PostgreSQL 9.1 以前,序列化事务隔离级别的情况和前面描述的信息是一样的。如果需要以前的序列化事务隔离级别,那可以使用现在的可重复读隔离级别。

13.2.3 序列化隔离级别

序列化事务隔离级别是最严格的事务隔离级别。这个级别把所有已经提交的事务模为拟序列化执行,就像是事务执行完一个执行另一个,顺序的,而不是并发的。当然,类似于可重复读事务隔离级别,应用程序在序列化失败的情况下必须要准备好重试。事实上,这个级别和可重复读事务隔离级别是一模一样的,除了会监控 except that it monitors for conditions which could make execution of a concurrent set of serializable transactions behave in a manner inconsistent with all possible serial (one at a time) executions of those transactions. This monitoring does not introduce any blocking beyond that present in repeatable read, but there is some overhead to the monitoring, and detection of the conditions which could cause a serialization anomaly will trigger a serialization failure.

举个例子,比如有表 mytab,初始的时候如下:

1
2
3
4
5
6
 class | value
-------+-------

1 | 10
1 | 20
2 | 100
2 | 200

假设在序列化事务 A 里面执行:

1
SELECT SUM(value) FROM mytab WHERE class = 1;

然后把结果(30)作为 value 字段,class = 2 作为新行插入回去。同时,序列化事务 B 里面执行:

1
SELECT SUM(value) FROM mytab WHERE class = 2;

得到结果 300,并把结果和 class = 1 作为新行插入回去。然后两个事务都尝试提交。如果任意事务执行在可重复读级别,那么两个事务都可以提交; but since there is no serial order of execution consistent with the result, 使用序列化事务隔离级别的话,会允许其中一个事务提交,而回滚另一个事务,并且报如下错误信息:

1
ERROR:  could not serialize access due to read/write dependencies among transactions

这是因为如果 A 在 B 之前执行的话,B 的计算结果就会是 330 而不是 300,类似的,其他顺序会导致 A 得到不同的结果。