微服务学习笔记--高级篇--(多级缓存)

这篇具有很好参考价值的文章主要介绍了微服务学习笔记--高级篇--(多级缓存)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

多级缓存

  • 安装OpenResty
  • OpenResty快速入门
  • 请求参数处理
  • 查询Tomcat
  • Redis缓存预热
  • 查询Redis缓存
  • Nginx本地缓存
初识OpenResty

OpenResty是一个基于Nginx的高性能Web平台,用于方便地搭建能够处理高并发、扩展性极高的动态Web应用、Web服务和动态网关。具备下列特点:

  • 具备Nginx的完整功能
  • 基于Lua语言进行扩展,集成了大量精良的Lua库、第三方模块
  • 允许使用Lua自定义业务逻辑、自定义库

官方网站:https://openresty.org/cn/


安装OpenResty


OpenResty快速入门

OpenResty快速入门,实现商品详情页面数据查询

商品详情页面目前展示的是假数据,在浏览器的控制台可以看到查询商品信息的请求:

Request URL:http://localhost/api/item.10001
Request Method:GET
Status Code:502 Bad Gateway
Remote Address:127.0.0.1:80
Referrer Policy:strict-orgin-when-cross-origin

而这个请求最终被反向代理到虚拟机的OpenResty集群:

upstream nginx-cluster{
	server 192.168.150.101:8081; #nginx-cluster集群,就是将来的nginx业务集群
}
server {
	listen       80;
	server_name  localhost;

	location /api {
		proxy_pass http://nginx-cluster; #监听/api路径,反向代理到nginx-cluster集群
	}
}

需求:在OpenResty中接收这个请求,并返回一段商品的假数据。


步骤:步骤一:修改nginx.conf文件

1.在nginx.conf的http下面,添加对OpenResty的Lua模块的加载:

# 加载lua模块
lua_package_path lua_package_path "/usr/local/openresty/lualib/?.lua;;"
# 加载c模块
lua_package)cpath "/usr/local/openresty/lualib/?.lua;;"

2.在nginx.conf的server下面,添加对/api/item这个路径的监听

location /api/item {
	# 默认的响应类型-*
	default_type application/json;
	# 响应结果由 lua/item.lua 这个文件来决定
	content_by_lua_file lua/item/lua;
}

步骤:步骤二:修改item.lua文件

1.在nginx目录创建文件夹:lua

nginx]# pwd
/usr/local/openresty/nginx
nginx]#mkdir lua

2.在lua文件夹下,新建文件:item.lua

nginx]#pwd
/usr/local/openresty/nginx
nginx]#touch lua/item/lua

3.内容如下:

-- 返回假数据,这里的ngx.say()函数,就是写数据到Response中
ngx.say('{"id":1001,"name":"SALSA AIR"}')

4.重新加载配置

nginx -s reload

请求参数处理

OpenResty获取请求参数

OpenResty提供了各种API用来获取不同类型的请求参数:

参数格式 参数示例 参数解析代码示例
路径占位符 /item/1001 # 1.正则表达式匹配: location ~ /item/(\d+) {content_by_lua_file lua/item.lua;} --2、匹配到的参数会存入ngx、var数组中 – 可以用角标获取 local id = ngx.var[1]
请求头 id:1001 –获取请求头,返回值是table类型 local headers = ngx.req.get_headers()
Get请求参数 ?id=1001 –获取GET请求参数,返回值是table类型 local getParams= ngx.req.get_uri_args()
Post表单参数 id=1001 –读取请求头 ngx.req.read_body() – 获取POST表单参数,返回值是table类型 local postParams = ngx.req.get_post_args()
JSON参数 {“id”:1001} – 读取请求头 ngx.req.read_body() – 获取bogy中的json参数,返回值是string类型 local jsonBody = ngx.req.get_body_data()

查询Tomcat

案例:获取请求路径中的商品id信息,根据id向Tomcat查询商品信息

这里要修改item.lua,满足下面的需求:
1.获取请求参数中的id
2.根据id向Tomcat服务发送请求,查询商品信息
3.根据id向Tomcat服务发送请求,查询库存信息
4.组装商品信息、库存信息,序列化为JSON格式并返回

nginx内部发送Http请求

nginx提供了内部API用以发送http请求:

local resp = ngx.location.capture("/path",{
	method = ngx.HTTP_GET,  -- 请求方式
	args = {a=1,b=2},       -- get方式传参数
	body = "c=3&d=4"        -- post方式传参数
})

返回的响应内容包括:

  • resp.status:响应状态码
  • resp.header:响应头,是一个table
  • resp.body:响应体,就是响应数据

注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理。
但是我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理:

location /path {
	# 这里是windows电脑的ip和java服务端口,需要确保Windows防火墙处于关闭状态
	proxy_pass http://192.168.150.1:8081;
}
封装http查询的函数

