马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
安装OpenResty
安装参考博客
OpenResty快速入门
nginx
是没有业务本事的,我们是把哀求转发到openResty,然后在openResty中摆设集群。
那怎样在openResty中吸收并处理惩罚如许的哀求呢?
- #user nobody;
- worker_processes 1;
- error_log logs/error.log;
- events {
- worker_connections 1024;
- }
- http {
- include mime.types;
- default_type application/octet-stream;
- sendfile on;
- keepalive_timeout 65;
- #lua 模块
- lua_package_path "/usr/local/openresty/lualib/?.lua;;";
- #c模块
- lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
- server {
- listen 8081;
- server_name localhost;
- location /api/item {
- # 默认的响应类型
- default_type application/json;
- # 响应结果由lua/item.lua文件来决定
- content_by_lua_file lua/item.lua;
- }
- location / {
- root html;
- index index.html index.htm;
- }
- error_page 500 502 503 504 /50x.html;
- location = /50x.html {
- root html;
- }
- }
- }
复制代码 编写lua文件,但我们这里先不搞那么复杂,先返回一个假数据
假数据直接从页面中拷贝一个,为了以示区别,把此中的数据稍微改一下,我把行李箱尺寸由21改成了26
重启后,重新访问我们的商品详情页
可以看出,我们乐成的把前端哀求通过nginx
, 然后nginx
转发到了配景的openresty上,然后由lua脚本返回了我们设置的参数。
OpenResty获取哀求参数
如许就可以拿到路径参数,这个参数会存到变量里,我们就可以去到了。
返来测试
返回的id确实随着id的改变而改变了
假如在openresty中实行nginx
-s reload没变革,又大概是重新加载不可,我们直接关掉openresty,
在启动
但是启动的时间报了个错误
- 提示:nginx
- : [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
复制代码- nginx
- : [error] open() "/usr/local/nginx
- /logs/nginx
- .pid" failed (2: No such file or directory)
复制代码
办理办法,直接杀掉全部的nginx
进程
killall -9 nginx
杀掉nginx
进程 然后重启就行了
在实行启动
封装Http哀求
我们现在的哀求可以打到Openresty中了,但现在我们怎样把Openresty的哀求打进tomcat中去呢?我们的Openresty在假造机中,tomcat也就是我们的springBoot项目在windows情况下,这得在从假造机打出来。
这里有个本事就是,只要你的windows呆板的ip地点和你的假造即的网段设置成一样的,末了的主机号设置成1,就可以把哀求从假造机打到windows主机上。(留意:防火墙得关闭)
nginx
反向署理把哀求发到tomcat中
先给openresty的nginx
设置反向署理(留意联合本身的网段)
由于发哀求的API经常要使用,我们可以给他封装成函数。
- -- 封装函数,发送http请求,并解析响应
- local function read_http(path, params)
- local resp = ngx.location.capture(path,{
- method = ngx.HTTP_GET,
- args = params,
- })
- if not resp then
- -- 记录错误信息,返回404
- ngx.log(ngx.ERR, "http 查询失败, path: ", path , ", args: ", args)
- ngx.exit(404)
- end
- return resp.body
- end
- -- 将方法导出
- local _M = {
- read_http = read_http
- }
- return _M
复制代码 由于我们在conf文件里设置了
- lua_package_path "/usr/local/openresty/lualib/?.lua;;";
复制代码 以是这个lualib包下面的全部.lua文件都会被加载到
向Tomcat发送http哀求
我们这里修改我们的lua脚本,调用这个API,把哀求发给tomcat,先简单返回一个商品的数据,拼接商品和库存的动作以后在做
- -- 导入common函数库
- local common = require('common')
- local read_http = common.read_http
- -- 获取路径参数
- local id = ngx.var[1]
- -- 查询商品信息
- local itemJSON = read_http("/item/" .. id, nil)
- -- 查询库存信息
- local stockJSON = read_http("/item/stock/" .. id, nil)
- -- 返回结果
- ngx.say(itemJSON)
复制代码 在欣赏器测试一下,发现果然可以返回商品数据了
那该怎么样完成数据的拼接呢?
我们得把数据转换为table,只有转换成了table才气完成数据的拼接这些操纵
cjson地点:https://github.com/openresty/lua-cjson
可以看出在lualib中已经有了cjson, 那就可以直接导入使用了
我们在item.lua脚本中完成组合数据并返回
- -- 导入common函数库
- local common = require('common')
- local read_http = common.read_http
- -- 导入cjson库
- local cjson = require('cjson')
- -- 获取路径参数
- local id = ngx.var[1]
- -- 查询商品信息
- local itemJSON = read_http("/item/" .. id, nil)
- -- 查询库存信息
- local stockJSON = read_http("/item/stock/" .. id, nil)
- -- JSON 转化为lua的table
- local item = cjson.decode(itemJSON)
- local stock = cjson.decode(stockJSON)
- -- 组合数据
- item.stock = stock.stock
- item.sold = stock.sold
- -- 把item序列化为JSON返回结果
- ngx.say(cjson.encode(item))
复制代码 回欣赏器再次测试
发现返回的数据已经有销量和库存了
根据商品id对tomcat集群负载平衡
我们刚刚测试的tomcat是单机的,现实中tomcat肯定是个集群。
标题形貌:
由于我们现实的tomcat肯定是集群的,那我们的nginx
会署理到tomcat的某一台服务器上(8081),在tomcat这一台服务器查询后返回效果,会形成一个JVM进程缓存,但是我们的nginx
的默认负载平衡战略是轮询,下一次查询同一个商品id,nginx
就把哀求发到另一个tomcat服务器上(8082),那前次tomcat形成的jvm进程(8081)缓存无法掷中,由于jvm进程缓存是不能共享的,以是,还得让8082服务器再次处理惩罚,那每次都如许,肯定低沉我们的掷中率,低沉性能。
以是,我们要修改nginx
的负载平衡战略,把轮询的负载平衡战略修改为hash,如许每次根据商品id盘算hash值,只要hash值同等,就能包管每次都哀求同一台tomcat服务器,从而进步掷中率。
修改nginx
的设置文件
- #user nobody;worker_processes 1;error_log logs/error.log;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; #lua 模块 lua_package_path "/usr/local/openresty/lualib/?.lua;;";
- #c模块 lua_package_cpath "/usr/local/openresty/lualib/?.so;;"; upstream tomcat-cluster { hash $request_uri; server 192.168.10.1:8081; server 192.168.10.1:8082; } server { listen 8081; server_name localhost; location /item { proxy_pass http://tomcat-cluster; } location ~ /api/item/(\d+) { # 相应范例,这里返回json default_type application/json; # 相应数据由 lua/item.lua这个文件决定 content_by_lua_file lua/item.lua; } location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }}
复制代码 然后启动两台tomcat服务器
去欣赏器访问测试,不管访问任何商品,只要访问了一次,都会天生进程缓存,而且确保了进程缓存永久见效,不管访问多少次,都可以直接在缓存中得到数据。
Redis缓存预热
按照我们最初的多级缓存的假想,我们在查询的时间不要直接打到tomcat上,而是应该先打到redis查询,假如redis查询失败在查询tomcat。我们应该到场redis的缓存举行预热。
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
复制代码
我这里是由于假造机里的docker有许多redis,这个开启的redis设置的映射的是6399端口,以是根据本身情况修改
然后我们来编写初始化类
- @Component
- public class RedisHandler implements InitializingBean {
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- @Autowired
- private IItemService itemService;
- @Autowired
- private IItemStockService stockService;
- // spring中的默认json处理工具
- private static final ObjectMapper MAPPER = new ObjectMapper();
- // afterPropertiesSet()会在bean创建完,Autowired注入以后执行,实现缓存预热效果
- @Override
- public void afterPropertiesSet() throws Exception {
- // 初始化缓存
- // 1. 查询商品信息,我们查所有的,实际上应该查询热点数据
- List<Item> itemList = itemService.list();
- // 2. 放入缓存
- for (Item item : itemList) {
- // 2.1 item序列化为JSON
- String json = MAPPER.writeValueAsString(item);
- // 2.2 存入redis
- stringRedisTemplate.opsForValue().set("item:id:" + item.getId(), json);
- }
- // 1. 查询库存信息,
- List<ItemStock> stockList = stockService.list();
- // 2. 放入缓存
- for (ItemStock stock : stockList) {
- // 2.1 item序列化为JSON
- String json = MAPPER.writeValueAsString(stock);
- // 2.2 存入redis
- stringRedisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json);
- }
- }
- }
复制代码 启动项目,就可以在redis中完成预热。
查询Redis缓存
OpenResty中操纵redis函数在这里



