1. 程式人生 > 其它 >HM-SpringCloud微服務系列11.3.5【實現多級快取(4)】

HM-SpringCloud微服務系列11.3.5【實現多級快取(4)】

6. 查詢Redis快取

現在,Redis快取已經準備就緒,我們可以再OpenResty中實現查詢Redis的邏輯了。如下圖紅框所示:

當請求進入OpenResty之後:

  • 優先查詢Redis快取
  • 如果Redis快取未命中,再查詢Tomcat

6.1 封裝Redis工具

OpenResty提供了操作Redis的模組,我們只要引入該模組就能直接使用。

但是為了方便,我們將Redis操作封裝到之前的common.lua工具庫中。

修改/usr/local/openresty/lualib/common.lua檔案:



完整的common.lua:

-- 匯入redis
local redis = require('resty.redis')
-- 初始化redis物件
local red = redis:new()
-- 設定redis超時時間(1000ms=1s,建立連線、傳送請求、接收響應)
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

6.2 實現Redis查詢

修改item.lua檔案,實現對Redis的查詢了。查詢邏輯是:

  • 根據id查詢Redis
  • 如果查詢失敗則繼續查詢Tomcat
  • 將查詢結果返回

1)新增一個查詢函式:

-- 匯入common函式庫
local common = require('common')
local read_http = common.read_http
local read_redis = common.read_redis
-- 封裝查詢函式
function read_data(key, path, params)
    -- 查詢本地快取
    local resp = read_redis("127.0.0.1", 6379, key) -- 因為本程式碼是由openResty執行的,而redis和openResty都在同一臺虛擬機器中,127.0.0.1代表本地
    -- 判斷查詢結果
    if not val then
        ngx.log(ngx.ERR, "redis查詢失敗,嘗試查詢http, key: ", key)
        -- redis查詢失敗,去查詢http
        val = read_http(path, params)
    end
    -- 返回資料
    return val
end

2)修改商品查詢、庫存查詢的業務:

-- 查詢商品資訊
local itemJSON = read_data("item:id:" .. id,  "/item/" .. id, nil)
-- 查詢庫存資訊
local stockJSON = read_data("item:stock:id:" .. id, "/item/stock/" .. id, nil)

完整的item.lua:

-- 引入自定義common工具模組,返回值是common中返回的 _M
local common = require("common")
-- 從 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)
    -- 判斷查詢結果
    if not resp then
        ngx.log(ngx.ERR, "redis查詢失敗,嘗試查詢http, key: ", key)
        -- redis查詢失敗,去查詢http
        resp = read_http(path, params)
    end
    -- 返回資料
    return resp
end

-- 獲取路徑引數
local id = ngx.var[1]

-- 根據id查詢商品
-- local itemJSON = read_http("/item/".. id, nil)
local itemJSON = read_data("item:id:"..id, "/item/"..id, nil)
-- 根據id查詢商品庫存
-- local itemStockJSON = read_http("/item/stock/".. id, nil)
local itemStockJSON = read_data("item:stock:id:"..id, "/item/stock/"..id, nil)

-- JSON轉換為lua的table(反序列化)
local item = cjson.decode(itemJSON)
local stock = cjson.decode(itemStockJSON)
-- 組合資料(item中原本沒有但現在需要的資料去stock中取)
item.stock = stock.stock
item.sold = stock.sold

-- 把item序列化為json返回結果
ngx.say(cjson.encode(item))

6.3 測試

瀏覽器訪問http://localhost/item.html?id=10004一次(此時本地idea中tomcat是執行狀態)

然後停掉tomcat

再訪問一次

老師演示的還可以正常訪問,我的報500,openResty有問題,lua程式碼問題?;難道是因為redis亂碼導致?

發現還能正常訪問,說明這時的資料流來自虛擬機器的redis快取(本地遠端檢視一下虛擬機器的redis資料,如下)

看一下openResty的日誌

應該就是redis快取亂碼的問題,因為key對不上所以查不到資料

6.4 RedisTempalte亂碼問題解決

參考:
https://blog.csdn.net/hy8363321/article/details/108967526
https://blog.csdn.net/qq_33764491/article/details/80955772

package com.heima.item.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * RedisTemplate,在傳遞String型別的資料結構後,檢視redis快取會發現資料亂碼現象,需要修改RedisTemplate的序列化策略
 * https://blog.csdn.net/qq_33764491/article/details/80955772
 * https://blog.csdn.net/hy8363321/article/details/108967526
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        
        /*// 使用Jackson2JsonRedisSerialize替換預設序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);*/
        
        // 設定key和value的序列化規則
//        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        /*redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());*/
        redisTemplate.afterPropertiesSet();
        
        return redisTemplate;
    }
}

亂碼問題解決,[4.7.3 測試]正常了,即在停止tomcat後再次訪問http://localhost/item.html?id=10004時可以直接從虛擬機器的redis快取中獲取資料,正常訪問資料,如下