Redis实战

Posted by AaronYang on July 11, 2020

Redis实战

NoSQL,泛指非关系型数据库,它们不保证数据的ACID(原子性、一致性、隔离性和持久性)特性。NoSQL有以下优点:易扩展,高并发读写性能,灵活的数据模型。

  • KV数据库: Redis,Voldemort,Oracle BDB
  • 列存储数据库: Cassandra, HBase, Riak
  • 文档型数据库: CouchDB, MongoDb
  • 图形数据库: Neo4J, InfoGrid, Infinite Graph

Redis是一个开源的(BSD)、使用ANSI C编写的、可基于内存亦可持久化的日志型KV数据库。它支持多种数据类型,如字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(ZSet)与bitmap、hyperloglog以及geospatial。Redis 内置了复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions)和不同级别的磁盘持久化(persistence), 并通过Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性。

Redis为什么这么快?

完全基于内存,大部分请求是纯粹的内存操作;数据结构简单,对数据的操作也简单;采用单线程,避免不必要的上下文切换和竞争条件,CPU不是Redis性能瓶颈,Redis的瓶颈是机器内存和网络带宽;多路 I/O 复用模型,多个网络连接,复用同一个线程。

树莓派安装Redis

  1. 安装redis编译的c环境,yum install gcc-c++(树莓派:sudo apt-get install gcc )
  2. 下载安装包并解压(tar -xvf redis-6.0.5.tar.gz
  3. 进入解压后的文件夹并编译Redis项目(make PREFIX=/home/pi/code/redis install
  4. 拷贝配置文件redis.conf到编译后的目录中( cp redis.conf /home/pi/code/redis
  5. 给redis文件规定所属 (sudo chown -R pi /home/pi/code/redis)
  6. Redis默认不是后台启动,需修改配置文件Redis.conf(nano /home/pi/code/redis/redis.conf
################################# GENERAL #####################################

# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes
  1. 启动并查看Redis服务
./bin/redis-server  // 启动Redis服务
ps -ef | grep -i redis  // 查看Redis服务是否正常运行
  1. 通过Redis客户端连接测试
./bin/redis-cli -h ip地址 -p 端口号
  1. 关闭Redis服务shutdown

Redis压测工具

redis性能测试命令

redis-benchmark [option] [option value]
序号 选项 描述 默认值
1 -h 指定服务器主机名 127.0.0.1
2 -p 指定服务器端口 6379
3 -s 指定服务器 socket  
4 -c 指定并发连接数 50
5 -n 指定请求数 10000
6 -d 以字节的形式指定 SET/GET 值的数据大小 2
7 -k 1=keep alive 0=reconnect 1
8 -r SET/GET/INCR 使用随机 key, SADD 使用随机值  
9 -P 通过管道传输 请求 1
10 -q 强制退出 redis。仅显示 query/sec 值  
11 –csv 以 CSV 格式输出  
12 -l 生成循环,永久执行测试  
13 -t 仅运行以逗号分隔的测试命令列表。  
14 -I Idle 模式。仅打开 N 个 idle 连接并等待。  

例:

./bin/redis-benchmark -c 100 -n 100000  # 100个并发 100000次请求

====== GET ======
  100000 requests completed in 4.14 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration "save": 3600 1 300 100 60 10000
  host configuration "appendonly": no
  multi-thread: no

0.00% <= 1 milliseconds
43.89% <= 2 milliseconds
97.44% <= 3 milliseconds
99.89% <= 4 milliseconds
99.95% <= 5 milliseconds
100.00% <= 5 milliseconds
24166.26 requests per second

Redis基本命令

Redis默认有16个数据库,默认使用的是第0个,可以使用select进行切换。

127.0.0.1:6379> DBSIZE     # 查看DB大小
(integer) 3
127.0.0.1:6379> select 2   # 选择数据库
OK
127.0.0.1:6379> keys *     # 查看数据库所有的key
1) "name"
2) "key:{tag}:__rand_int__"
3) "counter:{tag}:__rand_int__"
127.0.0.1:6379> set last abcd 
OK
127.0.0.1:6379> get last
"abcd"
127.0.0.1:6379> expire name 10  # 设置过期时间
(integer) 1
127.0.0.1:6379> ttl name   # 返回key的剩余生存时间
(integer) 7
127.0.0.1:6379> type last  # 返回key的类型
string
127.0.0.1:6379> randomkey  # 随机返回key的名称
"key:{tag}:__rand_int__"
127.0.0.1:6379> rename last name # 修改key的名称
OK
127.0.0.1:6379> expire last 30
(integer) 1
127.0.0.1:6379> ttl last
(integer) 27
127.0.0.1:6379> persist last   # 移除 key 的过期时间,key 将持久保持
(integer) 1
127.0.0.1:6379> ttl last
(integer) -1
127.0.0.1:6379> del k5   # 在 key 存在时删除 key
(integer) 1

清除当前数据库flushdb

清除所有数据库的内容flushdball

Redis数据类型

字符串(String)

127.0.0.1:6379> set key1 v1 # 设置指定 key 的值
OK
127.0.0.1:6379> get key1    # 获取指定 key 的值
"v1"
127.0.0.1:6379>  append key1 hello  # 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾
(integer) 7
127.0.0.1:6379> strlen key1 # 返回 key 所储存的字符串值的长度
(integer) 7
127.0.0.1:6379> GETRANGE key1 0 2  # 返回 key 中字符串值的子字符
"v1h"
127.0.0.1:6379> GETSET key1 hello,world # 将给定 key 的值设为 value ,并返回 key 的旧值
"v1hello"
127.0.0.1:6379> SETRANGE key1 6 Aaron  # 替换指定位置开始的字符串
(integer) 11
127.0.0.1:6379> GETRANGE key1 0 -1
"hello,Aaron"
127.0.0.1:6379> mset k1 a k2 b k3 c  # 同时设置一个或多个 key-value 对
OK
127.0.0.1:6379> mget k1 k2   # 获取所有(一个或多个)给定 key 的值
1) "a"
2) "b"
====================================================================================================
127.0.0.1:6379> set views 0  
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> set key2 a1
OK
127.0.0.1:6379> get key2
"a1"
127.0.0.1:6379> incr views   # 将 key 中储存的数字值增一(只能数字)
(integer) 1
127.0.0.1:6379> incr key2    
(error) ERR value is not an integer or out of range
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> decr views  # 将 key 中储存的数字值减一
(integer) 1
127.0.0.1:6379> incrby views 10   # 将 key 所储存的值加上给定的增量值
(integer) 11
127.0.0.1:6379> decrby views 5    # key 所储存的值减去给定的减量值
(integer) 6
127.0.0.1:6379> INCRBYFLOAT views 0.5  # 将 key 所储存的值加上给定的浮点增量值
"6.5"
====================================================================================================
# setex (set with expire) 设置过期时间
# setnx (set if not exist) 不存在则设置
127.0.0.1:6379> SETEX key3 5 val3
OK
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> SETNX key3 val3
(integer) 1
127.0.0.1:6379> SETNX key1 abc  # 如果key存在会创建失败
(integer) 0
127.0.0.1:6379> get key1
"hello,Aaron"
127.0.0.1:6379> msetnx k5 v7 k6 v6
(integer) 0

