MySQL数据库进行分库备份shell脚本
[root@shell scripts]# vi fenbiao_backup.sh分享原文 收起阅读 »
#!/bin/bash
MysqlUser=root
PassWord=backup123
Port=3306
Socket="/data/$Port/mysql.sock"
MysqlCmd="mysql -u$MysqlUser -p$PassWord -S $Socket"
Database=`$MysqlCmd -e "show databases;"|egrep -v "Database|_schema|mysql"`
MysqlDump="mysqldump -u$MysqlUser -p$PassWord -S $Socket"
IP=`ifconfig eth0|awk -F "[ :]+" 'NR==2 {print $4}'`
BackupDir=/backup/$IP
[ -d $BackupDir ] || mkdir -p $BackupDir
for dbname in $Database
do
[ ! -d /$BackupDir/$dbname ] && mkdir -p /$BackupDir/$dbname
TABLE=`$MysqlCmd -e "show tables from $dbname;"|sed '1d'`
for table in $TABLE
do
$MysqlDump $dbname $table|gzip >/$BackupDir/$dbname/${dbname}_${table}_$(date +%F).sql.gz
done
done
MySQL主从同步延迟原因和解决方法分享
企业面试题:MySQL出现同步延迟有哪些原因?如何解决?分享原文 收起阅读 »
1.从库太多导致复制延迟
优化:建议从库数量3-5个为宜
2.从库硬件比主库硬件差
优化:提升硬件性能
3.慢SQL语句过多
优化:SQL语句执行时间太长,需要优化SQL语句
4.主从复制的设计问题
优化:主从复制单线程,可以通过多线程IO方案解决;另外MySQL5.6.3支持多线程IO复制。
5.主从库之间的网络延迟
优化:尽量链路短,提升端口带宽
6.主库读写压力大
优化:前端加buffer和缓存。主从延迟不同步:
不管有多延迟,只要不影响业务就没事
7、业务设计缺陷导致延迟影响业务
优化:从库没有数据改读主库
nginx之upstream模块缓存系统详解
一般情况下,前端使用nginx做代理或7层负载并向后实现varish/squid做cache server的效果要好的多
nginx与squid做缓存比较,nginx比squid有着巨大的优势表现在:
参考:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path
语法:
levels=1:2 有2级子目录,最多为3级子目录,用冒号隔开定义3个数字,每个数字表示其目录名称;
keys_zone=one:10m 用多大的空间保存键值;
以下为缓存对象的名字,方便引用并且避免名称冲突
定义缓存必须在全局配置上下文中去定义
在location中来使用proxy_cache来指定是否使用缓存,也可以定义多个缓存在location中引用不同的缓存定义
步骤:
(1)定义缓存必须在http全局中定义
(2)而后在location中实现反向代理才有必要实现缓存
创建缓存目录
levels=1:2 有2级子目录,最多为3级子目录,用冒号隔开定义3个数字,每个数字表示其目录名称;
keys_zone=one:10m 用多大的空间保存键值,最大为10M;
max_size=1g 表示缓存空间最大有1G;
在location中定义缓存规则并调用proy_cache_path
那么再将upstream模块开启,因为upstream模块会给我们引入一些新的服务器变量,编辑如下:
比如cache_status,如果我们访问某个缓存页面的时候到底是否命中会通过这个变量保存下来,如果将其传递给客户端,那么我们就知道是否命中了
再将其加入首部header
如上所示,提示已命中,X-Via是我们自定义的header标签
也可以使用curl -I来查
nginx将响应报文发送至客户端之前可以启用压缩功能,这能够有效地节约带宽,并提高响应至客户端的速度。通常编译nginx默认会附带gzip压缩的功能,因此,可以直接启用之。
重新加载配置文件并访问
upstream模块常用的指令有:
示例:
实现后端服务器健康状态检查,使其一旦出现故障不再将其加进来,定义upstream:
proxy_cache通常也只能缓存本地服务器向后端服务器取得数据而进行缓存的,而后端通常都是静态服务器,如果基于fastcgi的方式获取数据的,而有希望对动态内容作缓存,那么就要涉及到fastcgi的自身缓存功能了
实现动态内容缓存
通过FastCGI协议取得的内容页可以缓存,但是时长需要自定义好
使用时将fastcgi_cache模块启用并定义参数即可,之前我们搭建了一套lnmp 这时我们要对fastcgi进行缓存
定义缓存目录
缓存目录分别为3级子目录;
Fcgi缓存大小为20M;
最大缓存为1G的空间;
而后location中启用fastcgi
nginx与squid做缓存比较,nginx比squid有着巨大的优势表现在:
nginx是异步
假如后端的web服务器,当用户的请求到达nginx的时候,nginx收到请求而不是立即将请求转发至web server;
如果用户请求比较大,nginx将其在本地缓存,内存中不够用则在磁盘中缓存,当缓存完毕之后,再将请求一次性提交至后端web服务器,转发完成之后再向客户端响应也就意味着用户的连接只需要跟nginx建立连接即可,nginx与后端web一般都在内网中对接,只要带宽满足,很可能瞬间完成,因此来说对于后端服务器的压力及小,只需要建立几秒的连接就可以处理完成非常大的请求。
squid是同步
当用户请求到达squid中,在刚接收到用户请求的第一个报文立即与后端建立连接,因此在处理过程中,依旧保持着连接。
所以说,nginx最大的优势就是在用户的连接处理的场景中,这样就使前端有大量的用户请求连接,但是在后端看上去只有少数部分,比如前端有1W并发进来,后端大概只能看到其10分之1 ,需要查找数据库的只有少数部分, 所以后端的web压力会小,但是前端分发器的压力会很大
nginx缓存机制
nginx要想实现反向代理那么就需要使用proxy_cache模块,以及配合其指令和参数,可以将用户的请求从上游服务器获取之后先存在本地磁盘。
缓存通常是键值对方式存储:
键 : 请求的rul
值 : 后端服务器响应的内容
所以当后续用户的请求到达之后,如果本地缓存服务器中存在的话则直接封装报文返回至用户;
但是如果服务器运行了很久,已保存N久的缓存数据,那在某一时刻后端服务器出现故障,nginx缓存已经无法找到其后端的服务器,那么缓存上游服务器对象是否还应该返回至用户;
这些都是可配置的,我们定义缓存的时候,像这些缓存 是否可缓存,缓存的位置都要自己去配置的,并且一旦服务器故障,缓存的数据还能否直接响应客户端等等
事实上用户所在浏览器上保存的缓存称为私有缓存,而服务器上缓存的数据叫做公共缓存
有些数据只能在私有缓存中进行缓存,比如:
用户登录网站的用户名和密码的信息,这类肯定不能在服务器上缓存;
而用户的cookie信息一般也不能缓存,所以缓存服务器公共缓存服务器只要发现用户请求中有cookie则不缓存,但是一般电商站点为了追中用户的行为规则,则将每个请求数都加cookie,但对于图片这种静态内容附加cookie是没有意义的。
所以对于缓存服务器来说必须处理这种机制,对于没必要加cookie的而用户已加cookie将其删掉并让缓存命中等,因此需要一系列缓存机制,这都是需要自己去定义的,比如缓存多久 否定缓存多久 重定向缓存多久 ...
配置Nginx缓存
参考:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path
语法:
proxy_cache_path path [levels=levels] keys_zone=name:size [inactive=time] [max_size=size] [loader_files=number] [loader_sleep=time] [loader_threshold=time];配置格式:
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=one:10m;#定义/data/nginx/cache为缓存目录;
levels=1:2 有2级子目录,最多为3级子目录,用冒号隔开定义3个数字,每个数字表示其目录名称;
keys_zone=one:10m 用多大的空间保存键值;
以下为缓存对象的名字,方便引用并且避免名称冲突
file names in a cache will look like this:使用示例
/data/nginx/cache/c/29/b7f54b2df7773722d382f4809d65029c
定义缓存必须在全局配置上下文中去定义
http {proxy_cache_path必须定义在全局配置中,定义完之后可以在各location中来引用
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:10m
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:10m
inactive=24h max_size=1g;
}
在location中来使用proxy_cache来指定是否使用缓存,也可以定义多个缓存在location中引用不同的缓存定义
server {
location / {
proxy_pass http://www.test.com;
proxy_set_header Host $host;
proxy_cache STATIC;
proxy_cache_valid 200 1d; #请求返回值为200的则缓存1day
proxy_cache_valid 301 302 10m; #请求返回值为301 302的则缓存10m
proxy_cache_vaild any 1m; #其他任何返回值缓存1m
#是否可以使用过期对象
proxy_cache_use_stale error timeout invalid_header updating
http_500 http_502 http_503 http_504;
}
}
}
实现缓存机制
步骤:
(1)定义缓存必须在http全局中定义
(2)而后在location中实现反向代理才有必要实现缓存
创建缓存目录
[root@node1 ~]# mkdir -p /data/cache/nginx/
编辑nginx配置文件[root@node1 nginx]# vim nginx.conf
在http { }上下文中定义缓存proxy_cache_path /data/cache/nginx/ levels=1:2 keys_zone=one:10m max_size=1g;定义/data/cache/nginx/为缓存目录;
levels=1:2 有2级子目录,最多为3级子目录,用冒号隔开定义3个数字,每个数字表示其目录名称;
keys_zone=one:10m 用多大的空间保存键值,最大为10M;
max_size=1g 表示缓存空间最大有1G;
在location中定义缓存规则并调用proy_cache_path
location / {保存退出检查语法并重新加载
proxy_pass http://10.0.10.83; #将请求都转发至10.0.10.83上去
proxy_cache one; #明确说明使用名称one这个cache
#以下为缓存规则
proxy_cache_valid 200 1h;
proxy_cache_valid 302 10m;
proxy_cache_valid any 1m;
}
[root@node1 nginx]# nginx -t查看缓存目录
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@node1 nginx]# nginx -s reload
[root@node1 nginx]# ll -th /data/cache/nginx/看到目录中是空的,那么我们来使用curl命令访问一下看其是否能生成缓存文件
total 0
[root@node1 nginx]# curl 10.0.10.61再次查看路径
10.0.10.83
[root@node1 nginx]# ll -th /data/cache/nginx/f/63/这时候目录中已经存在缓存信息,说明缓存已经生效,但是在某些场合,我们不能保证其已经被命中
total 4.0K
-rw-------. 1 nginx nginx 350 May 9 16:06 681ad4c77694b65d61c9985553a2763f
那么再将upstream模块开启,因为upstream模块会给我们引入一些新的服务器变量,编辑如下:
upstream webservers { #定义名称再定义location
server 10.0.10.83;
#server 10.0.10.61; #将之前定义的注释
}
location / {这样表示我们已经启用了upstream模块了而upstream模块会给我们引入一些新的服务器变量进来
proxy_pass http://webservers; #引用upstream名称
proxy_cache one; #引用定义的缓存模块名称
proxy_cache_valid 200 1h;
proxy_cache_valid 302 10m;
proxy_cache_valid any 1m;
}
比如cache_status,如果我们访问某个缓存页面的时候到底是否命中会通过这个变量保存下来,如果将其传递给客户端,那么我们就知道是否命中了
再将其加入首部header
location / {保存退出并检测语法
#root /web/htdocs/;
#index index.php index.html index.htm;
proxy_pass http://webservers;
proxy_cache one;
proxy_cache_valid 200 1h;
proxy_cache_valid 302 10m;
proxy_cache_valid any 1m;
add_header X-Via $server_addr; #定义这个header名为X-Via 通过变量$server_addr明确说明从哪个服务器来响应的 server_addr
add_header X-Cache-Status $upstream_cache_status; #明确说明是否命中 $upstream_cache_status为upstream模块
}
[root@node1 nginx]# /usr/local/nginx/sbin/nginx –t重新reload之后刷新测试,这里使用的是Google Chrome浏览器
[root@node1 nginx]# /usr/local/nginx/sbin/nginx –s reload
如上所示,提示已命中,X-Via是我们自定义的header标签
也可以使用curl -I来查
[root@node1 nginx]# curl -I http://10.0.10.61
HTTP/1.1 200 OK
Server: nginx/1.4.2
Date: Fri, 09 May 2014 08:22:41 GMT
Content-Type: text/html
Content-Length: 20
Connection: keep-alive
Last-Modified: Mon, 12 Aug 2013 10:48:13 GMT
ETag: "fd91-14-4e3bddc687540"
X-Via: 10.0.10.61
X-Cache-Status: HIT
Accept-Ranges: bytes
启动压缩功能
nginx将响应报文发送至客户端之前可以启用压缩功能,这能够有效地节约带宽,并提高响应至客户端的速度。通常编译nginx默认会附带gzip压缩的功能,因此,可以直接启用之。
http {gzip_proxied指令可以定义对客户端请求哪类对象启用压缩功能,如“expired”表示对由于使用了expire首部定义而无法缓存的对象启用压缩功能,其它可接受的值还有“no-cache”、“no-store”、“private”、“no_last_modified”、“no_etag”和“auth”等,而“off”则表示关闭压缩功能
gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript application/json;
gzip_disable msie6; #如果对方是ie6的话,则不再使用压缩功能,,因为ie6浏览器不支持压缩
}
重新加载配置文件并访问
upstream模块的使用
upstream模块常用的指令有:
- []ip_hash: 基于客户端IP地址完成请求的分发,它可以保证来自于同一个客户端的请求始终被转发至同一个上游服务器,与lvs的机制是一样的;[/][]keepalive:每个worker进程为发送到upstream服务器的连接所缓存的个数;转发至服务器之后能否使用长连接,也尽可能避免三次握手与四次断开的次数,如果参数过大的话,会无形之中对后端服务器产生很大的压力,因此建议开启但不要太大[/][]least_conn:最少连接调度算法;类似于lvs的wlc算法的效果,因此一般来尽量避免和ip_hash一起用[/][]server:定义一个upstream服务器的地址,还可包括一系列可选参数,如:[/][]weight:权重;[/][]max_fails:最大失败连接次数,失败连接的超时时长由fail_timeout指定;[/][]fail_timeout:等待请求的目标服务器发送响应的时长;[/][]backup:用于fallback的目的,所有服务均故障时才启动此服务器;[/][]down:手动标记其不再处理任何请求;[/]
示例:
upstream backend {upstream模块也能为非http类的应用实现负载均衡,如下面的示例定义了nginx为memcached服务实现负载均衡
server www.magedu.com weight=5;
server www2.magedu.com:8080 max_fails=3 fail_timeout=30s;
}
upstream memcachesrvs { #明确定义了一组实现负载均衡的服务器这样一来,找缓存的时候先在memcached里查找,如果不存在则再去找真实服务器,这样将nginx将memcached结合在了一起,将数据直接缓存在memcached内存当中,而不是nginx自己的缓存当中
server 172.16.100.6:11211; #而这组服务器向后分发的端口都是11211 ,是memcache的服务器端口
server 172.16.100.7:11211;
}
server {
location / {
set $memcached_key "$uri?$args"; #向后端memcache查询的时候,查询键是$uri?和$args组合起来的值
memcached_pass memcachesrvs; #通过memcachepass传递至memcachesrvs组服务器
error_page 404 = @fallback; #如果没有命中则则发送至其上游的其他服务器
}
location @fallback {
proxy_pass http://127.0.0.1:8080;
}
}
实现后端服务器健康状态检查,使其一旦出现故障不再将其加进来,定义upstream:
upstream webservers {一般如果条件允许的情况下,本机也启动一个web服务器,但是这个服务器不是专门提供工作的,一旦后端服务器出现故障,那么则转至backup服务器,使其服务器专门为用户提供错误页面
server 10.0.10.83 weight=1 max_fails=3 fail_timeout=2s; #最大允许3次失败,如果超过2秒则算超时
server 10.0.10.61 max_fails=3 fail_timeout=1s backup; #backup表示其主机始终不会生效除非组内所有主机全部故障
}
proxy_cache通常也只能缓存本地服务器向后端服务器取得数据而进行缓存的,而后端通常都是静态服务器,如果基于fastcgi的方式获取数据的,而有希望对动态内容作缓存,那么就要涉及到fastcgi的自身缓存功能了
实现动态内容缓存
通过FastCGI协议取得的内容页可以缓存,但是时长需要自定义好
使用时将fastcgi_cache模块启用并定义参数即可,之前我们搭建了一套lnmp 这时我们要对fastcgi进行缓存
定义缓存目录
[root@node1 nginx]# mkdir -p /data/cache/fastcgi
编辑配置文件,在http{}中加入以下参数:fastcgi_cache_path /data/cache/fastcgi levels=1:2:1 keys_zone=fcgi:20m max_size=1g;定义/data/cache/fastcgi为fastcgi缓存目录;
缓存目录分别为3级子目录;
Fcgi缓存大小为20M;
最大缓存为1G的空间;
而后location中启用fastcgi
location ~ \.php$ {保存退出并检查语法
root /web/htdocs/;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
}
以上参数为nginx默认配置,将以上参数启用之后,我们还需要对其加入一些参数:
location ~ \.php$ {
root /web/htdocs/;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
fastcgi_cache fcgi;
fastcgi_cache_valid 200 10m ;
fastcgi_cache_valid 301 2m ;
fastcgi_cache_valid any 1m ;
}
[root@node1 nginx]# nginx -t重新加载配置文件
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@node1 nginx]# nginx -s reload
这时我们再去请求动态内容[root@mode ~]# curl http://10.0.10.61/index.php
再来查看是否生产缓存[root@node1 ~]# ll /data/cache/fastcgi/e/27/4/使用ab压力测试其效果是否明显
total 48
-rw-------. 1 nginx nginx 45873 May 10 12:55 d41d8cd98f00b204e9800998ecf8427e
首先将fastcgi缓存功能关闭重新加载配置文件
location ~ \.php$ {
root /web/htdocs;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
#将以下参数注释
#fastcgi_cache fcgi;
#fastcgi_cache_valid 200 10m ;
#fastcgi_cache_valid 301 2m ;
#fastcgi_cache_valid any 1m ;
}
[root@node1 nginx]# nginx -s reload
使用ab命令对其进行压力测试[root@mode ~]# ab -c 100 -n 2000 http://10.0.10.61/index.php收起阅读 »
我们只关心Requests值 所以略过一部分信息
#···········略··············
Document Path: /index.php
Document Length: 45234 bytes
Concurrency Level: 100
Time taken for tests: 4.343977 seconds
Complete requests: 2000
Failed requests: 228
(Connect: 0, Length: 228, Exceptions: 0)
Write errors: 0
Total transferred: 90761752 bytes
HTML transferred: 90467752 bytes
Requests per second: 460.41 [#/sec] (mean)
Time per request: 217.199 [ms] (mean)
Time per request: 2.172 [ms] (mean, across all concurrent requests)
Transfer rate: 20403.88 [Kbytes/sec] received
#···········略··············
将缓存模块开启再次对其进行压力测试
得出数据:
Concurrency Level: 100
Time taken for tests: 1.576516 seconds
Complete requests: 2000
Failed requests: 0
Write errors: 0
Total transferred: 90896000 bytes
HTML transferred: 90602000 bytes
Requests per second: 1268.62 [#/sec] (mean)
Time per request: 78.826 [ms] (mean)
Time per request: 0.788 [ms] (mean, across all concurrent requests)
Transfer rate: 56304.53 [Kbytes/sec] received
腾讯,阿里,百度内部层级深度解析
互联网圈有这么一句话:百度的技术,阿里的运营,腾讯的产品。那么代表互联网三座大山的BAT,内部人才体系有什么区别呢?
下面就让我们来看看BAT的神秘面纱!
一、腾讯
1、职位
腾讯职级体系分6级,最低1级,最高6级
同时按照岗位又划分为四大通道,内部也叫"族",比如:
- []产品/项目通道,简称P族[/][]技术通道,简称T族[/][]市场通道,简称M族[/][]职能通道,简称S族[/]
T1:助理工程师(一般为校招新人)T2:工程师T3:高级工程师 3-1相当于阿里的p6+到p7(能力强可能到p7)T4:专家工程师(150-200人左右)T5:科学家T6:首席科学家目前全腾讯貌似就一个T6。每一级之间又分为3个子级,3-1是任命组长/副组长的必要条件;其他线也是这样;T4基本为总监级,也不排除有T3-3的总监,因为T4非常难晋级。 腾讯内部是按级别划分的从T1到T6。每个级别又分3等。级别越高base的薪酬也越高,一年根据你的performance大概能发15.3个月至18个月的工资,T3.1的base 2w+,T3以上级别的员工都会有股票期权,(啊啊啊啊口水流出来了)腾讯09以前的员工赚钱主要靠股票,从08到现在股票up了500%+。 暂时有不公平的话公司内部review的时候也会balance的。T5+的base薪酬在600w~800w/年(从此看到了迎娶高富帅,走上人生巅峰的希望)
腾讯的晋级还是很困难的。尤其是T2 升T3,T3升T4.非常多的人卡在2-3,3-3没有办法晋级啊。有的小伙伴做了3、4年的2-3 升不上去啊2、晋升体系
腾讯薪资架构:12+1+1=14薪年终奖:看部门盈利情况,一般是3个月BLABLA:据传英雄联盟团队拿了60个月,不知道是真是假啊啊啊啊啊!3、薪资结构
在深圳的很多腾讯员工,很多都买了房,想往杭州,北京挖人,太困难了。当你的房子,妻子的工作,儿子的学校,你的朋友圈,都在一个城市的时候,换城市就有困难了啊。所以只能挖一些比较浅的人走。 在北京:人数不少 ,不够骨干员工不多。腾讯视频的主要团队在北京倒是不少。在成都,大连在这些二线城市,腾讯就是当地最好的互联网公司了,提供的待遇也是非常高的,不少人都对自己的薪资比较满意,工作环境也很满意。跳槽的可能性低了很多。4、人才流动可能
腾讯的研发序列硕士学历的占多度,211大学,985大学占多数。大家都知道腾讯研究院解散了。去年走出来很多人,腾讯人才创业比例不高。在腾讯最常碰到的晋升问题就是天花板。可能新人进去,学东西会很多,但业务线就这些,没有那么多坑,自然也就很难晋升高级岗。 在腾讯最悲剧的时刻就是公司有收购和整合。搜狗合并,搜搜的人哭了,京东合作,易迅的人哭了。 在腾讯跳出来碰到最大的问题就是,外面的公司太不完善了。5、人才结构
我们来谈谈腾讯的价值观:代表价值观的四种动物(城里人真会玩):6、价值观与使命
- []长颈鹿:取其长长直直的脖子外形特点,象征“正直”。[/][]海燕:不惧困难,勇往直前,迎接挑战,代表了一种进取的精神。[/][]犀牛与犀牛鸟:取其在自然环境中形成相互协助生存关系的特点,象征“合作”。[/][]鹦鹉螺:鹦鹉螺初生时不会上浮,在生长过程中螺仓逐渐变多,成年的鹦鹉螺便可利用对螺仓充气的方式浮出海面,象征着“创新”。[/]
经营理念:一切以用户价值为依归
使命:通过互联网服务提升人类生活品质
愿景:最受尊敬的互联网企业
二、阿里巴巴
1、职位
阿里的职称是这么评价的,大部分都归纳在P序列 ,你的title+工种
一般P3为助理同时对应P级还有一套管理层的机制在:
P4=专员
P5=资深专员
P6=高级专员(也可能是高级资深)
P7=专家
P8=资深专家(架构师)
P9=高级专家(资深架构师)
P10=研究员
P11=高级研究员
P12=科学家
P13=首席科学家
P14=马云
M1=P6 主管在阿里早些时候P级普遍偏低,专员可能是P2这样,后来有了一次P级通货膨胀,出现了更多的P级。
M2=P7 经理
M3=P8 资深经理
M4 =P9 总监
M5= P10 资深总监
M6 =P11 副总裁
M7=P12 资深副总裁
M8=P13 子公司CEO 或集团其他O
M9=P14 陆兆禧(前马云)
在阿里只有P6(M1)后才算是公司的中层。不同的子公司给出P级的标准不一样。比如:B2B的普遍P级较高,但是薪资水平低于天猫子公司的同级人员。同时到达该P级员工才有享受公司RSU的机会。(低于P6的除非项目出色有RSU奖励,否则1股都拿不到)
BLABLA:阿里上市后,目前P7才有股权
2、晋升体系
1.晋升资格:上年度KPI达3.75
2.主管提名,一般你要是KPI不达3.75主管也不会提名你
3.晋升委员会面试(晋升委员会组成一般是合作方业务部门大佬、HRG、该业务线大佬等)
4.晋升委员会投票
P5升P6相对容易,再往上会越来越难,一般到P7都是团队技术leader了,P6到P7我感觉非常难,从员工到管理的那一步跨出去不容易,当然有同学说P一般都是专家,M才是管理,actually,专家线/管理线有时并不是分的那么清楚的。
3、薪酬结构
•阿里薪资结构:一般是12+1+3=16薪,P7开始有股权三、百度
•年底的奖金为0-6个月薪资,90%人可拿到3个月
•股票是工作满2年才能拿,第一次拿50%,4年能全部拿完
1、职位
技术序列 T: T3 - T12 (一般对应阿里高一级序列,如:百度T3=阿里P4,T5/T6属于部门骨干,非常抢手,T12李彦宏)
T4 开发工程师产品运营序列 P: p3-P12 (产品和运营岗,对应阿里高1-1.5级序列 百度p3=阿里P4-P5之间,P12李彦宏)
T5 高级开发工程师 M1-A
T6 高级开发工程师 M1-B
T7 资深高级开发/架构师 M1-B/M2-A
T8 高级架构师 M2-A/M2-B
T9 总架构师 M2-B/M3-A
T10 科学家
T11 首席科学家 王海峰,余凯,Andrew ng
T12=P12 李彦宏
后勤支持部门 S : S3-S11 (主要是公共、行政、渠道等等,晋升比较困难)
管理序列 M: M1-M5 (每一级又分为2个子级 M1A、M1B , 最低的是M1A,至少是部门二把手了,李明远是M3.2,以前的汤和松都是这个级别,李彦宏是唯一的M5,其实从M3开始就有机会加入E——star,类似于阿里的合伙人会议,属于最高战略决策层。)
还有B (business), U(design)系列,不多写了。
2、薪资结构
月薪14.6(12+0.6+2),其他岗位月薪14
BLABLA :去年百度奖金大涨,14.6今年没有参考价值,今年上半年百度的技术是不是特别难挖。。。
•T5以上为关键岗位,另外有股票、期权
•T5、T6占比最大的级别,T8、T9占比最小
•级别越高,每档之间的宽幅越大
3、晋升体系
基本上应届毕业生应该就是T3,但是内部晋升非常激烈,这个可以理解,公司那么大,部门和部门之间有业务竞争,那肯定也有人才竞争。一般情况是分2种:
通常应届毕业生入职1年左右能升到T4,但如果你的部门业务足够核心,或许1年就可以了。3年升T5。
从目前百度的情况来看,核心工程师集中在T5/6,但是从5/6到7是非常艰难的过程。
百度是很唯KPI至上的,其次部门很核心,再次老大话语权比较高,相对晋升容易些。
1.自己提名,当你自己觉得已经具备下一level的素质,可以自己提名,提名后进入考察期,主管设定考察期目标,考察通过顺利晋升,考察不通过维持原层级不变;
2.主管提名,如果是主管提名,一般都是直接通过的,但是如果你现层级已经比较高了,那就不是直接提名这么简单了。
P.S:如果你能升到T7,基本上是TL的级别,写代码/直接做业务的时间就很少了。
BLABLA:T9之前是必须每月要上传代码的,如果有T9以下说自己好几年不写代码了,那就是fraud。
以下薪资结构图供参考:
http://OpenSkill.CN 开源技术社区 基于互联网整理分享 收起阅读 »
Nginx+Keepalived实现网站高可用方案
公司内部 OA 系统要做线上高可用,避免单点故障,所以计划使用2台虚拟机通过 Keepalived 工具来实现 nginx 的高可用(High Avaiability),达到一台nginx入口服务器宕机,另一台备机自动接管服务的效果。
1. Keepalived介绍
VRRP全称 Virtual Router Redundancy Protocol,即 虚拟路由冗余协议。可以认为它是实现路由器高可用的容错协议,即将N台提供相同功能的路由器组成一个路由器组(Router Group),这个组里面有一个master和多个backup,但在外界看来就像一台一样,构成虚拟路由器,拥有一个虚拟IP(vip,也就是路由器所在局域网内其他机器的默认路由),占有这个IP的master实际负责ARP相应和转发IP数据包,组中的其它路由器作为备份的角色处于待命状态。master会发组播消息,当backup在超时时间内收不到vrrp包时就认为master宕掉了,这时就需要根据VRRP的优先级来选举一个backup当master,保证路由器的高可用。
在VRRP协议实现里,虚拟路由器使用 00-00-5E-00-01-XX 作为虚拟MAC地址,XX就是唯一的 VRID (Virtual Router IDentifier),这个地址同一时间只有一个物理路由器占用。在虚拟路由器里面的物理路由器组里面通过多播IP地址 224.0.0.18 来定时发送通告消息。每个Router都有一个 1-255 之间的优先级别,级别最高的(highest priority)将成为主控(master)路由器。通过降低master的优先权可以让处于backup状态的路由器抢占(pro-empt)主路由器的状态,两个backup优先级相同的IP地址较大者为master,接管虚拟IP。
与heartbeat/corosync等比较:
keepalived可以认为是VRRP协议在Linux上的实现,主要有三个模块,分别是core、check和vrrp。core模块为keepalived的核心,负责主进程的启动、维护以及全局配置文件的加载和解析。check负责健康检查,包括常见的各种检查方式。vrrp模块是来实现VRRP协议的。本文基于如下的拓扑图:
我的环境是CentOS 6.2 X86_64,直接通过yum方式安装最简单:
该脚本检测ngnix的运行状态,并在nginx进程不存在时尝试重新启动ngnix,如果启动失败则停止keepalived,准备让其它机器接管。
global_defs
根据上面的配置,初始化状态:172.29.88.224 (itoatest1,MASTER,101),172.29.88.222(itoatest2,BACKUP,100),nginx和keepalived都启动,虚拟IP 172.29.88.222 在 itoatest1 上:
同时可以看到两台服务器上 /var/log/messages:
参考
使用Keepalived实现Nginx高可用性
High Availability Support Based on keepalived
nginx+keepalived实现双机热备的高可用
LVS原理详解及部署之五:LVS+keepalived实现负载均衡&高可用
Keepalived双主模型中vrrp_script中权重改变故障排查
虚拟路由器冗余协议【原理篇】VRRP详解
Keepalived原理与实战精讲
原文分享地址:http://seanlook.com/2015/05/18/nginx-keepalived-ha/ 收起阅读 »
1. Keepalived介绍
Keepalived是一个基于VRRP协议来实现的服务高可用方案,可以利用其来避免IP单点故障,类似的工具还有heartbeat、corosync、pacemaker。但是它一般不会单独出现,而是与其它负载均衡技术(如lvs、haproxy、nginx)一起工作来达到集群的高可用。
1.1 VRRP协议
VRRP全称 Virtual Router Redundancy Protocol,即 虚拟路由冗余协议。可以认为它是实现路由器高可用的容错协议,即将N台提供相同功能的路由器组成一个路由器组(Router Group),这个组里面有一个master和多个backup,但在外界看来就像一台一样,构成虚拟路由器,拥有一个虚拟IP(vip,也就是路由器所在局域网内其他机器的默认路由),占有这个IP的master实际负责ARP相应和转发IP数据包,组中的其它路由器作为备份的角色处于待命状态。master会发组播消息,当backup在超时时间内收不到vrrp包时就认为master宕掉了,这时就需要根据VRRP的优先级来选举一个backup当master,保证路由器的高可用。
在VRRP协议实现里,虚拟路由器使用 00-00-5E-00-01-XX 作为虚拟MAC地址,XX就是唯一的 VRID (Virtual Router IDentifier),这个地址同一时间只有一个物理路由器占用。在虚拟路由器里面的物理路由器组里面通过多播IP地址 224.0.0.18 来定时发送通告消息。每个Router都有一个 1-255 之间的优先级别,级别最高的(highest priority)将成为主控(master)路由器。通过降低master的优先权可以让处于backup状态的路由器抢占(pro-empt)主路由器的状态,两个backup优先级相同的IP地址较大者为master,接管虚拟IP。
与heartbeat/corosync等比较:
Heartbeat、Corosync、Keepalived这三个集群组件我们到底选哪个好,首先我想说明的是,Heartbeat、Corosync是属于同一类型,Keepalived与Heartbeat、Corosync,根本不是同一类型的。Keepalived使用的vrrp协议方式,虚拟路由冗余协议 (Virtual Router Redundancy Protocol,简称VRRP);Heartbeat或Corosync是基于主机或网络服务的高可用方式;简单的说就是,Keepalived的目的是模拟路由器的高可用,Heartbeat或Corosync的目的是实现Service的高可用。
所以一般Keepalived是实现前端高可用,常用的前端高可用的组合有,就是我们常见的LVS+Keepalived、Nginx+Keepalived、HAproxy+Keepalived。而Heartbeat或Corosync是实现服务的高可用,常见的组合有Heartbeat v3(Corosync)+Pacemaker+NFS+Httpd 实现Web服务器的高可用、Heartbeat v3(Corosync)+Pacemaker+NFS+MySQL 实现MySQL服务器的高可用。总结一下,Keepalived中实现轻量级的高可用,一般用于前端高可用,且不需要共享存储,一般常用于两个节点的高可用。而Heartbeat(或Corosync)一般用于服务的高可用,且需要共享存储,一般用于多节点的高可用。这个问题我们说明白了。
又有博友会问了,那heartbaet与corosync我们又应该选择哪个好啊,我想说我们一般用corosync,因为corosync的运行机制更优于heartbeat,就连从heartbeat分离出来的pacemaker都说在以后的开发当中更倾向于corosync,所以现在corosync+pacemaker是最佳组合。
1.2 Keepalived + nginx
keepalived可以认为是VRRP协议在Linux上的实现,主要有三个模块,分别是core、check和vrrp。core模块为keepalived的核心,负责主进程的启动、维护以及全局配置文件的加载和解析。check负责健康检查,包括常见的各种检查方式。vrrp模块是来实现VRRP协议的。本文基于如下的拓扑图:
+-------------+2. keepalived实现nginx高可用
| uplink |
+-------------+
|
+
MASTER keep|alived BACKUP
172.29.88.224 172.29.88.222 172.29.88.225
+-------------+ +-------------+ +-------------+
| nginx01 |----| virtualIP |----| nginx02 |
+-------------+ +-------------+ +-------------+
|
+------------------+------------------+
| | |
+-------------+ +-------------+ +-------------+
| web01 | | web02 | | web03 |
+-------------+ +-------------+ +-------------+
2.1安装
我的环境是CentOS 6.2 X86_64,直接通过yum方式安装最简单:
# yum install -y keepalived
# keepalived -v
Keepalived v1.2.13 (03/19,2015)
2.2 nginx监控脚本
该脚本检测ngnix的运行状态,并在nginx进程不存在时尝试重新启动ngnix,如果启动失败则停止keepalived,准备让其它机器接管。
/etc/keepalived/check_nginx.sh :
#!/bin/bash你也可以根据自己的业务需求,总结出在什么情形下关闭keepalived,如 curl 主页连续2个5s没有响应则切换:
counter=$(ps -C nginx --no-heading|wc -l)
if [ "${counter}" = "0" ]; then
/usr/local/bin/nginx
sleep 2
counter=$(ps -C nginx --no-heading|wc -l)
if [ "${counter}" = "0" ]; then
/etc/init.d/keepalived stop
fi
fi
#!/bin/bash
# curl -IL http://localhost/member/login.htm
# curl --data "memberName=fengkan&password=22" http://localhost/member/login.htm
count = 0
for (( k=0; k<2; k++ ))
do
check_code=$( curl --connect-timeout 3 -sL -w "%{http_code}\\n" http://localhost/login.html -o /dev/null )
if [ "$check_code" != "200" ]; then
count = count +1
continue
else
count = 0
break
fi
done
if [ "$count" != "0" ]; then
# /etc/init.d/keepalived stop
exit 1
else
exit 0
fi
2.3 keepalived.conf
! Configuration File for keepalived在其它备机BACKUP上,只需要改变 state MASTER -> state BACKUP,priority 101 -> priority 100,mcast_src_ip 172.29.88.224 -> mcast_src_ip 172.29.88.225即可。
global_defs {
notification_email {
zhouxiao@example.com
itsection@example.com
}
notification_email_from itsection@example.com
smtp_server mail.example.com
smtp_connect_timeout 30
router_id LVS_DEVEL
}
vrrp_script chk_nginx {
# script "killall -0 nginx"
script "/etc/keepalived/check_nginx.sh"
interval 2
weight -5
fall 3
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
mcast_src_ip 172.29.88.224
virtual_router_id 51
priority 101
advert_int 2
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.29.88.222
}
track_script {
chk_nginx
}
}
# service keepalived restart
2.4 配置选项说明
global_defs
- []notification_email : keepalived在发生诸如切换操作时需要发送email通知地址,后面的 smtp_server 相比也都知道是邮件服务器地址。也可以通过其它方式报警,毕竟邮件不是实时通知的。[/][]router_id : 机器标识,通常可设为hostname。故障发生时,邮件通知会用到[/]
- []state : 指定instance(Initial)的初始状态,就是说在配置好后,这台服务器的初始状态就是这里指定的,但这里指定的不算,还是得要通过竞选通过优先级来确定。如果这里设置为MASTER,但如若他的优先级不及另外一台,那么这台在发送通告时,会发送自己的优先级,另外一台发现优先级不如自己的高,那么他会就回抢占为MASTER[/][]interface : 实例绑定的网卡,因为在配置虚拟IP的时候必须是在已有的网卡上添加的[/][]mcast_src_ip : 发送多播数据包时的源IP地址,这里注意了,这里实际上就是在那个地址上发送VRRP通告,这个非常重要,一定要选择稳定的网卡端口来发送,这里相当于heartbeat的心跳端口,如果没有设置那么就用默认的绑定的网卡的IP,也就是interface指定的IP地址[/][]virtual_router_id : 这里设置VRID,这里非常重要,相同的VRID为一个组,他将决定多播的MAC地址[/][]priority : 设置本节点的优先级,优先级高的为master[/][]advert_int : 检查间隔,默认为1秒。这就是VRRP的定时器,MASTER每隔这样一个时间间隔,就会发送一个advertisement报文以通知组内其他路由器自己工作正常[/][]authentication : 定义认证方式和密码,主从必须一样[/][]virtual_ipaddress : 这里设置的就是VIP,也就是虚拟IP地址,他随着state的变化而增加删除,当state为master的时候就添加,当state为backup的时候删除,这里主要是有优先级来决定的,和state设置的值没有多大关系,这里可以设置多个IP地址[/][]track_script : 引用VRRP脚本,即在 vrrp_script 部分指定的名字。定期运行它们来改变优先级,并最终引发主备切换。[/]
- []script : 自己写的检测脚本。也可以是一行命令如killall -0 nginx[/][]interval 2 : 每2s检测一次[/][]weight -5 : 检测失败(脚本返回非0)则优先级 -5[/][]fall 2 : 检测连续 2 次失败才算确定是真失败。会用weight减少优先级(1-255之间)[/][]rise 1 : 检测 1 次成功就算成功。但不修改优先级[/]
- []如果脚本执行结果为0,并且weight配置的值大于0,则优先级相应的增加[/][]如果脚本执行结果非0,并且weight配置的值小于0,则优先级相应的减少[/]
当然nginx没有什么可配置的,因为它与keepalived并没有联系。但记住,2台nginx服务器上的配置应该是完全一样的(rsync同步),这样才能做到对用户透明,nginx.conf 里面的 server_name 尽量使用域名来代替,然后dns解析这个域名到虚拟IP 172.29.88.222。更多关于nginx内容配置请参考 这里2.5 nginx配置
[list=1]
测试
根据上面的配置,初始化状态:172.29.88.224 (itoatest1,MASTER,101),172.29.88.222(itoatest2,BACKUP,100),nginx和keepalived都启动,虚拟IP 172.29.88.222 在 itoatest1 上:
# 使用ip命令配置的地址,ifconfig查看不了直接关闭 itoatest1 上的nginx:/usr/local/nginx-1.6/sbin/nginx -s stop:
[root@itoatest1 nginx-1.6]# ip a|grep eth0
2: eth0:mtu 1500 qdisc pfifo_fast state UP qlen 1000
inet 172.29.88.224/24 brd 172.29.88.255 scope global eth0
inet 172.29.88.222/32 scope global eth0
[root@localhost keepalived]# ip a|grep eth0vip消失,漂移到 itoatest2:
2: eth0:mtu 1500 qdisc pfifo_fast state UP qlen 1000
inet 172.29.88.224/24 brd 172.29.88.255 scope global eth0
同时可以看到两台服务器上 /var/log/messages:
[size=16] itoatest1[/size]你也可以通过在两台服务器上抓包来查看 优先级priority 的变化:
Jun 5 16:44:01 itoatest1 Keepalived_vrrp[44875]: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 172.29.88.222
Jun 5 16:44:06 itoatest1 Keepalived_vrrp[44875]: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 172.29.88.222
Jun 5 16:44:46 itoatest1 Keepalived_vrrp[44875]: VRRP_Script(chk_nginx) failed
Jun 5 16:44:48 itoatest1 Keepalived_vrrp[44875]: VRRP_Instance(VI_1) Received higher prio advert
Jun 5 16:44:48 itoatest1 Keepalived_vrrp[44875]: VRRP_Instance(VI_1) Entering BACKUP STATE
Jun 5 16:44:48 itoatest1 Keepalived_vrrp[44875]: VRRP_Instance(VI_1) removing protocol VIPs.
Jun 5 16:44:48 itoatest1 Keepalived_healthcheckers[44874]: Netlink reflector reports IP 172.29.88.222 removed
[size=16] itoatest2[/size]
Jun 5 16:44:00 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) Transition to MASTER STATE
Jun 5 16:44:00 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) Received higher prio advert
Jun 5 16:44:00 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) Entering BACKUP STATE
Jun 5 16:44:48 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) forcing a new MASTER election
Jun 5 16:44:48 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) forcing a new MASTER election
Jun 5 16:44:49 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) Transition to MASTER STATE
Jun 5 16:44:50 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) Entering MASTER STATE
Jun 5 16:44:50 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) setting protocol VIPs.
Jun 5 16:44:50 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 172.29.88.222
Jun 5 16:44:50 itoatest2 Keepalived_healthcheckers[35554]: Netlink reflector reports IP 172.29.88.222 added
Jun 5 16:44:55 itoatest2 Keepalived_vrrp[35555]: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 172.29.88.222
[size=16] itoatest1 上[/size]
[size=16] 直接输出,或后加 -w itoatest-kl.cap存入文件用wireshark查看[/size]
# tcpdump -vvv -n -i eth0 dst 224.0.0.18 and src 172.29.88.224
参考
使用Keepalived实现Nginx高可用性
High Availability Support Based on keepalived
nginx+keepalived实现双机热备的高可用
LVS原理详解及部署之五:LVS+keepalived实现负载均衡&高可用
Keepalived双主模型中vrrp_script中权重改变故障排查
虚拟路由器冗余协议【原理篇】VRRP详解
Keepalived原理与实战精讲
原文分享地址:http://seanlook.com/2015/05/18/nginx-keepalived-ha/ 收起阅读 »
Shell字符串的截取和拼接
一、Linux 的字符串截取
假设有变量 var=http://www.hadoope.com/123.htm
1.# 号截取,删除左边字符,保留右边字符。
[root@crh_ops sh]# echo ${var#*//}
www.hadoope.com/123.htm
其中 var 是变量名,# 号是运算符,*// 表示从左边开始删除第一个 // 号及左边的所有字符
即删除 http://
结果是 :www.hadoope.com/123.htm
2.[size=16] 号截取,删除左边字符,保留右边字符。[/size]
[root@crh_ops sh]# echo ${var[size=16]*/}[/size]
123.htm
[size=16]*/ 表示从左边开始删除最后(最右边)一个 / 号及左边的所有字符[/size]
即删除 http://www.hadoope.com/
结果是 123.htm
3.% 号截取,删除右边字符,保留左边字符。
[root@crh_ops sh]# echo ${var%/*}
结果是:http://www.hadoope.com
%/* 表示从右边开始,删除第一个 / 号及右边的字符
结果是:http://www.hadoope.com
4.%% 号截取,删除右边字符,保留左边字符。
[root@crh_ops sh]# echo ${var%%/*}
http:
%%/* 表示从右边开始,删除最后(最左边)一个 / 号及右边的字符
结果是:http:
5.从左边第几个字符开始,及字符的个数。
[root@crh_ops sh]# echo ${var:0:5}
http:
其中的 0 表示左边第一个字符开始,5 表示字符的总个数。
结果是:http:
6.从左边第几个字符开始,一直到结束。
[root@crh_ops sh]# echo ${var:7}
www.hadoope.com/123.htm
其中的 7 表示左边第8个字符开始,一直到结束。
结果是 :www.hadoope.com/123.htm
7.从右边第几个字符开始,及字符的个数。
[root@crh_ops sh]# echo ${var:0-7:3}
123
其中的 0-7 表示右边算起第七个字符开始,3 表示字符的个数。
结果是:123
8.从右边第几个字符开始,一直到结束。
[root@crh_ops sh]# echo ${var:0-7}
123.htm
表示从右边第七个字符开始,一直到结束。
结果是:123.htm
注:(左边的第一个字符是用 0 表示,右边的第一个字符用 0-1 表示)
二、Linux Shell 字符串的拼接方法
如果想要在变量后面添加一个字符,可以用一下方法:
[root@crh_ops sh]# value1=home把要添加的字符串变量添加{},并且需要把$放到外面。这样输出的结果是:home=,也就是说连接成功。
[root@crh_ops sh]# value2=${value1}"="
[root@crh_ops sh]# var3=${var1}${var2}
[root@crh_ops sh]# echo $var3
home=
又如:
[root@crh_ops sh]# var1=/etc/原文地址:http://devopsh.com/860.html 收起阅读 »
[root@crh_ops sh]# var2=yum.repos.d/
[root@crh_ops sh]# var3=${var1}${var2}
[root@crh_ops sh]# echo $var3
/etc/yum.repos.d/
Nginx性能优化实例
WEB 性能优化是一个系统工程,涵盖很多方面,做好其中某个环节并不意味性能就能变好,但可以肯定地说,如果某个环节做得很糟糕,那么结果一定会变差。
首先说明下,本文提到的一些 Nginx 配置,需要较高版本 Linux 内核才支持。在实际生产环境中,升级服务器内核并不是一件容易的事,但为了获得最好的性能,有些升级还是必须的。很多公司服务器运维和项目开发并不在一个团队,一方追求稳定不出事故,另一方希望提升性能,本来就是矛盾的。那就商量着来吧!
Nginx 关于 TCP 的优化基本都是修改系统内核提供的配置项,所以跟具体的 Linux 版本和系统配置有关,对这一块还不是非常熟悉,这里只能简单介绍下:
TCP_NOPUSH 是 FreeBSD 的一个 socket 选项,对应 Linux 的 TCP_CORK,Nginx 里统一用 tcp_nopush 来控制它,并且只有在启用了 sendfile 之后才生效。启用它之后,数据包会累计到一定大小之后才会发送,减小了额外开销,提高网络效率。
TCP_NODELAY 也是一个 socket 选项,启用后会禁用 Nagle 算法,尽快发送数据,可以节约 200ms。Nginx 只会针对处于 keep-alive 状态的 TCP 连接才会启用 tcp_nodelay。
可以看到 TCP_NOPUSH 是要等数据包累积到一定大小才发送,TCP_NODELAY 是要尽快发送,二者相互矛盾。实际上,它们确实可以一起用,最终的效果是先填满包,再尽快发送。
关于这部分内容的更多介绍可以看这篇文章:http://t37.net/nginx-optimization-understanding-sendfile-tcp_nodelay-and-tcp_nopush.html
配置最后一行用来指定服务端为每个 TCP 连接最多可以保持多长时间。Nginx 的默认值是 75 秒,有些浏览器最多只保持 60 秒,所以我统一设置为 60。
另外,还有一个 TCP 优化策略叫 TCP Fast Open(TFO),这里先介绍下,配置在后面贴出。TFO 的作用是用来优化 TCP 握手过程。客户端第一次建立连接还是要走三次握手,所不同的是客户端在第一个 SYN 会设置一个 Fast Open 标识,服务端会生成 Fast Open Cookie 并放在 SYN-ACK 里,然后客户端就可以把这个 Cookie 存起来供之后的 SYN 用。下面这个图形象地描述了这个过程:
关于 TCP Fast Open 的更多信息,可以查看RFC7413,或者这篇文章:Shaving your RTT with TCP Fast Open.需要注意的是,现阶段只有 Linux、ChromeOS 和 Android 5.0 的 Chrome / Chromium 才支持 TFO,所以实际用途并不大。
5 月 26 日发布的 Nginx 1.9.1,增加了 reuseport 功能,意味着 Nginx 也开始支持 TCP 的 SO_REUSEPORT 选项了。这里也先简单介绍下,具体配置方法后面统一介绍。启用这个功能后,Nginx 会在指定的端口上监听多个 socket,每个 Worker 都能分到一个。请求过来时,系统内核会自动通过不同的 socket 分配给对应的 Worker,相比之前的单 socket 多 Worker 的模式,提高了分发效率。下面这个图形象地描述了这个过程:
有关这部分内容的更多信息,可以查看 Nginx 的官方博客:Socket Sharding in NGINX Release 1.9.1。
我们在上线前,代码(JS、CSS 和 HTML)会做压缩,图片也会做压缩(PNGOUT、Pngcrush、JpegOptim、Gifsicle 等)。对于文本文件,在服务端发送响应之前进行 GZip 压缩也很重要,通常压缩后的文本大小会减小到原来的 1/4 - 1/3。下面是我的配置:
gzip_vary 用来输出 Vary 响应头,用来解决某些缓存服务的一个问题.
gzip_disable 指令接受一个正则表达式,当请求头中的 UserAgent 字段满足这个正则时,响应不会启用 GZip,这是为了解决在某些浏览器启用 GZip 带来的问题。特别地,指令值 msie6 等价于 MSIE [4-6]\.,但性能更好一些。另外,Nginx 0.8.11 后,msie6 并不会匹配 UA 包含 SV1 的 IE6(例如 Windows XP SP2 上的 IE6),因为这个版本的 IE6 已经修复了关于 GZip 的若干 Bug。
优化代码逻辑的极限是移除所有逻辑;优化请求的极限是不发送任何请求。这两点通过缓存都可以实现。
评论部分也早就换成了 Disqus,所以完全可以将页面静态化,这样就省掉了所有代码逻辑和数据库开销。实现静态化有很多种方案,我直接用的是 Nginx 的 proxy_cache
默认情况下,如果响应头里有 Set-Cookie 字段,Nginx 并不会缓存这次响应,因为它认为这次响应的内容是因人而异的。我的博客中,这个 Set-Cookie 对于用户来说没有用,也不会影响输出内容,所以我通过配置 proxy_ignore_header 移除了它。
服务端在输出响应时,可以通过响应头输出一些与缓存有关的信息,从而达到少发或不发请求的目的。HTTP/1.1 的缓存机制稍微有点复杂,这里简单介绍下:
首先,服务端可以通过响应头里的 Last-Modified(最后修改时间) 或者 ETag(内容特征) 标记实体。浏览器会存下这些标记,并在下次请求时带上 If-Modified-Since: 上次 Last-Modified 的内容 或 If-None-Match: 上次 ETag 的内容,询问服务端资源是否过期。如果服务端发现并没有过期,直接返回一个状态码为 304、正文为空的响应,告知浏览器使用本地缓存;如果资源有更新,服务端返回状态码 200、新的 Last-Modified、Etag 和正文。这个过程被称之为 HTTP 的协商缓存,通常也叫做弱缓存。
可以看到协商缓存并不会节省连接数,但是在缓存生效时,会大幅减小传输内容(304 响应没有正文,一般只有几百字节)。另外为什么有两个响应头都可以用来实现协商缓存呢?这是因为一开始用的 Last-Modified 有两个问题:1)只能精确到秒,1 秒内的多次变化反映不出来;2)时间采用绝对值,如果服务端 / 客户端时间不对都可能导致缓存失效 在轮询的负载均衡算法中,如果各机器读到的文件修改时间不一致,有缓存无故失效和缓存不更新的风险。HTTP/1.1 并没有规定 ETag 的生成规则,而一般实现者都是对资源内容做摘要,能解决前面两个问题。
另外一种缓存机制是服务端通过响应头告诉浏览器,在什么时间之前(Expires)或在多长时间之内(Cache-Control: Max-age=xxx),不要再请求服务器了。这个机制我们通常称之为 HTTP 的强缓存。
一旦资源命中强缓存规则后,再次访问完全没有 HTTP 请求(Chrome 开发者工具的 Network 面板依然会显示请求,但是会注明 from cache;Firefox 的 firebug 也类似,会注明 BFCache),这会大幅提升性能。所以我们一般会对 CSS、JS、图片等资源使用强缓存,而入口文件(HTML)一般使用协商缓存或不缓存,这样可以通过修改入口文件中对强缓存资源的引入 URL 来达到即时更新的目的。
这里也解释下为什么有了 Expire,还要有 Cache-Control。也有两个原因:1)Cache-Control 功能更强大,对缓存的控制能力更强;2)Cache-Control 采用的 max-age 是相对时间,不受服务端 / 客户端时间不对的影响。
另外关于浏览器的刷新(F5 / cmd + r)和强刷(Ctrl + F5 / shift + cmd +r):普通刷新会使用协商缓存,忽略强缓存;强刷会忽略浏览器所有缓存(并且请求头会携带 Cache-Control:no-cache 和 Pragma:no-cache,用来通知所有中间节点忽略缓存)。只有从地址栏或收藏夹输入网址、点击链接等情况下,浏览器才会使用强缓存。
默认情况下,Nginx 对于静态资源都会输出 Last-Modified,而 ETag、Expire 和 Cache-Control 则需要自己配置:
我的博客之前多次讲到过 HTTP/2(SPDY),现阶段 Nginx 只支持 SPDY/3.1,这样配置就可以启用了(编译 Nginx 时需要加上 --with-http_spdy_module 和 --with-http_ssl_module):
reuseport 就是用来启用前面介绍过的 TCP SO_REUSEPORT 选项的配置。
建立 HTTPS 连接本身就慢(多了获取证书、校验证书、TLS 握手等等步骤),如果没有优化好只能是慢上加慢。
TLS 会话恢复的目的是为了简化 TLS 握手,有两种方案:Session Cache 和 Session Ticket。他们都是将之前握手的 Session 存起来供后续连接使用,所不同是 Cache 存在服务端,占用服务端资源;Ticket 存在客户端,不占用服务端资源。另外目前主流浏览器都支持 Session Cache,而 Session Ticket 的支持度一般。
ssl_stapling 开始的几行用来配置 OCSP stapling 策略。浏览器可能会在建立 TLS 连接时在线验证证书有效性,从而阻塞 TLS 握手,拖慢整体速度。OCSP stapling 是一种优化措施,服务端通过它可以在证书链中封装证书颁发机构的 OCSP(Online Certificate Status Protocol)响应,从而让浏览器跳过在线查询。服务端获取 OCSP 一方面更快(因为服务端一般有更好的网络环境),另一方面可以更好地缓存。
这些策略设置好之后,可以通过 Qualys SSL Server Test 这个工具来验证是否生效,例如下图就是本博客的测试结果(via):
在给 Nginx 指定证书时,需要选择合适的证书链。因为浏览器在验证证书信任链时,会从站点证书开始,递归验证父证书,直至信任的根证书。这里涉及到两个问题:1)服务器证书是在握手期间发送的,由于 TCP 初始拥塞窗口的存在,如果证书太长很可能会产生额外的往返开销;2)如果服务端证书没包含中间证书,大部分浏览器可以正常工作,但会暂停验证并根据子证书指定的父证书 URL 自己获取中间证书。这个过程会产生额外的 DNS 解析、建立 TCP 连接等开销。配置服务端证书链的最佳实践是包含站点证书和中间证书两部分。有的证书提供商签出来的证书级别比较多,这会导致证书链变长,选择的时候需要特别注意。
收起阅读 »
首先说明下,本文提到的一些 Nginx 配置,需要较高版本 Linux 内核才支持。在实际生产环境中,升级服务器内核并不是一件容易的事,但为了获得最好的性能,有些升级还是必须的。很多公司服务器运维和项目开发并不在一个团队,一方追求稳定不出事故,另一方希望提升性能,本来就是矛盾的。那就商量着来吧!
TCP 优化
Nginx 关于 TCP 的优化基本都是修改系统内核提供的配置项,所以跟具体的 Linux 版本和系统配置有关,对这一块还不是非常熟悉,这里只能简单介绍下:
http {第一行的 sendfile 配置可以提高 Nginx 静态资源托管效率。sendfile 是一个系统调用,直接在内核空间完成文件发送,不需要先 read 再 write,没有上下文切换开销。
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 60;
... ...
}
TCP_NOPUSH 是 FreeBSD 的一个 socket 选项,对应 Linux 的 TCP_CORK,Nginx 里统一用 tcp_nopush 来控制它,并且只有在启用了 sendfile 之后才生效。启用它之后,数据包会累计到一定大小之后才会发送,减小了额外开销,提高网络效率。
TCP_NODELAY 也是一个 socket 选项,启用后会禁用 Nagle 算法,尽快发送数据,可以节约 200ms。Nginx 只会针对处于 keep-alive 状态的 TCP 连接才会启用 tcp_nodelay。
可以看到 TCP_NOPUSH 是要等数据包累积到一定大小才发送,TCP_NODELAY 是要尽快发送,二者相互矛盾。实际上,它们确实可以一起用,最终的效果是先填满包,再尽快发送。
关于这部分内容的更多介绍可以看这篇文章:http://t37.net/nginx-optimization-understanding-sendfile-tcp_nodelay-and-tcp_nopush.html
配置最后一行用来指定服务端为每个 TCP 连接最多可以保持多长时间。Nginx 的默认值是 75 秒,有些浏览器最多只保持 60 秒,所以我统一设置为 60。
另外,还有一个 TCP 优化策略叫 TCP Fast Open(TFO),这里先介绍下,配置在后面贴出。TFO 的作用是用来优化 TCP 握手过程。客户端第一次建立连接还是要走三次握手,所不同的是客户端在第一个 SYN 会设置一个 Fast Open 标识,服务端会生成 Fast Open Cookie 并放在 SYN-ACK 里,然后客户端就可以把这个 Cookie 存起来供之后的 SYN 用。下面这个图形象地描述了这个过程:
关于 TCP Fast Open 的更多信息,可以查看RFC7413,或者这篇文章:Shaving your RTT with TCP Fast Open.需要注意的是,现阶段只有 Linux、ChromeOS 和 Android 5.0 的 Chrome / Chromium 才支持 TFO,所以实际用途并不大。
5 月 26 日发布的 Nginx 1.9.1,增加了 reuseport 功能,意味着 Nginx 也开始支持 TCP 的 SO_REUSEPORT 选项了。这里也先简单介绍下,具体配置方法后面统一介绍。启用这个功能后,Nginx 会在指定的端口上监听多个 socket,每个 Worker 都能分到一个。请求过来时,系统内核会自动通过不同的 socket 分配给对应的 Worker,相比之前的单 socket 多 Worker 的模式,提高了分发效率。下面这个图形象地描述了这个过程:
有关这部分内容的更多信息,可以查看 Nginx 的官方博客:Socket Sharding in NGINX Release 1.9.1。
开启 Gzip
我们在上线前,代码(JS、CSS 和 HTML)会做压缩,图片也会做压缩(PNGOUT、Pngcrush、JpegOptim、Gifsicle 等)。对于文本文件,在服务端发送响应之前进行 GZip 压缩也很重要,通常压缩后的文本大小会减小到原来的 1/4 - 1/3。下面是我的配置:
http {这部分内容比较简单,只有两个地方需要解释下:
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_min_length 1000;
gzip_proxied any;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
... ...
}
gzip_vary 用来输出 Vary 响应头,用来解决某些缓存服务的一个问题.
gzip_disable 指令接受一个正则表达式,当请求头中的 UserAgent 字段满足这个正则时,响应不会启用 GZip,这是为了解决在某些浏览器启用 GZip 带来的问题。特别地,指令值 msie6 等价于 MSIE [4-6]\.,但性能更好一些。另外,Nginx 0.8.11 后,msie6 并不会匹配 UA 包含 SV1 的 IE6(例如 Windows XP SP2 上的 IE6),因为这个版本的 IE6 已经修复了关于 GZip 的若干 Bug。
开启缓存
优化代码逻辑的极限是移除所有逻辑;优化请求的极限是不发送任何请求。这两点通过缓存都可以实现。
服务端
评论部分也早就换成了 Disqus,所以完全可以将页面静态化,这样就省掉了所有代码逻辑和数据库开销。实现静态化有很多种方案,我直接用的是 Nginx 的 proxy_cache
proxy_cache_path /home/jerry/cache/nginx/proxy_cache_path levels=1:2 keys_zone=pnc:300m inactive=7d max_size=10g;首先,在配置最外层定义一个缓存目录,并指定名称(keys_zone)和其他属性,这样在配置 proxy_pass 时,就可以使用这个缓存了。这里我对状态值等于 200 和 304 的响应缓存了 2 小时。
proxy_temp_path /home/jerry/cache/nginx/proxy_temp_path;
proxy_cache_key $host$uri$is_args$args;
server {
location / {
resolver 127.0.0.1;
proxy_cache pnc;
proxy_cache_valid 200 304 2h;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_use_stale updating error timeout invalid_header http_500 http_502;
proxy_http_version 1.1;
proxy_ignore_headers Set-Cookie;
... ...
}
... ...
}
默认情况下,如果响应头里有 Set-Cookie 字段,Nginx 并不会缓存这次响应,因为它认为这次响应的内容是因人而异的。我的博客中,这个 Set-Cookie 对于用户来说没有用,也不会影响输出内容,所以我通过配置 proxy_ignore_header 移除了它。
客户端
服务端在输出响应时,可以通过响应头输出一些与缓存有关的信息,从而达到少发或不发请求的目的。HTTP/1.1 的缓存机制稍微有点复杂,这里简单介绍下:
首先,服务端可以通过响应头里的 Last-Modified(最后修改时间) 或者 ETag(内容特征) 标记实体。浏览器会存下这些标记,并在下次请求时带上 If-Modified-Since: 上次 Last-Modified 的内容 或 If-None-Match: 上次 ETag 的内容,询问服务端资源是否过期。如果服务端发现并没有过期,直接返回一个状态码为 304、正文为空的响应,告知浏览器使用本地缓存;如果资源有更新,服务端返回状态码 200、新的 Last-Modified、Etag 和正文。这个过程被称之为 HTTP 的协商缓存,通常也叫做弱缓存。
可以看到协商缓存并不会节省连接数,但是在缓存生效时,会大幅减小传输内容(304 响应没有正文,一般只有几百字节)。另外为什么有两个响应头都可以用来实现协商缓存呢?这是因为一开始用的 Last-Modified 有两个问题:1)只能精确到秒,1 秒内的多次变化反映不出来;2)时间采用绝对值,如果服务端 / 客户端时间不对都可能导致缓存失效 在轮询的负载均衡算法中,如果各机器读到的文件修改时间不一致,有缓存无故失效和缓存不更新的风险。HTTP/1.1 并没有规定 ETag 的生成规则,而一般实现者都是对资源内容做摘要,能解决前面两个问题。
另外一种缓存机制是服务端通过响应头告诉浏览器,在什么时间之前(Expires)或在多长时间之内(Cache-Control: Max-age=xxx),不要再请求服务器了。这个机制我们通常称之为 HTTP 的强缓存。
一旦资源命中强缓存规则后,再次访问完全没有 HTTP 请求(Chrome 开发者工具的 Network 面板依然会显示请求,但是会注明 from cache;Firefox 的 firebug 也类似,会注明 BFCache),这会大幅提升性能。所以我们一般会对 CSS、JS、图片等资源使用强缓存,而入口文件(HTML)一般使用协商缓存或不缓存,这样可以通过修改入口文件中对强缓存资源的引入 URL 来达到即时更新的目的。
这里也解释下为什么有了 Expire,还要有 Cache-Control。也有两个原因:1)Cache-Control 功能更强大,对缓存的控制能力更强;2)Cache-Control 采用的 max-age 是相对时间,不受服务端 / 客户端时间不对的影响。
另外关于浏览器的刷新(F5 / cmd + r)和强刷(Ctrl + F5 / shift + cmd +r):普通刷新会使用协商缓存,忽略强缓存;强刷会忽略浏览器所有缓存(并且请求头会携带 Cache-Control:no-cache 和 Pragma:no-cache,用来通知所有中间节点忽略缓存)。只有从地址栏或收藏夹输入网址、点击链接等情况下,浏览器才会使用强缓存。
默认情况下,Nginx 对于静态资源都会输出 Last-Modified,而 ETag、Expire 和 Cache-Control 则需要自己配置:
location ~ ^/static/ {expires 指令可以指定具体的 max-age,例如 10y 代表 10 年,如果指定为 max,最终输出的 Expires 会是 2037 年最后一天,Cache-Control 的 max-age 会是 10 年(准确说是 3650 天,315360000 秒)。
root /home/jerry/www/blog/www;
etag on;
expires max;
}
使用 SPDY(HTTP/2)
我的博客之前多次讲到过 HTTP/2(SPDY),现阶段 Nginx 只支持 SPDY/3.1,这样配置就可以启用了(编译 Nginx 时需要加上 --with-http_spdy_module 和 --with-http_ssl_module):
server {那个 fastopen=3 用来开启前面介绍过的 TCP Fast Open 功能。3 代表最多只能有 3 个未经三次握手的 TCP 链接在排队。超过这个限制,服务端会退化到采用普通的 TCP 握手流程。这是为了减少资源耗尽攻击:TFO 可以在第一次 SYN 的时候发送 HTTP 请求,而服务端会校验 Fast Open Cookie(FOC),如果通过就开始处理请求。如果不加限制,恶意客户端可以利用合法的 FOC 发送大量请求耗光服务端资源。
listen 443 ssl spdy fastopen=3 reuseport;
spdy_headers_comp 6;
... ...
}
reuseport 就是用来启用前面介绍过的 TCP SO_REUSEPORT 选项的配置。
HTTPS 优化
建立 HTTPS 连接本身就慢(多了获取证书、校验证书、TLS 握手等等步骤),如果没有优化好只能是慢上加慢。
server {我的这部分配置就两部分内容:TLS 会话恢复和 OCSP stapling。
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /xxx/root.crt;
resolver 8.8.4.4 8.8.8.8 valid=300s;
resolver_timeout 10s;
... ...
}
TLS 会话恢复的目的是为了简化 TLS 握手,有两种方案:Session Cache 和 Session Ticket。他们都是将之前握手的 Session 存起来供后续连接使用,所不同是 Cache 存在服务端,占用服务端资源;Ticket 存在客户端,不占用服务端资源。另外目前主流浏览器都支持 Session Cache,而 Session Ticket 的支持度一般。
ssl_stapling 开始的几行用来配置 OCSP stapling 策略。浏览器可能会在建立 TLS 连接时在线验证证书有效性,从而阻塞 TLS 握手,拖慢整体速度。OCSP stapling 是一种优化措施,服务端通过它可以在证书链中封装证书颁发机构的 OCSP(Online Certificate Status Protocol)响应,从而让浏览器跳过在线查询。服务端获取 OCSP 一方面更快(因为服务端一般有更好的网络环境),另一方面可以更好地缓存。
这些策略设置好之后,可以通过 Qualys SSL Server Test 这个工具来验证是否生效,例如下图就是本博客的测试结果(via):
在给 Nginx 指定证书时,需要选择合适的证书链。因为浏览器在验证证书信任链时,会从站点证书开始,递归验证父证书,直至信任的根证书。这里涉及到两个问题:1)服务器证书是在握手期间发送的,由于 TCP 初始拥塞窗口的存在,如果证书太长很可能会产生额外的往返开销;2)如果服务端证书没包含中间证书,大部分浏览器可以正常工作,但会暂停验证并根据子证书指定的父证书 URL 自己获取中间证书。这个过程会产生额外的 DNS 解析、建立 TCP 连接等开销。配置服务端证书链的最佳实践是包含站点证书和中间证书两部分。有的证书提供商签出来的证书级别比较多,这会导致证书链变长,选择的时候需要特别注意。
原文作者:QuQu
分享原文地址:https://imququ.com/post/my-nginx-conf-for-wpo.html
收起阅读 »
Centos下扩展php的memcache模块脚本
#!/bin/bash
#安装memcache的支持libevent
yum -y install libevent
#验证
ls -al /usr/lib | grep libevent || echo "libevent install failed"
#下载安装php的memcache扩展
yum -y install gcc gcc-c++ autoconf
cd /usr/local/src/;wget http://pecl.php.net/get/memcache-2.2.7.tgz
tar zxf memcache-2.2.7.tgz
cd memcache-2.2.7
phpize
./configure
make && make install
echo "extension=memcache.so" >> /etc/php.ini
php验证脚本
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");
$memcache->set('key', 'test');
$get_value = $memcache->get('key');
echo $get_value;
?>
收起阅读 »
Container引发的一场变革
Yii 1.x、thinkPHP、CodeIgniter在PHP 5.3之前,MVC的实现算是比较的足规中矩,大体解决办法是从REQUEST_URI中提取uri,根据uri规则分解出contraller、action、params或者还有app(Yaf)。逻辑也比较清晰,用notepad++就可以了解MVC的结构和主体思想。
现在的MVC框架随着PHP版本的升级,支持的特性越来越多,尤其匿名函数这概念的引入,使得服务容器Container在众多一流MVC新版本中极为受宠。laravel的Illuminate/Container使得laravel 5可以让开发者非常灵活组合地使用composer组件。其他如Symfony 2的Component/DependencyInjection/ContainerBuilder和Yii 2的di/Container各家都做了自己的实现。
服务容器也叫IoC 容器,或者另外一些说法叫控制反转、依赖注入。暂时叫依赖注入,这名字更贴切的表达服务容器的使命:为解决依赖而生。
先来简释Yii2的Container,事实上Yii2的容器实现非常复杂。以Controller::behaviors()这方法说起,先看下:
再细看Container::get($class, $params = , $config = )如何实现服务容器的。三个参数里config其实对于BaseYii::createObject()是暂时没用的,先关注前面两个参数。
好吧,那所有的都走一次Container::get($class, $params),满足了吧。所以也就有了Container::build($class, $params)方法里先解决一层AccessControl依赖,接着再来一次解决依赖Container::resolveDependencies()
在我们看代码之前,试想下,如果自己要实现一个服务容器,分别支持这三种类型该如何设计?实例不须做工作,先记录保存;字符串类名需要特别细心,通过上述层层依赖的反射最终可以解决;剩下的闭包可以通过call_user_func处理匿名函数就可以得到最终的实例。现在验证下Yii 2是不是也这样的策略。
在我们马上要彻底分析Container::get之前,还有一些工作需要我们理清楚的。get相当于依赖解析和实例化对象,而之前还有一个工作就是注入。到现在我们也还没有对注入进行分析,而在PHP中设计一个服务容器支持上面提到的三种类型,注入是重要的入口,只有注入优雅了,依赖解析才会优雅。
Yii 2的注入是在Container::set中实现,Container::set($class, $params)都支持哪些类注册方式?以达到我们可以随意的Container::get呢?我简单分类说明,代码可以忽略,以下注释就是对Container::set中唯一一个方法normalizeDefinition($class, $definition)对定义进行规范化处理的实例版本。
收起阅读 »
现在的MVC框架随着PHP版本的升级,支持的特性越来越多,尤其匿名函数这概念的引入,使得服务容器Container在众多一流MVC新版本中极为受宠。laravel的Illuminate/Container使得laravel 5可以让开发者非常灵活组合地使用composer组件。其他如Symfony 2的Component/DependencyInjection/ContainerBuilder和Yii 2的di/Container各家都做了自己的实现。
服务容器也叫IoC 容器,或者另外一些说法叫控制反转、依赖注入。暂时叫依赖注入,这名字更贴切的表达服务容器的使命:为解决依赖而生。
先来简释Yii2的Container,事实上Yii2的容器实现非常复杂。以Controller::behaviors()这方法说起,先看下:
public function behaviors() {这就得一路追起来
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'actions' => ['login', 'error'],
'allow' => true,
],
[
'actions' => ['logout', 'index'],
'allow' => true,
'roles' => ['@'],
],
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['post'],
],
],
];
}
- []yii\web\Application[/][]yii\base\Application::run()[/][]yii\base\Component::trigger()[/][]yii\base\Component::ensureBehaviors()[/][]yii\base\Component::attachBehaviorInternal()(到现在终于才看到controller::behavior()的影子)[/][]yii\BaseYii::createObject()(终于是服务容器登场了)[/][]yii\di\Container::get()[/]
再细看Container::get($class, $params = , $config = )如何实现服务容器的。三个参数里config其实对于BaseYii::createObject()是暂时没用的,先关注前面两个参数。
# 1、对于单例(对象)来说,无须检查依赖和参数传递Container::build()这个对象得需要知道这个对象的依赖关系:Container::getDependencies($class)
if (isset($this->_singletons[$class])) {
return $this->_singletons[$class];
# 2、好吧,首次创建这个对象
} elseif (!isset($this->_definitions[$class])) {
return $this->build($class, $params, $config);
}
$dependencies = ;此时已经得到一个AccessControl的反射类和相关依赖,好吧,回头一看Controller::behaviors()应该可以由服务容器提供一个AccessControl了吧。细心的同学在上面获取依赖类的过程,有一个细节:创建类Far是用了Instance::of('Far'),只是一个$id = 'Far'的Instance。并不是真正的Far类,而且能想到这个Far实例会不会也像AccessControl一样也有依赖呢?啊,这样下去还有完没完了!
# 先建立对象反射
$reflection = new ReflectionClass($class);
# 获取这个对象的初始化__construct(Foo $foo, $level = 0)依赖条件
$constructor = $reflection->getConstructor();
if ($constructor !== null) {
foreach ($constructor->getParameters() as $param) {
# 有默认值的好说,如level = 0
if ($param->isDefaultValueAvailable()) {
$dependencies = $param->getDefaultValue();
# 否则,我们得知道这个依赖的类是什么,如:类Far,并且创建这个对象Instance::of('Foo')
} else {
$c = $param->getClass();
$dependencies = Instance::of($c === null ? null : $c->getName());
}
}
}
# 记录$_reflections、$_dependencies并返回
$this->_reflections[$class] = $reflection;
$this->_dependencies[$class] = $dependencies;
好吧,那所有的都走一次Container::get($class, $params),满足了吧。所以也就有了Container::build($class, $params)方法里先解决一层AccessControl依赖,接着再来一次解决依赖Container::resolveDependencies()
list ($reflection, $dependencies) = $this->getDependencies($class);具体看Container::resolveDependencies()的实现
...
$dependencies = $this->resolveDependencies($dependencies, $reflection);
/**Container::build($class, $params)已经被打断两次了,好不容易把依赖都解决完了,终于可以创建最开始的实例AccessControl了。
* Resolves dependencies by replacing them with the actual object instances.
* 以最终实例化的对象来填充类的依赖
* @param array $dependencies the dependencies
* @param ReflectionClass $reflection the class reflection associated with the dependencies
* @return array the resolved dependencies
* @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
*/
protected function resolveDependencies($dependencies, $reflection = null) {
foreach ($dependencies as $index => $dependency) {
if ($dependency instanceof Instance) {
if ($dependency->id !== null) {
# 取回$id = 'Far'值,重新Container::get('Far')得到真正的实例,新的一轮Container::get()又开始了,直到所有依赖的依赖的依赖...都被解决
$dependencies[$index] = $this->get($dependency->id);
} elseif ($reflection !== null) {
$name = $reflection->getConstructor()->getParameters()[$index]->getName();
$class = $reflection->getName();
throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");
}
}
}
return $dependencies;
}
# 打断:解决依赖纵观上面服务容器可以支持实例singleton、类名string,但还不能支持闭包clourse,那Yii 2怎么好意思呢?刚才追到了Container::build($class, $params, $config),现在稍微回溯一级到Container::get($class, $params, $config)
list ($reflection, $dependencies) = $this->getDependencies($class);
...
# 打断:解决依赖的依赖...
$dependencies = $this->resolveDependencies($dependencies, $reflection);
# 利用反射类实例对象,顺手把$config数据元素赋到对象的属性
if (!empty($config) && !empty($dependencies) && is_a($class, 'yii\base\Object', true)) {
// set $config as the last parameter (existing one will be overwritten)
$dependencies[count($dependencies) - 1] = $config;
return $reflection->newInstanceArgs($dependencies);
} else {
$object = $reflection->newInstanceArgs($dependencies);
foreach ($config as $name => $value) {
$object->$name = $value;
}
return $object;
}
在我们看代码之前,试想下,如果自己要实现一个服务容器,分别支持这三种类型该如何设计?实例不须做工作,先记录保存;字符串类名需要特别细心,通过上述层层依赖的反射最终可以解决;剩下的闭包可以通过call_user_func处理匿名函数就可以得到最终的实例。现在验证下Yii 2是不是也这样的策略。
在我们马上要彻底分析Container::get之前,还有一些工作需要我们理清楚的。get相当于依赖解析和实例化对象,而之前还有一个工作就是注入。到现在我们也还没有对注入进行分析,而在PHP中设计一个服务容器支持上面提到的三种类型,注入是重要的入口,只有注入优雅了,依赖解析才会优雅。
Yii 2的注入是在Container::set中实现,Container::set($class, $params)都支持哪些类注册方式?以达到我们可以随意的Container::get呢?我简单分类说明,代码可以忽略,以下注释就是对Container::set中唯一一个方法normalizeDefinition($class, $definition)对定义进行规范化处理的实例版本。
#A:初级版本的注册,直接一个命名空间的类名,毫无挑战性,甚至都没有注册的必要既然注入是这么简单的规则,学习成本小,接下来的最后依赖解析Container::get($class, $params, $config)的完整代码。
$container->set('yii\db\Connection');
// register an interface
// When a class depends on the interface, the corresponding class
// will be instantiated as the dependent object
#B:对于用接口作为类型约束,那实例化时可不能对接口进行实例化,需要根据实际的继承类来实例化。
# 如:__construct(yii\mail\MailInterface $mailer),而最终实例化的是yii\swiftmailer\Mailer
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');
// register an alias name. You can use $container->get('db')
// to create an instance of Connection
#C:如果你觉得yii\db\Connection这货名字太长,可以别名为db,这样在model层就可以随意的$container->get('db')
$container->set('db', 'yii\db\Connection');
// register a class with configuration. The configuration
// will be applied when the class is instantiated by get()
#D:如果对于A版本,没法满足你了,需要在注入时就初始化该的一些属性,使用$params数组即可
$container->set('yii\db\Connection', [
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
// register an alias name with class configuration
// In this case, a "class" element is required to specify the class
#E:如果你想要C+D这种结合体,当然也可以,请在$params的key为class标明你原始类名
$container->set('db', [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
// register a PHP callable
// The callable will be executed when $container->get('db') is called
#F:总会有一些任性的同学,我想要自定义类,那总得支持吧,请使用闭包函数吧
# 虽然它不会像js那些做到真正的回调,但变量的作用域的思想是一致的
$container->set('db', function ($container, $params, $config) {
return new \yii\db\Connection($config);
});
/**
* Returns an instance of the requested class.
* 所谓好的IoC就是能static::get('啥都有')都能return正确的对象
*/
public function get($class, $params = , $config = ) {
# 1、对于单例(对象)来说,无须检查依赖和参数传递
if (isset($this->_singletons[$class])) {
return $this->_singletons[$class];
# 2、好吧,首次创建这个对象
} elseif (!isset($this->_definitions[$class])) {
return $this->build($class, $params, $config);
}
# 3、为什么已定义的对象不直接给返回?
# 此定义非已经创建过对象这种定义,而是Container::set($class, $definition, $params)注册了一个$class而已,跟laravel的bind相像。
$definition = $this->_definitions[$class];
# 4、$definition为闭包函数:
if (is_callable($definition, true)) {
$params = $this->resolveDependencies($this->mergeParams($class, $params));
$object = call_user_func($definition, $this, $params, $config);
# 5、$definition为数组:
} elseif (is_array($definition)) {
# 数组中必须要给出一个key为class的类名
$concrete = $definition['class'];
unset($definition['class']);
$config = array_merge($definition, $config);
$params = $this->mergeParams($class, $params);
# 对于没有别名的,可以直接创建该对象
if ($concrete === $class) {
$object = $this->build($class, $params, $config);
# 别名为什么要递归?而不是$this->build(concrete, $params, $config)
#
} else {
$object = $this->get($concrete, $params, $config);
}
# 6、$definition为对象:
} elseif (is_object($definition)) {
return $this->_singletons[$class] = $definition;
} else {
throw new InvalidConfigException("Unexpected object definition type: " . gettype($definition));
}
# 更新单例记录的对象,取最后更新值。假设Foo::__construct($level = 0) {}
# 当同一进程中有$this->_singletons['Foo'] = new Foo(1);
# 现在Container::get('Foo')
# 此时$this->_singletons[$class] = new Foo(0);
if (array_key_exists($class, $this->_singletons)) {
// singleton
$this->_singletons[$class] = $object;
}
return $object;
}
原文作者:花满树
分享原文链接:http://blog.huamanshu.com/?date=2015-06-26
收起阅读 »