Skip to content

Redis

安装

链接

Windows 版本可以从 tporadowski/redis Releases GitHub 下载。

安装单机 Redis

Docker 启动单机 Redis
shell
docker run \
       --name myredis \
       -p 6379:6379 \
       -v $PWD/redis.conf:/etc/redis/redis.conf \
       -v $PWD/data:/data \
       --restart=always \
       -d redis:3.2 \
       redis-server /etc/redis/redis.conf \
       --appendonly yes

命令说明:

  • --name myredis:指定容器名称。
  • -p 6379:6379:端口映射,格式是 宿主机端口:容器端口
  • -v $PWD/redis.conf:/etc/redis/redis.conf:将当前目录下的 redis.conf 配置文件挂载到容器内。
  • -v $PWD/data:/data:将当前目录下的 data 目录挂载到容器的 /data
  • --restart=always:Docker 容器退出后自动重启。
  • -d redis:3.2:使用 redis:3.2 镜像,并在后台运行容器。
  • redis-server /etc/redis/redis.conf --appendonly yes:在容器内启动 Redis,并开启 AOF 持久化。

注意事项

  • 如果不需要指定配置,-v $PWD/redis.conf:/etc/redis/redis.conf 可以不用。
  • redis-server 后面的那段 /etc/redis/redis.conf 也可以不用。
  • $PWD 主要适用于 macOS/Linux shell;Windows 下可以改成相对路径,或在 PowerShell 中使用 ${PWD}

客户端连接

客户端连接
shell
# 先查询到myredis容器的ip地址。
docker inspect myredis | grep IP
# 连接到redis容器。然后就进入redis命令行了。
docker run -it redis:latest redis-cli -h 192.168.42.32
源码编译安装 Redis
shell
# Cetnos安装需要的软件
yum -y install gcc gcc-c++ kernel-devel make
# Ubunt 安装需要的软件
sudo apt install gcc g++ make linux-kernel-headers kernel-package
# ubuntu 新内核版本要用到的
sudo apt install gcc g++ make linux-libc-dev

# 下载redis
wget http://download.redis.io/releases/redis-5.0.5.tar.gz
tar -zxvf redis-5.0.5.tar.gz
cd redis-5.0.5

# 安装redis
sudo make && make instal

# 注意make的时候可能会报错: #include <jemalloc/jemalloc.h>,使用下面的命令
make MALLOC=libc
redis.conf
ini
# 设置后台运行
daemonize yes
# 设置log文件路径
logfile /var/log/redis/redis-server.log
# 设置持久化文件存放路径
dir /var/lib/redis
准备 Redis 运行目录
shell
# 创建日志存放目录
mkdir /var/log/redis/
# 创建持久化文件存放目录
mkdir /var/log/redis/
# 创建存放配置的文件夹
mkdir /etc/redis
# 拷贝配置文件并改名
cp redis.conf /etc/redis/6379.conf
# 拷贝自启动脚本文件
cp /usr/local/redis-6.0.3/utils/redis_init_script /etc/init.d/redisd
  • 将启动脚本复制到/etc/init.d目录下,本例将启动脚本命名为redisd(通常都以d结尾表示是后台自启动服务)
配置 Redis 开机自启
shell
# Centos
#设置为开机自启动服务器
cd /etc/init.d/
chkconfig redisd on

# Ubuntu
#设置服务脚本有执行权限
sudo chmod +x /etc/init.d/redisd
#注册服务
cd /etc/init.d/
sudo update-rc.d redisd defaults
  • 此处直接配置开启自启动 chkconfig redisd on 将报错误: service does not support chkconfig
/etc/init.d/redisd
shell
#!/bin/sh
# chkconfig:   2345 90 10
# description:  Redis is a persistent key-value database
Redis 服务命令
shell
#启动Redis服务
sudo service redisd start
#关闭服务
sudo service redisd stop
#重启服务:
sudo service redisd restart

安装集群 Redis

安装 Redis Cluster
shell
#!/bin/bash
echo "start install redis-cluster..."
if [ ! -d /opt/docker-redis/7001/ ];then
	mkdir -p /opt/docker-redis/700{1,2,3,4,5,6}/data/
fi
cd /opt/docker-redis/7001/
wget http://download.redis.io/redis-stable/redis.conf -O /opt/docker-redis/7001/redis7001.conf
#
sed -i '/^#/d;/^$/d' redis7001.conf  #取出空行和注释行
sed -i 's/bind/#bind/g;s/appendonly no/appendonly yes/g;s/protected-mode yes/protected-mode no/g' redis7001.conf  #开启持久化,注释监听ip
#
echo '#集群配置' >> /opt/docker-redis/7001/redis7001.conf
echo 'cluster-enabled yes' >>/opt/docker-redis/7001/redis7001.conf
echo 'cluster-config-file nodes-7001.conf' >>/opt/docker-redis/7001/redis7001.conf
echo 'cluster-node-timeout 15000' >>/opt/docker-redis/7001/redis7001.conf
#
echo 'cluster-announce-ip 192.168.92.135' >>/opt/docker-redis/7001/redis7001.conf
echo 'cluster-announce-port 7001' >>/opt/docker-redis/7001/redis7001.conf
echo 'cluster-announce-bus-port 17001' >>/opt/docker-redis/7001/redis7001.conf
#
for port in `seq 7002 7006`;do
	cp redis7001.conf ../${port}/redis${port}.conf
	echo "cluster-config-file nodes-${port}.conf" >>/opt/docker-redis/${port}/redis${port}.conf
	echo "cluster-announce-port ${port}" >>/opt/docker-redis/${port}/redis${port}.conf
	echo "cluster-announce-bus-port 1${port}" >>/opt/docker-redis/${port}/redis${port}.conf
done
#
for port in `seq 7001 7006`;do
#	sed -i "s/logfile \"\"/logfile \"\/usr\/local\/docker\/redis-cluster\/log\/redis.log\"/g" redis${port}.conf
	sed -i "s/port 6379/port ${port}/g" /opt/docker-redis/${port}/redis${port}.conf
	docker run --restart always --name redis-cluster-${port} --net host --privileged=true -v /opt/docker-redis/${port}/redis${port}.conf:/usr/local/docker/redis-cluster/${port}/redis${port}.conf -v  \
	/opt/docker-redis/${port}/data/:/usr/local/docker/redis-cluster/data/ \
	-d redis redis-server /usr/local/docker/redis-cluster/${port}/redis${port}.conf
done
docker ps
sleep 2s
ss -tnulp|grep redis

