Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志
型、Key-Value 数据库,并提供多种语言的 API。为了保证效率,数据都是缓存在内存中。
string(字符串)
list(链表)
set(集合)
zset(sorted set --有序集合)
hash(哈希)
在我们日常的 Java Web 开发中,无不都是使用数据库来进行数据的存储,由于一般
的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉
及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一
使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的
性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写
操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服
务宕机的严重生产问题。
为了克服上述的问题,Java Web 项目通常会引入 NoSQL 技术,这是一种基于内存
的数据库,并且提供一定的持久化功能。
Redis 是当今使用较广泛的 NoSQL,而就 Redis 技术而言,它的性能十分优越,可以支持
每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配
置,原则上可以无限扩展,让更多的数据存储在内存中,更让人欣慰的是它还支持一定的
事务能力,这保证了高并发的场景下数据的安全和一致性。
Redis 的主从结构可以采用一主多从,Redis 主从复制可以根据是否是全量分为全量同
步和增量同步
全量同步
Redis 全量复制一般发生在 Slave 初始化阶段,这时 Slave 需要将 Master 上的所有数
据都复制一份。具体步骤如下:
完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收
来自用户的读请求。
Redis 增量复制是指 Slave 初始化后开始正常工作时主服务器发生的写操作同步到从
服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,
从服务器接收并执行收到的写命令。
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需
要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增
量同步,如不成功,要求从机进行全量同步。
如果多个 Slave 断线了,需要重启的时候,因为只要 Slave 启动,就会发送 sync 请求
和主机全量同步,当多个同时出现的时候,可能会导致 Master IO 剧增宕机。
Redis 的主从复制下,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主
节点,同时还要通知应用方更新主节点地址,对于很多应用场景这种故障处理的方法是无法
接受的。但是 Redis 从 2.8 开始正式提供了 Redis Sentinel(哨兵)架构来解决这个问题。
Redis Sentinel 是一个分布式架构,其中包含若干个 Sentinel 节点和Redis数据节点,
每个 Sentinel 节点会对数据节点和其余 Sentinel 节点进行监控,当它发现节点不可达时,
会对节点做下线标识。如果被标识的是主节点,它还会和其他 Sentinel 节点进行“协商”,
当大多数 Sentinel 节点都认为主节点不可达时,它们会选举出一个 Sentinel 节点来完成自
动故障转移的工作,同时会将这个变化通知给 Redis 应用方。整个过程完全是自动的,不需
要人工来介入,所以这套方案很有效地解决了 Redis 的高可用问题。
因为每隔一秒,每个 Sentinel 节点会向主节点、从节点、其余 Sentinel 节点发送一条
ping 命令做一次心跳检测,当这些节点超过 down-after-milliseconds 没有进行有效
回复,Sentinel 节点就会对该节点做失败判定,这个行为叫做主观下线。
当 Sentinel 主观下线的节点是主节点时,该 Sentinel 节点会向其他 Sentinel 节点询问
对主节点的判断,当超过个数,那么意味着大部分的 Sentinel 节点都对这
个主节点的下线做了同意的判定,于是该 Sentinel 节点认为主节点确实有问题,这时该
Sentinel 节点会做出客观下线的决定。
即使使用哨兵模式,redis 每个实例也是全量存储,每个 redis 存储的内容都是完整的
数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群模式(cluster)。
redis cluster 在设计的时候,就考虑到了去中心化,去中间件,也就是说,集群中的每个节
点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点
都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任
意一个节点,就可以获取到其他节点的数据。
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽
(hash slot)的方式来分配的。redis cluster 默认分配了 16384 个 slot,当我们 set 一个
key 时,会用 CRC16 算法来取模得到所属的 slot,然后将这个 key 分到哈希槽区间的节
点上,具体算法就是:CRC16(key) % 16384。
注意的是:主节点必须大于等于三个(因为选举时需要超过半数投票),否则在创建集群时
会失败。这里,我们设置 3 个节点组成集群,分别是:A, B, C 三个节点,它们可以是一
台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot)的方式
来分配 16384 个 slot 的话,它们三个节点分别承担的 slot 区间是:
• 节点 A 覆盖 0-5500;
• 节点 B 覆盖 5501-11000;
• 节点 C 覆盖 11001-16383.
上面那个例子里, 集群有 ABC 三个主节点, 如果这 3 个节点都没有加入从节点,如果
B 挂掉了,就无法访问整个集群了。A 和 C 的 slot 也无法访问。所以在集群建立的时候,
要为每个主节点添加从节点,当主节点宕机后,从节点变为新的主节点,当之前的主节点
重新启动后,自动变为新主节点的从节点,但是如果主从同时挂了,Redis 集群就无法继
续正确地提供服务了。
每一个节点都存有这个集群所有主节点以及从节点的信息。它们之间通过互相的 ping 判断
是否节点可以连接上。如果有一半以上的节点去 ping 一个节点的时候没有回应,集群就认
为这个节点宕机了,然后去连接它的备用节点。如果某个节点和所有从节点全部挂掉,我们
集群就进入 faill 状态。
基于 dockers 的容器化搭建,前提主机安装 docker
docker pull redis
docker images
docker run --name redis1 -p 6380:6379 -d redis:latest redis-server --appendonly yes --protected-mode no
appendonly yes: 开启持久化
protected-mode no:其他 host 可以访问
进入容器:docker exec -it redis1 /bin/bash
/usr/local/bin/redis-cli
127.0.0.1:6379> info
查询 redis 状态
#Server
redis_version:5.0.5 版本
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:f5cc35eb8e511133
redis_mode:standalone 单机
os:Linux 5.1.3-1.el7.elrepo.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:8.3.0
process_id:1
run_id:ebd5ff30cb280f08b9d12ccd7e9a967f115b0026
tcp_port:6379
uptime_in_seconds:1316
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:3149326
executable:/data/redis-server
config_file:
#Replication
role:master 主节点
connected_slaves:0 从节点数
master_replid:015f9edecb6436d3a7cbe12402549d04c7e7a0b4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
#Cluster
cluster_enabled:0
127.0.0.1:6379> keys *
查询所有的 key
查询 key 的类型
127.0.0.1:6379> type PRODUCT_ITEM:146
string
127.0.0.1:6379> type CART:85
hash
查询 string 类型的 key 值
127.0.0.1:6379> get PRODUCT_ITEM:146
“{“productId”:146,“salePrice”:1050.00,“productName”:“65 inch HD 3D 4K led
TV”,“productType”:1,“subTitle”:“55 65 inch HD 3D 4K led TV Android Full smart wifi
curved 1080P LED
TV”,“limitNum”:1,“num”:1,“productImageBig”:“http://172.16.23.195:7777/img/4abaed
dc40c2496fb54c83aabdefa7d6.jpg”,“detail”:”\u003cp align\u003d\“left\” data-spm-
anchor-id\u003d\“a2g0o.detail.1000023.i2.709852c7uVSDdx\”\u003e\u003cimg
class\u003d\“wscnph\”
src\u003d\“http://172.16.23.195:7777/img/e6681881bbb844f2812f365a6b5a3e62.jpg\”
/\u003e\u003cimg class\u003d\“wscnph\”
src\u003d\“http://172.16.23.195:7777/img/d1f909f582824dc5bae88050f6514787.jpg\”
/\u003e\u003cimg class\u003d\“wscnph\”
src\u003d\“http://172.16.23.195:7777/img/ae6899d0e6fb40089f3183cbf85d9b7a.jpg\”
/\u003e\u003c/p\u003e",“productImageSmall”:[“http://172.16.23.195:7777/img/4abae
ddc40c2496fb54c83aabdefa7d6.jpg”,“http://172.16.23.195:7777/img/5eed68adbe014a15b
d9280b117e93ae6.jpg”],“weight”:29.40,“promotion”:0,“cid”:1257,“itemPriceDtoList”:[{
“itemPriceId”:493…
查询 hash 类型的 key 的 field
127.0.0.1:6379> hkeys WISH:85
1)“176”
2)“192”
查询 hash 类型 key 的某个 field 的值
127.0.0.1:6379> hget WISH:85 176
“{“productId”:176,“salePrice”:900.99,“productType”:1,“limitNum”:98,“checked”:“1”,
“productName”:“Huawei P30
Pro”,“productImg”:“http://172.16.23.195:7777/img/e3458352965942d99ab50e95d8c1fd8
d.jpg”,“itemPriceId”:0}”
删除某个 key
127.0.0.1:6379> del WISH:85
(integer) 1
删除所有 key 值
插入 String 类型的 key 值
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> get a
“1”
插入 hash 类型的 key 值
127.0.0.1:6379> hset b bb 2
(integer) 1
127.0.0.1:6379> hget b bb
“2”
3 主 3 从,端口分别为 6380-6385
docker network create redis-cluster-net
mkdir redis-cluster
cd redis-cluster
创建 redis-cluster.tmpl 配置文件
vi redis-cluster.tmpl
port ${PORT}
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.16.23.126
cluster-announce-port P O R T c l u s t e r − a n n o u n c e − b u s − p o r t 1 {PORT} cluster-announce-bus-port 1 PORTcluster−announce−bus−port1{PORT}
appendonly yes
for port in seq 6380 6385
; do mkdir -p ./KaTeX parse error: Expected 'EOF', got '&' at position 13: {port}/conf &̲& PORT={port} envsubst
< ./redis-cluster.tmpl > ./${port}/conf/redis.conf; done
注意 ip 和路径
同一宿主机
for port in seq 6380 6385
; do docker run -d -ti -p p o r t : {port}: port:{port} -p
1 p o r t : 1 {port}:1 port:1{port} -v /root/redis-
cluster/ p o r t / c o n f / r e d i s . c o n f : / u s r / l o c a l / e t c / r e d i s / r e d i s . c o n f − − r e s t a r t a l w a y s − − n a m e r e d i s − {port}/conf/redis.conf:/usr/local/etc/redis/redis.conf --restart always -- name redis- port/conf/redis.conf:/usr/local/etc/redis/redis.conf−−restartalways−−nameredis−{port} --net redis-cluster-net --sysctl net.core.somaxconn=1024
redis redis-server /usr/local/etc/redis/redis.conf; done
不同宿主机
for port in seq 6380 6385
; do docker run -d -ti -p p o r t : {port}: port:{port} -p
1 p o r t : 1 {port}:1 port:1{port} -v /root/redis-
cluster/ p o r t / c o n f / r e d i s . c o n f : / u s r / l o c a l / e t c / r e d i s / r e d i s . c o n f − − r e s t a r t a l w a y s − − n a m e r e d i s − {port}/conf/redis.conf:/usr/local/etc/redis/redis.conf --restart always -- name redis- port/conf/redis.conf:/usr/local/etc/redis/redis.conf−−restartalways−−nameredis−{port} --sysctl net.core.somaxconn=1024 redis redis-server
/usr/local/etc/redis/redis.conf; done
docker search ruby
docker pull ruby
下载对应 redis 版本的源码(https://redis.io/)
找到文件/src/redis-trib.rb
编写 dockerfile(最好在新文件夹中创建,redis-trib.rb 拷贝到同一文件夹下)
vi Dockerfile
FROM ruby:latest
MAINTAINER hong.liang
RUN gem install redis
RUN mkdir /redis
WORKDIR /redis
ADD ./redis-trib.rb /redis/redis-trib.rb
进入 Dockerfile 所在目录下
docker build -t redis-trib .
docker run -it --rm redis-trib ruby redis-trib.rb create --replicas 1 172.16.23.126:6380
172.16.23.126:6381 172.16.23.126:6382 172.16.23.126:6383 172.16.23.126:6384
172.16.23.126:6385
登陆任意一台 redis
docker exec -it redis-6380 bash
/usr/local/bin/redis-cli --cluster create 172.16.23.126:6380 172.16.23.126:6381
172.16.23.126:6382 172.16.23.126:6383 172.16.23.126:6384 172.16.23.126:6385 --cluster-
replicas 1
登陆任意一台 redis
docker exec -it redis-6380 bash
redis-cli --cluster check 172.16.23.126:6382
redis-cli --cluster info 172.16.23.126:6382
172.16.23.126:6382 (5a5b176f…) -> 0 keys | 5461 slots | 1 slaves.
172.16.23.126:6381 (94b6caee…) -> 0 keys | 5462 slots | 1 slaves.
172.16.23.126:6380 (cf0bc8f2…) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
登陆 6380
redis-cli -c -h 172.16.23.126 -p 6380
172.16.23.126:6380> hset b bb 2
(integer) 1
172.16.23.126:6380> set c 2
-> Redirected to slot [7365] located at 172.16.23.126:6381
OK
放到了 6381
172.16.23.126:6380> set a 1
-> Redirected to slot [15495] located at 172.16.23.126:6382
OK
放到了 6382
172.16.23.126:6380> keys *
1)“b”
172.16.23.126:6381> keys *
1)“c”
172.16.23.126:6381> get a
-> Redirected to slot [15495] located at 172.16.23.126:6382
“1”
172.16.23.126:6381> hget b bb
-> Redirected to slot [3300] located at 172.16.23.126:6380
“2”
退出,登陆 6383
redis-cli -c -h 172.16.23.126 -p 6383
172.16.23.126:6383> keys *
1)“a”
复制了 6382,是正确的
主节点下线,从节点代替
停掉主节点 6380
docker stop redis-6380
检查集群信息
redis-cli --cluster info 172.16.23.126:6382
Could not connect to Redis at 172.16.23.126:6380: Connection refused
172.16.23.126:6382 (5a5b176f…) -> 1 keys | 5461 slots | 1 slaves.
172.16.23.126:6381 (94b6caee…) -> 1 keys | 5462 slots | 1 slaves.
172.16.23.126:6384 (d2f3e6f1…) -> 1 keys | 5461 slots | 0 slaves.
[OK] 3 keys in 3 masters.
0.0 keys per slot on average.
6384 从节点变更为主节点
查询属于 6380 节点的数据 b,
172.16.23.126:6383> hget b bb
-> Redirected to slot [3300] located at 172.16.23.126:6384
“2”
在 6384 中可以查到
启动 6380
docker start redis-6380
root@76cbd3379368:/data# redis-cli --cluster info 172.16.23.126:6382
172.16.23.126:6382 (5a5b176f…) -> 1 keys | 5461 slots | 1 slaves.
172.16.23.126:6381 (94b6caee…) -> 1 keys | 5462 slots | 1 slaves.
172.16.23.126:6384 (d2f3e6f1…) -> 1 keys | 5461 slots | 1 slaves.
6380 恢复为 6384 的从节点
样例为 springboot 下整合 redis,将商品信息缓存到 redis 中
pom 依赖
org.springframework.boot
spring-boot-starter-data-redis
application.yml 配置
spring:
#redis 配置
redis:
host: 172.16.23.195
port: 6379
timeout: 1000
database: 0
pool:
max-active: 10
max-idle: 8
min-idle: 2
max-wait: 100
通过装配 redistemplate 访问 redis
@Autowired
RedisTemplate redisTemplate;
先查询缓存中是否有该商品信息,如果有则读取
//查询缓存
try {
//有缓存则读取
String json = redisTemplate.opsForValue().get(PRODUCT_ITEM + ":" + id);
if (json != null) {
ProductDet productDet = new Gson().fromJson(json,
ProductDet.class);
log .info("读取了商品" + id + "详情缓存");
//重置商品缓存时间
redisTemplate.expire(PRODUCT_ITEM + ":" + id, ITEM_EXPIRE, TimeUnit. SECONDS );
return productDet;
}
}
catch (Exception e) {
e.printStackTrace();
}
如果没有则查询数据库,将结果塞入
//无缓存 把结果添加至缓存
try {
redisTemplate.opsForValue().set(PRODUCT_ITEM + ":" + id, new Gson().toJson(productDet));
//设置过期时间
redisTemplate.expire(PRODUCT_ITEM + ":" + id, ITEM_EXPIRE, TimeUnit. SECONDS );
log .info("添加了商品" + id + "详情缓存");
}
catch (Exception e) {
e.printStackTrace();
}
application.yml 配置
增加 cluster 的配置
redis:
cluster:
#(普通集群,不使用则不用开启)以逗号分隔的“主机:端口”对列表进行引导。
nodes:
172.16.23.126:6380,172.16.23.126:6381,172.16.23.126:6382,172.16.23.126:6383,172.16.23.12
6:6384,172.16.23.126:6385