黑马点评18——多级缓存-OpenResty

news/2024/9/18 0:32:35/ 标签: 缓存, openresty, redis, lua

文章目录

  • 安装OpenResty
  • OpenResty快速入门
  • OpenResty获取请求参数
  • 封装Http请求
  • 向Tomcat发送http请求
  • 根据商品id对tomcat集群负载均衡
  • Redis缓存预热
  • 查询Redis缓存
  • Nginx本地缓存

安装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 -s stop

在启动

nginx

但是启动的时候报了个错误

提示: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 进程 然后重启就行了
在执行启动

nginx

封装Http请求

在这里插入图片描述
我们现在的请求可以打到Openresty中了,但现在我们如何把Openresty的请求打进tomcat中去呢?我们的Openresty在虚拟机中,tomcat也就是我们的springBoot项目在windows环境下,这得在从虚拟机打出来。
这里有个技巧就是,只要你的windows机器的ip地址和你的虚拟即的网段设置成一样的,最后的主机号设置成1,就可以把请求从虚拟机打到windows主机上。(注意:防火墙得关闭)
在这里插入图片描述
nginx反向代理把请求发到tomcat中

在这里插入图片描述
先给openresty的nginx配置反向代理(注意结合自己的网段)
在这里插入图片描述
因为发请求的API经常要使用,我们可以给他封装成函数。
在这里插入图片描述

lua">-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)local resp = ngx.location.capture(path,{method = ngx.HTTP_GET,args = params,})if not resp then-- 记录错误信息,返回404ngx.log(ngx.ERR, "http 查询失败, path: ", path , ", args: ", args)ngx.exit(404)endreturn 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,先简单返回一个商品的数据,拼接商品和库存的动作以后在做

lua">-- 导入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脚本中完成组合数据并返回

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+) {# 响应类型,这里返回jsondefault_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 {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate IItemService itemService;@Autowiredprivate IItemStockService stockService;// spring中的默认json处理工具private static final ObjectMapper MAPPER = new ObjectMapper();// afterPropertiesSet()会在bean创建完,Autowired注入以后执行,实现缓存预热效果@Overridepublic void afterPropertiesSet() throws Exception {// 初始化缓存// 1. 查询商品信息,我们查所有的,实际上应该查询热点数据List<Item> itemList = itemService.list();// 2. 放入缓存for (Item item : itemList) {// 2.1 item序列化为JSONString json = MAPPER.writeValueAsString(item);// 2.2 存入redisstringRedisTemplate.opsForValue().set("item:id:" + item.getId(), json);}// 1. 查询库存信息,List<ItemStock> stockList = stockService.list();// 2. 放入缓存for (ItemStock stock : stockList) {// 2.1 item序列化为JSONString json = MAPPER.writeValueAsString(stock);// 2.2 存入redisstringRedisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json);}}
}

启动项目,就可以在redis中完成预热。

查询Redis缓存

OpenResty中操作redis函数在这里
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们把这个代码封装一下,放到我们的common.lua中去。
在这里插入图片描述
在这里插入图片描述
修改我们的common

lua">-- 导入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 thenngx.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 thenngx.log(ngx.ERR, "连接redis失败 : ", err)return nilend-- 查询redislocal resp, err = red:get(key)-- 查询失败处理if not resp thenngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)end--得到的数据为空处理if resp == ngx.null thenresp = nilngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)endclose_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-- 记录错误信息,返回404ngx.log(ngx.ERR, "http 查询失败, path: ", path , ", args: ", args)ngx.exit(404)endreturn resp.body
end
-- 将方法导出
local _M = {  read_http = read_http,read_redis = read_redis
}  
return _M

修改我们的item.lua

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)-- 查询redislocal resp = read_redis("127.0.0.1", 6399, key)-- 判断查询结果if not resp thenngx.log("redis查询失败, 产生查询http, key: ", key)-- redis 查询失败,去查询httpresp = read_http(path, params)endreturn 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 thenngx.log(ngx.ERR, "本地缓存查询失败,尝试查询redis, key: ", key)-- 查询redisval = read_redis("127.0.0.1", 6399, key)-- 判断查询结果if not val thenngx.log(ngx.ERR, "redis查询失败, 产生查询http, key: ", key)-- redis 查询失败,去查询httpval = 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!!!