# 对象
127.0.0.1:6379> mset user:1:name Aaron user:1:age 26
OK
127.0.0.1:6379> mget user:1:name
1) "Aaron"
127.0.0.1:6379> mget user:1:name user:1:age
1) "Aaron"
2) "26"

列表(List)

一个列表最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)

127.0.0.1:6379> LPUSH list a b c  # 将一个或多个值左插入到表头 (->c b a)
(integer) 3
127.0.0.1:6379> LPUSHX list2 a    # 将一个值插入到已存在的列表头部
(integer) 0
127.0.0.1:6379> LLEN list         # 获取列表长度
(integer) 3
127.0.0.1:6379> LRANGE list 1 2   # 获取列表指定范围内的元素
1) "b"
2) "a"
127.0.0.1:6379> LPUSH list d      # ->d c b a
(integer) 4
127.0.0.1:6379> RPUSH list 1      # 将一个或多个值右插入到表头 (d c b a 1 <-)
(integer) 5
127.0.0.1:6379> LINDEX list 4     # 通过索引获取列表中的元素 
"1"
127.0.0.1:6379> RPOP list         # 移除列表的最后一个元素,返回值为移除的元素 ( d c b a 1->)
"1"             
127.0.0.1:6379> LPOP list         # 移除列表的第一个元素,返回值为移除的元素 (<-d c b a)
"d"
127.0.0.1:6379> LPUSH list 1 2 3  # ->3 2 1 c b a
(integer) 6
127.0.0.1:6379> LINSERT list AFTER 1 d  # 在列表的元素前或者后插入元素 (3 2 1 ->d c b a)
(integer) 7
127.0.0.1:6379> lrem list 1 d    # 移除列表元素LREM key count value (3 2 1 d-> c b a)
(integer) 1 
127.0.0.1:6379> lrem list 1 1    # (3 2 1-> c b a)
(integer) 1
127.0.0.1:6379> lset list 2 1    # 通过索引设置列表元素的值 (3 2 1 b a)
OK
127.0.0.1:6379> ltrim list 1 3   # 让列表只保留指定区间内的元素 (3 [2 1 b] a)
OK
127.0.0.1:6379> RPOPLPUSH list newlist  # 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 (2 1 b->)(->b)
"b"

