豪翔天下

Change My World by Program

0%

LEMP: Linux, Nginx, MySQL, PHP环境

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
# 依赖安装
yum install epel-release gcc automake autoconf libtool make gcc gcc-c++ glibc-y

# MySQL
yum install mysql-server -y
/etc/init.d/mysqld restart
/usr/bin/mysql_secure_installation # 初始化设置数据库

# Nginx
yum install nginx -y
/etc/init.d/nginx start

# PHP
add-apt-repository universe && apt-get install php-fpm php-mysql # for ubuntu
yum install php-fpm php-mysql # for centos
vim /etc/php.ini,将cgi.fix_pathinfo=1改为cgi.fix_pathinfo=0
vim /etc/php-fpm.d/www.conf将apache用户更改为nginx用户
user = nginx
group = nginx

service php-fpm restart # 如果php-fpm: unrecognized service,可以在/usr/lib/systemd/system/目录下看具体的服务名
service nginx restart

# 开机启动
systemctl enable mysqld
systemctl enable php-fpm
systemctl enable nginx

# 老版本使用这个命令
chkconfig --levels 235 mysqld on
chkconfig --levels 235 nginx on
chkconfig --levels 235 php-fpm on

nginx配置php-fpm

  • 如果发现9000端口没有启动那么php-fpm应该是以socket的方式启动的