http://www.ppmy.cn/news/1524387.html

相关文章

【精彩瞬间】2024外滩大会回顾

9月8号&#xff0c;为期3天的“2024 inclusion外滩大会”在上海黄浦圆满落下帷幕。本届大会&#xff0c;共吸引了5.2万人到场参观&#xff0c;无论是参会规模还是国际嘉宾的数量都创下历史新高。 500位演讲嘉宾分别在1场开幕主论坛、36场见解分论坛上聚焦“ai产业新实践”“科技…

再获新认可!创客匠人被评2024年度腾讯云教育行业“最佳新锐奖”

近日&#xff0c;2024腾讯全球数字生态大会在深圳国际会展中心举行。知识变现整体解决方案服务商创客匠人受邀参会&#xff0c;并凭借其卓越的创新能力和显著的市场成绩荣获2024年度腾讯云教育行业“最佳新锐奖”。 这一荣誉不仅是对创客匠人在知识服务领域持续深耕与探索的充…

git-repo使用

即使用 XML 格式文件&#xff08;manifest 清单文件&#xff09;定义一个项目的多仓库关联&#xff0c;然后用 repo 客户端工具操作多仓库 git repo命令行格式&#xff1a; git repo <子命令> <参数>创建一个空目录&#xff0c;作为工作区。 $ mkdir workspace$ …

AI教程_AI大模型 Prompt提示词工程 Langchain AI原生应用开发视频教程分享(IT营)

AI&#xff08;人工智能&#xff09;正在以惊人的速度席卷着各行各业&#xff0c;其影响深远且广泛。十九大开幕式把人工智能列入了报告内容&#xff0c; 普京表示过人工智能是整个人类的未来&#xff0c;马斯克说人工智能有可能是人类科技的终极战场。雷总说过一句话&#xff…

Ribbon快速了解

Ribbon 一、Ribbon 介绍 Ribbon 是一个客户端负载均衡器&#xff0c;它是 Netflix 开源的一个组件&#xff0c;常与 Spring Cloud 一起使用。 二、Ribbon 的作用 客户端负载均衡 Ribbon 可以在客户端实现负载均衡&#xff0c;即在服务消费者端根据一定的算法从多个服务提供者实…

如何使用Docker快速启动Nginx服务器

Nginx 是一款高性能的 HTTP 和反向代理服务器&#xff0c;它以高稳定性、丰富的功能集、简单的配置和低资源消耗而闻名。Docker 是一个开源的应用容器引擎&#xff0c;可以让开发者打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上…

JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级

这里写目录标题 JavaScript高级第03天1.函数的定义和调用1.1函数的定义方式1.2函数的调用 2.this2.1函数内部的this指向2.2改变函数内部 this 指向2.2.1 call方法2.2.2 apply方法2.2.3 bind方法2.2.4 call、apply、bind三者的异同 3.严格模式3.1什么是严格模式3.2开启严格模式3…

微服务重构:Mysql+DTS+Kafka+ElasticSearch解决跨表检索难题

1、背景 在微服务拆分过程里&#xff0c;会对数据库模块重新进行建模拆分&#xff0c;导致部分表和数据&#xff0c;出现物理隔离&#xff0c;导致跨库JOIN的SQL不可行&#xff0c;并在数据检索上也有性能损耗的风险。下面我们来一起探讨一下&#xff0c;具体的解决方案。 1.1 …

《食品安全导刊》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《食品安全导刊》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《食品安全导刊》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a; 中国商业联合会 主办单…

leetcode18-27

矩阵问题 18.矩阵置零 自己解法&#xff0c;空间复杂度高 自己思路写出来就好了&#xff0c;第一遍先不追求最完美。况且有时候最完美也不易读 class Solution:def setZeroes(self, matrix: List[List[int]]) -> None:"""Do not return anything, modify …