我们把这个代码封装一下,放到我们的common.lua中去。


修改我们的common
- -- 导入redis
- local redis = require('resty.redis')
- -- 初始化redis
- local red = redis:new()
- red:set_timeouts(1000,1000,1000)
- -- 关闭redis连接的工具方法,其实是放入连接池
- local function close_redis(red)
- local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒
- local pool_size = 100 --连接池大小
- local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
- if not ok then
- ngx.log(ngx.ERR, "放入redis连接池失败: ", err)
- end
- end
- -- 查询redis的方法 ip和port是redis地址,key是查询的key
- local function read_redis(ip, port, key)
- -- 获取一个连接
- local ok, err = red:connect(ip, port)
- if not ok then
- ngx.log(ngx.ERR, "连接redis失败 : ", err)
- return nil
- end
- -- 查询redis
- local resp, err = red:get(key)
- -- 查询失败处理
- if not resp then
- ngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)
- end
- --得到的数据为空处理
- if resp == ngx.null then
- resp = nil
- ngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)
- end
- close_redis(red)
- return resp
- end
- -- 封装函数,发送http请求,并解析响应
- local function read_http(path, params)
- local resp = ngx.location.capture(path,{
- method = ngx.HTTP_GET,
- args = params,
- })
- if not resp then
- -- 记录错误信息,返回404
- ngx.log(ngx.ERR, "http 查询失败, path: ", path , ", args: ", args)
- ngx.exit(404)
- end
- return resp.body
- end
- -- 将方法导出
- local _M = {
- read_http = read_http,
- read_redis = read_redis
- }
- return _M
复制代码 修改我们的item.lua
- -- 导入common函数库
- local common = require('common')
- local read_http = common.read_http
- local read_redis = common.read_redis
- -- 导入cjson库
- local cjson = require('cjson')
- -- 封装查询函数
- function read_data(key, path, params)
- -- 查询redis
- local resp = read_redis("127.0.0.1", 6399, key)
- -- 判断查询结果
- if not resp then
- ngx.log("redis查询失败, 产生查询http, key: ", key)
- -- redis 查询失败,去查询http
- resp = read_http(path, params)
- end
- return resp
- end
- -- 获取路径参数
- local id = ngx.var[1]
- -- 查询商品信息
- local itemJSON = read_data("item:id:" .. id,"/item/" .. id, nil)
- -- 查询库存信息
- local stockJSON = read_data("item:stock:id:" .. id,"/item/stock/" .. id, nil)
- -- JSON 转化为lua的table
- local item = cjson.decode(itemJSON)
- local stock = cjson.decode(stockJSON)
- -- 组合数据
- item.stock = stock.stock
- item.sold = stock.sold
- -- 把item序列化为JSON返回结果
- ngx.say(cjson.encode(item))
复制代码 这就是使用lua来先查询redis, 当redis查询失效后在查询我们的tomcat服务器。
Nginx本地缓存