#创建集群
#redis-cli  --cluster create 192.168.92.135:7001 192.168.92.135:7002 192.168.92.135:7003 192.168.92.135:7004 192.168.92.135:7005 192.168.92.135:7006  --cluster-replicas 1

下载,解压,编译安装

  • 用一台虚拟机模拟6个节点,创建出3 master、3 salve 环境。
  • 创建文件前,先编译好Redis程序

创建节点配置

redis.conf
ini
# 端口7000 7001 7002 7003 7004 7005
port  7000
# 自己建议修改为0.0.0.0
bind 0.0.0.0
# redis后台运行
daemonize yes
# pidfile文件对应7000 7001 7002 7003 7004 7005
pidfile  /var/run/redis_7000.pid
# 开启集群  把注释#去掉
cluster-enabled  yes
# 集群的配置,配置文件首次启动自动生成7000 7001 7002 7003 7004 7005
cluster-config-file  nodes_7000.conf
# 请求超时  默认15秒,可自行设置
cluster-node-timeout  15000
# AOF日志开启
appendonly  yes

创建文件夹

创建集群节点目录
shell
cd /usr/local
mkdir redis_cluster
cd redis_cluster
mkdir 7000 7001 7002 7003 7004 7005
cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7000/
cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7001/
cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7002/
cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7003/
cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7004/
cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7005/

分别修改三个文件夹里的配置文件,修改如下内容

启动节点的redis/usr/local/bin/redis-server

启动集群节点
shell
/usr/local/bin/redis-server /usr/local/redis_cluster/7000/redis.conf
/usr/local/bin/redis-server /usr/local/redis_cluster/7001/redis.conf
/usr/local/bin/redis-server /usr/local/redis_cluster/7002/redis.conf
/usr/local/bin/redis-server /usr/local/redis_cluster/7003/redis.conf
/usr/local/bin/redis-server /usr/local/redis_cluster/7004/redis.conf
/usr/local/bin/redis-server /usr/local/redis_cluster/7005/redis.conf

检查 redis 启动情况

检查 Redis 进程
shell
ps -ef | grep redi
root      61020      1  0 02:14 ?        00:00:01 redis-server 0.0.0.0:7000 [cluster]
root      61024      1  0 02:14 ?        00:00:01 redis-server 0.0.0.0:7001 [cluster]
root      61029      1  0 02:14 ?        00:00:01 redis-server 0.0.0.0:7002 [cluster]
root      61029      1  0 02:14 ?        00:00:01 redis-server 0.0.0.0:7002 [cluster]
root      61029      1  0 02:14 ?        00:00:01 redis-server 0.0.0.0:7002 [cluster]
root      61029      1  0 02:14 ?        00:00:01 redis-server 0.0.0.0:7002 [cluster]

启动集群

  • 装的redis是5.x的版本,这里没有应用到redis-trib.rb,所以就不需要装ruby
  • 这里启动的时候可能会有报错,因为默认开启了protected-mode
创建 Redis Cluster
shell
cd /usr/local/bin
redis-cli --cluster create 192.168.0.100:7003 192.168.0.100:7004 192.168.0.100:7005 192.168.0.179:7000 192.168.0.179:7001 192.168.0.179:7002 --cluster-replicas 1

Can I set the above configuration? (type 'yes' to accept): yes
yes

校验,等运行完成

校验 Redis Cluster
shell
[root@worker1 src]# redis-cli --cluster check 192.168.0.179:7000
192.168.0.179:7000 (27bce53b...) -> 0 keys | 5462 slots | 1 slaves.
192.168.0.100:7004 (6b0173d9...) -> 0 keys | 5461 slots | 1 slaves.
192.168.0.100:7003 (9f15a932...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 192.168.0.179:7000)
M: 27bce53bda92341ca4a5c82c2361ab99f24c0b27 192.168.0.179:7000
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: c7ebcd900fb7d9afb1980797acba45518cb7d877 192.168.0.100:7005
   slots: (0 slots) slave
   replicates 27bce53bda92341ca4a5c82c2361ab99f24c0b27
S: ed5256f8db1bf556a8dadbe8f2b07699507e17d9 192.168.0.179:7001
   slots: (0 slots) slave
   replicates 6b0173d925f70807a9081b7bc09bcd37be857342
S: 758609eaea88bac25b864f2badbab2171a68089b 192.168.0.179:7002
   slots: (0 slots) slave
   replicates 9f15a9329a9d0ec5c7fcb5abbba817730f0942f9
M: 6b0173d925f70807a9081b7bc09bcd37be857342 192.168.0.100:7004
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
M: 9f15a9329a9d0ec5c7fcb5abbba817730f0942f9 192.168.0.100:7003
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

Cluster配置

redis.conf
ini
# 设置加入cluster,成为其中的节点
cluster-enabled yes|no

# cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容
cluster-config-file < filename>

# 节点服务响应超时时间,用于判定该节点是否下线或切换为从节点
cluster-node-timeout < milliseconds>

# master连接的slave最小数量
cluster-migration-barrier < count>

Cluster节点操作命令

Redis Cluster 常用命令
shell
# 连接到集群,加一个-c就行
redis-cli -c

# 查看集群节点信息
cluster nodes

# 进入一个从节点redis,切换其主节点
cluster replication <master-id>

# 发现一个新节点,新增主节点
cluster meet ip:port

# 忽略一个没有solt的节点
cluster forget

# 手动故障转移
cluster failover

# 计算键 key 应该被放置在哪个槽上
cluster keyslot <key>

# 返回槽 slot 目前包含的键值对数量
cluster countkeysinslot <slot>

# 返回 count 个 slot 槽中的键
cluster getkeysinslot <slot> <count>

配置

redis.conf
ini
# 取消 requirepass foobared 的注释,并设置密码
# 修改后重启:systemctl restart redis
requirepass 123456

# 允许所有 IP 地址远程访问
# 修改后重启:systemctl restart redis
bind 0.0.0.0

Redis的数据类型

redis自身是一个Map,其中所有的数据都是采用key:value的形式存储

数据类型指的是存储的数据的类型,也就是value部分的类型,key部分永远都是字符串

  • string --> String
  • hash --> Hashmap
  • list --> LinkList
  • set --> HashSet
  • sorted_set --> TreeSet

String

  • redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的
shell
# 设值
# 格式 set <key> <value>
192.168.245.129:0>set k1 anthony
"OK"

# 取值
# 格式 get <key>
192.168.245.129:0>get k1
"anthony"

