1. 缓存设计原则
- 能够使用快速存取的设备,一般采用内存作为缓存设计的基本设备(内存具备快速存取,速度是磁盘的几千倍,弊端:断电即丢失);
- 采用分布式就能扩展对应的节点,缓存离用户越近,用户访问数据走的链路就越少,查询效率就越高;
- 脏缓存清理,缓存具有丢失性。如果缓存不更新到磁盘上,不能算是更新成功,因此关键的数据一定要存储在不能丢失的设备上,一旦数据库中的数据发生了变更,那么缓存中的数据都变成了脏数据,例如一个商品的变价;
2. 多级缓存方案
- redis缓存,有集中式管理缓存的特点;
- 热点内存本地缓存
- nginx proxy cache缓存(性能问题并不采用)
- nginx lua缓存
3. Redis集中式缓存
4. Guava cache(JVM级缓存)
@Service
public class CacheServiceImpl implements CacheService {
private Cache<String,Object> commonCache = null;
@PostConstruct
public void init(){
commonCache = CacheBuilder.newBuilder()
//设置缓存容器的初始容量为10
.initialCapacity(10)
//设置缓存中最大可以存储100个KEY,超过100个之后会按照LRU的策略移除缓存项
.maximumSize(100)
//设置写缓存后多少秒过期
.expireAfterWrite(60, TimeUnit.SECONDS).build();
}
@Override
public void setCommonCache(String key, Object value) {
commonCache.put(key,value);
}
@Override
public Object getFromCommonCache(String key) {
return commonCache.getIfPresent(key);
}
}
5. nginx proxy cache缓存
- nginx proxy cache的启用必须是nginx必须可以是反向代理前置;
- 依靠文件系统存索引级的文件,本质上是将一个请求当作一个文件存储在本地使得下一次用户的请求过来直接看本地有没有一个对应的文件来决定proxy
cache是否启用; - 依靠内存缓存文件地址,缓存的内容存储在磁盘中,但是缓存的key是在内存当中,并且缓存的key在内存当中的内容就是缓存的内容文件在文件系统中的地址;
vim nginx.conf
#声明一个cache缓存节点的内容
#做一个二级目录,先将对应的url做一次hash,取最后一位做一个文件目录的索引;
#在取一位做第二级目录的索引来完成对应的操作,文件内容分散到多个目录,减少寻址的消耗;
#在nginx内存当中,开了100m大小的空间用来存储keys_zone中的所有的key
#文件存取7天,文件系统组多存取10个G
#在http中声明proxy_cache_path
proxy_cache_path /usr/local/openresty/nginx/tmp_cache levels=1:2 keys_zone=tmp_cache:100m inactive=7d max_size=10g;
#在location中多指定一个结点
location / {
proxy_cache tmp_cache;
proxy_cache_key $uri;
#只有后端返回的状态码是这些,对应的cache操作才会生效,缓存周期10天
proxy_cache_valid 200 206 304 302 7d;
}
[root@localhost nginx]# sbin/nginx -s reload
请求之后 看到nginx磁盘中有 缓存有数据
[root@localhost nginx]# cd tmp_cache/
[root@localhost tmp_cache]# ls
8
[root@localhost tmp_cache]# cd 8
[root@localhost 8]# ls
f6
[root@localhost 8]# cd f6
[root@localhost f6]# ls
86e4d1b3ba4f1464e409c74be4ef6f68
[root@localhost f6]# cat 86e4d1b3ba4f1464e409c74be4ef6f68
©b^ÿÿÿÿÿÿÿÿ)(w^ksr¯`
KEY: /item/get
HTTP/1.1 200
Access-Control-Allow-Methods: HEAD, POST, GET, OPTIONS, DELETE, PUT
Access-Control-Max-Age: 3600
Access-Control-Allow-Headers: x-requested-with
Access-Control-Allow-Credentials: true
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 22 Mar 2020 08:56:09 GMT
{"status":"success","data":{"id":6,"title":"苹果8","price":8888.00,"stock":1000,"description":"苹果89999","sales":0,"imgUrl":"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584609244675&di=1dec5a523bb7153cc51efd60d2086be2&imgtype=0&src=http%3A%2F%2Fimages01.mopimg.cn%2Fimgs%2F20180209%2F20180209_1bf319190fa11cea019b055c7101855d.JPEG","promoStatus":0,"promoPrice":null,"promoId":null,"startDate":null}}
注意:nginx proxy cache缓存在本地磁盘中,不是在内存中,性能还不如反向代理获取数据。
6. nginx lua缓存
- lua协程机制
- nginx协程机制
- nginx插载点
- OpenResty,将lua脚本和nginx打包在一起
6.1 什么是协程
协程,线程空间站的执行单元,有自己独立的运行空间,本身依托于线程,在编写对应代码就无需考虑异步的方式,完全可以同步编写;
- 依附于线程的内存模型,切换开销小;
- 遇阻塞归还对应的执行权限,代码同步;
- 协程在线程中还是串行访问,无需加锁
6.2 Nginx协程机制
Nginx协程:
- nginx的每一个Worker进程都是在epoll或queue这种事件模型之上,封装成协程;
- 每一个请求都有一个协程进行处理;
- 即使ngx_lua需要运行lua,相对与C有一定的开销,但依旧能保证高并发的能力;
Nginx协程机制
- nginx每个工作进程创建一个lua虚拟机
- 工作进程内的所有协程共享同一个vm
- 每一个外部请求都是由一个lua协程处理,之间数据隔离;
- lua代码调用io等异步接口时,协程被挂起,上下文数据保持不变;
- 自动保存,不阻塞工作进程;
- io异步操作完成后还原协程上下文,代码继续执行;
Nginx处理阶段
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0, //读取请求头,例如get还是post,cookie中有哪些方法
NGX_HTTP_SERVER_REWRITE_PHASE, //执行rewrite - rewrite_handler,uri与location匹配前,修改uri的阶段,用于重定向
NGX_HTTP_FIND_CONFIG_PHASE, //根据uri替换location
NGX_HTTP_REWRITE_PHASE, //根据替换结果继续执行rewrite - rewrite_handler,上一阶段找到location块后再修改uri
NGX_HTTP_POST_REWRITE_PHASE, //执行rewrite后处理,防止重写URL后导致的死循环
NGX_HTTP_PREACCESS_PHASE, //认证预处理 请求限制,连接限制 -limit_conn_handler -limit_req_handler
NGX_HTTP_ACCESS_PHASE, //认证处理 - auth_basic_handler,access_handler,让HTTP模块判断是否允许这个请求进入Nginx服务器
NGX_HTTP_POST_ACCESS_PHASE, //认证后处理, 认证不通过, 丢包, 向用户发送拒绝服务的错误码,用来响应上一阶段的拒绝
NGX_HTTP_TRY_FILES_PHASE, //尝试try标签,为访问静态文件资源而设置
NGX_HTTP_CONTENT_PHASE, //内容处理 - static_handler 处理HTTP请求内容的阶段
NGX_HTTP_LOG_PHASE //日志处理 - log_handler 处理完请求后的日志记录阶段
} ngx_http_phases;
6.3 Nginx lua插载点
Nginx与Lua编写脚本的基本构建块是指令。 指令用于指定何时运行用户Lua代码以及如何使用结果。 下面是显示指令执行顺序的图。
image.pngNginx提供了许多再执行lua脚本的挂载方案,用的最多的几个nginx lua插载点
- init_by_lua:系统启动时调用;
- init_worker_by_lua:worker进程启动时调用;
- set_by_lua:nginx变量用复杂lua return
- rewrite_by_lua:重写url规则
- access_by_lua:权限验证阶段
- content_by_lua:内容输出结点
测试
建一个lua内容输出结点文件staticitem.lua
[root@localhost openresty]# cd lua
[root@localhost lua]# vim staticitem.lua
vim nginx.conf
#在server中多指定一个location
location /staticitem/get{
default_type "text/html";
content_by_lua_file ../lua/staticitem.lua;
}
[root@localhost nginx]# sbin/nginx -s reload
直接返回静态资源文件:
image.png6.4 OpenResty nginx缓存,shared dic共享内存字典
- OpenResty由Nginx核心加很多第三方模块组成,默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用;
- 借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序;
- OpenResty提供了大量组件如Mysql、Redis、Memcached等等,使在Nginx上开发应用更方便,更简单;
也是key和value类型的缓存。优势:基于nginx内存的直接缓存,并且是离用户最近的节点,缺点:但是更新机制不太好,占用nginx的内存
使用lua脚本,/usr/local/openresty/lua/itemsharedic.lua
--从缓存中获取
function get_from_cache(key)
local cache_ngx = ngx.shared.my_cache
local value = cache_ngx:get(key)
return value
end
--设置缓存
function set_to_cache(key,value,expire)
if not expire then
expire = 0
end
local cache_ngx = ngx.shared.my_cache
local succ,err,forcible = cache_ngx:set(key,value,expire)
return succ
end
--获取请求的参数/item/get?id=6
local args = ngx.req.get_uri_args()
local id = args["id"]
local item_model = get_from_cache("item_"..id)
--从缓存中获取
if item_model == nil then
--调用后台
local resp = ngx.location.capture("/item/get?id="..id)
item_model = resp.body
set_to_cache("item_"..id,item_model,1*60)
end
ngx.say(item_model)
修改nginx.conf, 引入lua脚本
http {
lua_shared_dict my_cache 128m; #定义nginx共享字典缓存
server {
listen 80;
server_name localhost;
location /luaitem/get {
default_type "application/json";
content_by_lua_file ../lua/itemsharedic.lua;
}
}
}
image.png
6.5.nginx直接获取redis缓存数据
openresty redis支持:
若nginx可以连接到redis上,进行只读不写,若redis内没有对应的数据,那就回源到miaoshaserver上面,然后对应的miaoshaserver也判断一下redis内有没有对应的数据,
若没有,回源mysql读取,读取之后放入redis中 ,那下次h5对应的ajax请求就可以直接在redis上做一个读的操作,nginx不用管数据的更新机制,下游服务器可以填充redis,nginx只需要实时的感知redis内数据的变化,在对redis添加一个redis slave,redis slave通过redis master做一个主从同步,更新对应的脏数据。
优点:避免访问后台应用的网络时间,减少消耗。虽然增加redis的负担,但是redis集群有多台主备redis分担,影响不大
新建itemnginx.lua文件
local args = ngx.req.get_uri_args();
local id = args["id"]
local redis = require "resty.redis"
local cache = redis:new()
local ok,err = cache:connect("47.99.51.246",6379)
if not ok then
ngx.log(ngx.ERR,"connect error")
return
end
local item_model = cache:get("item_"..id)
ngx.log(ngx.ERR, item_model)
if item_model == ngx.null or item_model == nil then
local resp = ngx.location.capture("/item/get?id="..id)
item_model = resp.body
end
ngx.say(item_model)
修改nginx.conf,引入lua文件
location /luaitem/get {
default_type "application/json";
#content_by_lua_file ../lua/itemsharedic.lua;
content_by_lua_file ../lua/itemnginx.lua;
}
网友评论