多级缓存架构

多级缓存架构

Lua

标题明明是缓存架构, 为什么要提到Lua呢.

因为Lua是一个脚本语言, 可以和各种应用程序结合使用, 方便扩展.

比如生成验证码的功能, 会直接发送到server中, 由java生成并返回, 但是在tomcat接收请求之后, 速度就变的非常慢了.

因此可以将生成验证码这样非常简单的事情放在nginx这层来做, 也就是确认到请求后直接在nginx处理.

Redis+Lua

在Redis中跑Lua脚本

先看看redis的帮助

redis-cli --help

--eval <file>      Send an EVAL command using the Lua script at <file>.

可以使用eval来跑一个lua脚本,

redis-cli eval "local msg='hello:' return msg..ARGV[1]" 1 name echi
"hello:echi"

eval后面跟着的字符串就是lua脚本的内容, 后面的数字代表有几个参数, 参数以key-value的形式跟在数字后面.

字符串中 ARGV代表value数组, KEYS代表key数组, 下标从1开始.

也可以将lua写在一个文件里, 可以使用redis.call(“命令”, “参数”)来操作redis中的数据.

内部不光有redis.call()方法, 还有redis.pcall()方法.

二者的区别在于, call()方法会将错误抛出, pcall()方法会将错误内容返回.

可以将脚本缓存在redis内

上面的提交都是一次性的, 总不可能每次都传一个脚本文件到redis内吧.

因此redis可以将脚本缓存起来.

redis-cli script help

这个缓存还是存储在内存中的, redis重启后就没了.

加载test.lua到redis中.

redis-cli script load "${cat test.lua}"

执行结果会返回一串值, 调的时候就用这一串值去调.

redis-cli evalsha "6092ad511056d42d42cc36432bb626d4fa0cd6f33" 0

注:因为redis是单线程执行, 所以如果脚本写的有问题, 比如出现死循环. 其他客户端来尝试请求的时候会受到影响.