# 删除值,成功返回1,不成功返回0
192.168.245.129:0>del k1
"1"
192.168.245.129:0>del k1234
"0"

# 一次性存入多个值
192.168.245.129:0>mset k1 v1 k2 v2 k3 v3
"OK"

# 一次性取出多个值
192.168.245.129:0>mget k1 k2 k3
 1)  "v1"
 2)  "v2"
 3)  "v3"

# 打印值的长度
192.168.245.129:1>set name anthony
"OK"
192.168.245.129:1>get name
"anthony"
192.168.245.129:1>strlen name
"7"

# 追加
# 有数据就追加
192.168.245.129:1>append name 666
"10"
192.168.245.129:1>get name
"anthony666"
# 没数据就新建
192.168.245.129:1>append othername frankie
"7"
192.168.245.129:1>get othername
"frankie"

# 递增递减,小数不行
192.168.245.129:1>set num 1
"OK"
192.168.245.129:1>incr num
"2"
192.168.245.129:1>incr num
"3"
192.168.245.129:1>decr num
"2"
192.168.245.129:1>decr num
"1"

# 递增递减指定值,小数不行
192.168.245.129:1>incrby num 4
"6"
192.168.245.129:1>decrby num 2
"4"
# 小数不行
192.168.245.129:1>incrby num 1.5
"ERR value is not an integer or out of range"

# 递增指定小数,貌似没有递减
192.168.245.129:1>incrbyfloat num 1.5
"5.5"

# 指定过期时间
# EX 用于指定 key 的过期时间。EX(秒),PX(毫秒),KEEPTTL 保持原有的过期时间不变。
# PX 毫秒-设置指定的到期时间(以毫秒为单位)。
# NX 没有key的时候才能set成功,XX key存在的时候才能set成功
SET key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX] [GET]

# 指定过期时间
# time,以秒为单位。
SETEX key time value

# 功能等价NX,  SET if Not eXists
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0

Hash

Hash 适合存储对象。一个 Redis key 下面可以放多个字段,类似把一个用户对象拆成 nameageemail 这些属性。

shell
# 设置或修改字段
# 格式:HSET key field value
192.168.245.129:0>HSET user:1001 name zhangsan
"1"
192.168.245.129:0>HSET user:1001 age 38
"1"
192.168.245.129:0>HSET user:1001 email [email protected]
"1"

# 获取一个字段值
192.168.245.129:0>HGET user:1001 age
"38"

# 获取多个字段值
192.168.245.129:0>HMGET user:1001 age name
 1)  "38"
 2)  "zhangsan"

# 获取整个 Hash
192.168.245.129:0>HGETALL user:1001
 1)  "name"
 2)  "zhangsan"
 3)  "age"
 4)  "38"
 5)  "email"
 6)  "[email protected]"

# 删除一个字段
192.168.245.129:0>HDEL user:1001 email
"1"
192.168.245.129:0>HGETALL user:1001
 1)  "name"
 2)  "zhangsan"
 3)  "age"
 4)  "38"

# 查看字段数量
192.168.245.129:0>HLEN user:1001
"2"

# 获取所有字段名
192.168.245.129:0>HKEYS user:1001
 1)  "age"
 2)  "name"

# 获取所有字段值
192.168.245.129:0>HVALS user:1001
 1)  "38"
 2)  "zhangsan"

# 对数字字段递增
192.168.245.129:0>HINCRBY user:1001 age 1
"39"

List

List 适合存储有顺序的数据,可以从左侧或右侧插入、弹出元素。常见用法是消息队列、任务列表、最新记录列表。

先区分一下几个常见结构:

结构特点适合场景
顺序表/数组数据连续存放,按下标读取快;中间插入、删除需要移动元素。按索引查询多,增删少。
单向链表每个节点只知道下一个节点;从头往后遍历方便,不能直接往回走。从头部开始处理数据。
双向链表每个节点同时知道前一个和后一个节点;两端操作更方便。需要从两端插入、删除。

顺序表/数组:

单向链表:

双向链表:

Redis 的 List 更关注“两端操作”:可以从左侧插入、右侧插入,也可以从左侧弹出、右侧弹出。

shell
# 从左侧依次插入任务
192.168.245.129:0>LPUSH queue:tasks task:1 task:2 task:3
"3"

# 查看整个列表
# LRANGE key start stop
# 0 表示第一个元素,-1 表示最后一个元素
192.168.245.129:0>LRANGE queue:tasks 0 -1
 1)  "task:3"
 2)  "task:2"
 3)  "task:1"

# 从右侧追加任务
192.168.245.129:0>RPUSH queue:tasks task:4
"4"
192.168.245.129:0>LRANGE queue:tasks 0 -1
 1)  "task:3"
 2)  "task:2"
 3)  "task:1"
 4)  "task:4"

# 从左侧弹出一个任务
192.168.245.129:0>LPOP queue:tasks
"task:3"

# 从右侧弹出一个任务
192.168.245.129:0>RPOP queue:tasks
"task:4"

# 查看列表长度
192.168.245.129:0>LLEN queue:tasks
"2"

# 阻塞取值
# 有数据会立即返回;没有数据会等待最多 20 秒
192.168.245.129:0>BLPOP queue:blocking 20
 1)  "queue:blocking"
 2)  "task:100"

# 删除指定数据
# LREM key count value
# count 表示要删除多少个匹配的 value
192.168.245.129:0>RPUSH post:likes user:1 user:2 user:3 user:2
"4"
192.168.245.129:0>LREM post:likes 1 user:2
"1"
192.168.245.129:0>LRANGE post:likes 0 -1
 1)  "user:1"
 2)  "user:3"
 3)  "user:2"

Set

shell
# 添加
192.168.245.129:0>sadd users zs
"1"
192.168.245.129:0>sadd users lisi
"1"
192.168.245.129:0>sadd users ww
"1"

# 查列表
192.168.245.129:0>smembers users
 1)  "lisi"
 2)  "ww"
 3)  "zs"

 # 查数量
192.168.245.129:0>scard users
"3"

# 判断是否有指定数据
192.168.245.129:0>sismember users ls
"0"
192.168.245.129:0>sismember users ww
"1"
192.168.245.129:0>smembers users
 1)  "lisi"
 2)  "ww"
 3)  "zs"

 # 删除指定数据
