OpenResty + Tomcat + Redis实现多级缓存

标签: 缓存

为什么要使用缓存?

其实在大部分场景中都需要使用缓存,比如购物网站上商品详情页的信息。如果不使用缓存,用户每发起一起请求,都需要到服务端数据库查询,数据库的每一次操作都会涉及到磁盘IO,这样不仅增加服务端的压力,同时也降得了响应速度。而缓存存在于内存中,对缓存的操作比对磁盘的操作要快得多。但也不是什么数据都可以存入缓存中。首先内存大小有限,不可能将所有数据都存入内存中。其次对于那些更新比较频繁的数据,如果存入缓存中,就需要经常更新缓存,这有可能得不偿失。

所以缓存中存储的数据应该是一些比较热点(即经常访问)同时又不经常修改的数据。因为缓存的设计是为了提高数据处理速度,提高响应速度,所以也应该将缓存推到离用户最近的地方。

系统环境

在使用缓存之前整个系统的结构如下图所示:

服务器端使用的是SpringBoot2.0.5版本。

这里以查询商品详情信息为例。服务器端关于查询商品详情的部分代码如下:

/**
 * @author: Charviki
 * @create: 2019-09-03 14:47
 **/
@RestController
@RequestMapping("/item")
public class ItemController{

    @Autowired
    private ItemService itemService;

    @GetMapping(value = "/getItemById")
    public ItemModel getItemById(@RequestParam("id") Integer itemId){
        // 直接从数据库查询
        ItemModel itemModel = itemService.getItemById(itemId);     
		return itemModel;
    }
}
redis缓存

首先,我们可以在首次向查询商品数据之后,将数据存入redis中,下一次再查询商品数据时先从Redis缓存中取,如果缓存中没有,再从数据库中查询。修改controller,代码如下:

/**
 * @author: Charviki
 * @create: 2019-09-03 14:47
 **/
@RestController
@RequestMapping("/item")
public class ItemController{

    @Autowired
    private ItemService itemService;

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping(value = "/getItemById")
    public ItemModel getItemById(@RequestParam("id") Integer itemId){
        // 生成key,item+id
        String key = "item_" + itemId;
        // 先到redis查询
        ItemModel itemModel = (ItemModel) redisTemplate.opsForValue().get(key);
        // 判断redis中是否存在相应的商品信息
        if (itemModel == null){
            // 不存在,查询数据库
            itemModel = itemService.getItemById(itemId);
            // 将商品模型数据存入redis,并设置过期时间
            redisTemplate.opsForValue().set(key,itemModel,10, TimeUnit.MINUTES);
        }
		return itemModel;
    }
}
java本地热点缓存

我们还可以通过将缓存推向tomcat的位置,减少每次都需要访问redis的网络开销。缓存放到tomcat的位置,即在程序中将一些热点数据缓存起来,放入jvm堆中,实现了本地缓存。之后用户每次获取数据时先在本地中查询,如果本地没有,再向下一级缓存redis中查询。

引入依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

定义本地缓存操作接口:

**
 * 本地缓存操作类
 * @author: Charviki
 * @create: 2019-09-22 9:56
 **/
public interface CacheService {
    /**
     * 存入缓存
     * @param key
     * @param value
     */
    void setCommonCache(String key,Object value);

    /**
     * 根据key取出缓存
     * @param key
     * @return
     */
    Object getCommonCache(String key);
}

实现该接口:

/**
 * @author: Charviki
 * @create: 2019-09-22 9:57
 **/
@Service
public class CacheServiceImpl implements CacheService {

    private Cache<String,Object> commonCache = null;

    @PostConstruct
    public void init(){
        commonCache = CacheBuilder.newBuilder()
                // 设置缓存容器初始容量大小为10
                .initialCapacity(10)
                // 设置缓存容器最多存储100个key,超过100个之后会根据LRU的策略移除缓存项
                .maximumSize(100)
                // 设置缓冲过期时间
                .expireAfterWrite(60, TimeUnit.SECONDS).build();
    }

    @Override
    public void setCommonCache(String key, Object value) {
        commonCache.put(key,value);
    }

    @Override
    public Object getCommonCache(String key) {
        return commonCache.getIfPresent(key);
    }
}

修改controller代码:

/**
 * @author: Charviki
 * @create: 2019-09-03 14:47
 **/
@RestController
@RequestMapping("/item")
public class ItemController{

    @Autowired
    private ItemService itemService;

    @Autowired
    private RedisTemplate redisTemplate;
    