我们可以把http查询的请求封装为一个函数,放到OpenResty函数库中,方便后期使用。

1.在/usr/local/openresty/lualib目录下创建common.lua文件:

vi /usr/local/openresty/lualib/common.lua

2.在common.lua中封装http查询的函数

-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)
	local resp = ngx.location.capture(pathm{
		method = ngx.HTTP_GET,
		args = params,
	})
	if not resp then
		-- 记录错误信息,返回404
		ngx.log(ngx.ERR, "http not found,path: ", path, ".args:",args)
		ngx.exit(404)
	end
	return resp.body
end
-- 将方法导出
local -M = {
	read_http = read_http
}
return -M
使用Http函数查询数据

刚才已经把http查询的请求封装为一个函数,放到OpenResty函数库中,接下来就可以使用这个库了。

  • 修改item.lua文件
-- 引入自定义工具模块
local common = require("common")
local read_http = common.read_http

-- 获取路径参数
local id = ngx.var[1]

-- 根据id查询商品
local itemJSON = read_http("/item/" .. id,nil)
-- 根据id查询商品库存
local itemStockJSON = read_http("/item/stock/" .. id,nil)

查询到的是商品、库存的json格式数据,我们需要将两部分数据组装,需要用到JSON处理函数库。

JSON结果处理

OpenResty提供了一个cjson的模块用来处理JSON序列化和反序列化。

官方地址:https://github.com/openresty/lua-cjson/

  • 引入cjson模块:
local cjson = require "cjson"
  • 序列化:
local obj = {
	name = 'jack'
	age = 21
}
local json = cjson.encode(obj)

反序列化:

local json = '{"name": "jack", "age": 21}'
-- 反序列化
local obj = cjson.decode(json);
print(obj.name)

item.lua

-- 导入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集群的负载均衡

# 反向代理配置,将/item路径的请求处理到tomcat集群
location /item {
	proxy_pass http://tomcat-cluster}
# tomcat集群配置
upstream tomcat-cluster{
	hash $request_uri;
	server 192.168.150.1:8081;
	server 192.168.150.1:8082;
}

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

4.编写初始化类

@Component
public class RedisHandle 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.1item序列化为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.1item序列化为JSON
		String json = MAPPER.writeValueAsString(stock);
		//2.2存入redis
		redisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json);
	}
  }
}

查询Redis缓存

OpenResty的Redis模块

OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用:

  • 引入Redis模块,并初始化Redis对象
-- 引入redis模块
local redis = require("resty.redis")
-- 初始化Redis对象
local red = redis:new()
-- 设置Redis超时时间
red:set_timeouts(1000,1000,1000)
  • 封装函数,用来释放Redis连接,其实是放入连接池
-- 关闭redis连接的工具方法,其实是放入连接池
local function close_redis(red)
	local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒
	local pool_size = 100 -- 连接池大小
	local ok, err = redis:set_keepalive(pool_max_idle_time, pool_size)
	if not ok then
		ngx.log(ngx.ERR,"放入Redis连接池失败:",err)
	end
end

OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用:

  • 封装函数,从Redis读数据并返回
-- 查询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 = redis:get(key)
	-- 查询失败处理
	if not resp then
		ngx.log(ngx.ERR, "查询Redis失败:",err,", key = ",key)
	end
	-- 得到的数据为空处理
	if resp == ngx.null then
		resp = null
		ngx.log(ngx.ERR,"查询Redis数据为空,key = ", key)
	end
	close_redis(red)
	return resp
end

案例:查询商品时,优先Redis缓存查询

需求:

  • 修改item.lua,封装一个函数read_data,实现先查询Redis,如果未命中,再查询tomcat
  • 修改item.lua,查询商品和库存时都调用read_data这个函数
-- 封装函数,先查询redis,再查询http
local function read_data(key,path,params)
	-- 查询redis
	local resp = read_redis("127.0.0.1",6379,key)
	-- 判断redis是否命中
	if not resp then
		-- Redis查询失败,查询http
		resp = read_http(path, parms)
	end
	return resp
end
nginx本地缓存

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

  • 开启共享字典,在nginx.conf的http下添加配置:
# 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m
lua_shared_dict item_cache 150m;
  • 操作共享字典
-- 获取本地缓存对象
local item_cache = ngx.shared.item_cache
-- 存储,指定key、value、过期时间,单位为s,默认为0代表永不过期
item_cache:set('key','value',1000)
--读取
local val = item_cache:get('key')

案例:在查询商品时,优先查询OpenResty的本地缓存

需求:文章来源地址https://www.toymoban.com/news/detail-488283.html

  • 修改item.lua中的read_data函数,优先查询本地缓存,未命中时再查询Redis、Tomcat
  • 查询Redis或Tomcat成功后,将数据写入本地缓存,并设置有效期
  • 商品基本信息,有效期30分钟
  • 库存信息,有效期1分钟
-- 导入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

