OpenResty + Tomcat + Redis实现多级缓存

为什么要使用缓存?

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

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

系统环境

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

OpenResty + Tomcat + Redis实现多级缓存_第1张图片

服务器端使用的是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.guavagroupId>
    <artifactId>guavaartifactId>
    <version>18.0version>
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端缓存。实现后系统结构图如下:
OpenResty + Tomcat + Redis实现多级缓存_第2张图片

你可能感兴趣的:(Spring,Java)