1
2
3
4
5
6
7
8
location ~ \.php$ {    
fastcgi_pass 127.0.0.1:9000; # 端口方式
fastcgi_pass unix:/run/php/php7.0-fpm.sock; # socket方式

fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

参考理解OAuth 2.0

目前网络上的数据传输普遍基于HTTP/HTTPS,然而,他们都是无状态的协议,也就是说你本次的请求与下一次的请求是毫无关系的,为了使得我们能够保存用户的状态,于是诞生了cookie,诞生了session,诞生了token,当然,这三者的概念其实都差不多,总体思想就是,在第一次请求过后,给用户生成唯一的一个标识,用户在下一次请求时,携带这个标识,这样服务端就能通过它来判断用户的状态以及合法性。

注意: OAuth确实增加了安全性,但是也增加了应用的复杂性,对于安全要求没那么高的应用,依然可以使用简单的加密算法进行双向加解密认证。

OAuth

OAuth主要用于社会化登录,仅仅需要一个系统来存储用户的信息,其他的系统或者第三方系统均可以使用,其他系统不需要维护自己的用户系统,免去了用户注册账号的麻烦,并且用户的密码等重要信息都集中保存在信任方,提高了安全性。

根据阮一峰的博客,客户端获取授权有以下几种认证方式:

  1. 授权码模式(authorization code): 是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与”服务提供商”的认证服务器进行互动。
  2. 简化模式(implicit): 不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了”授权码”这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
  3. 密码模式(resource owner password credentials):用户向客户端提供自己的用户名和密码。客户端使用这些信息,向”服务商提供商”索要授权。
  4. 客户端模式(client credentials):指客户端以自己的名义,而不是以用户的名义,向”服务提供商”进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求”服务提供商”提供服务,其实不存在授权问题。

举个例子
具体流程(感觉不用图也能说清楚呀):

  1. 第三方站点获取未授权的Request Token
  2. 获取用户授权的Request Token
  3. 用授权的Request Token换取Access Token
  4. 通过Access Token访问该用户的资源

另外,对于客户端的每一次获取token的请求,都对应着一定的权限,比如微博的第三方认证,通常仅有获取用户基本信息的权限,或者如github的权限就更加详细一点,包括获取基本信息,公开库的访问权限和私有库的访问权限(github的认证请求权限使用的是X-OAuth-Scopes作为参数放在请求头部,例如user,表示能获取用户的所有信息,而user.email则表示只能获取用户的邮箱)。

对于第三方应用,仅需要维护token的值,一般来说,有一种用户基本信息表,多张token存储表对应着多个公开应用。例如接入了微博的第三方可以有这么一张表: (id, user_id, weibo_id, weibo_access_token, weibo_expires)

注: 社会化登录的回调地址(callback_url)与返回地址(next)的区别,回调地址指用户点击第三方登录过后,由资源服务器回调到的地址,而返回地址则是用户欲访问的地址,一般为应用自己的首页,但也有可能是用户直接知道url后想去的地址,这时候可以在请求的时候带上next参数,然后由资源服务器原样返回过去。

最后,几乎所有关于OAuth的文章都会遗漏的一点,那就是资源服务器和认证服务器之间的认证。当访问者获取了token过后,向资源服务器发送请求时,资源服务器肯定会向认证服务器验证这个token的合法性,这也是一个很危险的步骤,在实际项目中,我将token与访问者的appkey一起进行认证,并且作为认证服务器同样要对资源服务器的认证请求一起认证。具体可参考stackoverflow

SSO(单点登录)

解决的是跨域的用户校验问题,常用于一个公司内部有多个子网站,然后提供统一的登录,一个站点登录过后其他站点不用重复登录。多用于存在多个子系统的公司。
具体流程:

  1. 用户请求任意一个子站的login页面
  2. 用户输入用户名密码,子站将它传递给认证中心SSO Server
  3. SSO Server验证成功后

虽然SSO和OAuth是不一样的东西,但是我认为SSO只是OAuth的一种简化模式,可以归为OAuth的一类。通常由于子系统的域名不一样,不方便设置各自的cookie,于是这里又有两种解决方案:

  1. 使用公共加解密函数进行双向加解密(加密字符串直接放在GET请求中)
  2. 同样使用公共的加解密函数,但是是使用JSOPN解决跨域问题(用户登录子应用时,会带上父应用域名下的cookie访问父应用提供的JSONP接口,父应用验证登录状态,最后返回加密后的用户信息)

例如,本人在一个项目中所使用的认证流程:

图片

签名算法的设计

无论是OAuth或是SSO都涉及到认证的过程,一般都不推荐使用可逆的加密算法,而使用单向的加密算法,只要双方对数据进行加密后的结果一致就表示该请求是合法的。参考网上很多的签名算法,最后总结了一下使用最广也是最安全的一种签名算法步骤:

  1. 将请求的参数转换为’key=value’形式的字符串,如: “k1=v1”, “k2=v2”
  2. 将格式化后的字符串以字典升序进行排序,然后拼接在一起,如: “k1=v1k2=v2”
  3. 在拼接后的字符串的前后加上Secret Key,如:”keyk1=v1k2=v2key”
  4. 对拼接后的字符串进行MD5加密得到最终的结果

Python实现

1
2
3
4
5
6
7
8
9
10
import hashlib
def apiSign(parameters, secret):
keys = list(parameters.keys())
keys.sort()

parameters_str = "%s%s%s" % (
secret,
str().join('%s%s' % (key, parameters[key]) for key in keys),
secret)
return hashlib.md5(parameters_str.encode('utf8')).hexdigest()j

PHP实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function apiSign($parameters, $secret)
{
$str = '';

if (gettype($parameters) === 'array') {
ksort($parameters);
foreach ($parameters as $k => $v) {
$str .= $k.$v;
}
} elseif (gettype($parameters) === 'string') {
$str = $parameters;
} else {
return false;
}
$str = $secret.$str.$secret;

return md5($str);
}

Java实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static String apiSign(List<NameValuePair> nvps) throws NoSuchAlgorithmException, IOException {
List<String> nvs = Lists.newArrayList();
for (NameValuePair nvp: nvps) {
boolean noSignValue = nvp == null || nvp.getValue() == null ||
SIGN_EXCEPTIONS.contains(nvp.getName());
if (noSignValue) continue;
nvs.add(String.format("%s=%s", nvp.getName(), nvp.getValue()));
}
Collections.sort(nvs);
StringBuilder sb = new StringBuilder();
for (String nv: nvs) sb.append(String.format("%s", nv));
String encodeSource = String.format("%s%s", sb.toString(), APP_SECRET);
return MD5Util.MD5Encode(encodeSource, ENCRYPT_CHARSET).toLowerCase();
}

travis-ci

与github紧密联系的自动化持续集成工具。需要注意的是它仅是一个测试工具,并不能代替webhook的功能。

.travis.yml文件

travis.yml是travis-ci的配置文件,具体语法如下:

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
sudo: required		# 是否需要sudo权限
dist: trusty # 目标操作系统,这是ubutnu 14.04

notifications: # 发送构建结果的通知
email:
- haoflynet@gmail.com

addons:
apt:
sources: # 源,但只能是白名单内的https://github.com/travis-ci/apt-source-whitelist/blob/master/ubuntu.json
- google-chrome
packages:
- google-chrome-stable

branches: # 指定需要跑travis-ci的分支
only:
- master

language: node_js # 项目所使用的语言
node_js: # 该语言所运行的版本,可多个版本同时测试
'4'
'6'

# 下面几条指令会按顺序执行
before_install:
- nvm install 6.0.0

install: npm install

before_script: # 在开始测试前需要运行的指令,也可在这里执行sudo apt-get 安装一些基础trusty没有的包,当然,安装包可以直接使用addons的apt
- npm install
- cd tests

script: phpunit -v # 执行脚本

after_failure:
- cat /home/travis/build/haoflynet/haoflynet-repo/abc.log

after_success:
- ...

before_deploy: # 一些部署操作,比如生成APK包
- ...

deploy:
provider: script
script: ci/deploy.sh
skip_cleanup: true

after_deploy: # 发布的一些操作,比如将包发布到fir.im上去
- ...

说实话,李笑来的这本《把时间当作朋友》确实绕过去绕过来,把我饶得有点晕了,但是书中的主要观点我还是非常认同的。把时间当作朋友。从来都只听过如何去管理时间,却从来没听说过要去和时间做朋友。上帝给每个人的时间都是一样的,不是说你想怎么管理就怎么管理,从小学到大学到工作,每次考试前你不都有很多的复习计划,每次假期前不都有很多的出游计划吗,但是,有多少我们真正实现过呢。很多时候计划越详细,我们反而越不会执行。为什么?因为我们不了解时间,也不了解自己。就像你想和一个人做朋友一样,你们都不互相了解,非要在一起,那也是互相折磨而已。所以,要想与时间和睦相处,首先是要了解自己,自己是一个怎么样的人,自己喜欢做什么样的事情,什么样的事情会让自己有动力去做,到底为什么而活?

没有人不懒惰,没有人不爱做计划,只是那些成功的人普遍都是做了计划就立马行动的,这真的真的是我目前为止发现的最大的缺点。想太多,做得少。基本上从高中就开始意识到这个缺点了,也一直在改,在改的过程中又发现了另一个缺点,那就是只要我心里有一块石头,就不能放下它去做其他的事情了。我在外面实习都快一年了,这一年里感觉还没我大一一个暑假成长得快,我知道自己遇到了一个瓶颈,也做了很多的计划去冲破它,但就是碍于学校里数不清的杂事,一直没放心大胆地去做。最后导致两边都互相拖着,然后两边都没做好。其实专注于一件事没有什么不好,但是心中担心一件事情,就直接把那件事情做了就好呀,这有什么好犹豫的呢,有什么好纠结的呢。那件事情制约着我,我干嘛就不把他做了呢,一定要拖到最后。不过还好,毕业答辩做了充足的准备,还是顺利通过了。在昨天那个不存在的日子里进行的答辩,而且在答辩前几天我就已经开始我的全栈实践之路了。马上进入社会,希望能真的和时间做好朋友,然后和它并肩作战。

语录

当你把时间花在一个人身上的时候,相当于在他的身上倾注了你生命的一段,不管最终结果如何,反正,那个人、那件事都成了你生命的一部分——不管最后你是喜欢还是不喜欢。

一个人遗忘痛苦的能力特别强的一个具体表现就是,这个人会很轻易地原谅自己。

往往并不是有兴趣才能做好,而是做好了才有兴趣。

浪费生命、虚度年华的人,有个共同的特征——他们拼命想控制自己完全不能控制的,却在自己真正能掌控的地方彻底失控。

当我们不停地鼓励所有人的时候,最大的受益者其实是我们自己,因为最终我们会发现,自己开始进入一种他人无法想象的状态,成为一个不需要他人鼓励的人。

心智真正成熟的人在一些情况下能够做到无须亲自经历,仅凭思考就得到深刻的体会。

所谓的幸运,就是当你准备好了的时候,机会来了。

你比别人强一点根本没用,真正有用的是你比别人强很多很多。

学东西前不用定义一定要有用——别觉得知识现在没用就拒绝去学它。

人是没办法管理时间的,时间也不听从任何人的管理,它只会自顾自一如既往地流逝。”管理时间”只不过是人们的一厢情愿而已。

这些人其实并不是对自己正在做的事情没有兴趣,而是没有能力把目前正在做的事情做好。

本科教育之”本”在于培养学生的自学能力。从理论上讲,一个人本科毕业之后,应该有能力自学他所需要的任何知识。

今天,我顺利地完成了人生第一次的迷你马拉松。

其实在去年,我就最高跑过七公里,当时因为心情失落,所以闷着头跑,一直跑到手脚发麻才停下来。开始实习过后,我就几乎没有跑过步了。在上个公司是下班过后尽量提前几个站下车,然后走着回家。来了现在公司过后好一点,每天能够骑自行车上下班了。所以从体力上我还是觉得自己可以的。不过为了保险起见,在前天我去了石子山体育公园试跑了一下,感觉还不错,只不过跑完了过后腰酸背疼的。直到几天都还在痛。

今天六点钟,我们运维团的几个朋友就起床了,其实是听错了时间,以为8点开跑,结果是8点集合,10点开跑。到那儿过后天一直飘着雨,不过南滨路挨着嘉陵江,风景真的好,更好看的是…如图所示
图片
运维取经团的几个朋友:
图片
刚开始跑时,我突然感觉自己脚有点抽筋,于是放慢了一下脚步,之后才渐渐地匀速前进,但是主办方的路标是在是坑人,按常理来说应该是一公里一个提示,结果,它一个公里到两公里中间有一个提示,但是却没提示这时候的距离,只能让人以为这是1.5公里了,并且在折返点离2.5公里的路标是在太远,所以我们预计,总的里程大概在六公里多。折返跑有一个好处是,你能看到最前面的几个人,也能看到最后面的一群人。最后的一百米我还小小的冲刺了一下。其实我每次跑步最后都能够有冲刺的力气,但就是在前面无法提速,每次都是那样的速度,下次比赛我一定要克服这个问题,争取跑进半小时。今天的成绩大概在三十五六分钟。看看中途的我(白衣服短裤那个):
图片

总之,总体感觉真的很爽,虽然这次的比赛都是给公司的员工参加的,也就是说大家尽量在宣传自己的公司,不过,好多好多的人还是跑了个开心,run for fun,这才是比赛的宗旨。仔细想想自己为什么喜欢跑步呢,我想应该是因为跑步这个运动项目,每个人到达终点的距离一样,每个人的路线也都一样,只要坚持就一定可以到达终点,无论名词高低,到达终点后你都会如释重负,心胸特别舒畅。不像其他的运动项目,有很多的技巧、必须经过很多的练习或者场地总是有所限制。

随着马拉松的落幕,我的毕业论文也经过了查重,周末就上传毕业论文了,然后下周就答辩了,希望一切顺利,然后,就可以开始我的全栈工程师实践之旅。

爱情是甜美的?爱情是苦涩的?花花世界,谁又能说得清呢。

《我依然爱你,我只是不喜欢你了》这本书是无意间在以前一位喜欢过的女生的一篇微博看到的,光看标题就有一种台湾的那种小清新爱情片的味道,看了一下简洁,是讲爱情的不是讲分手的爱情的,所以果断入手,毕竟和女朋友还处在热恋期,不知道为什么,我们俩热恋期咋这么长呢。

全书是由几十个小小的爱情故事汇聚而成,不知道真的是作者身边朋友的经历还是有很多朋友主动向作者倾诉的呢,我也好希望我身边能有一位情感专家,有什么心里问题她都能迎刃而解,什么心里话都能对她说,但是至今我的朋友圈也没出现过这样的人。作为书评,在这里我并不打算把我对爱情的理解写出来,因为也许可能大概我也写不出来,有些东西,在心里,就够了,是万万不能用我那笨拙的文字功底写出来的。另外,看了看豆瓣上的评分,其实还算不错,但是下面的有些评论我就不大赞成了。平淡、口水文,对于爱情来说,这样子有错吗?如果真的觉得平淡就觉得这是一本烂书,那我是该说你们不懂文学,还是该说你们不懂爱情呢?至少在我,一看到这个书名,就觉得这就是一本平平淡淡的书,就是一本贴近生活的书。这本书也绝不是一本只讲故事不走心的故事集,应该说它是很走心的,谁都可以深陷进那一个个的故事,也谁都可以从中体会到什么。

小井岩(作者)是个温柔的人,只有温柔的人,人们才会想他倾诉吧。很多时候,我也想做一个温柔的男朋友。虽然我比我女朋友小,我也很幼稚。和她刚在一起的时候就觉得她是那种很有气质型的,没想到,现在已经被我”调教“成一个每天都会向我撒娇的可爱女孩子了,虽然我也每天撒娇。她很好,我是她第五个男朋友,她是我第三个女朋友,我们谁都不知道会不会一直走下去,但又一直都朝着一直走下去的目标努力着。不仅我们过去爱的是人渣还是真的错过,过去的都已经翻篇了,就像我最开始说的以前喜欢过的那个女生,我现在女朋友也知道我以前喜欢过她,但是女朋友并不会阻止我和她聊天,说了半天,我到底想说什么呢,我也不知道。反正,米娜,有爱到来的时候就好好爱吧,不要刻意去回避,也不要刻意去追逐,一切随缘,不矫揉造作的爱,才是最真最纯的爱。(不要问我为什么不多谈谈书中的内容,因为看了大半年了吧,已经忘记了,只能从kindle里的笔记中找一点点当时的体会)

语录

我知道我已离开你的世界,也知道,彼此不再有交集,然而心中某块地方,始终无法抹去存在的痕迹。我依然爱你,我只是知道,自己不能再像以前那样喜欢你了。 依然祝你,平安幸福。

“你知道年轻的时候穷最大的问题是什么吗?”
“什么?”
“以为所有得不到的一切都是穷的错。眼睛盯着遥远的地方看不到身边一朵花的美丽。”

对那时的我来说,爱情是什么并不重要,就像我知道苹果就是苹果,而不需要知道它是蔷薇科还是落叶乔木;就像当我遇见你,我就知道遇见了爱情。

最温暖的相处模式,就是两个人在一起的时候,有一种自然而然的舒适氛围,能够消解心中的那些戾气,恢复成最放松而且淡然的自己。没有强烈的惴惴不安,也没有莫名的看不顺眼,没有所有那些消耗神经的累的东西。只要我知道你在,一切就都很好。

真的有些人,让你相信人是有上辈子的,不然为何一相见就可以自然而然跳过试探、了解、熟悉的人际程序直接成为那个直达灵魂的亲密之人。

做个称职的前任吧,不纠缠,不打扰,不撩拨。心里那些残存的想念,是怀念和感激。放下心中那多愁善感的郁金香,才会开出畅然伸展的蓝莲花。

我觉得真正爱一个人的表现肯定是努力使自己和对方变得更好,怎么会是拉着对方一起下沉呢?

我不认为一辈子只爱一个人是件可惜的事,也不认为一辈子爱过很多人就是一件不道德的事。

秒速五厘米间,樱花蹁跹了看客;人生聚散离合,伊人明媚了岁月。

如果我们费尽所有心力,都不能在那个心爱的心上留下一个深一点的痕迹,这不是对方的错,也不是你的错。因为不是你不努力,而是对方跑得太快,飞得太高。

感动不是爱情,爱情根本不需要这么累,你追我跑,那是狩猎,我又不是猎物!

最后献上知乎的几位朋友对”我爱你,我只是不再喜欢你了”的解释

《One Day》里安妮海瑟薇的一句台词 我无法控制自己对你的难以忘怀 可是我关于你的一切已经再也没有了期待。 ——Pengsiya
我会想起我们的过去,却不会再去想我们的未来。 ——兵荒马乱
“不介意孤独,比爱你舒服” ——江白粥
我还是曾经的那个我,你已经不是曾经的那个你了。 —— 鬼木知

最近真的太忙,但是再忙我也强迫自己抽出一些碎片时间来阅读,所以写书评也是必要的,因为,一本书不写书评,我根本就看不下去另一本书。对了,其实前几天还看过一本书叫《程序员的数学思维修炼》,这本书几乎是直接翻过去的,因为里面的内容基本上是给没上过大学的人看的,所以也就没有写书评的必要了。

《暗时间》是本与效率有很大关系的书,有很多套路,但是也不乏新奇之处,很多章节虽然啰嗦而且段落太长(不知道是不是我kindle显示的原因),但是书中对于每一个结论的得出,都是经过了层层推敲,循序渐进地引导我们进入作者的思维中去。而不是如其他效率书一样,上来就是一个结论,然后教我们步骤。整体感觉上,这本书还是挺不错的,可以给四分。对于“暗时间”的概念,我是觉得作者在这里并不是标题党,确实作者的“暗时间”并不仅仅是指传统意义上的碎片时间。

更深层次地讲,此书,并不是完全讲时间管理,而更多的是一种引导,引导我们认知、学习,文中提到很多关于学习的方法以及很多学习不好的方法对我来说都受益匪浅。对于时间管理,我现在使用的是番茄工作法,基本上解决了碎片时间的利用率问题和工作休息切换的问题;但另一方面,天秤座最主要的特点,犹豫不决,我在这一点上依然会浪费很多的时间,而且总是给自己和身边的人造成困扰。我想或许经历得更多,我就能有更多的果断和从容,但是又该怎样在有限的时间里比普通人经历更多呢?

书中关于学习算法那个章节我的印象是十分深刻的,因为我也曾经参加过ACM比赛,也曾经疯狂地刷题,虽然最终放弃了,但是那段一心钻研算法的日子真心值得回忆。看了该书,我仿佛茅塞顿开,仔细想想,曾经的很多算法书,特别是我们的教材上面讲算法,包括数学的公式证明,在引导我们的中途几乎都会突然来一个转折,“不妨在这里…”这样的坑我以前也踩过很多次,特别是在啃那部《算法导论》的时候,当时我就一直想弄明白,这里凭什么就突然要这么做了。事实上,正如作者所说好多的书籍都弱化了思考的这一环节,事实上,我猜,那些书的作者可能也没理解到这样转折的理由,如果真要把思考的过程全部加起来,那么随便一个K(kan)M(mao)P(pian)算法也足矣成一本书了。在刷OJ的那几个月里,我记住了很多算法,也记不住很多算法,但现在仔细想来,基本上能记住的算法,我都能清清楚楚地知道那个算法是怎么演变而来的以及为什么要用那个算法和什么时候用那个算法。

书中还提到几个日常中非常实用的思维训练以及时间管理方法:

  • 设置自己的进度条,将目标分割成一个个里程碑,再讲里程碑分割为一个个TODO list列表
  • 如果有什么难题困扰你了,那就在睡觉前一直想,你永远不知道大脑在你睡着的时候会怎么奇迹般地解决这个问题。这点相信很多人都是有实际体会的吧。
  • 走路睡觉吃饭都可以思考,当你完完全全陷入一个问题的时候你就会知道你突然多了好多的时间
  • 时常反省和注意自己的思维过程。尤其是当遇到无法理解或解决的问题之后,最需要把原先的思维过程回顾一遍,看看到底是哪个过程被阻塞住了妨碍了理解。

后记:写完了整个书评,突然觉得这本书比我之前认为的还要有意义得多,我现在可以给它五星了。

语录

你会在这本书当中看到的一个重复出现的现象就是自学,大规模的自学,逃课自学,上网找书自学,程序员行业是最适合自学的行业,网络是程序员的天堂,需要的资源、工具,比课堂上的多出何止百倍,如果说还有一个学科,并不需要传统的教育就可以成才,估计非程序员莫属了。作为程序员如果没有查过wikipedia,没有看过几本原版电子书,没有在国内外主要邮件列表里面提过问题吵过架,没有用技术博客记录学习的独特体会,没有订阅技术牛人们的博客,怎么好意思说身在这个行业呢?

利用走路和吃饭的时候思考,还有睡觉前必然要弄一个问题放在脑子里面,在思考中迷糊入睡。发现这样一来往往在不知不觉中多出来大量的思考时间。将思考成为习惯还有一个很大的好处–避免焦虑。

尽量避免琐事骚扰,不重要的事情能不做就不做。有时候,紧急的事情往往只是当事人觉得必须马上做完才显得紧急或者干脆就是紧他人之急,最糟糕的就是纯属性格上原因觉得每件事情都得第一时间完成,很多看上去紧急的事情实际上并不是真的”不能再拖了”,有的干脆就并不需要或值得去做。有很多事情都是可以先放一放甚至完全let go的,否则的话就整天被所谓”紧急”的事情牵着鼻子走了。

有一句话说:看一个人,只要看他读的书和见的人。还是很有道理的,这两者是一个人成长中最有价值的信息来源。

一个你不明白其证明的定理在我看来比不知道这个定理还要糟糕,因它给你造成一种懂了的错觉。与看定理必看证明类似,看一个问题的解法,必然要看解法所诞生的过程,背后是否隐藏着更具一般性的解决问题的思路和原则。否则一个解法就只是一个问题的解法,跟背口诀一样。即,知道了算法是怎样一步步被推导出来的,我们就一下拥有了大量的记忆提取线索:对算法发现过程中的任何一个关键步骤(尤其是本质)的回忆都可能使我们能够自己动手推导出剩余的内容

网络监听原理

无线网卡的监听模式

  • 托管模式(Managed mode): 在这个模式下,无线网卡只专注于接受从WAP发给自己的数据包文。
  • 监听模式(Monitor mode): 无线网卡会监听空气中所有的无线通信。不同的无线网卡启用监听模式的方式可能不相同。

表达式语法

  • IP过滤

    ip.src == 192.168.1.1 # 过滤源地址

     ip.dst == 192.168.1.1 # 过滤目的地址
     ip.addr == 192.168.1.1 # 过滤源或者目的地址
     !(ip.src == 192.168.1.1) # 排除某地址
     http.request.uri matches "login" # 过滤url中含有login的http请求
    
  • 端口过滤
    tcp.port == 80

  • 协议过滤
    http # 只显示http协议

  • 内容过滤
    udp.length > 20

     http.content_length > 20
    

HTTPS

https://imququ.com/post/http2-traffic-in-wireshark.html

在做毕业设计的时候,由于后端有一个耗时任务,所以想到了异步,又由于长期使用Python,进而想到了Tornado,然后,我就半个月没做毕设了,说来全是坑啊。在了解异步与阻塞的原理之前我就盲目地想从代码层面去实现,这样只会浪费时间。所以这里我就先描述一下我对这几个概念的理解。

异步与同步:是消息通信机制的层面

采用异步的时候,程序并不关心该操作的结果,所以并不会有返回结果,比如ajax,一般会给异步操作赋予一个回调函数,通过这个回调函数对结果进行处理,而不是直接将结果返回给外部(在ajax如果return结果则会是一个null值)

阻塞与非阻塞:指程序在等待调用结果时的状态

如果是阻塞,则程序会一直等待程序返回结果,如果是非阻塞,则不会等待,而继续执行下面或者其他的代码了。

阻塞式IO

耗时型任务一般分为两类:CPU耗时型任务和IO耗时型任务。CPU指一般的代码运算执行过程,IO一般分为两大类,计算型IO和阻塞式IO。如果仅有一个线程,那么同一时刻只能有一个任务在计算,但如果是阻塞式IO,它可以让它先阻塞掉,然后去计算其他的任务,等到内核告诉程序那边没有被阻塞了就、再回到之前的地方进行之后的运算。

所以,在了解了这些概念过后,我就知道了为什么要发挥tornado的异步特性就得依赖异步库(Tornado官方提供的第三方异步库),而不是随便一行代码都能变成异步非阻塞式的代码。比如我试验时使用的一个sleep函数:

1
2
3
4
5
def sleep(self):
for i in range(100000000):
if i % 100000 == 0:
print(i)
self.set_cookie('setting', 'hao')

看吧,这是一个计算型任务,由于tornado是单进程单线程,所以无论怎么做也不可能实现在访问该请求的时候访问其他请求,因为CPU只能执行当前任务,其他请求必须等到这个请求结束后才能成功,这也是为什么部署tornado的时候几乎都是用nginx+多实例事实上,同理,其他的框架基本上都是需要nginx、apache等配合才能同时服务于多个请求的。Tornado的异步库,几乎都是用来进行阻塞式IO任务的,所以只有他们才能发挥其异步特性。

Tornado的异步实现就是将当前请求的协程暂停,等待其返回结果,在等待的过程中当前请求不能继续往下执行,但是如果有其他请求(同样是一个协程),只要不也是阻塞式IO,那么就会直接去处理其他的请求了。

当然,包括nodejs的异步等,这些统统都是有历史原因的,JavaScript和Python在发展之初都只支持单进程单线程,即使使用多线程技术最多也只能利用到100%的单核,多核在这里似乎并不使用,而如果要使用多进程变成,光靠框架是做不到的,必须自己根据实际需求来处理多进程之前的数据共享、资源竞争等问题。所以,在我的理解里,如果能直接利用多线程编程就不需要用服务端异步,毕竟,多线程的发明本身也是为了解决阻塞式IO的问题。

多线程实现异步、非阻塞、并行请求、并行计算

其实我认为多线程相比于异步非阻塞有很大的优点,不可否认,多线程在线程切换上存在开销,并且在资源竞争上需要写更多的逻辑,稍微控制不好就会导致服务出错,然而,多线程在处理并行任务上有先天的优势,这一点光看名字就看得出来,下面介绍Tornado的多线程和Flask多线程的用法,其中Tornado的多线程是指由程序将当前请求中的代码交由其他线程处理,而flask的多线程就是类似apache服务器,另起一个进程来处理请求。

注意:两者都可以使用global来引用全局变量

Tornado实现:当前请求会立马返回一个结果并断开当前http连接,所以不能在这里设置cookie

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
import tornado.ioloop
import tornado.web
import time
from concurrent.futures import ThreadPoolExecutor
import tornado.httpclient
from tornado.concurrent import run_on_executor

class Executor(ThreadPoolExecutor):
_instance = None

def __new__(cls, *args, **kwargs):
if not getattr(cls, '_instance', None):
cls._instance = ThreadPoolExecutor(max_workers=10)
return cls._instance

class SleepHandler(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(10)

def get(self):
tornado.ioloop.IOLoop.instance().add_callback(self.sleep) # 相当于丢到下一个时间循环去
self.write("when i sleep") # 请求会立马返回这个值并断开连接

@run_on_executor
def sleep(self):
for i in range(100000000):
if i % 100000 == 0:
print(i)
self.set_cookie('username', 'hao')

print("yes")
return 5

class TestHandler(tornado.web.RequestHandler):
def get(self):
if not self.get_cookie('username'):
self.write('没有')
else:
self.write('有')

application = tornado.web.Application([
(r"/test", TestHandler),
(r"/sleep", SleepHandler),
], debug=True)

if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()

flask实现:直接在启动时添加参数,当前请求不会立马返回一个返回值,会一直处于连接状态,所以可以设置cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask,request,make_response
app = Flask(__name__)

@app.route('/')
def hello_world():
username = request.cookies.get('username')
if username is None:
return '没有'
else:
return '有'
return username

@app.route('/s')
def sleep():
for i in range(100000000):
if i % 100000 == 0:
print(i)
resp = make_response('ok')
resp.set_cookie('username', 'the username')
return resp

if __name__ == '__main__':
app.run(debug=True, threaded=True)

参考文章
知乎:怎样理解阻塞非阻塞与同步异步的区别?
Tornado文档:异步非阻塞I/O
Tornado教程:异步Web服务
使用tornado的coroutine进行编程
我在segmentfault的提问:无法理解tornado的异步

注意,Redis是单线程的,运行耗时任务时,会阻塞,导致不能响应其他的请求(对于耗时大的删除任务, Redis4.0提供lazy free功能)。

配置文件

如果是线上,可以用rename机制禁用一些命令例如keys、flushall、flushdb

在redis shell外部,可以通过命令行的方式获取或者设置一些配置,例如:

1
redis-cli config set notify-keyspace-events KEA  # 可以直接设置notify-keyspace-events的信息

常用配置:

1
2
3
4
5
6
hz = 10  # 表示每秒检查过期键的次数
save 900 1 # 数据写入硬盘的频率,表示如果900秒内有1个key发生变化就写入一次,300秒内有10个key发生变化写入一次,60秒内10000个key发生变化写入一次
save 300 10
save 60 10000
bind 127.0.0.1 # 绑定IP,表示只有该IP能够连接到该redis,默认所有IP均能连
requirepass 密码 # 访问需要密码

内存满了的策略

redis使用的内存超过maxmemory参数的时候,maxmemory-policy这个策略就开始生效了。默认是noeviction(不删除键,只返回错误),可以设置其他的策略如下(LRU算法及最近最少使用算法):

1
2
3
4
5
6
volatile-lru: 使用LRU算法删除一个键(只对设置了生存时间的键)
volallkeys-lru 使用LRU算法删除一个键
volatile-random 随机删除一个键(只对设置了生存时间的键)
allkeys-random 随机删除一个键
volatile-ttl 删除生存时间最近的一个键
noeviction 不删除键,只返回错误

查看信息

info命令可以查看redis的所有信息,常用的字段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Memory	内存信息
used_memory_human: 就是当前Redis所使用的内存
used_memory_peak_human:Redis的内存消耗峰值

# Keyspace
db0:keys=100,expires=1,avg_ttl=7298 # 数据库0,设置了生存时间的key有1个,平均过期时间是7298ms

# stats 记录一般的统计信息
total_connections_received:服务器已经接受的连接请求数量
total_commands_processed:服务器已经执行的命令数量
instantaneous_ops_per_sec:服务器每秒钟执行的命令数量
rejected_connections:因为最大客户端数量限制而被拒绝的连接请求数量
expired_keys:因为过期而呗自动删除的数据库建数量

常用操作

系统

1
2
3
4
5
6
# redis-cli
redis-cli -h 127.0.0.1 -p 6379 -a password # 连接服务端
client list # 列出所有的客户端连接,客户端的IP
client kill 127.0.0.1:44444 # 断开某个连接

# redis-cli --bigkeys 查看redis中非常大的key

通用

  • 严禁在生产环境使用keys *进行搜索,因为这表命令会引发Redis锁,导致其他查询不可用,如果真有类似业务,可以使用scan命令
  • setnx常用于分布式锁,建议将value设置为一个随机字符串,而不是无意义的”OK”啥的,因为这样在释放锁的时候可以验证一下是否释放的是正确的锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SETNX	key value	# 将键key的值设置为value,如果key不存在则set成功返回1,如果key存在,则设置不成功返回0,常用与锁中
SET key value EX 10 PX 10000 NX# 原始SET命令后面其实也可以跟很多参数的,EX表示多少秒后过期,PX表示多少毫秒后过期,NX相当于SETNX,XX表示只有键盘存在时才设置

flushall # 删除所有数据库的key
flushdb # 删除当前数据库所有的key
del key # 删除某个key
redis-cli KEYS *name* | xargs redis-cli DEL # 使用通配符进行批量删除操作

select 2 # 切换数据库

key name # 查找某个key
keys pattern # 查找所有符合给定模式pattern 的key
keys * # 列出所有的key
exists key # 查找该key是否存在
TYPE KEY # 获取某个key的类型

scan 0 # 使用游标的方式取出一定数量的redis key,与keys不同的是,keys是一次性全部扫描

expire key seconds # 为某个key指定生存时间,单位为秒,时间到了后就不存在了,默认时间为永久
ttl key # 查看剩余生存时间

字符串

列表

1
2
3
4
5
6
7
8
9
10
11
12
13
lindex keyname index              # 返回列表中下标为index的元素
lpush keyname value value2 # 将一个或多个值插入到表头
lpush keyname value [value ...] # 将一个或多个值插入表头
lpushx keyname value [value ...] # 当且仅当key存在且是一个列表时插入表头
rpush keyname value # 插入表尾
rpushx keyname value # 当且仅当key存在且是一个列表时插入表尾
lrem key count value # 从列表删除元素,其中count>0时表示从头往尾移除count个值为value的元素,count为0时表示移除所有,count<-1时则是从尾往头移除
lrange keyname start count # 从表头的第start位开始取出count个元素
llen keyname # 返回列表长度
lpop keyname # 移除并返回key的头元素
rpop keyname # 移除并返回key的尾元素
lset key index value # key中下标为index的元素的值设置为value,如果key不存在则会报错no such key
LTRIM key start stop # 只会保留列表从左往右的start至stop数量的元素

集合(SET)

1
2
3
SADD key member [member...]
SMEMBERS key # 返回集合key中的所有成员
SISMEMBER key member # 判断集合key中是否存在成员

有序集合(ZSET)

  • 需要注意start/stopmin/max的区别,一个是排序后的顺序,一个是score值
  • 最大分数值可以用"+inf"代替,最小分数值可以用"-inf"代替
1
2
3
4
5
6
7
ZADD key score member	# 像有序集合中添加元素
ZCARD key # 返回有序集合的成员数量
ZCOUNT key min max # 返回有序集key中,score值在min和max之间的成员数量
ZRANGE key start stop [WITHSCORES] # 返回有序集key中,指定区间内的成员,其中成员的位置按score值递增(从小到大)来排序
ZRANGEBYSCORE key min max # 返回有序集key中,所有score值在min和max之间的成员
ZREM key member # 移除指定元素
ZREVRANGEBYSCORE key max min # 与上面相反,这是分数由高到低排列的

Hash(哈希)

1
2
3
4
5
6
7
8
9
HKEYS key	# 取出哈希表key中所有的域
HEXISTS key field # 判断field是否存在于hash中
HMGET key field [field...] # 取出某个key指定域的值
HSET key field value # 将hash表key中的域field的值设为value,如果key不存在则会新建,返回结果为1,如果已有field则会覆盖,返回结果为0
HSETNX key field value # 只在 key 指定的哈希集中不存在指定的字段时,设置字段的值。成功返回1,如果已经存在field,那么会失败返回0
HMSET key field value [field value ...] # 同时将多个field-value(域-值)对设置到哈希表key中,会覆盖哈希表中已存在的域
HINCRBY key field increment # 将hash key中的域field增加increment,如果没有key则会新建key,如果没有域则默认为0并增加increment
HGETALL key # 取出hash表中所有的域和值
HVALS key # 取出哈希表key中所有域的值

过期策略

根据官方文档,redis对于过期的键有两种策略(过期的键并不会立马执行删除操作),分为主动与被动:

  1. 客户端试图去获取某个key的时候就会直接进行过期删除操作
  2. 由服务器去探测,探测方案如下(每秒执行10次下面的操作):
    1. 从设置了过期时间的所有key中随机取20个键
    2. 删除实际上已经过期了的键
    3. 如果删除的超过了已过期键的25%,重复前面的操作

如果想看效果,那么可以设置100000个过期时间很大的键,再设置一个过期时间很短的键,并开启键空间通知,你就知道多久才会发现那个键了😭,不过,如果全部时间多一样,那100000个键也能在瞬间完成所有通知,也就是说,每次扫描出来的20个键如果都满足第三条要求,那连续探测的频率是非常高的。检测频率可通过hz进行设置,默认为20

Keyspace notifications(键空间通知)

键空间通知,允许Redis客户端从“发布/订阅”通道中建立订阅关系,让客户端能够从Redis中接收到相应的事件。redis-cli config get notify-keyspace-events获取其配置值,如果value为空表示没有设置。可以直接用命令设置如:redis-cli config set notify-keyspace-events KEA其中,最后面的字符,每个字符都有特殊的含义:

  • K: 键空间通知,所有通知以__keyspace@__为前缀
  • E: 键事件通知,所有通知以__keyevent@__为前缀
  • g: DEL、EXPIRE、RENAME等类型无关的通用命令的通知
  • $: 字符串命令的通知
  • l: 列表命令的通知
  • s: 集合命令的通知
  • h: 哈希命令的通知
  • z: 有序集合命令的通知
  • x: 过期事件: 每当有过期键被删除的通知
  • e: 驱逐(evict)事件: 每当有键因为maxmemory政策被删除的通知
  • A: 所有通知

使用方法:

1
2
3
4
5
6
7
8
> config set notify-keyspace-events Ex	# 必须加E,这样才会通知事件
第一个终端进行监听:
> PSUBSCRIBE __keyevent@0__:expired

第二个终端进行操作
> set a b EX 1

这样第一个终端就会输出过期的键值

Redis组件

Redis从4.0开始支持组件的开发,能为redis提供更多实用的定制功能,热门组件可以参考Redis Modules

Redis数据库设计

  • 统计聚合情况

    例如,需要统计PV数量,精确到分钟,但是又有按小时、按天、按星期统计的需求,那么可以使用hash来进行聚合统计,例如这样设计

    1
    2
    3
    4
    5
    pv:post:id = {	 # 这是key值
    '2016-08-22': 2333 # 按天统计
    '2016-08-22:15': 23 # 按小时统计
    }
    pv:post:id:2016-08-22:15:list = [] # 那一个小时每分钟的数据,作为一个列表

Redis Sentinel高可用方案

Redis Cluster集群方案

redis sentinel不同的是,前者主要是高可用,每一个机器都保存完整的数据,而cluster则住重在分片,当内存占用大于每台机器实际内存时候更实用。Python连接Redis集群需要使用redis-py-cluster

TroubleShooting

  • 小数据量本地迁移数据

    网上找到的Redis的迁移方法都是从一个服务器至另一个服务器做主从复制,但我现在面临的情况是如何将localhost的数据迁移到Server上面去,用最笨也最简单的方法,直接将dump.rdb覆盖服务器上Redis目录,需要注意的是,覆盖的时候得先把原Redis进程关闭掉,覆盖后再重启

  • Redis 显示中文

    启动时redis-cli --raw

  • **windows长时间运行出现错误:Redis-Server:Windows is reporting that there is insufficient disk spaceavailable for this file (Windows error 0x70)**。原因是分配的堆栈太小了,默认的应该只有1M,这时候需要修改器配置文件redis.windows.conf,修改maxheap的值为2000000000,即2G

  • Redis自动退出,log无报错: 目前遇到的情况是可能连接数过高。操作系统让它挂掉了

  • **MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.**持久化的时候磁盘不可写了,一般是因为磁盘满了

扩展阅读