赞
踩
RDB持久化
数据库备份文件,也叫快照,把内存数据存到磁盘。使用save进行主动RDB,会阻塞所有命令。建议使用bgsave开启子进程执行RDB。Redis停机时会被动执行一次RDB。
bgsave开始会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后读取内存数据并写入RDB文件。
注:在fork时插入的数据会影响一致性,所以采用copy-on-write技术,当主进程执行读操作,刚问共享内存。当主进程执行写操作,则拷贝一份数据,执行写。
Redis内部可在redis.conf配置RDB触发机制。
AOF持久化
追加文件。redis每一条写命令都记录在AOF文件,是命令日志文件。
Redis的AOF默认关闭,需修改redis.conf的appendonly来开启AOF。可配置appendfsync改变刷盘策略的记录频率:always,everysec(默认),no(由操作系统决定)。
auto-aof-rewrite-percentage 100 文件比上次增长超过几%则触发重写
auto-aof-rewrite-min-size 64mb 文件多大重写
AOF会比RDB大很多,而且对同一个KEY多次写操作,只有最后一次才有意义,通过执行bgrewriteaof命令,可以让AOF文件执行重写。
如果对数据安全性要求高,则需结合两者使用
单节点Reds并发不高,就需要主从集群,实现读写分离。
要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录。开启RDB关闭AOF。拷贝redis.conf到三个目录。
Redis读多写少,主节点做写操作,从节点做读操作。
使用replicaof或者slaveof命令,有临时和永久两种模式。
主从第一次同步是全量同步,第二次是同步记录rdb期间的所有命令,第三次发送repl_baklog中的命令。
master将完整内存数据生成rdb,发送给slave,后续命令则记录在repl_baklog,逐个发给slave
注:slave节点第一次链接master时进行全量同步,或者slave断开太久repl_baklog中的offset已被覆盖
slave将自己的offset提交到master,master获取repl_baklog中从offset之后的命令给slave
注:slave节点断开又恢复,并在repl_baklog中找到offset时
哨兵机制-主从切换
Redis哨兵(Sentinel)机制实现主从集群的自动故障恢复。
服务状态监控
Sentinel基于心跳机制检测服务状态,每1s向集群的每个实例发送ping命令:
选举新的master规则:
如何实现故障转移(当选择slave1为新master后):
具体搭建流程参考百度:《Redis集群.md》
spring的RedisTemplate底层利用lettuce实现节点的感知和自动切换。
/**
* MASTER:从主节点读取
* MASTER_PREFERRED:优先master读取,不可用读取replica
* REPLICA:从slave读取
* ERPLICA_PREFERRED:优先从slave节点读取,所有的slave都不可用才读取master
*/
@Bean
public LettuceClientConfigurationBuilderCustomizer configurarionBuilderCustomizer(){
return configBuilder -> configBuilder.readFrom(ReadFrom.ERPLICA_PREFERRED);
}
为什么需要分片集群?
主从和哨兵可以解决高可用,高并发读问题,但依然还有两个问题没有解决:
使用分片集群可以解决上述问题,分片集群特征:
具体搭建流程参考百度:《Redis分片集群.md》
Redis会把每一个master节点映射到0~16383个插槽(hash slot)上去,查看集群信息时就能看到。
数据的key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,计算方式为CRC16算法得到一个hash值,然后对16384取余得到slot值,分两种情况:
问题:为什么数据要绑定插槽上呢?
数据跟着插槽走,宕机时对应插槽可以转移到活着的节点。
问题:如何将同一类的数据固定的保存在同一个redis实例?
使用相同{key}。
添加一个节点到集群:redis -cli --cluster add -node
需求:向集群中添加一个新的master节点,并向其中存储num=10
当集群中有一个master宕机后会发生什么事情?(自动)
数据转移(手动)
利用cluster failover命令可以手动让集群中的某个master宕机,切换到cluster failover命令的这个slave节点,实现无感知的数据迁移。《图:分片集群手动数据迁移》
Failover三种模式:缺省,force,takeover
案例:在7002这个slave节点执行手动故障转移,重回master低位:
RedisTemplate底层同样基于lettuce实现了分片集群的支持而使用的步骤和哨兵模式基本一致:
传统缓存的问题:传统缓存策略一般是请求到tomcat后,先查询redis,如果未命中则查询数据库。
多级缓存方案:利用处理请求的每个环节,分别添加缓存,减轻Tomcat压力。
用户–浏览器端缓存(静态资源渲染:检验码304,90%请求可拦截)–nginx(反向代理)–nginx本地缓存(集群)–nginx端redis缓存–tomcat进程缓存–mysql
缓存分为两类:
初识Caffeine:java8开发Spring内部缓存使用(本地缓存中最优)。Cache<String, String> cache = Caffeine.newBuilder().build();
利用工厂模式构造cache对象。
Caffine缓存驱逐策略:基于容量,基于时间,基于引用(性能差,不建议)。在一次读写操作后或空闲时间完成数据驱逐。
Lua:标准C编写的脚本语言,用于嵌入应用程序中,为应用程序提供灵活的扩展和定制功能。
Lua数据结构:nil,boolean,number,string,function(c或Lua编写的函数),table(hashmap)。可用type函数返回变量类型。
Lua定义变量:local str = ‘test’,local arr = {‘java’,‘py’},local map = {name = ‘Jack’,age = 32}
Lua获取变量:arr1,map[‘name’],map.name
Lua遍历数组:for index,value in ipairs(arr) do //todo end
Lua遍历table:for key,value in pairs(map) do //todo end
Lua函数:function 函数名(arg1,arg2…) //函数体 return nil end
Lua条件控制:类似javaif,else
OpenResty:基于Nginx的高性能web平台。用于搭建处理高并发,扩展性高的动态web应用,web服务和动态网关。功能:
OpenResty构思:前端请求被nginx反向代理到虚拟机OpenResty集群,请求在OpenResty中接收这个请求,并返回数据。
OpenResty流程:
nginx内部发送Http请求:
local resp = ngx.location.capture("/path",{
method = ngx.HTTP_GET, --请求方式
args = {a=1,b=2}, --get方式传参
body = "c=3&d=4" -- post方式传参
})
//注意:这里的/path是路径,并不包含ip和端口。这个请求会被nginx内部的server监听并处理
暂时不经过redis缓存,直接向tomcat服务器访问获取数据,还需一个server对这个路径做反向代理:
location /path {
#这里是windows电脑的ip和java服务端口,需确保windows防火墙处关闭状态
proxy_pass http://192.168.150.1:8081
}
返回响应内容:resp.status(状态码),resp.header(响应头,table),resp.body(响应数据)
封装http查询的函数:我们可以把http查询请求封装为一个函数,放到openresty函数库中,方便后面使用。
--封装函数,发送http请求,并解析响应 local function read_http(path, params) local resp = ngx.location.capture(path,{ method = ngx.HTTP_GET, args = params, }) if not resp then --记录错误信息,返回404 ngx.log(ngx.ERR, "http查询失败, path: ",path,",args:",args) ngx.exit(404) end return resp.body end --将方法导出为table local _M = { read_http = read_http read_redis = read_redis //预留:后面需要加载redis模块 } return _M
使用http查询函数:
--导入封装的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 id = ngx.var[1] --查询商品信息 local itemJSON = read_http("/item/" .. id, nil) --查询库存信息 local stockJSON = read_http("/iem/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))
JSON处理结果:
OpenResty提供了一个cjson的模块用来处理JSON的序列化和反序列化。
tomcat端做负载均衡:
每个tomcat的实例缓存都在本地,需要做hash映射。
upstream tomcat-clusrer{ hash $request_uri; --基于requestUri做hash映射 server ip:port1 server ip:port2 } server{ listen 8081; server_name = loclahost; location / item { proxy_pass http://tomcat-cluster } location ~ /api/item/(\d+) { #默认的响应类型 default_type application/json #响应结果由lua/item.lua文件决定 content_by_lua_file lua/item.lua } }
添加Redis缓存(冷启动与缓存预热):
@Component public class RedisHandler implements InitializingBean { @Autowired private StringRedisTemplate redisTemplate; @Autowired private Service service; private static final ObjectMapper MAPPER = new ObjectMapper(); //json序列化 @Override public void afterPropertiesSet() throws Exception{ //初始化缓存 //1.查询信息 List<Bean> list = service.list(); //2.放入缓存 for(Bean bean : list){ String json = MAPPER.writeValueAsString(bean); redisTemplate.opsForValue.set("KEY",json); } } }
OpenResty加载Redis模块,放入common模块,并在lcoal _M{}中将read_redis方法暴露出去:
local redis = require('resty.redis')
local red = redis::new()
red:set_timeout(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连接池失败:', e)
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失败:", e) return nil end //查 lcoal resp,err = red.get(key) if not resp then ngx.log(ngx.ERR, "查询Redis失败:", e) end if resp == ngx.null then resp = nil; ngx.log(ngx.ERR, "查询Redis为空:", e) end closr_redis(red) return resp end
local function read_data(key, path, params)
localresp = read_redis("127.0.0.1", 6379, key)
if not resp then
resp = read_http(path, params)
end
return resp
end
OpenResty为Nginx提供了shard dict功能,可在nginx的多个worker间共享数据,实现缓存功能。
需求:1.修改item.lua中的read_data函数,优先查询本地缓存,未命中再查redis,tomcat。2.查redis或tomcat写入本地设置有效期。
canal基于数据库增量日志解析,提供增量数据订阅&消费。可监听mysql数据库binlog通知缓存服务,更新redis,代码侵入低。
canal基于mysql的主从同步实现。
canal就是把自己伪装成mysql的一个slave节点,从而监听master的binlog变化。
开启canal需要修改mysql主从。
坐标:<artifactId>canal-spring-boot-starter</artifactId>
配置:
canal:
destination: xuy
server: ip:port
编写监听类,监听canal:
canal推送给canal-client的是被修改的这一行数据,其会把行数据转换成Item实体类。需要在字段上加@TableName,@Id,@Column,@TableField(exist=false)+@Transient(非数据库字段)等注解
/** * 监听增,改,删的消息 */ @CanalTable("tb_item") @Component public class ItemHandler implements EntryHandler<Item> { @Autowired private RedisHandler redisHandler; @Autowired private Cache<Long, Item> itemCache; @Override public void insert(Item item) {//新增数据到redis} @Override public void update(Item before, Item after) {//更新reids,本地缓存} @Override public void delete(Item item) {//删除reids,本地缓存} }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。