-- 封装函数
local function read_data(key,expire,path,params)
	-- 查询本地缓存
	local val = item_cahche:get(key)
	if not val then
		ngx.log("本地缓存查询失败,尝试查询Redis,key:",key)
		-- 查询redis
		var= read_redis("127.0.0.1",6379,key)
		-- 判断查询结果
		if not val then
			ngx.log("redis查询失败,尝试查询http,key:",key)
			-- redis查询失败,查询http
			val = read_http(path, parms)
		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))

到了这里,关于微服务学习笔记--高级篇--(多级缓存)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微服务(多级缓存)

    目录 多级缓存 1.什么是多级缓存 2.JVM进程缓存 2.2.初识Caffeine 2.3.实现JVM进程缓存 2.3.1.需求 2.3.2.实现 3.Lua语法入门 3.1.初识Lua 3.1.HelloWorld  3.2.变量和循环 3.2.1.Lua的数据类型 3.2.2.声明变量 3.2.3.循环 3.3.条件控制、函数 3.3.1.函数 3.3.2.条件控制 3.3.3.案例 4.实现多级缓存 4.1.安装

    2024年02月11日
    浏览(32)
  • 微服务08-多级缓存

    传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图: 存在下面的问题: •请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 •Redis缓存失效时,会对数据库产生冲击 多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻T

    2024年02月13日
    浏览(43)
  • 微服务中间件--多级缓存

    传统缓存的问题 传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,存在下面的问题: 请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 Redis缓存失效时,会对数据库产生冲击 多级缓存方案 多级缓存就是充分利用请求处理的每个环节,分别

    2024年02月11日
    浏览(49)
  • 服务端应用多级缓存架构方案

    20w的QPS的场景下,服务端架构应如何设计? 可使用分布式缓存来抗,比如redis集群,6主6从,主提供读写,从作为备,不提供读写服务。1台平均抗3w并发,还可以抗住,如果QPS达到100w,通过增加redis集群中的机器数量,可以扩展缓存的容量和并发读写能力。同时,缓存数据对于

    2024年02月16日
    浏览(39)
  • Redis学习(三)分布式缓存、多级缓存、Redis实战经验、Redis底层原理

    单节点Redis存在着: 数据丢失问题:单节点宕机,数据就丢失了。 并发能力和存储能力问题:单节点能够满足的并发量、能够存储的数据量有限。 故障恢复问题:如果Redis宕机,服务不可用,需要一种自动的故障恢复手段。 RDB持久化 RDB(Redis database backup file,Redis数据库备份

    2024年02月16日
    浏览(40)
  • Redis学习(三)持久化机制、分布式缓存、多级缓存、Redis实战经验

    单节点Redis存在着: 数据丢失问题:单节点宕机,数据就丢失了。 并发能力和存储能力问题:单节点能够满足的并发量、能够存储的数据量有限。 故障恢复问题:如果Redis宕机,服务不可用,需要一种自动的故障恢复手段。 RDB持久化 RDB(Redis database backup file,Redis数据库备份

    2024年02月16日
    浏览(50)
  • 微服务---分布式多级缓存集群实现方案(Caffeine+redis+nginx本地缓存+Canal数据同步)

    传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图: 存在下面的问题: •请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 •Redis缓存失效时,会对数据库产生冲击 多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻T

    2024年02月12日
    浏览(37)
  • 微服务系统面经之三: 以秒杀系统为例-多级缓存及其更新机制

    答:本地内存+redis 答:当redis预减库存为0的时候,这个就需要改为false 22.4.1 内存标记和redis预减库存的操作,哪一个属于一级缓存哪一个属于二级 在秒杀系统设计中,\\\"内存标记\\\"和\\\"Redis预减库存\\\"都是优化手段,旨在减少对数据库的访问以提高系统的性能。但在缓存的层级分

    2024年02月09日
    浏览(32)
  • Eureka 学习笔记6:服务端实例缓存

    版本 awsVersion = ‘1.11.277’ 缓存 类型 registry ConcurrentHashMapString, MapString, LeaseInstanceInfo AbstractInstanceRegistry成员变量 readWriteCacheMap LoadingCache ResponseCacheImpl成员变量 readOnlyCacheMap ConcurrentMapKey, Value ResponseCacheImpl成员变量 registry evictionIntervalTimerInMs 指定清理未续约服务实例的时间间隔

    2024年02月12日
    浏览(34)
  • Eureka 学习笔记1:服务端实例缓存

    版本 awsVersion = ‘1.11.277’ 缓存 类型 registry ConcurrentHashMapString, MapString, LeaseInstanceInfo AbstractInstanceRegistry成员变量 readWriteCacheMap LoadingCache ResponseCacheImpl成员变量 readOnlyCacheMap ConcurrentMapKey, Value ResponseCacheImpl成员变量 registry evictionIntervalTimerInMs 指定清理未续约服务实例的时间间隔

    2024年02月15日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包