黑马Redis笔记高级篇 | 多级缓存

黑马Redis笔记高级篇 | 多级缓存(黑马教程云服务器踩坑记录)

  • 1、JVM进程缓存(tomcat服务内部)
    • 1.1 导入商品案例
    • 1.2 初识Caffeine
    • 1.3 实现进程缓存
  • 2、Lua语法入门
    • 2.1 初识Lua
    • 2.2 变量和循环
    • 2.3 条件控制、函数
  • 3、多级缓存
    • 3.1 安装OpenResty
    • 3.2 OpenResty快速入门
    • 3.3 请求参数处理
    • 3.4 查询Tomcat
    • 3.5 Redis缓存预热
    • 3.6 查询Redis缓存
    • 3.7 Nginx本地缓存
  • 4、缓存同步策略
    • 4.1 数据同步策略
    • 4.2 安装Canal
    • 4.3 监听Canal
  • 多级缓存总结


黑马Redis笔记高级篇 | 多级缓存_第1张图片
黑马Redis笔记高级篇 | 多级缓存_第2张图片

1、JVM进程缓存(tomcat服务内部)

1.1 导入商品案例

1、安装docker教程:黑马-Centos7安装Docker
2、根据本章资料中【案例导入说明.md】启动mysql镜像,注意要先关闭本地的mysql服务,否则3306端口被占用,mysql镜像会启动失败。
3、接下来完全按照【案例导入说明.md】导入

1.2 初识Caffeine

黑马Redis笔记高级篇 | 多级缓存_第3张图片
黑马Redis笔记高级篇 | 多级缓存_第4张图片
黑马Redis笔记高级篇 | 多级缓存_第5张图片
黑马Redis笔记高级篇 | 多级缓存_第6张图片

1.3 实现进程缓存

黑马Redis笔记高级篇 | 多级缓存_第7张图片


2、Lua语法入门

黑马Redis笔记高级篇 | 多级缓存_第8张图片

2.1 初识Lua

黑马Redis笔记高级篇 | 多级缓存_第9张图片
黑马Redis笔记高级篇 | 多级缓存_第10张图片

2.2 变量和循环

黑马Redis笔记高级篇 | 多级缓存_第11张图片
黑马Redis笔记高级篇 | 多级缓存_第12张图片
黑马Redis笔记高级篇 | 多级缓存_第13张图片

2.3 条件控制、函数

黑马Redis笔记高级篇 | 多级缓存_第14张图片
黑马Redis笔记高级篇 | 多级缓存_第15张图片
黑马Redis笔记高级篇 | 多级缓存_第16张图片


3、多级缓存

3.1 安装OpenResty

黑马Redis笔记高级篇 | 多级缓存_第17张图片
按照【安装OpenResty】教程安装,云服务器记得打开配置文件中对应端口(8081)

3.2 OpenResty快速入门

黑马Redis笔记高级篇 | 多级缓存_第18张图片
黑马Redis笔记高级篇 | 多级缓存_第19张图片
黑马Redis笔记高级篇 | 多级缓存_第20张图片

本地访问商品详情页时需要开启本地nginx,同时云服务器也要reload配置。

3.3 请求参数处理

黑马Redis笔记高级篇 | 多级缓存_第21张图片
黑马Redis笔记高级篇 | 多级缓存_第22张图片

//服务器OpenResty的nginx.conf
#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/(\d+) {
            # 默认的响应类型
            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;
        }
    }
}
--OpenResty的item.lua
-- 获取路径参数
local id = ngx.var[1]
-- 返回结果
ngx.say('{"id":' .. id .. ',"name":"SALSA AIR","title":"RIMOWA 26寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4","price":19900,"image":"https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp","category":"拉杆箱","brand":"RIMOWA","spec":"","status":1,"createTime":"2019-04-30T16:00:00.000+00:00","updateTime":"2019-04-30T16:00:00.000+00:00","stock":2999,"sold":31290}')

3.4 查询Tomcat

黑马Redis笔记高级篇 | 多级缓存_第23张图片
黑马Redis笔记高级篇 | 多级缓存_第24张图片
黑马Redis笔记高级篇 | 多级缓存_第25张图片
黑马Redis笔记高级篇 | 多级缓存_第26张图片
黑马Redis笔记高级篇 | 多级缓存_第27张图片
黑马Redis笔记高级篇 | 多级缓存_第28张图片
这里由于本人的OpenResty安装在云服务器,而运行的程序(即视频中说到的tomcat服务器)在本地,实操踩了很多坑,详细记录一下。

首先我要先理清请求的过程:
1、浏览器向windows本地80端口发送请求:http://localhost/api/item/10003
2、该请求被本地的nginx拦截处理,转发给:云服务器IP:8081,即云服务器OpenResty
3、云服务器OpenResty(基于nginx)监听到符合/api/item/(\d+)的请求,调用lua脚本查询数据。由于此时云服务器被反向代理到windows本地,lua脚本查询到本地8081端口的tomcat服务器中的数据。

因此,想要正确地查到数据,首先需要先保证本地程序正处于运行状态,为8081端口提供服务。
其次需要保证本地端口能被云服务器(外网)访问,因此需要对本地windows进行内网穿透,这里我使用的是花生壳。
接下来贴出我的对应配置即代码以供参考:(出于隐私保护,我将对应ip和域名替换成【解释】的形式)
1、本地nginx配置文件


#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    #nginx的业务集群,nginx本地缓存,redis缓存,tomcat查询
    upstream nginx-cluster{
        server 【云服务器ip】:8081;
    }
    server {
        listen       80;
        server_name  localhost;

	location /api {
            proxy_pass http://nginx-cluster;
        }

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

2、云服务器OpenResty中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;;";  

    server {
        listen       8081;
        server_name  localhost;
        location /item {
            # windows电脑的ip和java服务端口
            proxy_pass http://【本地windows内网穿透后的外网域名】:【网穿透后的端口】;
        }
        location ~ /api/item/(\d+) {
            # 默认的响应类型
            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;
        }
    }
}