192.168.245.129:0>srem users ww
"1"
192.168.245.129:0>smembers users
 1)  "lisi"
 2)  "zs"

 # 随机获取集合中指定数量的数据,获取之后,原来的队列数据不变
 srandmember key count
 # 随机获取集合中的某个数据并讲该数据移除集合
 spop key

 --------------------------------------------------------------------------------------
 > sadd u1 a1
1
> sadd u1 a2
1
> sadd u1 a3
1
> sadd u2 a1
1
> sadd u2 a2
1
# 交集
> sinter u1 u2
a2
a1
# 并集
> sunion u1 u2
a1
a2
a3
# 差集 (u1,u2)顺序不一样,结果不一样
> sdiff u1 u2
a3

redis应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热点旅游线路,应用APP推荐,大V推荐等

sort_set

shell
# 添加数据
> zadd scores 100  zhangsan
1
> zadd scores 90  lisi
1
> zadd scores 95  wangwu
1

# 获取全部数据
> zrange scores 0 -1
lisi
wangwu
zhangsan

# 获取全部数据
> zrange scores 0 -1 withscores
lisi
90
wangwu
95
zhangsan
100

# 反向获取全部数据
> zrevrange scores 0 -1 withscores
zhangsan
100
wangwu
95
lisi
90

# 删除数据
> zrem scores zhangsan
1
> zrange scores 0 -1 withscores
lisi
90
wangwu
95

# 按条件获取数据,可以用作分页
zrangebyscore key min max [WITHSCORES] [LIMIT]
zrevrangebyscore key max min [WITHSCORES]

# 条件删除
zremrangebyrank key start stop
zremrangebyscore key min max

-----------------------------------------排名-------------------------------------------------------
# 添加模拟数据
> zadd movies 143 aa 97 bb 201 cc
3

# 获取数据对应的索引(排名)
> zrank movies bb
0
# 反向获取
> zrevrank movies bb
2

# score 值获取与修改
zscore key member
zincrby key increment member

Redis 通用命令

shell
# 删除
del key

# 获取key是否存在
exists key

# 获取key的类型
type key

# 设置指定有效期,秒
expire key second
# 设置指定有效期,毫秒
pexpire key milliseconds
# 有效期是时间戳
expireat key timestamp

# 获取key的有效期
ttl key
pttl key  毫秒

# 集群环境下,模糊查询key
# 要是查不出来数据,99999要换成一个很大的值
SCAN 0 MATCH * count 99999

持久化

Redis 的数据主要放在内存里。如果机器重启、Redis 进程退出,内存里的数据会丢失,所以需要把数据写到磁盘,这个过程就叫持久化。

Redis 常见持久化方式有两种:

方式核心思路文件特点
RDB定时保存某一刻的全量快照dump.rdb恢复快,适合备份;可能丢失最近一段时间的数据
AOF记录每一次写命令appendonly.aof数据更完整;文件更大,恢复通常比 RDB 慢

简单理解:

  • RDB 像“拍照片”:每隔一段时间给 Redis 内存数据拍一张快照。
  • AOF 像“记流水账”:每次执行写命令,都把命令追加到日志里。

RDB 快照

RDB 的全称是 Redis Database。它会把 Redis 当前内存中的数据保存成一个二进制快照文件,默认文件名是 dump.rdb

恢复时,Redis 读取这个 RDB 文件,把快照里的数据重新加载回内存。

RDB 文件配置

redis.conf
ini
# RDB 文件名,默认是 dump.rdb
dbfilename dump.rdb

# RDB 文件保存目录
dir ./

常见结果:

text
dir + dbfilename = ./dump.rdb

触发 RDB 的方式

RDB 主要有三种触发方式:

方式是否阻塞 Redis 主进程说明
SAVE阻塞直接在主进程里生成 RDB 文件,线上不建议使用
BGSAVE基本不阻塞Redis fork 子进程,由子进程生成 RDB 文件
save second changes 配置基本不阻塞达到配置条件后自动触发 BGSAVE

线上环境注意

SAVE 会阻塞 Redis 主进程。数据量大时,Redis 在生成 RDB 文件期间不能正常处理其他命令,所以线上环境一般使用 BGSAVE 或自动快照配置。

BGSAVE

BGSAVE 会让 Redis 在后台保存快照。Redis 主进程 fork 一个子进程,子进程负责把内存数据写成 RDB 文件,主进程继续处理客户端请求。

shell
BGSAVE

执行成功后,可以在日志里看到类似内容:

shell
58142:M 07 Aug 2020 07:23:17.355 * Starting BGSAVE for SYNC with target: disk
58142:M 07 Aug 2020 07:23:17.355 * Background saving started by pid 58183
58183:C 07 Aug 2020 07:23:17.357 * DB saved on disk
58183:C 07 Aug 2020 07:23:17.357 * RDB: 0 MB of memory used by copy-on-write
58142:M 07 Aug 2020 07:23:17.456 * Background saving terminated with success

可以用 LASTSAVE 查看最近一次成功生成快照的时间。

shell
LASTSAVE

自动快照配置

save second changes 表示:在 second 秒内,如果至少有 changes 次写操作,就自动触发一次 BGSAVE

redis.conf
ini
# second:监控时间范围
# changes:key 的变化次数
save second changes

# 900 秒内至少 1 次写操作,触发 BGSAVE
save 900 1

# 300 秒内至少 10 次写操作,触发 BGSAVE
save 300 10

# 60 秒内至少 10000 次写操作,触发 BGSAVE
save 60 10000

配置要根据业务写入频率调整:

  • 配置太频繁:fork 和磁盘 IO 压力变大。
  • 配置太宽松:故障时可能丢失更多数据。
  • 自动 save 配置实际触发的是 BGSAVE,不是阻塞式 SAVE

RDB 的特点

特点说明
文件小RDB 是紧凑的二进制快照文件
恢复快大数据量恢复通常比 AOF 快
适合备份可以定时把 dump.rdb 复制到远程机器或对象存储
可能丢数据两次快照之间的数据,如果 Redis 宕机,可能丢失
fork 有成本BGSAVE 需要 fork 子进程,大内存实例会有额外开销

RDB 适合什么场景

适合:

  • 可以接受丢失最近几分钟数据。
  • 更看重恢复速度。
  • 需要定期做全量备份或灾难恢复。
  • Redis 主要作为缓存,数据可以从数据库重新构建。

不适合:

  • 不能接受最近写入数据丢失。
  • 对数据完整性要求高。
  • 写入非常频繁,并且 fork/磁盘 IO 压力已经明显。

RDB 和 AOF 的关系