集合(Set)

127.0.0.1:6379> sadd set a        # 在集合中插入元素
(integer) 1
127.0.0.1:6379> sadd set b c d    # 在集合中插入多个元素
(integer) 3
127.0.0.1:6379> scard set         # 返回集合中元素个数
(integer) 4
127.0.0.1:6379> sadd set2 c d e  
(integer) 3
127.0.0.1:6379> SMEMBERS set2     # 返回集合中所有元素
1) "d"
2) "e"
3) "c"
127.0.0.1:6379> SISMEMBER set2 e  # 判断元素是否在集合中
(integer) 1
====================================================================================================
# 差 交 并
127.0.0.1:6379> SDIFF set set2    # 集合差集在集合set中且不在集合set2中
1) "b"
2) "a"
127.0.0.1:6379> SDIFF set2 set
1) "e"
127.0.0.1:6379> SINTER set set2  # 集合交集
1) "d"
2) "c"
127.0.0.1:6379> SUNION set set2  # 集合并集
1) "a"
2) "c"
3) "d"
4) "b"
5) "e"
127.0.0.1:6379> SINTERSTORE set3 set2 set # 将集合set和集合set2的交集存储到set3中
(integer) 2
127.0.0.1:6379> SMEMBERS set3   
1) "d"
2) "c"
====================================================================================================
# 移动 弹出 删除
127.0.0.1:6379> SRANDMEMBER set   # 随机出一个集合set中的元素
"a"
127.0.0.1:6379> SRANDMEMBER set 2
1) "b"
2) "c"
127.0.0.1:6379> SMOVE set set2 a  # 将集合set中的元素a移动到集合set2中
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "d"
2) "b"
3) "c"
127.0.0.1:6379> SMEMBERS set2
1) "d"
2) "e"
3) "c"
4) "a"
127.0.0.1:6379> srem set2 a     # 删除集合set2中的元素a
(integer) 1
127.0.0.1:6379> SMEMBERS set2
1) "d"
2) "e"
3) "c"
127.0.0.1:6379> spop set2 2    # 弹出集合set2中的多个元素
1) "d"
2) "e"
127.0.0.1:6379> SMEMBERS set2
1) "c"

