赞
踩
传统缓存问题:
解决方案,在tomcat之前再添加缓存,使得部分请求没有经过tomcat,提升tomcat的并发处理能力。在各个处理环节都添加上缓存,一层一层的进行过滤,减少后面的压力,也能防止部分缓存不可用能有其他缓存抵上
将性能较高的nginx挡在大量动态请求的第一关,在它内部进行缓存的查取,进行高并发的处理能力,只有缓存未查找到,过滤出的部分请求到达tomcat,因为tomcat性能较弱,这样由nginx挡住大量请求,减轻tomcat压力
设计数据库
配置nginx
到此处已完成
jvm内存缓存即编写java代码,在运行时保存一些数据作为缓存,运行在tomcat中。jvm的缓存形式有很多,例如定义一个变量 map,将产生的数据放入map中,即在运行时,放在JVM进程的缓存
进程缓存与分布式缓存比较:
一款进程缓存类技术,命中率高,性能高,使用类似与map
使用方式:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
例:
创建对象
使用缓存
tomcat中 使用 java 执行业务,nginx+lua 执行业务
lua是一种脚本语言
,类似于python,使用c语言编写,通常能够嵌入c语言
编写的程序。
linux内置lua环境
变量的定义
lua的数据类型
数据类型 | 说明 |
---|---|
nil | 相当于一个null 在条件语句中,nil=false |
boolean | true、false |
number | 类似java的double |
string | 就是String,可以用 " 也可以用‘ |
function | 函数,类似于js的函数声明 |
table | 一些map,list,数组 |
变量定义上与java的一些区别
local
进行本地变量的声明,是一种弱类型语言。若不加 类型 为全局变量,类似于 let
..
进行拼接数组、集合的遍历
没有大括号用来标志开始、结束。使用do
标志开始,使用end
标志结束
遍历数组的格式:
效果
map的遍历
效果
条件控制
与java的不同在于 条件判断不能使用 符号 ,只能使用单词。 &->and
、| ->or
、 !->not
创建函数、调用函数
函数的调用
例 :定义一个函数,可以打印table,当参数为nil时,打印错误信息
一款基于于nginx
平台与lua脚本
的web服务器,是对nginx的一种补充,为nginx提供了大量脚本用于业务处理,使得nginx结合lua脚本 到达 tomcat服务器的效果,但性能远高于tomcat
特点:
使得nginx可以向tomcat一样处理web业务
安装过程
依赖开发库
yum install -y pcre-devel openssl-devel gcc --skip-broken
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
yum install -y openresty
yum install -y openresty-opm
默认 安装在了 /usr/local/openresty
中
OpenResty的本质就是在nginx的基础上进行的补充,因此使用nginx的命令即可操作(自身提供的命令也是调用nginx
)
例 :
可以将nginx的命令配置到环境变量,使得命令在任意位置可用
打开系统配置文件
vi /etc/profile
加上
export NGINX_HOME=/usr/local/openresty/nginx
export PATH=${NGINX_HOME}/sbin:$PATH
重新加载
source /etc/profile
附加:nginx配置文件简化版
worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
将win中的nginx用作反向代理,将vm中的openResty用作处理业务
配置方式:win中的nginx监听浏览器请求,将请求代理到vm中的nginx(openResty),vm中的openResty接收请求处理业务
代理的nginx配置
openResty配置
到这里只是相当于配置了总的Mapping,还需要配置controller以及controller内部调用的service
进一步补充controller
由于已经指定了lua文件的位置(默认的根路径是指nginx目录),接下来只需要编写被调用的 item.lua(service)
进行测试:
整体流程:
controller层 vs nginx.conf
service vs lua脚本
到达他们的请求都是由浏览器发出,到达nginx代理服务器,然后由代理服务器发出的,可以实现负载均衡
OpenResty获取请求参数:
由于OpenResty是业务的处理,肯定有的业务需要参数。参数由浏览器发出,再经过代理服务器,将参数传给lua脚本进行业务处理
参数五种传递方式
测试路径传参:
免去方向代理,直接访问路径测试更方便
要注意:两个主机如果不在一个服务器可能有防火墙问题。这里由于我的OpenResty是在vm中,而tomcat是在win中,为了方便,将tomcat修改端口运行在vm中。
将项目进行打包,然后本次测试通过后,放入vm中运行,这样就不会有防火墙问题
但是:我的vm中的jdk8版本运行不了在win中9打包的jar,更新或使用docker又过于麻烦,所以采用在win运行,关闭防火墙的方式
在vm中使用 curl 地址
的方式进行验证
前面只是关闭了防火墙,并未进行修改,下一步需要使用openResty去访问tomcat,即修改lua脚本(OpenResty只负责映射反向代理来的请求,然后交给lua脚本执行业务)
例:
编写案例前的补充
由于使用的OpenResty它的最大特点就是在nginx基础上封装了许多API供脚本使用
例:
脚本的请求最终会发给自身的nginx,再将nginx的请求进行反向代理发送给对用服务器
整体流程
由于频繁需要发起请求,执行固定的代码,可以将脚本中有关请求的代码进行封装,然后引用
进行封装的代码可以在lualib
目录下创建一个包,包下创建每一功能封装成一个lua文件,在文件中将封装后提供的Api进行暴露
在脚本中引用
开始完成案例
由于发送的请求是将参数在路径中进行传递(/api/item/10001),要想获取请求参数,要将openResty中的映射路径进行修改
由于lua脚本中向外发送的api也需要被nginx进行捕获,所以配置反向代理部分,将脚本中的请求送达到tomcat服务器
编辑脚本,引用api模块,获取参数,然后发送请求,解析参数
![在这里插入图片描述](https://img-blog.csdnimg.cn/d8053a14e3f44a3facb1223734b35ef7.png
然后重启openResty即可通过浏览器发送请求,通过反向代理找到OpenResty
,在由OpenResty
通过lua脚本访问tomcat
即可获取到数据
将tomcat升级为集群
问题:由于采用集群,如果是负载均衡的方式,那么由于每次请求产生的JVM进程缓存在本地,而请求下一次将访问另一台机器。由于JVM的进程缓存不共享导致缓存命中率特别低。
解决思路:每一个请求只访问一台主机,在JVM的进程缓存才可以重复利用。在nginx的反向代理中,对各个集群不采用负载均衡
的方式,而是获取请求的hash值,除以集群的个数找到对应的服务器。由于hash值永远是固定的,且不同的请求对应的hash值不同,这样就使得每个请求可能
访问不同的主机,但相同的请求一定
会访问相同的主机
做法: 只需要在定义集群时添加添加 hash $request_uri;
即可
这样就可以使得lua脚本查询的是tomcat集群,且相同的请求会访问到同一台服务器,使得进程缓存有效
Redis的冷启动
第一次查询需要从数据库中,将查询结果在放入缓存
Redis的热启动
事先判断哪些数据会被频繁访问,在启动时就将他们放入redis缓存中。这样即使是第一次访问,也可以在缓存中查找到,提升效率
在lua脚本中连接redis,同tomcat一样也需要引入openResty提供的依赖库,这里需要引入'resty.redis
模块,获取redis对象,然后创建连接池。
例:
最后在脚本下面 read_redis("127.0.0.1",6379,"item:id:10001")
测试失败,通过查看日志可知错误是redis有密码,而脚本中没有指定。可以在其脚本中加上 red:auth(password)
或者直接先关闭掉redis的密码
最后直接访问OpenResty业务集群
与tomcat不同的是,脚本中发起的是连接,而不是路径请求。因此无需在nginx.conf中配置反向代理
为了使用方便,同样将其封装成一个脚本,只暴露连接使用的函数。可以与之前的common脚本放在一起,这个脚本就向外提供两个方法,分别是发起tomcat请求
,查询redis缓存
可以引入封装的函数,通过条件判断语句,使得先去查询redis,若结果为空,在去查询tomcat
达到二级缓存的效果,这样就将redis的查询挪到了tomcat之前,减少tomcat的压力和并发量
-- 连接redis local redis = require('resty.redis') local red = redis:new() red:set_timeouts(1000, 1000, 1000) 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 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 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 -- 向tomcat发起请求 local function read_http(path, params) local resp = ngx.location.capture(path,{ method = ngx.HTTP_GET, args = params, }) if not resp then 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
OpenResty同样提供了方法库(shard dict功能
)使得通过脚本可以使用本地缓存(缓存集群可共享)
本地缓存使用只需要 获取 ngx.shared.item_cache
的对象,可以配置其大小,结构类似于 Redis的K_V键值对,且在指定键值对时要设置过期时间 ,0代表永不过期
使用例子:
2. 在脚本中获取缓存对象即可存放入、取出
有了OpenResty本地缓存、redis缓存、tomcat线程缓存,将他们组合一起即可实现多级缓存。浏览器发送的请求被反向代理到OpenResty,然后OpenResty先查询本地是否有,若没有再去查询redis,若没有再去查询JVM线程缓存,若没有再去查询数据库。请求经历了三层缓存,大大较少了到达数据库的请求,使得响应更快,系统并发量更高
对应的业务脚本:
-- 导入自己编写的common模块 其中有发起请求,查询redis 两种功能方法 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))
本地缓存修改不方便,应方一些对一致性不敏感且不经常修改的数据
所有缓存在提升性能的同时,都面临一个问题:数据一致性
缓存常见三种同步策略:
缓存过期
同步双写
:异步通知
可能
有少量代码侵入主业务
使用cannal方式
Canal是阿里开源
的一款基于Mysql binlog
文件的订阅消费组件。相比于mq,它能够自动监听mysql数据的变化,而不用编写业务手动放入队列。监听数据变化后能自动 信息发送至队列,在消费者端,只需要编写代码获取队列中修改数据的信息,然后更新缓存
能够做到代码零侵入
监听数据库原理
由于mysql的主从复制原理就是,从节点通过监听biglog
文件变化来实时更新自己的数据,达到主从数据一致的效果
而Canal伪装成一个slave,就可以具有从节点的功能,获取数据的实时变化,然后放入通知队列
Canal的安装配置:这里使用docker
开启mysql主从同步的master端
找到mysql的 conf/my.cnf
文件
创建用于同步数据的账户
create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' identified by 'canal';
FLUSH PRIVILEGES;
重新启动服务器,然后运行 show master status;
命令
2. 创建一个docker网络,将mysql放入 docker network create xxxx(自定义的网络名)
3. 安装canal
下载出镜像后,运行命令,将canal与要监控的mysql绑定
docker run -p 11111:11111 --name canal \
-e canal.destinations=canal-test \
-e canal.instance.master.address=mysql:3307 \
-e canal.instance.dbUsername=canal \
-e canal.instance.dbPassword=canal \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false \
-e canal.instance.filter.regex=canal-test\\..* \
--network test-canal-net \
-d canal/canal-server:v1.1.5
安装完成
由于官方的较为麻烦,使用第三方的客户端依赖
<dependency>
<groupId>top.javatool</groupId>
<artifactId>canal-spring-boot-starter</artifactId>
<version>1.2.1-RELEASE</version>
</dependency>
进行配置
创建一个包 名为canal
,在创建一个类实现 接口 EntryHandler<pojo类>
,专门用于监听数据变化后进行处理
Canal常使用三个注解
@Id
: 指定属性对应的字段是表的id@Transient
:该属性没有与表对应的字段@Column(name="xxx")
: 指明该属性对应表的名字例:当数据库中的表发生变化时,会自动执行
这里只对redis
与JVM线程的缓存的进行更新,因为tomcat中无法直接操作OpenResty中的缓存。OpenResty中放置数据一致性较低的缓存。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。