RDB 解决的是“定期保存某一刻的数据状态”。它的缺点是不能记录两次快照之间的每一次写入。

如果希望尽量少丢数据,就要看 AOF。AOF 会记录写命令,通常最多丢失最近 1 秒左右的数据,代价是文件更大、恢复过程通常更慢。

AOF

AOF 的全称是 Append Only File。它会把 Redis 执行过的写命令追加到日志文件中,Redis 重启时再重新执行这些命令,把数据恢复回来。

简单理解:RDB 保存的是“某一刻的数据快照”,AOF 保存的是“数据是怎么一步一步写出来的”。

AOF 写入流程

Redis 执行一条写命令时,AOF 大致会经历这些步骤:

  1. 客户端发送写命令,例如 SET name anthony
  2. Redis 先在内存中执行命令。
  3. Redis 把这条写命令追加到 AOF 缓冲区。
  4. Redis 根据 appendfsync 策略,把缓冲区内容同步到磁盘。
  5. Redis 重启时读取 AOF 文件,按顺序重放写命令恢复数据。

核心点

AOF 不是直接保存最终数据,而是保存写命令。恢复数据时,Redis 会重新执行这些写命令。

appendfsync 三种策略

appendfsync 决定 AOF 什么时候真正刷到磁盘。这个配置很重要,因为它直接影响数据安全和写入性能。

策略含义优点缺点常用程度
always每次写命令都 fsync 到磁盘数据最安全性能最差,磁盘 IO 压力大很少用于高并发场景
everysec每秒 fsync 一次性能和安全比较均衡宕机时最多可能丢最近 1 秒数据最常用,默认推荐
no交给操作系统决定什么时候刷盘性能最好宕机时丢失数据不可控不适合重要数据
redis.conf
ini
# 每次写入都刷盘,最安全,但性能最差
appendfsync always

# 每秒刷盘一次,默认推荐
appendfsync everysec

# 由操作系统决定刷盘时机,性能最好,但数据风险最大
appendfsync no

实际使用时,一般选择:

redis.conf
ini
appendonly yes
appendfsync everysec

AOF 配置

redis.conf
ini
# 是否开启 AOF,默认 no
appendonly yes

# AOF 刷盘策略,常用 everysec
appendfsync everysec

# AOF 文件名,Redis 7 以前常见默认值是 appendonly.aof
appendfilename "appendonly.aof"

# AOF/RDB 文件保存目录
dir /var/lib/redis

Redis 7.0 之后的 AOF

Redis 7.0 开始使用 multi-part AOF。AOF 不一定只是单个 appendonly.aof 文件,而是可能由 base 文件、incremental 文件和 manifest 文件组成。理解原理时仍然可以按“记录写命令 + 重放恢复数据”来理解。

AOF 重写

AOF 会不断追加写命令,文件会越来越大。比如同一个 key 被反复修改:

AOF 原始命令示例
shell
SET name a
SET name b
SET name c

最终内存里只有:

最终数据
shell
name = c

如果一直保留三条历史命令,AOF 文件会越来越大,Redis 重启时重放也会越来越慢。所以 Redis 提供 AOF 重写:根据当前内存里的最终数据,重新生成一份更小的 AOF 文件。

重写后的结果可以理解为:

重写后的 AOF
shell
SET name c

重点

AOF 重写不是读取旧 AOF 文件再压缩,而是根据 Redis 当前内存数据重新生成一份更小的 AOF。

AOF 重写的作用

  • 减少 AOF 文件体积。
  • 减少 Redis 重启时重放命令的时间。
  • 去掉已经没有意义的历史命令。
  • 降低长期运行后的磁盘占用。

AOF 重写规则

  • 已过期的数据不会写入新的 AOF。
  • 被删除的数据不会写入新的 AOF。
  • 同一个 key 的多次修改,会尽量合并成最终状态。
  • listsethashzset 等集合类型会拆分写入,避免单条命令过大。

重写方式

手动触发 AOF 重写
shell
BGREWRITEAOF
redis.conf
ini
# 自动重写
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage 100

这两个配置一起决定是否触发自动重写:

  • auto-aof-rewrite-min-size 64mb:AOF 文件至少达到 64MB 才考虑重写。
  • auto-aof-rewrite-percentage 100:当前 AOF 文件大小比上次重写后的大小增长 100% 时触发重写。

例如上次重写后 AOF 是 80MB,当前增长到 160MB,并且超过 64mb,就满足自动重写条件。

手动重写流程:

自动重写可以通过 INFO persistence 观察:

查看 AOF 状态
shell
INFO persistence

重点关注:

INFO persistence
text
aof_current_size
aof_base_size
aof_rewrite_in_progress

AOF和RDB的区别

对比项RDBAOF
保存内容某一刻的全量快照每条写命令
文件大小通常更小通常更大
恢复速度通常更快通常更慢,需要重放命令
数据安全可能丢失两次快照之间的数据everysec 下一般最多丢最近 1 秒左右
适合场景备份、灾难恢复、缓存型数据对数据完整性要求更高的场景

RDB和AOF的选择

  • 如果不能接受几分钟级别的数据丢失,优先开启 AOF,并使用 appendfsync everysec
  • 如果 Redis 主要作为缓存,数据可以从数据库重建,可以只使用 RDB 或不开启持久化。
  • 如果更看重恢复速度和备份文件体积,RDB 更合适。
  • 如果想尽量降低数据丢失风险,可以同时开启 RDB 和 AOF。

注意

同时开启 RDB 和 AOF 时,Redis 重启恢复数据会优先使用 AOF,因为 AOF 通常比 RDB 保存得更完整。

Redis 4.0 对于持久化机制的优化

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

事务

# 开启事务,设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
multi

# 执行提交事务,加入事务的命令暂时到任务队列中,并没有立即执行,只有执行exec命令才开始执行
exec

# 取消事务,终止当前事务定义,发生在multi之后,exec之前
discard

事务的工作流程

事务的注意事项

  • 语法错误指命令书写格式有误
  • 处理结果如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会被执行。包括那些语法正确的命令

  • 运行错误指命令格式正确,但是无法正常的执行。例如对list进行incr操作
  • 处理结果能够正确运行的命令会执行,运行错误的命令不会执行注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。

手动进行事务回滚(基本没法用)

  • 记录操作过程中被影响的数据之前的状态单数据:string多数据:hash,list,set,zset
  • 设置指令恢复所有的被修改的项单数据:直接set(注意周边属性,例如时效)多数据:修改对应值或整体克隆复制

