RDB全称Redis Database Backup file (Redis数据备份文件), 也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
快照文件称为RDB文件,默认是保存在当前运行目录。
# 先连接redis
redis-cli
# 保存(方式1)
save
# 保存(方式2)
bgsave
bgsave
,开启子进程执行RDB,避免主进程受到影响redis默认会在关闭之前对数据做一次持久化,在运行的目录下。如果在备份的时候redis突然宕机,那么数据就丢失了,这显然不是我们想要的,我们希望隔一段时间备份一次
# 900秒内,如果至少有1个key被修改,则执行bgsave,如果是save ""则表示禁用RDB
save 900 1
save 300 10
save 60 10000
# 是否压缩, 建议不开启,压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes
# RDB文件名称
dbfilename dump.rdb
#文件保存的路径目录
dir ./
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入RDB文件。执行流程如下:
AOF全称为Append Only File (追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。默认
关闭
# 是否开启AOF功能,默认是no
appendonly yes
#AOF文件的名称
appendfilename "appendonly.aof"
#表示每执行一次写命令,立即记录到AOF文件
appendfsync always
#写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
#写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
# 在这个地方写入 save ""
# save 900 1
# save 300 10
# save 60 10000
save ""
set num 123
set name jack ----------bgrewriteaof--------> mset name jack num 666
set num 666
# AOF文件比上次文件增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
RDB | AOF | |
---|---|---|
持久化方式 | 定时对整个内存做快照 | 记录每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会丢失 | 相对完整,取决于刷盘策略 |
文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积很大 |
宕机恢复速度 | 很快 | 慢 |
数据恢复优先级 | 低,因为数据完整性不如AOF | 高,因为数据完整性更高 |
系统资源占用 | 高,大量CPU和内存消耗 | 低,主要是磁盘IO资源但AOF重写时会占用大量CPU和内存资源 |
使用场景 | 可以容忍数分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求较高常见 |
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
为什么redis要采用主从集群呢,而不是传统的负载均衡集群?
下面就开始实操吧!
# 首先我们先建立三个目录
mkdir 7001 7002 7003
#方式一:逐个拷贝
cp redis-6.2.4/redis.conf 7001
cp redis-6.2.4/redis.conf 7002
cp redis-6.2.4/redis.conf 7003
#方式二:管道组合命令,一键拷贝
echo 7001 7002 7003| xargs -t -n 1 cp redis-6.2.4/redis.conf
# sed 修改7001/redis.conf,s代表替换,将6379替换为7001,/g代表全局;后面也一样,将dir .替换为: dir /tem/7001
sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/tmp\/ 7001\//g' 7001/redis.conf
sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/tmp\/ 7002\//g' 7001/redis.conf
sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/tmp\/ 7003\//g' 7001/redis.conf
# redis 实例的声明IP
replica-announce-ip 192.168.150.101
# 1a表示在第一行后面追加一行
sed -i '1a replica-announce-ip 192.168.150.101' 7001/redis.conf
sed -i '1a replica-announce-ip 192.168.150.101' 7002/redis.conf
sed -i '1a replica-announce-ip 192.168.150.101' 7003/redis.conf
# 或者一键修改
printf '%s\n' 7001 7002 7003| xargs -I{} -t sed -i '1a replica-announce-ip
192. 168.150.101' {}/redis.conf
# 首先切换到tmp目录,或者写全路径也行,然后分别启动
redis-server 7001/redis.conf
但是目前他们是独立的redis,并没有主从联系,要配置主从可以使用replicaof或者slaveof (5.0以前) 命令。
有临时和永久两种模式:
修改配置文件(永久生效):
slaveof
使用redis-cli客户端连接到redis服務,执行slaveof命令 (重启后失效):
# slaveof 如下
SLAVEOF 192.168.150.101 7001
# 如果的当前客户端连接的是7002,表示7002要成为7001的slave
# 要指定客户端启动可以使用命令
redis-cli -p 7002
注意:在5.0以后新增命令replicaof,与salveof效果一致。
怎么查看集群信息呢?
INFO replication
master如何判断slave是不是第一 次来同步数据?这里会用到两个很重要的概念:
因此slave做数据同步,必须向master声明自己的replication id和offset, master才可以判断到底需要同步哪些数据
后面就是增量同步:
可以从以下几个方面来优化Redis主从就集群:
slave节点宕机恢复后可以找master节点同步数据,那master节点宕机怎么办?
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:
监控: Sentinel 会不断检查您的master和slave是否按预期工作
自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也
以新的master为主.
通知: Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送
给Redis的客户端
mkdir s1 s2 s3
# 端口,s2要改成27002,s3为27003
port 27001
# 声明ip
sentinel announce-ip 192.168.150.101
# 声明监控 mymaster为集群/主节点名称,2为选举master的quorum
sentinel monitor mymaster 192.168.150.101:7001 2
# 超时时间
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
# 工作目录
dir "/tmp/s1'
#方式一:逐个拷贝
cp s1/sentinel.conf s2
cp s1/sentinel.conf s3
#方式二:管道组合命令,一键拷贝
echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf
sed -i -e 's/27001/27002/g' -e' "s/s1/s2/g' s2/sentinel.conf
sed -i -e 's/27001/27003/g' -e' "s/s1/s3/g' s3/sentinel.conf
redis-sentinel s1/sentinel.conf
redis-sentinel s2/sentinel.conf
redis-sentinel s3/sentinel.conf
在Sentinel集群监管下的Redis主从集群, 其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。
在pom文件中引入redis的starter依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
spring:
redis:
sentinel :
master: mymaster #指定master名称
nodes: #指定redis-sentinel集群信息
-192.168.150.101 :27001
-192.168.150.101 :27002
-192.168.150.101 :27003
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
return configBuilder -> configBuilder.readFrom(ReadFrom. REPLICA_PREFERRED);
}
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
使用分片集群可以解决.上述问题,分片集群特征:
开始搭建,删除之前的7001、7002、 7003这几个目录,重新创建出7001、7002、 7003、 8001、 8002、 8003目录:
#进入/tmp目录
cd /tmp
#删除旧的,避免配置干扰
rm -rf 7001 7002 7003
#创建目录
mkdir 7001 7002 7003 8001 8002 8003
port 6379
#开启集群功能
cluster-enabled yes
#集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /tmp/6379/nodes.conf
#点心跳失败的超时时间
cluster-node-timeout 5000
#持款化文件存放目录
dir /tmp/6379
#绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
#注册的实例ip
replica-announce-ip 192.168.150.101
#保护模式
protected-mode no
#数据库数量
databases 1
#日志
logfile /tmp/6379/run.log
#进入/tmp目录
cd /tmp
#执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf
# 进入tmp目录
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
# -键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
# 通过ps查看状态
ps -ef | grep redis
ps -ef | grep redis awk '{print $2}' | xargs kill
# 或者(推荐这种方式)
printf '%s\n' 7001 7002 7003 8001 8002 8003| xargs -I{} -t redis-cli-p {} shutdown
# redis-cli --cluste create创建集群
# --cluster-replicas 集群副本数量
# 1 代表主从比例1:1,所以前三个为主,后三个为从
redis-cli --cluste create --cluster-replicas 1 192.168.150.101:7001
192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002
192.168.150.101:8003
redis-cli -p 7001 cluster nodes
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上, 查看集群信息时就能看到
数据key不是与节点绑定,而是与插槽绑定。redis会 根据key的有效部分计算插槽值,分两种情况:
例如: key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。 计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是siot值。
# 这个集群模式下连接要加-c
redis-cli -c -p 7001
redis-cli --cluster --help
# 创建7004目录
mkdir 7004
# 将redis.conf拷贝到该目录
cp redis.conf 7004
# 批量修改文件中的内容
sed -i s/6379/7004/g 7004/redis.conf
# 添加集群中的节点,后面这个ip和端口是用来通知集群添加了新节点的
redis-cli --cluster add-node 192.168.150.101:7004 192.168.150.101:7001
# 开始重新分片
redis-cli --cluster reshard 192.168.150.101:7001
# 弹出多少个插槽你想从集群移动过去
# 然后弹出谁接收这个插槽,填写ID
# 然后弹出从哪里开始拷贝,填写ID,填写done结束
同上面的故障转移差不多,这个就不多说
手动故障转移:故障迁移
spring:
redis:
cluster:
nodes: #指定分片集群的每一个节点信息
192.168.150.101:7001
192.168.150.101:7002
192.168.150.101:7003
192.168.150.101:8001
192.168.150.101:8002
192.168.150.101:8003
传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,存在下面的问题:
多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压力,提升服务性能:
#进入/tmp目录
cd /tmp
#创建文件夹
# mkdir mysql
# 进入mysql目录
cd mysql
docker run \
-p 3306:3306 \
--name mysql \
-V $PWD/conf:/etc/mysql/conf.d \
-V $PWD/logs:/logs \
-V $PWD/data: /var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123 \
--privileged \
-d \
mysql:5.7.25
#创建文件
touch /tmp/mysql/conf/my.cnf
[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
server-id=1000
接下来就可以导入数据了
运行nginx服务器
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:
Caffeine是一个基于Java8开发的, 提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址: https://github.com/ben-manes/caffeine
可以通过item-service项目中的单元测试来学习Caffeine的使用:
@Test
void testBasicOps() {
//创建缓存对象
Cache<String,String> cache = Caffeine.newBuilder().build() ;
//存数据
cache.put("gf","迪丽热巴");
//取数据,不存在则返回null
String gf = cache.getIfPresent("gf") ;
System.out.println("gf="+ gf);
//取数据,不存在则去数据库查询
String defaultGF = cache. get("defaultGF",key -> {
//这里可以去数据库根据key 查询value
return "柳岩";
});
System.out.println("defaultGF="+defaultGF) ;
}
缓存不能无限制增加,要么设置过期时间或者其他方法
Caffeine提供了三种缓存驱逐策略:
//创建缓存对象
Cache<String,String> cache = Caffeine.newBuilder()
.maximumSize(1) //设置缓存大小上限为 1
.build() ;
//创建缓存对象
Cache<String,String> cache =Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(10)) //设置缓存有效期为10秒,从最后一-次写入开始计时
.build() ;
在默认情况下,当一个缓存元素过期的时候,Caffeine不会 自动立即将其清理和驱逐。而是在一次读 或写操作后,或者在空闲时间完成对失效数据的驱逐。
利用Caffeine实现下列需求:
首先先写一个配置类
@Configuration
public class CaffeineConfig {
@Bean
public Cache<Long,Item> itemCache(){
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(10_000)
.build();
}
@Bean
public Cache<Long,ItemStock> stockCache() {
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(10_000)
.build();
}
}
@GetMapping("/{id}")
public Item findById(@PathVariable("id") Long id) {
return itemCache.get(id,key -> itemService.query()
.ne(column:"status",val:3).eq(column:"id",key)
.one()
);
}
touch hello.lua
print("hello World")
lua hello.lua
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false) |
boolean | 包含两个值: false和true |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由C或Lua编写的函数 |
table | Lua中的表(table)其实是一个"关联数组" (associative arrays) ,数组的索引可以是数字、字符串或表类型。在Lua 里,table的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
print(type("hello,world"))
-- 声明字符串
local str = 'hello'
-- 声明数字
local num = 21
-- 声明布尔类型
local flag = true
-- 声明数组key为索引的table
local arr = {'java','python','lua'}
-- 声明table,类似java的map
local map = {name='Jack', age=21}
-- 访问数组,lua数组的角标从1开始
print(arr[1])
-- 访问table
print(map['name'])
print (map.name)
数组、table都可以利用for循环来遍历:
-- 声明数组key为索引的table
local arr = {'java','python', 'lua'}
-- 遍历数组
for index,value in ipairs(arr) do
print(index,value)
end
-- 声明map,也就是table
local map = {name='Jack',age=21}
-- 遍历table
for key,value in pairs(map) do
print(key, value)
end
定义函数的语法:
function函数名(argument1, argument2...,argumentn)
-- 函数体
return 返回值
end
例如,定义一个函数,用来打印数组:
function printArr(arr)
for index,value in ipairs(arr) do
print(value )
end
end
类似Java的条件控制,例如if、else语法 :
if(布尔表达式)
then
--[布尔表达式为true时执行该语句块--]
else
--[布尔表达式为false 时执行该语句块--]
end
与java不同,布尔表达式中的逻辑运算是基于英文单词:and,or,not
OpenResty是一个基于Nginx的高性能Web平台,用于方便地搭建能够处理超高并发、 扩展性极高的动态Web应用、Web服务和动态网关。具备下列特点:
首先要安装OpenResty的依赖开发库,执行命令:
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 yum-utils
yum install -y openresty
yum install -y openresty-opm
默认情况下,OpenResty安装的目录是: /usr/local/openresty
然后配置nginx的环境变量
vi /etc/profile
export NGINX_HOME=/usr/local/openresty/nginx
export PATH=${NGINX_HOME}/sbin:$PATH
source /etc/profile
#启动nginx
nginx
#重新加载配置
nginx -s reload
#停止
nginx -s stop
#user nobody;
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 8081;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
#加载lua模块
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
#加载c模块
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
location /api/item {
#响应类型,这里返回json
default_type application/json;
#响应数据由lua/item.lua这个文件来决定
content_by_lua_file lua/item.lua;
}
# 去nginx目录创建
mkdir lua
touch lua/item.lua
--返回假数据,这里的ngx. say()函数,就是写数据到Response中,这个假数据就自己改
ngx.say('{"id":10001,"name":"SALSA AIR}')
nginx -s reload
参数格式 | 参数示例 | 参数解析代码示例 |
---|---|---|
路径占位符 | /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() –获取body中的json参数,返回值是string类型 local jsonBody = ngx.req.get_body_data() |
案例:获取请求路径中的商品id信息,根据id向Tomcat查询商品信息
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请求
})
location /path{
# 这里是wi ndows电脑的i p和Java服务端口,需要确保wi ndows防火墙处于关闭状态
proxy_pass http://192.168.150.101:8081;
}
vi /usr/local/openresty/lualib.common.lua
--封装函数,发送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 not found, path: ",path,",args: ",args)
ngx.exit(404)
end
return resp.body
end
--将方法导出
local _M={
read_http = read_http
}
return _M
-- 导入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
-- 返回结果
ngx.say(cjson.encode(item))
local cjson = require "cjson"
upstream tomcat-cluster {
hash $require_uri;
server 192.168.150.101:8081;
server 192.168.150.101:8082;
}
# proxy_pass 要改为 http://tomcat-cluster
我们在实际开发中应该先查询redis,再去查询tomcat
冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第-次查询时添加缓存,可能会给数据库带来较大压力
缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中。
我们数据量较少,可以在启动时将所有数据都放入缓存中。
利用Docker安装Redis
docker run --name redis -p 6379:6379 -d redis redis-server --appendonly yes
<dependency>
<group>org.springframework.bootgroup>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
spring:
redis:
host: 192.168.150.101:8081
@Component
public class RedisHandler implements InitializingBean {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void afterPropertiesSet() throws Exception { //初始化缓存... }
}
OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用:
引入Redis模块,并初始化Redis对象
--引入redis模块
local redis = require("resty.redis")
-- 初始化Redis对象
local red = redis:new( )
--设置Redis超时时间
red:set_timeouts(1000,1000,1000)
--关闭redi s连接的工具方法,其实是放入连接池
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
--查询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 = 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
--封装查询函数
function read_data(key,path,params)
--查询redis
local resp = read_redis("127.0.0.1",6379,key)
--判断查询结果
if not resp then
ngx.log("redis查询失败,尝试查询http,key: ",key)
-- redis查询失败,去查询http
resp = read_http(path, params)
end
return resp
end
-- 然后将下面调用read_http改成read_data
#共享字典,也就是本地缓存,名称叫做: 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')
--封装查询函数
function read_data(key,expire,path,params)
--查询本地缓存
local val = item_cache:get(key)
if not val then
ngx.log(ngx.ERR,"本地缓存查询失败,尝试查询http,key: ",key)
--查询redis
val = read_redis("127.0.0.1",6379,key)
--判断查询结果
if not val then
ngx.log(ngx.ERR,"查询失败,尝试查询http,key: ",key)
-- redis查询失败,去查询http
val = read_http(path, params)
end
end
--查询成功,写入本地缓存
item_cache:set(key,val, expire)
-- 返回数据
return val
end
--然后将下面的查询添加过期时间参数
Canal,译意为水道/管道/沟渠,canal是阿里巴巴旗下的一款开源项目,基于Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。GitHub的地址: https://github.com/alibaba/canal
Canal是基于mysql的主从同步来实现的,MySQL主从同步的原理如下:
Canal就是把自己伪装成MySQL的一个slave节点,从而监听master的binary log变化。再把得到的变化信息通知给Canal的客户端,进而完成对其它数据库的同步。
下面我们就开启mysql的主从同步机制,让Canal来模拟salve
修改文件:
vi /tem/mysql/conf/my.cnf
# binlog存放的地址和名称
log-bin=/var/lib/mysql/mysql-bin
# 数据库名称
binlog-do-db=test
create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT,REPLICATION SLAVE,REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%'
identified by 'canal';
FLUSH PRIVILEGES;
docker restart mysql
show master status;
# 创建一个名为test的网络
docker network create test
# 让mysql加入这个网络
docker network connect test mysql
dockersload -i canal.tar
# -e canal.destinations=test 集群名称为test
# -e canal.instance.master.address=mysql:3306 这个是mysql的地址
# dbUsername,dbPassword,connectionCharset对应用户名和密码和编码
# -e canal.instance.filter.regex=test\\..* 只监听test这个库
docker run -p 11111:11111 --name canal \
-e canal.destinations=test \
-e canal.instance.master.address=mysql:3306 \
-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=test\\..* \
--network test \
-d canal/canal-server:v1.1.5
# 进入容器内部
docker exec -it canal bash
# 查看canal运行日志
tail -f canal-server/logs/canal/canal.log
# 也可以看数据库运行日志
tail -f canal-server/logs/test/test.log
<dependency>
<groupId>top.javatoolgroupId>
<artifactId>canal-spring-boot-starterartifactId>
<version>1.2.1-RELEASEversion>
dependency>
canal :
destination: test #canal实例名称,要跟canal-server运行时设置的destination一致
server: 192.168.150.101:11111 # canal地址
@CanalTable("tb_item")
@Component
public class ItemHandler implements EntryHandler<Item> {
@Override
public void insert(Item item) {
//新增数据到redis
}
@Override
public void update(Item before, Item after) {
//更新redis数据
//更新本地缓存
}
@Override
public void delete (Item item) {
//删除redis数据
//清理本地缓存
}
}
Redis的Key虽然可以自定义,但最好遵循下面的几个最佳实践约定:
例如:我们的登录业务,保存用户信息,其key是这样的:login:user:10
BigKey通常以Key的大小和Key中成员的数量来综合判定,例如:
BigKey的危害:
如何发现BigKey
如何删除BigKey
比如存储一个User对象,我们有三种存储方式:
假如有hash类型的key,其中有100万对field和value, field是 自增id,这个key存在什么问题?如何优化?
存在的问题:
方案2:拆分为string类型:
方案三:拆分为小的hash,将id / 100作为key,将id % 100作为field,这样每100个元素为一个Hash
单个命令执行
n条命令批量执行
MSET虽然可以批处理,但是却只能操作部分数据类型,因此如果有对复杂数据类型的批处理需要,建议使用Pipeline功能:
@Test
void testPipeline() {
//创建管道
Pipeline pipeline = jedis.pipelined() ;
for(int i=1;i<=100000;i++){
//放入命令到管道
pipeline.set("test:key_" + i,"value_”+ i) ;
if(i%1000==0){
//每放入1000条命令, 批量执行
pipeline.sync() ;
}
}
}
串行命令 | 串行slot | 并行slot | hash_tag | |
---|---|---|---|---|
实现思路 | for循环遍历,依次 执行每个命令 |
在客户端计算每个key的 slot,将slot一致分为一组,每组都利用Pipeline 批处理。 串行执行各组命令 |
在客户端计算每个key的 slot,将slot一致分为一组,每组都利用Pipeline 批处理。 并行执行各组命令 |
将所有key设置相同的hash_tag,则所有key的slot一定相同 |
耗时 | N次网络耗时+ N次 命令耗时 |
m次网络耗时+N次命令耗 时m=key的slot个数 |
1次网络耗时+N次命令耗时 | 1次网络耗时+N次命令耗时 |
优点 | 实现简单 | 耗时较短 | 耗时非常短 | 耗时非常短、实现简单 |
缺点 | 耗时非常久 | 实现稍复杂 slot越多, 耗时越久 |
实现复杂 | 容易出现数据倾斜 |
Redis的持久化虽然可以保证数据安全,但也会带来很多额外的开销,因此持久化请遵循下列建议:
部署有关建议:
慢查询:在Redis执行时耗时超过某个阈值的命令,称为慢查询。
慢查询的阈值可以通过配置指定:
慢查询会被放入慢查询日志中,日志的长度有上限,可以通过配置指定:
修改这两个配置可以使用: config set命令:
config set slowlog-log-slower-than 1000
config set slowlog-max-len 1000
Redis会绑定在0.0.0.0:6379,这样将会将Redis服务暴露到公网上,而Redis如果没有做身份认证,会出现严重的安全漏洞,漏洞重现方式: https://cloud.tencent.com/developer/article/1039000
漏洞出现的核心的原因有以下几点:
为了避免这样的漏洞,这里给出一些建议:
内存占用 | 说明 |
---|---|
数据内存 | 是Redis最主要的部分,存储Redi s的键值信息。主要问题是Bi gKey问题、内存碎片问题 |
进程内存 | Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redi s数据占用的内存相比可以忽略。 |
缓冲区内存 | 一般包括客户端缓冲区、AOF缓冲区、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大,不当使用BigKey,可能导致内存溢出。 |
Redis提供了一些命令,可以查看到Redis目前的内存分配状态:
内存缓冲区常见的有三种:
慢查询的阈值可以通过配置指定:
慢查询会被放入慢查询日志中,日志的长度有上限,可以通过配置指定:
修改这两个配置可以使用: config set命令:
config set slowlog-log-slower-than 1000
config set slowlog-max-len 1000
Redis会绑定在0.0.0.0:6379,这样将会将Redis服务暴露到公网上,而Redis如果没有做身份认证,会出现严重的安全漏洞,漏洞重现方式: https://cloud.tencent.com/developer/article/1039000
漏洞出现的核心的原因有以下几点:
为了避免这样的漏洞,这里给出一些建议:
内存占用 | 说明 |
---|---|
数据内存 | 是Redis最主要的部分,存储Redi s的键值信息。主要问题是Bi gKey问题、内存碎片问题 |
进程内存 | Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redi s数据占用的内存相比可以忽略。 |
缓冲区内存 | 一般包括客户端缓冲区、AOF缓冲区、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大,不当使用BigKey,可能导致内存溢出。 |