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

文章目录

  • 安装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经常要使用,我们可以给他封装成函数。
在这里插入图片描述

-- 封装函数,发送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!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/874746.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NLP基础及其代码-BERT系列

1.基础知识 BERT系列RoBERTa ALBERT ERINE详解与使用学习笔记-腾讯云开发者社区-腾讯云 (tencent.com) 2.BERT 分词&#xff1a;wordpiece 预训练&#xff1a; mask&#xff1a;选择15%的分词进行mask&#xff08;80%mask 10%随机替换分词 10%分词保持不变&#xff09; NSP…

替换传统数据处理平台,TDengine 与华风数据达成合作

在全球能源转型的大背景下&#xff0c;新能源产业正迎来前所未有的发展机遇。随着国家对可再生能源的政策支持和市场需求的不断增长&#xff0c;风电、光伏和储能等新能源项目如雨后春笋般蓬勃发展。然而&#xff0c;随之而来的数据处理与管理挑战也日益凸显。面对海量的设备运…

构建常态化安全防线:XDR的态势感知与自动化响应机制

当前&#xff0c;网络安全威胁日益复杂多变&#xff0c;企业正面临前所未有的严峻挑战。为有效应对这些挑战&#xff0c;态势感知与自动化响应机制在提升网络安全运营效率与防御效果中扮演着至关重要的角色。它们能够实时监测网络状态&#xff0c;智能分析潜在威胁&#xff0c;…

Spring Cloud之二 微服务注册

1&#xff1a;Intellij 新建服务 user-service 2&#xff1a;pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"…

[leetcode-python]杨辉三角2

给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: rowIndex 3 输出: [1,3,3,1]示例 2: 输入: rowIndex 0 输出: [1]示例 3: 输入: rowIndex 1 输出: [1,1]提示…

如何将 Electron 项目上架 Apple Store

前言 Electron 是一个开源框架,它允许开发者使用 Web 技术(HTML、CSS 和 JavaScript)来构建跨平台的桌面应用程序。 Electron 应用程序可以运行在 Windows、macOS 和 Linux 上,为用户提供了一种统一的方式来开发和维护软件。 本文将探讨如何将 Electron 构建的桌面应用程…

CTF——简单的《WEB》

文章目录 一、WEB1、easysql2、baby_web3、baby_sql4、upload_easy5、easygame拓展1.1拓展1.2 6、ht_ssti7、包容乃大 一、WEB 1、easysql 题目描述&#xff1a; sql注入漏洞 1.常用的sql注入测试语句 2.sql注入bypass 解题思路 这边提示基本给的也很完整的&#xff0c;不…

【Linux】Linux介绍及CentOS虚拟机环境搭建

内容大纲介绍 文章目录 内容大纲介绍1.计算机简介2.Linux系统介绍3.虚拟化软件介绍4.Linux环境搭建5.扩展_虚拟机的快照6.Linux的目录介绍 1.计算机简介 概述 全称叫电子计算机, 英文名叫Computer, 俗称叫: 电脑, 简称叫: PC, 就是有硬件和软件组成的电子设备. 组成 计算机硬件…

QGis二次开发 —— 3、程序加载栅格tif与矢量shp文件可进行切换控制,可进行导出/导入工程(附源码)

效果 功能说明 软件可同时加载.tif栅格图片与.shp矢量图片、加载图片后可进行自由切换查看图层、可对加载的图片进行关闭 关闭后清空图层、可对加载的图片进行导出.qgs的QGIS工程、可对.qgs的QGis工程导入并导入后可进行自由切换查看图层。 源码 注意: 在加载tif栅格文件后会在…

华为 HCIP-Datacom H12-821 题库 (16)

1.需要题库的小伙伴至博客最下方添加微信公众号关注后回复题库 2.有兴趣交流IT问题的小伙伴微信公众号回复交流群&#xff0c;加入微信IT交流群 1. OSPF 邻居关系建立出现故障&#xff0c;通过 display ospf error 命令来检查&#xff0c;输出结果如图所示&#xff0c;根据图中…

OceanBase 4.x 存储引擎解析:如何让历史库场景成本降低50%+

据国际数据公司&#xff08;IDC&#xff09;的报告显示&#xff0c;预计到2025年&#xff0c;全球范围内每天将产生高达180ZB的庞大数据量&#xff0c;这一趋势预示着企业将面临着更加严峻的海量数据处理挑战。随着数据日渐庞大&#xff0c;一些存储系统会出现诸如存储空间扩展…

【Python 千题 —— 算法篇】寻找最长回文子串

Python 千题持续更新中 …… 脑图地址 &#x1f449;&#xff1a;⭐https://twilight-fanyi.gitee.io/mind-map/Python千题.html⭐ 题目背景 回文串是指一个字符串从左到右和从右到左读都是一样的。寻找一个字符串中的最长回文子串是许多经典算法问题之一&#xff0c;广泛应…

使用docker安装jenkins,然后使用jenkins本地发版和远程发版

使用docker安装jenkins&#xff0c;然后使用jenkins本地发版和远程发版 1、安装docker 1.安装必要的一些系统工具 sudo yum install docker-ce 2.添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 3.更新…

DAY74

#ifndef WIDGET_H #define WIDGET_H#include <QWidget>#include <QPainter> //画家类 #include <QTimer> //定时器类 #include <QTime> //时间类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : …

风控——贷中管理介绍

一、概念介绍 贷中管理&#xff0c;指从贷款发放之日起&#xff0c;至贷款本息收回日期为止的贷款管理&#xff0c;贷中管理策略也集中在贷款发放后的管理和监控阶段&#xff0c;其目的是确保贷款资金的安全和有效使用&#xff0c;贷中预警和贷中调额在贷中管理中至关重要。 …

STMCuBeMX新建项目的两种匪夷所思的问题

错误一、保存地址名中有中文 错误&#xff1a;error1-haveCHinese_有中文\error1-haveCHinese_有中文.axf: error: L6002U: Could not open file error1-havechinese_???\stm32f1xx_it.o: No such file or directory 解决方法&#xff1a;重新导出&#xff0c;并且不要用中文…

Leetcode 701-二叉搜索树中的插入操作

给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入树中的值 value &#xff0c;将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 &#xff0c;新值和原始二叉搜索树中的任意节点值都不同。 注意&#xff0c;可能存在多种有效的插入方式&a…

c# Avalonia 架构开发跨平台应用

Avalonia&#xff0c;读&#xff1a;阿瓦隆尼亚 由于以前的c#开发的windows平台项目想移植到信创平台&#xff08;UOS,Kylin)上&#xff0c;起初想用python重写&#xff0c;后来发现了这个Avalonia,用这个改动起来工作相对较少于是就先了解一下。 官网Avalonia Docs | Avalon…

贪心-单调递增的数字

给定一个非负整数 N&#xff0c;找出小于或等于 N 的最大的整数&#xff0c;同时这个整数需要满足其各个位数上的数字是单调递增。 &#xff08;当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。&#xff09; 示例 1: 输入: …

如何显示Dialog窗口

文章目录 1. 概念介绍2. 使用方法2.1 Overlay效果2.1 Dialog效果 3. 示例代码4. 内容总结 我们在上一章回中介绍了"使用get显示snackBar"相关的内容&#xff0c;本章回中将介绍使用get显示Dialog.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在…