# 对key添加监视锁,在执行exec前如果key发生了变化,终止事务执行
watch key1 [key2…]

# 取消对所有key的监视
unwatch

分布式锁

# 加锁
setnx lock-key value

# 设置超时时间
expire lock-key second

# 删除锁
dek lock-key
  • 利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
  • 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
  • 对于返回设置失败的,不具有控制权,排队或等待操作完毕通过del操作释放锁

删除策略

过期数据删除策略

  • Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态
  • XX : 具有时效性的数据
  • 1 : 永久有效的数据
  • 2 : 已经国企的数据 或 被删除的数据 或 未定义的数据

时效性数据的存储结构

定时删除

  • 创建一个定时器,当key设置过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
  • 优点:节约内存,到时就删除,快速释放掉不必要的内存占用
  • 缺点:CPU压力很大,无论CPU此时负载多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
  • 总结:用处理器性能换取存储空间

惰性删除

  • 数据到达过期时间,不做处理。等下次访问该数据
  1. 如果未过期,返回数据
  2. 发现已经过期,删除,返回不存在
  • 优点:节约CPU性能,发现必须删除的时候才删除
  • 缺点:内存压力很大,出现长期占用内存的数据
  • 总结:用存储空间换取处理器性能

定期删除

  • Redis启动服务器初始化时,读取配置server.hz的值,默认为10
  • 每秒钟执行server.hz次serverCron()

  • 周期性轮询redis库中时效性数据,采用随机抽取的策略,利用过期数据占比的方式删除频度
  • 特点1:CPU性能占用设置有峰值,检测频度可自定义设置
  • 特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
  • 总结:周期性抽查存储空间

逐出算法

  • Redis使用内存存储数据,在执行每一个命令前,会调用freeMemorylfNeeded()检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。
  • 注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息

相关配置

# 最大可使用内存
maxmemory

# 每次选取代删除数据的个数
maxmemory-samples

# 删除策略
maxmemory-policy
属性说明
volatile-lru从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
volatile-lfu从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
allkeys-lru当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key (这个是最常用的)
allkeys-random从数据集(server.db[i].dict)中任意选择数据淘汰
allkeys-lfu当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
no-eviction禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

数据逐出策略配置依据

  • 使用INFO命令输出监控信息,查询缓存int和miss的次数,根据业务需求调优Redis配置

配置文件

# 设置服务器以守护进程的方式运行
deamonize yes|no

# 绑定主机地址
bind 127.0.0.1

# 设置服务器端口号
port 6379

# 设置数据库数量
databases 16

----------------------------------日志配置--------------------------------------
# 设置服务器以指定日志记录级别
loglevel debug|verbose|notice|warning

# 日志记录文件名
logfile 端口号.log

# 持久化文件存放目录
dir ./

----------------------------------日志配置--------------------------------------

# 设置同一时间最大客户链接数,默认无限制。当客户端连接到达上线,Redis会关闭新的链接
maxclients 0

# 客户端限制等待最大时常,达到最大之后关闭连接。如需关闭该功能,设置为0
timeout 300

Redis-Cluster结构设计

数据存储设计

Key -> CRC16(Key) 相当于HashCode值--->%16384

  • 将所有的存储空间计划切割成16384份,每台主机保存一部分,每份代表的使一个存储空间,不是一个key的保存空间
  • 将key按照计算出的结果放到对应的存储空间

当有新的机器加入集群的时候,就会每台机器转移一些数据空间

集群内部通讯设计

集群常用命令

参考:https://www.cnblogs.com/kevingrace/p/7910692.html

以下命令是Redis Cluster集群所独有的,执行下面命令需要先登录redis

# (客户端命令:redis-cli -c -p port -h ip)
[root@manage redis]# redis-cli -c -p 6382 -h 192.168.10.12
192.168.10.12:6382>
  • 集群cluster info :打印集群的信息cluster nodes :列出集群当前已知的所有节点( node),以及这些节点的相关信息。
  • 节点cluster meet &lt;ip&gt; &lt;port&gt; :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。cluster forget &lt;node_id&gt;:从集群中移除 node_id 指定的节点。cluster replicate &lt;master_node_id&gt; :将当前从节点设置为 node_id 指定的master节点的slave节点。只能针对slave节点操作。cluster saveconfig :将节点的配置文件保存到硬盘里面。
  • slotcluster addslots &lt;slot&gt; [slot ...] :将一个或多个槽( slot)指派( assign)给当前节点。cluster delslots &lt;slot&gt; [slot ...] :移除一个或多个槽对当前节点的指派。cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。cluster setslot &lt;slot&gt; node &lt;node_id&gt;:将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给
  • 另一个节点,那么先让另一个节点删除该槽>,然后再进行指派cluster setslot &lt;slot&gt; migrating &lt;node_id&gt;:将本节点的槽 slot 迁移到 node_id 指定的节点中。cluster setslot &lt;slot&gt; importing &lt;node_id&gt; :从 node_id 指定的节点中导入槽 slot 到本节点。cluster setslot &lt;slot&gt; stable:取消对槽 slot 的导入( import)或者迁移( migrate)。
  • cluster keyslot &lt;key&gt;:计算键 key 应该被放置在哪个槽上。cluster countkeysinslot &lt;slot&gt;:返回槽 slot 目前包含的键值对数量。cluster getkeysinslot &lt;slot&gt; &lt;count&gt;:返回 count 个 slot 槽中的键 。

解决方案

缓存预热

在高请求之前,做好一系列措施,保证大量用户数量点击造成灾难。

  1. 请求数量较高
  2. 主从之间数据吞吐量较大,数据同步操作频度较高

缓存预热解决方案

前置准备工作:

  1. 日常例行统计数据访问记录,统计访问频度较高的热点数据
  2. 利用LRU数据删除策略,构建数据留存队列例如:storm与kafka配合

准备工作:

  1. 将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据
  2. 利用分布式多服务器同时进行数据读取,提速数据加载过程

实施:

  1. 使用脚本程序固定出大数据预热过程
  2. 如果条件允许,使用CDN(内容分发网络),效果会更好

缓存预热总结:缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

缓存雪崩

  1. 系统平稳运行过程中,忽然数据库连接量激增
  2. 应用服务器无法及时请求
  3. 大量408,500错误页面出现
  4. 客户反复刷新页面获取数据
  5. 数据库崩溃
  6. 应用服务器崩溃
  7. 重启应用服务器无效
  8. Redis服务器崩溃
  9. Redis集群崩溃
  10. 重启数据之后再次被瞬间流量放倒