散列(Hash)

值是一个Map集合

127.0.0.1:6379> hset hash key1 val1     # 插入单个KV对
(integer) 1
127.0.0.1:6379> hget hash key1          # 获取hash中的元素
"val1"
127.0.0.1:6379> hmset hash key2 val2 key3 val3   # 插入多个元素对
OK
127.0.0.1:6379> hmget hash key1 key3   # 获取多个元素的值
1) "val1"
2) "val3"
127.0.0.1:6379> hlen hash              # 返回hash的元素个数
(integer) 3
127.0.0.1:6379> hgetall hash           # 获取所有的键值对
1) "key1"
2) "val1"
3) "key2"
4) "val2"
5) "key3"
6) "val3"
127.0.0.1:6379> hkeys hash             # 获取所有的键
1) "key1"
2) "key2"
3) "key3"
127.0.0.1:6379> hvals hash             # 获取所有的值
1) "val1"
2) "val2"
3) "val3"
127.0.0.1:6379> hset hash key1 val1_1  # 修改元素
(integer) 0
127.0.0.1:6379> hvals hash
1) "val1_1"
2) "val2"
3) "val3"
127.0.0.1:6379> hsetnx hash key2 val2_1  # 如果不存在则插入
(integer) 0
127.0.0.1:6379> hvals hash
1) "val1_1"
2) "val2"
3) "val3"
127.0.0.1:6379> hdel hash key1   # 删除键值对
(integer) 1
====================================================================================================
127.0.0.1:6379> hset hash key4 5
(integer) 1
127.0.0.1:6379> hincrby hash key4 3   # 增加
(integer) 8
127.0.0.1:6379> hget hash key4
"8"
127.0.0.1:6379> hdecrby hash key4 1   # 没有该命令
(error) ERR unknown command `hdecrby`, with args beginning with: `hash`, `key4`, `1`, 
127.0.0.1:6379> hincrbyfloat hash key4 0.4   # 增加一个浮点数
"8.40000000000000036"

有序集合(ZSet)

127.0.0.1:6379> ZADD zset 1 key1   # 插入单个元素
(integer) 1
127.0.0.1:6379> ZADD zset 2 key2 3 key3  # 插入多个元素
(integer) 2
127.0.0.1:6379> zrange zset 0 -1   # 返回指定的返回
1) "key1"
2) "key2"
3) "key3"
127.0.0.1:6379> zcard zset   # 返回zset集合中的元素个数
(integer) 3
127.0.0.1:6379> zcount zset 0 2   # 返回zset集合0 2范围内的元素个数
(integer) 2
127.0.0.1:6379> ZRANGEBYSCORE zset -inf +inf # 返回分数范围内的元素
1) "key1"
2) "key2"
3) "key3"
127.0.0.1:6379> ZREVRANGE zset 0 -1  # 逆序
1) "key3"
2) "key2"
3) "key1"
127.0.0.1:6379> ZRANGEBYSCORE zset -inf +inf withscores
1) "key1"
2) "1"
3) "key2"
4) "2"
5) "key3"
6) "3"
127.0.0.1:6379> ZRANGEBYSCORE zset -inf 2 withscores  # 附带分数
1) "key1"
2) "1"
3) "key2"
4) "2"
127.0.0.1:6379> ZINCRBY zset 3 key1  # 指定key增加值
"4"
127.0.0.1:6379> zrank zset key2  # 返回指定key的排名
(integer) 1
====================================================================================================
127.0.0.1:6379> zadd mid 10 asc
(integer) 1
127.0.0.1:6379> zadd mid 10 han
(integer) 1
127.0.0.1:6379> zadd mid 99 tim
(integer) 1
127.0.0.1:6379> zadd fin 18 asc
(integer) 1
127.0.0.1:6379> zadd fin 10 han
(integer) 1
127.0.0.1:6379> zadd fin 99 tom
(integer) 1
127.0.0.1:6379> zinterstore sum 2 mid fin  # 将两个集合的交集聚合到指定集合
(integer) 2
127.0.0.1:6379> zrange sum 0 -1 withscores
1) "han"
2) "20"
3) "asc"
4) "28"
127.0.0.1:6379> zrem fin tom  # 删除元素
(integer) 1
127.0.0.1:6379> ZSCORE fin han  # 返回指定元素的分数
"10"