css-loader/style-loader/less-loader/sass-loader/postcss-loader各有什么作用,一次性说明白

大家都清楚在使用webpack构建前端项目时都会使用到sass-loader、less-loader、postcss-loader、css-loader、style-loader&#xff0c;但这些loader在其中起到什么作用呢&#xff1f;本篇主要阐述这些loader在打包中所扮演的角色。 概述 1、css-loader: 加载.css文件的loader&…

八戒:再不上市就要破产了!

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 这是猪八戒网刚刚发布的声明&#xff0c;热乎的&#xff0c;大概就2个意思&#xff1a; (1)猪八戒网运营正常&#xff0c;对未来整体看好。 (2)公司创始人没拿高报酬。 事情是这样的&#xff1a;8月底9月初&am…

Spring Cloud Gateway中的常见配置

问题 最近用到了Spring Cloud Gateway&#xff0c;这里记录一下这个服务的常见配置。 spring:data:redis:host: ${REDIS_HOST:xxx.xxx.xxx.xxx}port: ${REDIS_PORT:2345wsd}password: ${REDIS_PASS:sdfsdfgh}database: ${REDIS_DB:8}session:redis:flush-mode: on_savenamespa…

Vivado时序报告之Report pulse width详解

目录 一、前言 二、Report pulse width 2.1 Report pulse width 2.2 配置界面 2.3 分析结果 一、前言 在进行时序分析时&#xff0c;除了slack的分析&#xff0c;还存在pulse width的检查&#xff0c;下面将对pulse width检查进行详细说明。在report timing summary报告中…

Java语言程序设计基础篇_编程练习题*18.20 (显示多个圆)

目录 题目&#xff1a;*18.20 (显示多个圆) 习题思路 代码示例 输出结果 题目&#xff1a;*18.20 (显示多个圆) 编写一个Java程序显示多个圆&#xff0c;如图18-12b所示。这些圆都处于面板的中心位置。两个相邻圆之间相距10像素&#xff0c;面板和最大圆之间也相距10像素。…

Centos7 Hadoop 单机版安装教程(图文)

本章教程,主要记录如何在Centos7中安装Hadoop单机版。 一、软件安装包和基础环境 CentOS7.x,jdk8,hadoop 通过网盘分享的文件:Hadoop 链接: https://pan.baidu.com/s/1_qGI9QeXMAJNb3TydHhQGA?pwd=xnz4 提取码: xnz4 当然你也可以自己去官网下载。 java8:https://www.ora…

黑马点评22——最佳实践-批处理优化

文章目录 pipeline和mset集群模式下的批处理问题 pipeline和mset pipeline就是大数据量的导入&#xff0c;pipeline是在单机模式下的。 redis的处理耗时相比较网络传输的耗时其实是比较低的。 所以我们最好采用批处理&#xff0c; 集群模式下的批处理问题 在集群环境…

redis基本数据类型和常见命令

引言 Redis是典型的key-value&#xff08;键值型&#xff09;数据库&#xff0c;key一般是字符串&#xff0c;而value包含很多不同的数据类型&#xff1a; Redis为了方便我们学习&#xff0c;将操作不同数据类型的命令也做了分组&#xff0c;在官网&#xff08; Commands | Do…

Mysql树形结构表-查询所有子集数据

表结构&#xff0c;这里只是个例子&#xff0c;所有的树形结构表均可用&#xff1a; CREATE TABLE zhkt_course_chapter (id bigint NOT NULL COMMENT 唯一id,course_id bigint NOT NULL COMMENT 所属课程id,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general…

最新版微服务项目搭建

一&#xff0c;项目总体介绍 在本项目中&#xff0c;我将使用alibabba的 nacos 作为项目的注册中心&#xff0c;使用 spring cloud gateway 做为项目的网关&#xff0c;用 openfeign 作为服务间的调用组件。 项目总体架构图如下&#xff1a; 注意&#xff1a;我的Java环境是17…