问题排查

简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉

  1. 在一个较短的时间内,缓存中较多的key集中过期
  2. 此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据
  3. 数据库同时接受到大量的请求无法即时处理
  4. Redis大量请求被积压,开始出现超时现象
  5. 数据库流量激增,数据库崩溃
  6. 重启后任然面对缓存中无数据可用
  7. Redis服务器资源被严重占用,Redis服务器崩溃
  8. Redis集群呈现崩塌,集群瓦解
  9. 应用服务器无法即时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃
  10. 应用服务器,redis,数据库全部重启,效果不理想

问题分析

  • 短时间范围内
  • 大量key集中过期

解决方案(道)

  1. 更多的页面静态化处理
  2. 构建多级缓存架构Nginx缓存+redis缓存+ehcache缓存
  3. 检测Mysql严重耗时业务进行优化对数据库的瓶颈排查:例如超时查询、耗时较高事务等
  4. 灾难预警机制监控redis服务器性能指标1、CPU占用、CPU使用率2、内存容量3、查询平均响应时间4、线程数
  5. 限流、降级短时间范围内习生一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐渐放开访问

解决方案(术)

  1. LRU与LFU切换
  2. 数据有效期策略调整根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟过期时间使用固定形式+随机值的形式,稀释集中到期的key的数量
  3. 超热数据使用永久key
  4. 定期维护(自动+人工)对即将过期数据做访问量分析,确认是否演示,配合访问量统计,做热点数据的延时
  5. 加锁 慎用!!!!!!!

总结缓存雪崩式瞬间过期数量太大,导致对数据库服务器造成压力。如果能有效避免过期时间集中,可以有效解决雪崩现象的出现(约40%)。配合其他策略一起使用,并监控服务器的运行数据,根据运行巨鹿做快速调整

缓存击穿

数据库服务器崩溃(2)

  1. 系统平稳运行过程中
  2. 数据库连接量瞬间激增
  3. Redis服务器无大量key过期
  4. Redis内存平稳,无波动
  5. Redis服务器CPU正常
  6. 数据库崩溃

问题排查

  1. Redis中某个key过期,该key访问量巨大
  2. 多个数据请求从服务器直接压到Redis后,均为命中
  3. Redis在短时间内发起了大量对数据库中同一个数据的访问

问题分析

  • 单个key高热数据
  • key过期

解决方案(术)

  1. 预先设定以电商为例,每个商家根据店铺等级,指定若干款主打商品,在购物节期间,加大此类信息key的过期时常注意:购物节不仅仅指当天,以及后续若干天,访问峰值呈现逐渐降低趋势
  2. 现场调整监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key
  3. 后台刷新数据启动定时任务,高峰期来临之前,刷新数据有效期,保存不丢失
  4. 二级缓存设置不同的失效时间,保障不会被同时淘汰就行
  5. 加锁分布式锁,防止被击穿,但是要注意也是性能瓶颈,慎重!!!!!!!!

总结:缓存击穿就是单个高热数据过期的瞬间,数据访问较大,未命中redis后,发起了大量对同一数据的数据库访问,导致对数据库服务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度较高,配合雪崩处理策略即可

缓存穿透

数据库服务器崩溃(3)

简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

  1. 系统平稳运行过程中
  2. 应用服务器流量随时间增量较大
  3. Redis服务器命中率随时间逐步降低
  4. Redis内存平稳,内存无压力
  5. Redis服务器CPU占用激增
  6. 数据库服务器压力激增
  7. 数据库崩溃

问题排查

  1. redis中大面积出现未命中
  2. 出现非正常URL访问

问题分析

  • 获取的数据在数据库中也不存在,数据库查询未得到对应数据
  • Redis获取到null数据未进行持久化,直接返回
  • 下次此类数据到达重复上述过程
  • 出现黑客攻击服务器

解决方法(术)

  1. 缓存null对查询结果为null的数据进行缓存(长期使用,定期清理),设定短时限,例如30-60秒,最高五分钟
  2. 白名单策略提前预热各种分类数据id对应的bitmaps,id作为bitmaps的offset,相当于设置了数据白名单。当加载正常数据后放型,加载异常数据时直接拦截(效率偏低)使用布隆过滤器(有关布隆过滤器的命中问题对当前状态可以忽略)
  3. 实时监控试试监控redis命中率(业务正常范围时,通常回有一个波动值)与null数据的占比非活动时间波动:通常检测3-5倍,超过5倍纳入重点排查对象活动时间波动:通常检测10-50倍,超过50倍纳入重点排查对象根据背书不同,启动不同的排查流程。然后使用黑名单进行防控(运营)
  4. key加密问题出现后,临时启动防灾业务key,对key进行业务层传输加密服务,设定校验程序,过来的key校验例如每天随机分配60个加密串,挑选2-3个,混淆到页面数据id中,发现访问key不满足规则,驳回数据访问

总结缓存穿透是访问了不存在的数据,跳过了合法数据的redis数据缓存阶段,每次访问数据库,导致对数据库服务器造成压力。通常此类数据的出现量是一个较低的值,当出现此类情况以毒攻毒,并即时报警。应对策略应该在临时预案防范方面多做文章无论是黑名单还是白名单,都是对整体系统的压力,警报解除后尽快移除

Redis-Java

单机Jedis

/**
 * 192.168.0.5 单机
 */
public class SingletonDemo {

    private Jedis jedis;

    @Before
    public void before() {
        //指定Redis服务Host和port
        jedis = new Jedis("192.168.0.5", 6379);
    }

    @After
    public void after() {
        //使用完关闭连接
        jedis.close();
    }

    /**
     * 设值
     */
    @Test
    public void set() {
        //访问Redis服务
        jedis.set("k1", "v1");
        System.out.println(jedis.get("k1"));
    }

    /**
     * 设值,并且设置超时时间
     */
    @Test
    public void setTime() {
        String key = "setTimeKey";
        jedis.setex(key, 20000, "setTimeValue");
        System.out.println(jedis.get(key));
        System.out.println(jedis.ttl(key));
    }

    /**
     * 分布式锁原理,
     * key不存在的时候才插入
     */
    @Test
    public void setNX() {
        String key = "setNXKey";
        // 1.先插入一个值
        jedis.set(key, "setNXValue");

        // 2.NX是不存在时才set, XX是存在时才set, EX是秒,PX是毫秒
        String set = jedis.set(key, "setNXValue---NX", "NX", "EX", 20000L);
        System.out.println(set);
        System.out.println(jedis.get(key));
        System.out.println(jedis.ttl(key));

        System.out.println("======================================================");

        // 3.设置个没有的key
        String key2 = "setNXKey2";
        String set2 = jedis.set(key2, "setNXValue2---NX", "NX", "EX", 20000L);
        System.out.println(set2);
        System.out.println(jedis.get(key2));
        System.out.println(jedis.ttl(key2));
    }