特殊数据类型

HyperLogLog

Redis 在 2.8.9 版本添加了 HyperLogLog 结构 。HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的 , 每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。标准误差为0.81%

什么是基数

{1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5

127.0.0.1:6379> pfadd cosmos aaron
(integer) 1
127.0.0.1:6379> pfadd cosmos lyric
(integer) 1
127.0.0.1:6379> pfadd cosmos alice
(integer) 1
127.0.0.1:6379> pfadd cosmos alice
(integer) 0
127.0.0.1:6379> pfadd cosmos alice
(integer) 0
127.0.0.1:6379> pfcount cosmos
(integer) 3
127.0.0.1:6379> pfadd universe alice tom lyric james
(integer) 1
127.0.0.1:6379> pfcount universe 
(integer) 4
127.0.0.1:6379> pfmerge merge cosmos universe
OK
127.0.0.1:6379> pfcount merge
(integer) 5

BitMap

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
127.0.0.1:6379> getbit sign 2
(integer) 0
127.0.0.1:6379> getbit sign 4
(integer) 1
127.0.0.1:6379> bitcount sign
(integer) 5

Geospatial

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。

当坐标位置超出上述指定范围时,该命令将会返回一个错误。 sorted set使用一种称为Geohash的技术进行填充。经度和纬度的位是交错的,以形成一个独特的52位整数.

# geoadd 添加地理位置
127.0.0.1:6379> GEOADD china 116.40 39.9 beijing
(integer) 1
127.0.0.1:6379> GEOADD china 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> GEOADD china 106.50 29.53 chongqin 114.05 22.52 shengzhen
(integer) 2
127.0.0.1:6379> GEOADD china 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2

# geodist 计算距离默认单位m
# m 米
# km 千米
# mi 英里
# ft 英尺
127.0.0.1:6379> GEODIST china beijing xian
"910056.5237"
127.0.0.1:6379> GEODIST china beijing shanghai km
"1067.3788"

# georadius 以给定的经纬度为中心,找出某一个半径内地元素
127.0.0.1:6379> GEORADIUS china 110 30 100 km
(empty array)
127.0.0.1:6379> GEORADIUS china 110 30 1000 km
1) "chongqin"
2) "xian"
3) "shengzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china 110 30 500 km withdist withcoord
1) 1) "chongqin"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china 110 30 500 km withdist withcoord count 1
1) 1) "chongqin"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
 
# georadisbymember 找出指定元素周围的其他元素
127.0.0.1:6379> GEORADIUSBYMEMBER china shanghai 400 km
1) "hangzhou"
2) "shanghai"

# geohash 返回一个或多个元素的geohash值
127.0.0.1:6379> geohash china shanghai
1) "wtw3sj5zbj0"

# 可以用zset来操作
127.0.0.1:6379> zrange china 0 -1
1) "chongqin"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"

事务

Redis事务可以一次执行多个命令:

  • 批量操作在发送Exec命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

事务执行的三个阶段:

  • 开始事务
  • 命令入队
  • 执行事务
127.0.0.1:6379> multi   # 开始事务
OK
127.0.0.1:6379> hset hash key1 val1  # 命令入队
QUEUED
127.0.0.1:6379> hset hash key2 val2
QUEUED
127.0.0.1:6379> set str num1
QUEUED
127.0.0.1:6379> hget hash key2
QUEUED
127.0.0.1:6379> exec  # 执行事务
1) (integer) 1
2) (integer) 1
3) OK
4) "val2"
# discard取消事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> discard  # 事务取消,队列中的命令都不会执行
OK
127.0.0.1:6379> get k3  # 不会进入队列
(nil)
# 代码有错,事务中所有命令不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3 v3
QUEUED
127.0.0.1:6379> getset k4
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> exec