    @Autowired
    private CacheService cacheService;

    @GetMapping(value = "/getItemById")
    public ItemModel getItemById(@RequestParam("id") Integer itemId){
        // 生成key,item+id
        String key = "item_" + itemId;
        // 先根据key到本地缓存查找
        ItemModel itemModel = (ItemModel) cacheService.getCommonCache(key);
        // 判断本地缓存是否存在该商品信息
        if (itemModel == null){
            // 不存在,根据key到redis查询
            itemModel = (ItemModel) redisTemplate.opsForValue().get(key);
            // 判断redis中是否存在相应的商品信息
            if (itemModel == null){
                // 不存在,查询数据库
                itemModel = itemService.getItemById(itemId);
                // 将商品模型数据存入redis
                redisTemplate.opsForValue().set(key,itemModel,10, TimeUnit.MINUTES);
            }
            // 将商品模型数据存入本地缓存
            cacheService.setCommonCache(key,itemModel);
        }
		return itemModel;
    }
}
nginx缓存

我们再把缓存往上推,推到nginx的位置。我们能否在nginx上实现对商品的缓存呢?答案是肯定的。nginx自身有一套缓存机制,该缓存机制是将数据中存放在磁盘中,将数据的key(数据在磁盘中的地址)存放到内存中。

实现nginx自身的缓存机制,需要对nginx的配置文件nginx.conf作如下配置:

# 这里忽略了无关的配置,只需要在相应的位置下增加配置即可
http{
   # 申明一个cache缓存节点的内容 
   # levels=1:2 缓存文件可存储到二级目录        			   	  
   # keys_zone=tmp_cache:100m 内存中用于存储该keys_zone的内存大小
   # inactive=7d 缓存过期时间
   # max_size=10g 该文件系统最大存储的文件总量,超过后会采取LRU策略清除
   # 新增以下配置
   proxy_cache_path /usr/local/openresty/nginx/tmp_cache levels=1:2 	          	 	    keys_zone=tmp_cache:100m inactive=7d max_size=10g;
   
   server {
     
     # 这里使用监听根路径访问为例
     location / {
       # 新增以下配置
       # 缓存空间名
	   proxy_cache tmp_cache;
	   # 将uri作为缓存的key
	   proxy_cache_key $uri;
	   # 返回状态码为以下状态码时对数据进行缓存 最后一个7d指缓存过期时间
	   proxy_cache_valid 200 300 302 304 7d;        
     }
   }
   
}

nginx自身的缓存机制虽然将key值放入内存中,提供了查找速度,但是实际数据存放在磁盘中,在获取数据时会涉及磁盘IO,使用该缓存机制甚至无法提供响应速度,不推荐使用。

lua缓存

OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。我们可以在nginx上使用lua脚本实现数据的缓存。

修改nginx配置文件,删除上面的配置,新增以下配置:

lua_shared_dict my_cache 128m;

在openresty目录下新建lua目录,在lua目录下新增itemsharedic.lua脚本:

-- 从lua缓存中获取
function get_from_cache(key)
        local cache_ngx = ngx.shared.my_cache
        local value = cache_ngx:get(key)
        return value
end
-- 将数据存入lua缓存
function set_to_cache(key,value,exptime)
        if not expitime then
                expitime = 0
        end
        local cache_ngx = ngx.shared.my_cache
        local succ,err,forcible = cache_ngx:set(key,value,exptime)
        return succ
end

local args = ngx.req.get_uri_args()
local id = args["id"]
local item_model = get_from_cache("item_"..id)
if item_model == nil then
        local resp = ngx.location.capture("/item/getItemById?id="..id)
        item_model = resp.body
        set_to_cache("item_"..id,item_model,1*60)
end
ngx.say(item_model)

使用lua脚本实现的缓存数据都存储在内存中,操作速度比nginx自身的缓存机制要快得多。

lua + redis缓存(推荐使用)

使用lua脚本在nginx上实现了热点数据的缓存,这样虽然减轻了服务端的压力,但是却把压力都增加到了nginx上。同时,将数据缓存放到nginx的lua缓存中,一旦数据修改,对于lua缓存的更新是比较困难的。在openresty上同时集成了连接redis的插件,我们可以使用lua脚本,将数据获取直接转到redis中查询,当redis中获取不到时,再从tomcat中获取。这样缓存在redis中也方便管理。

修改nginx配置文件,删除上面的配置,新增以下配置:

# 这里的路径根据实际情况设置
location /luaitem/get {
	# 返回的数据类型
    default_type "application/json";
    # lua脚本
    content_by_lua_file ../lua/itemredis.lua;
}

在lua目录下新增itemredis.lua脚本:

local args = ngx.req.get_uri_args()
local id = args["id"]
local redis = require "resty.redis"
local cache = redis:new()
# 连接redis
local ok,err = cache:connect("172.18.88.10",6379)
# redis的第几个库
ok,err  = cache:select(10)
local item_model = cache:get("item_"..id)
if item_model == ngx.null or item_model == nil then
        local resp = ngx.location.capture("/item/getItemById?id="..id)
        item_model = resp.body
end
ngx.say(item_model)
总结

这里推荐使用的是lua+redis实现nginx端的缓存,还有java本地缓存和redis端缓存。实现后系统结构图如下:

版权声明:本文为qq_40674098原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_40674098/article/details/102053140

智能推荐

mybatis+ehcache二级缓存

导入jar包 mapper.xml文件开启二级缓存 pojo类实现序列化接口 配置ehcache.xml 测试...

python+opencv实现图像拼接

任务 拍摄两张图片去除相同部分,拼接在一起 原图 结果 步骤 读取两张图片 使用sift检测关键点及描述因子 匹配关键点 处理并保存关键点 得到变换矩阵 图像变换并拼接 代码实现 扩展 这里对右边图像进行变换,右边变得模糊,可以修改代码对左边图像变换 这里只有两张图片拼接,可以封装实现多张图片拼接 可以修改代码实现上下图片的拼接...

python_sklearn机器学习算法系列之AdaBoost------人脸识别(PCA,决策树)

          注:在读本文之前建议读一下之前的一片文章python_sklearn机器学习算法系列之PCA(主成分分析)------人脸识别(k-NearestNeighbor,KNN)         本文主要目的是通过一个简单的小...

memmove函数与memcpy函数的模拟实现

memmove函数和memcpy函数都是在内存复制任意类型的,但是它俩也有区别。当源区域和目标区域有重复的,memmove函数会复制缓冲区重叠的部分,而memcpy相反,会报出未知错误。 下面给出两个函数的实现 首先,memmove函数。 实现的基本原理如下图。 具体代码如下: memcpy函数的实现很简单,就直接给出源代码了...

猜你喜欢

SpringFramework核心 - IOC容器的实现 - 总结

1. 概述 把Spring技术内幕第一章和第二章过了一遍,也做了一些笔记, 对IOC容器的实现有了一定皮毛理解,现在跟着源码再过一遍总结一下IOC容器的初始化,Bean的初始化的过程,做一下总结 ① IOC容器和简单工厂模式 在开始之前,先想想我们平时是怎么使用IOC容器为我们管理Bean的,假设我们要把下面的User类交给IOC容器管理 我们不想关心如何创建一个User对象实例的,仅仅在需要他的...

Python和Django的安装

个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈  一、下载并安装Python Python 官方下载地址:http://www.python.org/ftp/python/ 我们这里选择的是 Python 2.7.2 。虽然目前最新版是Python 3.2.2, 但是Django目前还不支持 Python 3.2.2。 安装步骤很简单,双击安装包开...

OpenStack代码贡献初体验

为什么80%的码农都做不了架构师?>>>     OpenStack如今已成为开源云平台中的明星项目,得到广泛关注。OpenStack的优秀出众依赖于众多开发者的努力,在享受其带来的便利与快捷的同时,为其做一份贡献也是一个开发者的义务。  在前段时间的OpenStack的测试过程中,我发现Nova项目中的一个Bug,于是向社区提交了Bug报...

SQL Server之8:sql查询每个学生得分最高的两门课

这是一道面试题,今天有空把它记下来,以后遇到此类问题作个参考!刚一看到这个题目,估计好多人都会想到关键字top,其实这里用到的关键字是partition, 好了,先看看表结构,及数据吧!     接下来看一看partition的功能,执行语句   结果如下:   到这里一目了然知道最终结果了!   View Code     &...

vue-video-player 浏览器缩放

文章目录 前言 一、vue-video-player的封装 二、调用 1. 引入 2. vue template代码 2. 主要js代码 效果 前言 此篇是在上一次《[Vue 播放rtmp直播流]》基础之上的更新及补充;近期接到客户需求,需要在视频流上显示额外的信息;当然,视频流上叠加信息可以通过canvas来完成(透明canvas实现),但是在测试的过程中发现,当浏览器缩放时,叠加的图层信息与初...