    /**
     * key存在时才插入
     */
    @Test
    public void setXX() {
        String key = "setXXKey";
        // 1.先插入一个值
        jedis.set(key, "setXXValue");

        // 2.NN
        String set = jedis.set(key, "setXXValue---XX", "XX", "EX", 20000L);
        System.out.println(set);
        System.out.println(jedis.get(key));
        System.out.println(jedis.ttl(key));

        System.out.println("======================================================");

        // 3.设置个没有的key
        String key2 = "setXXKey2";
        String set2 = jedis.set(key2, "setXXValue2---NX", "XX", "EX", 20000L);
        System.out.println(set2);
        System.out.println(jedis.get(key2));
        System.out.println(jedis.ttl(key2));
    }

    @Test
    public void expire() {
    }

    /**
     * redis监控信息
     */
    @Test
    public void info() {
        String info = jedis.info();
        Stream.of(info.split("\r\n")).forEach(row -> {

                    String[] split = row.split(":");
                    if (split.length == 2) {

                        System.out.printf("key:%s ====  value:%s \r\n",split[0],split[1]);
                    }

                }
        );
    }

    /**
     * jedispool
     */
    @Test
    public void jedisPool() throws InterruptedException {

        StopWatch watch = new StopWatch();
        watch.start();

        for (int i = 0; i < 100000; i++) {
            jedis.set(i + "", i + "");
            jedis.close();
        }
        watch.stop();
        System.out.println("5-"+watch.getTime());
    }

    @Test
    public void jedisPool2() {

        // 替换成你的reids地址和端口
        String redisIp = "192.168.0.5";
        int reidsPort = 6379;
        JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), redisIp, reidsPort);
        Jedis resource = jedisPool.getResource();
        StopWatch watch = new StopWatch();
        watch.start();

        for (int i = 0; i < 100000; i++) {
            resource.set(i + "", i + "");
        }
        watch.stop();
        System.out.println("5-"+watch.getTime());

    }
}

单机Jedis连接集群节点

会报错

redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 12706 192.168.0.8:6379
	at redis.clients.jedis.Protocol.processError(Protocol.java:115)
	at redis.clients.jedis.Protocol.process(Protocol.java:161)
	at redis.clients.jedis.Protocol.read(Protocol.java:215)
	at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:340)
	at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:239)
	at redis.clients.jedis.Jedis.set(Jedis.java:121)
	at reids.ClusterDemo.set(ClusterDemo.java:68)

集群Jedis

/**
 * anthony@base:/var/log/redis$ redis-cli --cluster check 192.168.0.6:6379
 * 192.168.0.6:6379 (99af86e5...) -> 0 keys | 5461 slots | 1 slaves.
 * 192.168.0.8:6379 (982f74c7...) -> 3 keys | 5461 slots | 1 slaves.
 * 192.168.0.7:6379 (1069a632...) -> 0 keys | 5462 slots | 1 slaves.
 * [OK] 3 keys in 3 masters.
 * 0.00 keys per slot on average.
 * >>> Performing Cluster Check (using node 192.168.0.6:6379)
 * M: 99af86e59c7f5a4ddf212b29e9e1b12fa5e7a866 192.168.0.6:6379
 *    slots:[0-5460] (5461 slots) master
 *    1 additional replica(s)
 * S: 102517bddae6af2434519e8f348cf062b0152fa7 192.168.0.9:6379
 *    slots: (0 slots) slave
 *    replicates 982f74c79ed5c5811b7011507c56f81374c70a0b
 * S: 392c5e4dcd5608c74a76902668ed58fb6ab50aaf 192.168.0.11:6379
 *    slots: (0 slots) slave
 *    replicates 1069a6320a0489f63e807c970c27f86f13f417cf
 * S: 4d12dd0de1af9d90bfac7dd141c36c348071d59f 192.168.0.10:6379
 *    slots: (0 slots) slave
 *    replicates 99af86e59c7f5a4ddf212b29e9e1b12fa5e7a866
 * M: 982f74c79ed5c5811b7011507c56f81374c70a0b 192.168.0.8:6379
 *    slots:[10923-16383] (5461 slots) master
 *    1 additional replica(s)
 * M: 1069a6320a0489f63e807c970c27f86f13f417cf 192.168.0.7:6379
 *    slots:[5461-10922] (5462 slots) master
 *    1 additional replica(s)
 * [OK] All nodes agree about slots configuration.
 * >>> Check for open slots...
 * >>> Check slots coverage...
 * [OK] All 16384 slots covered.
 */
public class ClusterDemo {

    private JedisCluster jedis;

    @Before
    public void before() {
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.0.6", 6379));
        nodes.add(new HostAndPort("192.168.0.7", 6379));
        nodes.add(new HostAndPort("192.168.0.8", 6379));
        nodes.add(new HostAndPort("192.168.0.9", 6379));
        nodes.add(new HostAndPort("192.168.0.10", 6379));
        nodes.add(new HostAndPort("192.168.0.11", 6379));

        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        jedis = new JedisCluster(nodes,poolConfig);
    }

    @After
    public void after() throws IOException {
        //使用完关闭连接
        jedis.close();
    }

    /**
     * 设值
     */
    @Test
    public void set() {
        //访问Redis服务
        jedis.set("k1", "v1");
        System.out.println(jedis.get("k1"));
    }

    /**
     * redis集群的节点信息
     * map.key = IP:PORT
     */
    @Test
    public void info() {
        Map<String, JedisPool> clusterNodes = jedis.getClusterNodes();
        clusterNodes.keySet().forEach(key->{
            System.out.printf("%s==%s\r\n",key,clusterNodes.get(key).getResource().info());
        });
    }
}

RedisTemplate

# 插入
myVO.setCode(typeCode);
redisTemplate.opsForList().rightPush(key, JSONUtil.toJsonStr(myVO));

# 取出
List<String> range = redisTemplate.opsForList().range(key, 0, 100);

# 删除取出的
# trim 是保留的意思
if(range.size()  >0){
    redisTemplate.opsForList().trim(key, range.size() + 1, -1);
}

技术笔记