单个Redis命令的执行时原子性的,但Redis没有在事务上增加任何维持原子性的机制,所以Redis事务的执行并不是原子性的。事务可以理解为一个大包的批量执行脚本,中间某条指令的失败不会导致前面已做的指令的回滚,也并不会导致后面的指令不做。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) "v2"

Watch监视一个或多个key,如果再事务执行之前这个key被其他命令所改动,那么事务将会被打断。

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 100
QUEUED
127.0.0.1:6379> incrby out 100
QUEUED
127.0.0.1:6379> exec
1) (integer) 0
2) (integer) 100
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money  # 监视 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby money -20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec  # 执行之前监视的值被修改,事务执行失败
(nil)
127.0.0.1:6379> unwatch  # 释放
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 69
2) (integer) 20

SpringBoot整合

  • Jedis:采用的直连,多个线程操作是不安全的,如果想要避免不安全,使用Jedis Pool连接池,更像BIO模式
  • SpringBoot 2.x之后,使用lettuce。采用netty,实例可以在多个线程中共享,更像NIO。
  1. 配置远端Redis

修改redis.conf配置,注释掉 bind 127.0.0.1 , 若需要设置授权, 那么找到requirepass 将其后的值改为自已的密码。同时设置protected-mode no。然后重启Redis。

./bin/redis-server redis.conf
  1. 导入依赖
<!-- Redis -->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# =========================== Redis ============================
spring.redis.host=192.168.0.112
spring.redis.port=6379
  1. 测试
@SpringBootTest
public class RedisTemplateTest {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void redisOperation(){
        redisTemplate.opsForValue().set("key","abc");
        System.out.println(redisTemplate.opsForValue().get("key"));
    }
}

Redis.conf配置详解

#绑定的ip
bind 127.0.0.1  
#保护模式
protected-mode yes
# 端口
port 6379  
# Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no 
# 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 0
# 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug (很多信息, 对开发/测试比较有用)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel verbose
# 日志记录方式,默认为标准输出,如果配置为redis为守护进程方式运行,而这里又配置为标准输出,则日志将会发送给/dev/null
logfile stdout
# 设置数据库的数量,默认数据库为0,可以使用select <dbid>命令在连接上指定数据库id
# dbid是从0到‘databases’-1的数目
databases 16
# 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   满足以下条件将会同步数据:
#   900秒(15分钟)内有1个更改
#   300秒(5分钟)内有10个更改
#   60秒内有10000个更改
#   Note: 可以把所有“save”行注释掉,这样就取消同步操作了
save 900 1
save 300 10
save 60 10000
# 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
# 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
# 工作目录.
# 指定本地数据库存放目录,文件名由上一个dbfilename配置项指定

# Also the Append Only File will be created inside this directory.

# 注意,这里只能指定一个目录,不能指定文件名
dir ./

# 主从复制。使用slaveof从 Redis服务器复制一个Redis实例。注意,该配置仅限于当前slave有效
# so for example it is possible to configure the slave to save the DB with a
# different interval, or to listen to another port, and so on.
# 设置当本机为slav服务时,设置master服务的ip地址及端口,在Redis启动时,它会自动从master进行数据同步
# slaveof <masterip> <masterport>

# 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过auth <password>命令提供密码,默认关闭
requirepass foobared

# 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max Number of clients reached错误信息
maxclients 128

# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,
# 当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。
# Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory <bytes>

# 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。
# 因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no

# 指定更新日志文件名,默认为appendonly.aof
# appendfilename appendonly.aof

