OpenResty-实战
原创地址:http://jinnianshilongnian.iteye.com/blog/2190344
常用命令:
# vi /usr/local/nginx/conf/nginx.conf
# vi /usr/server/example/example.conf
# /usr/local/nginx/sbin/nginx -s reload & tail -f /usr/local/nginx/logs/error.log
目录:
1.OpenRestry(Nginx+Lua)开发环境
2.OpenRestry开发入门
3.Lua模块开发
3.1 常用Lua开发库1-redis、mysql、http客户端 附-Redis/SSDB+Twemproxy安装与使用
3.2 常用Lua开发库2-JSON库、编码转换、字符串处理
3.3 常用Lua开发库3-模板渲染
4.实战
4.1 Web开发实战1——HTTP服务
4.2 Web开发实战2——商品详情页
4.3 流量复制/AB测试/协程
4.1 Web开发实战1——HTTP服务
标注:
这里的HTTP服务不是指的web页面,而是页面中异步加载的相关数据,比如热门搜索、用户登录、实时价格、实时库存、服务支持、广告语等。
这些服务的特点:访问量巨大、逻辑比较单一;但是如实时库存逻辑其实是非常复杂的。
1.良好的架构
京东这些服务每天有几亿十几亿的访问量,比如实时库存服务曾经在没有任何IP限流、DDos防御的情况被刷到600多万/分钟的访问量,而且能轻松应对。
支撑如此大的访问量就需要考虑设计良好的架构,并很容易实现水平扩展。
2.几种架构形式
3.实践架构:Nginx+Lua+ Redis集群+Mysql集群架构
3.1 图
>>>>>需求:
假设京东有10亿商品,那么广告词极限情况是10亿;所以在设计时就要考虑:
1、数据量,数据更新是否频繁且更新量是否很大;
2、是K-V还是关系,是否需要批量获取,是否需要按照规则查询。
而对于本例,
(1)广告词更新量不会很大,每分钟可能在几万左右;
(2)而且是K-V的,其实适合使用关系存储;
(3)因为广告词是商家维护,因此后台查询需要知道这些商品是哪个商家的;而对于前台是不关心商家的,是KV存储,所以前台显示的可以放进如Redis中。
即存在两种设计:
1、所有数据存储到Mysql,然后热点数据加载到Redis;
2、关系存储到Mysql,而数据存储到如SSDB这种持久化KV存储中。
基本数据结构:商品ID、广告词、所属商家、开始时间、结束时间、是否有效
>>>>>>>>后台逻辑
1、商家登录后台;
2、按照商家分页查询商家数据,此处要按照商品关键词或商品类目查询的话,需要走商品系统的搜索子系统,如通过Solr或elasticsearch实现搜索子系统;
3、进行广告词的增删改查;
4、增删改时可以直接更新Redis缓存或者只删除Redis缓存(第一次前台查询时写入缓存);
>>>>>>>>前台逻辑
1、首先Nginx通过Lua查询Redis缓存;
2、查询不到的话回源到Tomcat,Tomcat读取数据库查询到数据,然后把最新的数据异步写入Redis(一般设置过期时间,如5分钟);此处设计时要考虑假设Tomcat读取Mysql的极限值是多少,然后设计降级开关,如假设每秒回源达到100,则直接不查询Mysql而返回空的广告词来防止Tomcat应用雪崩。
为了简单,我们不进行后台的设计实现,只做前端的设计实现,此时数据结构我们简化为[商品ID、广告词]。另外有朋友可能看到了,可以直接把Tomcat部分干掉,通过Lua直接读取Mysql进行回源实现。为了完整性此处我们还是做回源到Tomcat的设计,因为如果逻辑比较复杂的话或一些限制(比如使用Java特有协议的RPC)还是通过Java去实现更方便一些。
>>>>>>>>项目搭建
/usr/chapter6
redis_6660.conf
redis_6661.conf
nginx_chapter6.conf
nutcracker.yml
nutcracker.init
webapp
WEB-INF
lib
classes
web.xml
>>>>>>>>>>Redis+Twemproxy配置
此处根据实际情况来决定Redis大小,此处我们已两个Redis实例(6660、6661),在Twemproxy上通过一致性Hash做分片逻辑。
安装:之前已经介绍过Redis和Twemproxy的安装了。
1.Redis配置redis_6660.conf和redis_6661.conf
#分别为6660 6661
port 6660
#进程ID 分别改为redis_6660.pid redis_6661.pid
pidfile "/var/run/redis_6660.pid"
#设置内存大小,根据实际情况设置,此处测试仅设置20mb
maxmemory 20mb
#内存不足时,按照过期时间进行LRU删除
maxmemory-policy volatile-lru
#Redis的过期算法不是精确的而是通过采样来算的,默认采样为3个,此处我们改成10
maxmemory-samples 10
#不进行RDB持久化
save “”
#不进行AOF持久化
appendonly no
注:将如上配置放到redis_6660.conf和redis_6661.conf配置文件最后即可,后边的配置会覆盖前边的。
2.Twemproxy配置nutcracker.yml
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
timeout: 1000
servers:
- 127.0.0.1:6660:1 server1
- 127.0.0.1:6661:1 server2
复制nutcracker.init到/usr/chapter6下,并修改配置文件为/usr/chapter6/nutcracker.yml。
3.启动
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/chapter6/redis_6660.conf &
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/chapter6/redis_6661.conf &
/usr/chapter6/nutcracker.init start 注:1.需要赋予执行权限,比如755; 2.${prog} $OPTIONS--->/usr/servers/twemproxy-0.4.0/src/${prog} $OPTIONS
ps -aux | grep -e redis -e nutcracker
>>>>>>>>>Mysql+Atlas配置
Atlas类似于Twemproxy,是Qihoo 360基于Mysql Proxy开发的一个Mysql中间件,据称每天承载读写请求数达几十亿,可以实现分表、分库(sharding版本)、读写分离、数据库连接池等功能,
缺点:没有实现跨库分表功能,需要在客户端使用分库逻辑,目前Atlas不活跃。
另一个选择是使用如阿里的TDDL,它是在客户端完成之前说的功能。到底选择是在客户端还是在中间件根据实际情况选择。
此处我们不做Mysql的主从复制(读写分离),只做分库分表实现。
Mysql初始化:为了测试我们此处分两个表(做分库分表)
===实践:在单机的mysql上运行===
CREATE DATABASE chapter6 DEFAULT CHARACTER SET utf8;
use chapter6;
CREATE TABLE chapter6.ad_0(
sku_id BIGINT,
content VARCHAR(4000)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE chapter6.ad_1(
sku_id BIGINT,
content VARCHAR(4000)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
【Atlas安装与配置】
前提得配置好主从:http://www.cnblogs.com/super-d2/p/4802990.html
首先,先去下载Altas的rpm包,下载地址:https://github.com/Qihoo360/Atlas/releases
要看清楚版本,centos 5.x:Atlas-1.0.3.el5.x86_64.rpm centos 6.x:
我的系统是centos 6.4 ,所以下载Atlas-1.0.3.el6.x86_64.rpm
千万别搞错了,否则最后启动会出错。大家也可以采取源码编译安装,不过rpm安装比较省时省力,而且简单。
下载rpm
wget https://github.com/Qihoo360/Atlas/releases/download/1.0.3/Atlas-1.0.3.el6.x86_64.rpm
安装
rpm -i Atlas-1.0.3.el6.x86_64.rpm
安装的目录是/usr/local/mysql-proxy
conf文件夹下有一个自带的配置文件test.cnf,我们可以直接修改,下面是我修改的
主数据库服务器:192.168.83.11
从数据库服务器:192.168.83.12
proxy服务器:192.168.83.13
复制代码
[mysql-proxy]
#Atlas加载的模块名称,不需要改
plugins = admin, proxy
#管理接口的用户名
admin-username = root
#管理接口的密码
admin-password = 123456
#实现管理接口的Lua脚本所在路径
admin-lua-script = /usr/local/mysql-proxy/lib/mysql-proxy/lua/admin.lua
#Atlas后端连接的MySQL主库的IP和端口,可设置多项,用逗号分隔
proxy-backend-addresses = 192.168.83.11:3306
#Atlas后端连接的MySQL从库的IP和端口,@后面的数字代表权重,用来作负载均衡,若省略则默认为1,可设置多项,用逗号分隔
proxy-read-only-backend-addresses = 192.168.83.12:3306@1
#设置Atlas的运行方式,设为true时为守护进程方式,设为false时为前台方式,一般开发调试时设为false,线上运行时设为true
daemon = false
#设置Atlas的运行方式,设为true时Atlas会启动两个进程,一个为monitor,一个为worker,monitor在worker意外退出后会自动将其重启,设为false时只有worker,没有monitor,一般开发调试时设为false,线上运行时设为true
keepalive = false
#工作线程数,推荐设置与系统的CPU核数相等
event-threads = 4
#日志级别,分为message、warning、critical、error、debug五个级别
log-level = message
#日志存放的路径
log-path = /usr/local/mysql-proxy/log
#实例名称,用于同一台机器上多个Atlas实例间的区分
instance = test
#Atlas监听的工作接口IP和端口
proxy-address = 0.0.0.0:1234
#Atlas监听的管理接口IP和端口
admin-address = 0.0.0.0:2345
#连接池的最小空闲连接数,应设为event-threads的整数倍,可根据业务请求量大小适当调大或调小
min-idle-connections = 8
#分表设置,此例中person为库名,mt为表名,id为分表字段,3为子表数量,可设置多项,以逗号分隔,若不分表则不需要设置该项
#tables = person.mt.id.3
#用户名与其对应的加密过的MySQL密码,密码使用PREFIX/bin目录下的加密程序encrypt加密,此设置项用于多个用户名同时访问同一个Atlas实例的情况,若只有一个用户名>则不需要设置该项
#pwds = user1:+jKsgB3YAG8=, user2:GS+tr4TPgqc=
#默认字符集,若不设置该项,则默认字符集为latin1
charset = utf8
#允许连接Atlas的客户端的IP,可以是精确IP,也可以是IP段,以逗号分隔,若不设置该项则允许所有IP连接,否则只允许列表中的IP连接
#client-ips = 127.0.0.1, 192.168.1
#Atlas前面挂接的LVS的物理网卡的IP(注意不是虚IP),若有LVS且设置了client-ips则此项必须设置,否则可以不设置
#lvs-ips = 192.168.1.1
复制代码
最后,进入到bin文件夹下,启动Altas
./mysql-proxy --defaults-file=../conf/test.cnf
最好配置文件中的 daemon设置为true,为后台守护运行。
文件目录:/usr/local/mysql-proxy
启动:
使用官网的
/usr/local/mysql-proxy/bin/mysql-proxyd test start
查看Altas运行情况
/usr/local/mysql-proxy/bin/mysql-proxyd test status
有两个进程的。
我后来使用NaviCat连接工作端口,用户名和密码就是上面配置文件的管理用户和密码,成功了。
经过测试,Altas的读写分离和事务支持很好,明天发布2.0版本,增加对JDBC的支持。
参考:
https://github.com/Qihoo360/Atlas
-- 测试
use chapter6;
insert into ad values(1,'测试1');
insert into ad values(2,'测试2');
insert into ad values(3,'测试3');
select * from ad where sku_id=1;
select * from ad where sku_id=2;
INSERT INTO ad VALUES(1,1);
./mysql-proxyd test start
[报错:启动报错,因为这个test需要和配置文件的INSTANCE一致]
nginx配置
vi /usr/chapter6/nginx_chapter6.conf
我这里配置针对web服务器的地址
upstream配置:http://nginx.org/cn/docs/http/ngx_http_upstream_module.html。
server:指定上游到的服务器,
weight:权重,权重可以认为负载均衡的比例;
fail_timeout+max_fails:在指定时间内失败多少次认为服务器不可用,通过proxy_next_upstream来判断是否失败。
check:ngx_http_upstream_check_module模块,上游服务器的健康检查,interval:发送心跳包的时间间隔,rise:连续成功rise次数则认为服务器up,fall:连续失败fall次则认为服务器down,timeout:上游服务器请求超时时间,type:心跳检测类型(比如此处使用tcp)更多配置请参考https://github.com/yaoweibin/nginx_upstream_check_module和http://tengine.taobao.org/document_cn/http_upstream_check_cn.html。
keepalive:用来支持upstream server http keepalive特性(需要上游服务器支持,比如tomcat)。默认的负载均衡算法是round-robin,还可以根据ip、url等做hash来做负载均衡。更多资料请参考官方文档。
tomcat keepalive配置: http://tomcat.apache.org/tomcat-7.0-doc/config/http.html。
maxKeepAliveRequests:默认100;
keepAliveTimeout:默认等于connectionTimeout,默认60秒;
location proxy配置:http://nginx.org/cn/docs/http/ngx_http_proxy_module.html。
rewrite:将当前请求的url重写,如我们请求时是/backend/ad,则重写后是/ad。
proxy_pass:将整个请求转发到上游服务器。
proxy_next_upstream:什么情况认为当前upstream server失败,需要next upstream,默认是连接失败/超时,负载均衡参数。
proxy_pass_request_headers:之前已经介绍过了,两个原因:1、假设上游服务器不需要请求头则没必要传输请求头;2、ngx.location.capture时防止gzip乱码(也可以使用more_clear_input_headers配置)。
keepalive:keepalive_timeout:keepalive超时设置,keepalive_requests:长连接数量。此处的keepalive(别人访问该location时的长连接)和upstream keepalive(nginx与上游服务器的长连接)是不一样的;此处注意,如果您的服务是面向客户的,而且是单个动态内容就没必要使用长连接了。