redis || 缓存预热

添加redis缓存的需求

冷启动与缓存预热

冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力。

缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中。

我们数据量较少,可以在启动时将所有数据都放入缓存中。

缓存预热

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: 172.16.3.152

4、编写初始化类

缓存预热需要在项目启动时完成,并且必须是拿到RedisTemplate之后。这里我们利用InitializingBean接口来实现,因为InitializingBean可以在对象被Spring创建并且成员变量全部注入后执行。

package com.h.item.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.h.item.pojo.Item;
import com.h.item.pojo.ItemStock;
import com.h.item.service.IItemService;
import com.h.item.service.IItemStockService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;

@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);
        }
    }
}

本地缓存API

OpenResty为Nginx提供了shard dict的功能,可以在nginx的多个worker之间共享数据,实现缓存功能。

1)开启共享字典,在nginx.conf的http下添加配置:

 # 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m
 lua_shared_dict item_cache 150m; 

2)操作共享字典:

-- 获取本地缓存对象
local item_cache = ngx.shared.item_cache
-- 存储, 指定key、value、过期时间,单位s,默认为0代表永不过期
item_cache:set('key', 'value', 1000)
-- 读取
local val = item_cache:get('key')

实现本地缓存查询

1. 修改 '/usr/local/openresty/lua/item.lua' 文件,修改read_data查询函数,添加本地缓存逻辑:

-- 导入共享词典,本地缓存
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", 6379, key)
        -- 判断查询结果
        if not val then
            ngx.log(ngx.ERR, "redis查询失败,尝试查询http, key: ", key)
            -- redis查询失败,去查询http
            val = read_http(path, params)
        end
    end
    -- 查询成功,把数据写入本地缓存
    item_cache:set(key, val, expire)
    -- 返回数据
    return val
end

修改item.lua中查询商品和库存的业务,实现最新的read_data函数:

其实就是多了缓存时间参数,过期后nginx缓存会自动删除,下次访问即可更新缓存。这里给商品基本信息设置超时时间为30分钟,库存为1分钟。因为库存更新频率较高,如果缓存时间过长,可能与数据库差异较大。完整的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", 6379, key)
        -- 判断查询结果
        if not val then
            ngx.log(ngx.ERR, "redis查询失败,尝试查询http, key: ", key)
            -- 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))

### Redis 缓存预热概述 Redis缓存预热是指在应用启动或者特定时间点,预先将可能被频繁访问的数据加载到Redis缓存中[^1]。这一过程不仅涉及简单的数据加载操作,还包含了对缓存数据的维护机制,以确保缓存中的数据能够及时更新并与数据库中的原始数据保持一致性。 #### 实现方法 为了实现高效的缓存预热,在实际开发过程中可以采用多种策略和技术手段: 1. **多线程或多进程并行处理** 鉴于热数据量通常较大,单一线程或单一服务可能会导致效率低下。因此可以通过多个服务实例并行读取数据,并将其写入Redis缓存来提升性能[^2]。这种方式利用分布式架构的优势,显著缩短了整个预热的时间周期。 2. **基于业务逻辑筛选热数据** 并不是所有的数据都需要进行缓存预热,只有那些访问频率较高、查询耗时较长的关键数据才值得优先考虑。通过分析历史访问记录或其他指标,识别出这些热点数据集作为目标对象。 3. **编程接口支持复杂结构存储** 对于某些特殊场景下的需求,比如需要保存带有权重分值的关系型数据,则可借助像`zAdd()`这样的API函数完成向有序集合类型的键值对添加成员及其对应分数的操作[^3]。下面给出了一段具体的代码示例用于展示如何调用该功能: ```java public void zAdd(String key, Object value, double score) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); zset.add(key, value, score); } ``` 此片段定义了一个名为`zAdd`的方法,它接受三个参数——代表唯一标识符的字符串形式key、任意类型的value以及表示顺序依据的浮点数score;最终实现了针对指定Key下Value-Score映射关系的确立。 ### 注意事项 尽管上述提到的技术方案有助于提高系统的整体表现力,但在具体实施之前还需注意以下几点: - 数据同步问题:由于源端(通常是后台数据库)和目的端(Redis Cache之间可能存在延迟差异),所以要设计合理的刷新策略防止出现脏读现象。 - 资源消耗评估:大规模并发写入动作会对服务器造成额外负担,故而应当合理规划参与节点数量及每批次传输规模大小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韩未零

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值