(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

Redis Lua脚本管理

  1. script load: 用于将Lua脚本加载到Redis内存中
  2. script exists scripts exists sha1 [sha1 ..] : 用于判断sha1 是否已经加载到Redis内存中.
  3. script flush: 用于清除Redis内存已经加载的所有Lua脚本, 在执行script flush后, sha1不复存在.
  4. script kill: 用于杀掉正在执行的Lua脚本.

Nginx + Lua

Nginx是一个主进程配合多个工作进程的工作模式,每个进程由单个线程来处理多个连接。

在生产环境中,我们往往会把cpu内核直接绑定到工作进程上,从而提升性能。

安装

预编译安装

以CentOS举例 其他系统参照:http://openresty.org/cn/linux-packages.html

你可以在你的 CentOS 系统中添加 openresty 仓库,这样就可以便于未来安装或更新我们的软件包(通过 yum update 命令)。运行下面的命令就可以添加我们的仓库:

 yum install yum-utils

 yum-config-manager –add-repo https://openresty.org/package/centos/openresty.repo

然后就可以像下面这样安装软件包,比如 openresty:

 yum install openresty

如果你想安装命令行工具 resty,那么可以像下面这样安装 openresty-resty 包:

 sudo yum install openresty-resty

源码编译安装

下载

http://openresty.org/cn/download.html

./configure

然后在进入 openresty-VERSION/目录, 然后输入以下命令配置:

./configure

默认, --prefix=/usr/local/openresty 程序会被安装到/usr/local/openresty目录。

依赖 gcc openssl-devel pcre-devel zlib-devel

安装:yum install gcc openssl-devel pcre-devel zlib-devel postgresql-devel

您可以指定各种选项,比如

./configure --prefix=/opt/openresty \

           --with-luajit \

           --without-http_redis2_module \

           --with-http_iconv_module \

           --with-http_postgres_module

试着使用 ./configure --help 查看更多的选项。

make && make install

服务命令

启动
Service openresty start
停止
Service openresty stop
检查配置文件是否正确
Nginx -t

重新加载配置文件

Service openresty reload
查看已安装模块和版本号
Nginx -V

测试lua脚本

在Nginx.conf 中写入
   location /lua {

        default_type text/html;
        content_by_lua '
           ngx.say("<p>Hello, World!</p>")
         ';
      }

lua-nginx-module

创建配置文件lua.conf
   server {
        listen       80;
        server_name  localhost;

   location /lua {

        default_type text/html;

        content_by_lua_file conf/lua/hello.lua;

         }
}
在Nginx.conf下引入lua配置
include lua.conf;
创建外部lua脚本
conf/lua/hello.lua

内容:

ngx.say("<p>Hello, World!</p>")
获取Nginx uri中的单一变量
    location /nginx_var {

         default_type text/html;

        content_by_lua_block {

            ngx.say(ngx.var.arg_a)

        }
    }
获取Nginx uri中的所有变量
local uri_args = ngx.req.get_uri_args()  

for k, v in pairs(uri_args) do  

    if type(v) == "table" then  

        ngx.say(k, " : ", table.concat(v, ", "), "<br/>")  

    else  

        ngx.say(k, ": ", v, "<br/>")  

    end  
end
获取Nginx请求头信息
local headers = ngx.req.get_headers()                         

ngx.say("Host : ", headers["Host"], "<br/>")  

ngx.say("user-agent : ", headers["user-agent"], "<br/>")  

ngx.say("user-agent : ", headers.user_agent, "<br/>")

for k,v in pairs(headers) do  

    if type(v) == "table" then  

        ngx.say(k, " : ", table.concat(v, ","), "<br/>")  

    else  

        ngx.say(k, " : ", v, "<br/>")  

    end  

end  
获取post请求参数
ngx.req.read_body()  

ngx.say("post args begin", "<br/>")  

local post_args = ngx.req.get_post_args()  

for k, v in pairs(post_args) do  

    if type(v) == "table" then  

        ngx.say(k, " : ", table.concat(v, ", "), "<br/>")  

    else  

        ngx.say(k, ": ", v, "<br/>")  

    end  
end
http协议版本
ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>")
请求方法
ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>")  
原始的请求头内容
ngx.say("ngx.req.raw_header : ",  ngx.req.raw_header(), "<br/>")  
body内容体
ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>")

redis2-nginx-module

redis2-nginx-module是一个支持 Redis 2.0 协议的 Nginx upstream 模块,它可以让 Nginx 以非阻塞方式直接防问远方的 Redis 服务,同时支持 TCP 协议和 Unix Domain Socket 模式,并且可以启用强大的 Redis 连接池功能。

https://github.com/openresty/redis2-nginx-module

test
location = /foo {

default_type text/html;

     redis2_query auth 123123;

     set $value 'first';

     redis2_query set one $value;

     redis2_pass 192.168.199.161:6379;

 }
get
location = /get {

default_type text/html;

     redis2_pass 192.168.199.161:6379;

     redis2_query auth 123123;

     set_unescape_uri $key $arg_key;  # this requires ngx_set_misc

     redis2_query get $key;

}
set
# GET /set?key=one&val=first%20value

location = /set {

default_type text/html;

redis2_pass 192.168.199.161:6379;

redis2_query auth 123123;


     set_unescape_uri $key $arg_key;  # this requires ngx_set_misc

     set_unescape_uri $val $arg_val;  # this requires ngx_set_misc

     redis2_query set $key $val;

 }
pipeline
     set $value 'first';

     redis2_query set one $value;

     redis2_query get one;

     redis2_query set one two;

     redis2_query get one;

redis2_query del key1;
list
    redis2_query lpush key1 C;

    redis2_query lpush key1 B;

    redis2_query lpush key1 A;

redis2_query lrange key1 0 -1;
集群
upstream redis_cluster {

     server 192.168.199.161:6379;

     server 192.168.199.161:6379;

 }

location = /redis {

default_type text/html;

         redis2_next_upstream error timeout invalid_response;

         redis2_query get foo;

         redis2_pass redis_cluster;
   }

lua-resty-redis访问redis

常用方法
local res, err = red:get("key")

local res, err = red:lrange("nokey", 0, 1)

ngx.say("res:",cjson.encode(res))
创建连接
red, err = redis:new()

ok, err = red:connect(host, port, options_table?)
timeout
red:set_timeout(time)
keepalive
red:set_keepalive(max_idle_timeout, pool_size)
close
ok, err = red:close()

null

nil

error

exception

crash

pipeline
red:init_pipeline()

results, err = red:commit_pipeline()
认证
    local res, err = red:auth("foobared")

    if not res then

        ngx.say("failed to authenticate: ", err)

        return
end
redis-cluster支持

https://github.com/steve0511/resty-redis-cluster

多级缓存

CDN

请求来到时, 会分为两部分, 一部分希望获取静态资源, 另一部分希望获取动态资源。

对于静态资源, 可以使用CDN来缓存这些资源达到节省带宽,快速响应的效果, 这就是多级缓存的第一级缓存。

数据中心

在CDN解决了一部分静态资源后, 剩余的动态资源会请求到服务器上, 也就是tomcat中。

那如果请求过多, tomcat也撑不住, 势必要起多个镜像,以多实例的方式进行处理。

这中间就设计到tomcat集群的问题, 多个tomcat之间, session如何共享。

可以本地存储session, 集群中对session进行同步, 但是请求过多时, 巨量的session会对tomcat造成大量压力。

或者将session存储在第三方,数据库或者redis中。 随着请求的增大, 第三方存储可能也会撑不住, 也要进行扩容集群, 这也会造成第三方存储session的性能下降。

那么我们为什么不让从一个地方发出的请求尽量的都分发到同一个server呢? 这样就可以使session只在一个server中出现。

因此我们就需要进行流量分发。

首先想到的就是nginx, 因为nginx中有ip_hash模块, 可以根据ip分配服务。

当然nginx也有一个上限, nginx号称可以处理5万并发, 如果接近5万或者超过,就需要对nginx再做集群。

ip_hash也有一个缺点, 如果恰恰好, 所有的数据都倾斜到一台服务器上, 散列不均匀的时候。服务器也撑不住。

现在tomcat的集群是allinone,做全量复制, 每个tomcat都负责全部的功能, 那么可以将服务拆分,按功能拆分。

那么现在问题又来了, 如果有一个功能, 请求量特别大,一台服务器撑不住,生成网页的速度太慢了。

这时可以进行网页文件静态化, 因为一般修改的操作是很少的, 而查询的操作很多, 如果我们提前将查询的页面生成好,请求的时候直接找出来返回,那么速度会快很多。

但是生成好的页面很难管理,同时放在磁盘上的IO也是有带宽限制的,

那么可以将这些生成好的页面作为缓存放到内存中去。但是终归不是一个好的方案。


   转载规则


《多级缓存架构》 echi1995 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
消息中间件(一) 消息中间件(一)
消息中间件之ActiveMQ首先先看一个例子: 完成一个功能需要三步调用, step1耗时15ms, step2耗时100ms,step3耗时300ms。 如果是同步调用 , 返回到用户的时间是415ms。 这样肯定是响应较慢的,用户体验
下一篇 
Spring Cloud Spring Cloud
Spring Cloud 微服务概述Spring Cloud技术点 Eureka: 服务注册于发现, 用于服务管理 Feign: web调用客户端, 能够简化HTTP接口的调用 Ribbon: 基于客户端的负载均衡 Hystrix: 熔断降
  目录