# 指定更新日志条件,共有3个可选值:
# no:表示等操作系统进行数据缓存同步到磁盘(快)
# always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
# everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec
# appendfsync no

# 指定是否启用虚拟内存机制,默认值为no,
# VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中
# 把vm-enabled设置为yes,根据需要设置好接下来的三个VM参数,就可以启动VM了
vm-enabled no
# vm-enabled yes

# 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多少,所有索引数据都是内存存储的(Redis的索引数据就是keys)
# 也就是说当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0

# Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的数据大小来设定的。
# 建议如果存储很多小对象,page大小最后设置为32或64bytes;如果存储很大的对象,则可以使用更大的page,如果不确定,就使用默认值
vm-page-size 32

# 设置swap文件中的page数量由于页表(一种表示页面空闲或使用的bitmap)是存放在内存中的,在磁盘上每8个pages将消耗1byte的内存
# swap空间总容量为 vm-page-size * vm-pages
vm-pages 134217728

# 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 512
hash-max-zipmap-value 64

# 指定是否激活重置哈希,默认为开启
activerehashing yes

# 指定包含其他的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各实例又拥有自己的特定配置文件
# include /path/to/local.conf
# include /path/to/other.conf

Redis持久化

Redis提供了两种数据持久化的机制,分别是RDB(Redis DataBase)和AOF(Append Only File)。

RDB机制

RDB持久化机制是将当前进程中的数据生成快照保存到硬盘的过程,有手动触发和自动触发两种方式。RDB方式适合大规模的数据恢复,对数据的完整性要求不高。不适合实时持久化。

  • 手动触发

save命令:阻塞当前Redis服务器,直到RDB过程结束为止,对于内存比较大的实例会造成长时间阻塞,线上不建议使用。

bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞值发生在fork阶段。

  • 自动触发
  1. save的规则满足
  2. 执行flushall命令
  3. 退出redis

只需要将rbd文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据。通过以下命令查看文件位置。

127.0.0.1:6379> config get dir

AOF机制

以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用 是解决了数据持久化的实时性

AOF文件名 通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同 RDB持久化方式一致,通过dir配置指定。AOF的工作流程操作:命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)。

相对于数据文件来说,AOF远远大于RDB,数据恢复也比RDB慢。

  • 手动触发

直接调用bgrewriteaof命令。

  • 自动触发

根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机

auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认 为64MB。

auto-aof-rewrite-percentage:代表当前AOF文件空间 (aof_current_size)和上一次重写后AOF文件空间(aof_base_size)的比值。

持久化阻塞主线程场景有:fork阻塞和AOF追加阻塞。fork阻塞时间 跟内存量和系统有关,AOF追加阻塞说明硬盘资源紧张。

Redis订阅发布

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

127.0.0.1:6379> SUBSCRIBE Aaron 
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "Aaron"
3) (integer) 1
1) "message"
2) "Aaron"
3) "hello,world"
127.0.0.1:6379> PUBLISH Aaron hello,world
(integer) 1
127.0.0.1:6379> PUBLISH aaron hello
(integer) 0

Redis主从复制

主从复制是将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点,后者称为从节点;数据的复制是单向的,只能由主节点到从节点,Master以写为主,Slave以读为主。

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:57c33a647d36baef984926f2901f114fd8845b17
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

复制配置,并修改端口号,进程,日志文件,rdb文件并启动

pi@raspberrypi:~/programs/redis $ ps -ef|grep redis
pi        1491     1  0 21:05 ?        00:00:08 ./bin/redis-server *:6379
pi        1803  1266  0 22:04 pts/2    00:00:00 ./redis-cli
pi        1840  1805  0 22:08 pts/0    00:00:00 ./bin/redis-cli
pi        1980     1  0 22:35 ?        00:00:00 ./bin/redis-server *:6380
pi        1986  1956  0 22:36 pts/1    00:00:00 grep --color=auto redis
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1594478376
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:0bbe8fad0403122d7e5271066ccab0e7278024cf
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