3、云服务器OpenResty中执行的lua脚本(这里调用的 read_http 等函数可以自行去黑马资料中找)

--导入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))

多台tomcat负载均衡案例的云服务器OpenResty中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 【本地windows内网穿透后的外网域名】:【网穿透后的端口1;# 注意这里没有http://
        server 【本地windows内网穿透后的外网域名】:【网穿透后的端口2;# 注意这里没有http://
    }
    server {
        listen       8081;
        server_name  localhost;
        location /item {
            # windows电脑的ip和java服务端口
            proxy_pass http://tomcat-cluster;
        }
        location ~ /api/item/(\d+) {
            # 默认的响应类型
            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;
        }
    }
}

3.5 Redis缓存预热

黑马Redis笔记高级篇 | 多级缓存_第29张图片
黑马Redis笔记高级篇 | 多级缓存_第30张图片
1、云服务器利用Docker安装Redis

docker run --name redis -p 6379:6379 -d redis redis-server --appendonly yes

2、在item-service服务中引入Redis依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

3、配置Redis地址

spring:
  redis:
    host: 【云服务器的ip(用于访问docker中6379端口的redis)】

4、编写初始化类

@Component
public class RedisHandler implements InitializingBean {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IItemService itemService;
    @Autowired
    private IItemStockService stockService;
    private static final ObjectMapper MAPPER = new ObjectMapper();

    @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
            redisTemplate.opsForValue().set("item:id:" + item.getId(), json);
        }

        // 3.查询商品库存信息
        List<ItemStock> stockList = stockService.list();
        // 4.放入缓存
        for (ItemStock stock : stockList) {
            // 2.1.item序列化为JSON
            String json = MAPPER.writeValueAsString(stock);
            // 2.2.存入redis
            redisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json);
        }
    }
}

3.6 查询Redis缓存

黑马Redis笔记高级篇 | 多级缓存_第31张图片
黑马Redis笔记高级篇 | 多级缓存_第32张图片
黑马Redis笔记高级篇 | 多级缓存_第33张图片
1、修改common.lua引入Redis模块

-- 引入redis模块
local redis = require('resty.redis')
-- 初始化Redis对象
local red = redis:new()
-- 设置Redis超时时间
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

2、修改item.lua,设置为先查redis后查tomcat,减小tomcat服务器的压力,这里的127.0.0.1要替换成云服务器ip
这里我也不清楚原因,明明OpenResty和redis都在云服务器上,但使用127.0.0.1查询就只能查到前三个商品。如果有大佬懂的话欢迎在评论区解答!

--导入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",6379,key)
	local resp = read_redis("【云服务器ip】",6379,key)
	-- 判断查询结果
	if not resp then
	    -- 这里一定要加(key or nil),否则会报错lua entry thread aborted: runtime error
		ngx.log("redis查询失败,尝试查询http key:",(key or nil))
		-- 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))

3.7 Nginx本地缓存

黑马Redis笔记高级篇 | 多级缓存_第34张图片
黑马Redis笔记高级篇 | 多级缓存_第35张图片
黑马Redis笔记高级篇 | 多级缓存_第36张图片
黑马Redis笔记高级篇 | 多级缓存_第37张图片

1、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;;";  
    # 添加共享词典(本地缓存)
    lua_shared_dict item_cache 150m;

    upstream tomcat-cluster {
        hash $request_uri;
        server 【本地windows内网穿透后的外网域名】:【网穿透后的端口1;# 注意这里没有http://
        server 【本地windows内网穿透后的外网域名】:【网穿透后的端口2;# 注意这里没有http://
    }
    server {
        listen       8081;
        server_name  localhost;
        location /item {
            # windows电脑的ip和java服务端口
            proxy_pass http://tomcat-cluster;
        }
        location ~ /api/item/(\d+) {
            # 默认的响应类型
            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;
        }
    }
}

2、修改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 or nil))
		-- 查redis
		-- val = read_redis("127.0.0.1",6379,key)
		val = read_redis("124.222.157.95",6379,key)
		-- 判断查询结果
		if not val then
			ngx.log(ngx.ERR, "redis查询失败,尝试查询http key:",(key or nil))
			-- redis查询失败,查询http
			val = read_http(path, params)
		end
	end
	-- 查询成功,写入本地缓存
	item_cache:set(key, val, expire)
	-- 返回数据
	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))

4、缓存同步策略

4.1 数据同步策略

黑马Redis笔记高级篇 | 多级缓存_第38张图片
1、基于MQ的异步通知:发消息对于item-service仍然有侵入
黑马Redis笔记高级篇 | 多级缓存_第39张图片
2、基于Canal的异步通知:无侵入
黑马Redis笔记高级篇 | 多级缓存_第40张图片

4.2 安装Canal

黑马Redis笔记高级篇 | 多级缓存_第41张图片
黑马Redis笔记高级篇 | 多级缓存_第42张图片
根据【安装Canal.md】安装即可。

4.3 监听Canal

黑马Redis笔记高级篇 | 多级缓存_第43张图片
黑马Redis笔记高级篇 | 多级缓存_第44张图片
黑马Redis笔记高级篇 | 多级缓存_第45张图片
黑马Redis笔记高级篇 | 多级缓存_第46张图片
跟着视频教程操作即可,云服务器11111端口记得开放。


多级缓存总结

黑马Redis笔记高级篇 | 多级缓存_第47张图片

你可能感兴趣的:(redis,缓存,redis,笔记)