现在我们的多级缓存就差OpenResty的本地缓存了。
怎么实现呢?


我们在nginx
.conf中设置共享辞书

在item.lua的业务逻辑中导入我们的共享辞书,而且把我们的查询逻辑举行修改,在查询redis之前,先查询本地缓存。
我们的业务逻辑item.lua要修改成这个样子了
- -- 导入common函数库
- local common = require('common')
- local read_http = common.read_http
- local read_redis = common.read_redis
- -- 导入cjson库
- local cjson = require('cjson')
- -- 导入共享词典,本地缓存
- local item_cache = ngx.shared.item_cache
- -- 封装查询函数
- function read_data(key, expire, path, params)
- -- 查询本地缓存
- local val = item_cache:get(key)
- if not val then
- ngx.log(ngx.ERR, "本地缓存查询失败,尝试查询redis, key: ", key)
- -- 查询redis
- val = read_redis("127.0.0.1", 6399, key)
- -- 判断查询结果
- if not val then
- ngx.log(ngx.ERR, "redis查询失败, 产生查询http, key: ", key)
- -- redis 查询失败,去查询http
- val = read_http(path, params)
- end
- -- 查询成功, 把数据写入本地缓存
- item_cache:set(key, val, expire)
- end
- -- 返回数据
- return val
- end
- -- 获取路径参数
- local id = ngx.var[1]
- -- 查询商品信息
- local itemJSON = read_data("item:id:" .. id, 1800 ,"/item/" .. id, nil)
- -- 查询库存信息
- local stockJSON = read_data("item:stock:id:" .. id, 60 ,"/item/stock/" .. id, nil)
- -- JSON 转化为lua的table
- local item = cjson.decode(itemJSON)
- local stock = cjson.decode(stockJSON)
- -- 组合数据
- item.stock = stock.stock
- item.sold = stock.sold
- -- 把item序列化为JSON返回结果
- ngx.say(cjson.encode(item))
复制代码 如许我们在去欣赏器测试一次,如许就完备实现了我们的多级缓存。
so cool!!!
|