Redis


  • 开源
  • ANSI C语言编写
  • 支持网络
  • 可基于内存亦可持久化的日志型
  • Key-Value数据库
  • 提供多种语言的API

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

缓存层通常使用内存缓存来实现,毕竟,传统 SQL 数据库的性能瓶颈通常发生在二级存储(即硬盘)的 I/O 层面。随着主内存 (RAM) 的价格在过去十年中下降,将(至少部分)数据存储在主内存中以提高性能便是一种性价比较高的解决方案。基于当前的技术发展现状,Redis 便成为当下一种较为流行的选择。

当然,大多数系统只将所谓的“热数据”存储在缓存层(即主内存)中。基于帕累托原理(也称为 80/20 法则),对于大多数事件,大约 80% 的影响来自 20% 的原因。为了节省成本,我们只需要将这 20% 存储在缓存层中。为了识别“热数据”,我们可以指定驱逐策略(例如 LFU 或 LRU )来确定哪些数据将过期

安装

官网下载对应版本包

tar -zxvf redis-6.0.10.tar.gz

需要 gcc 环境

yum install gcc-c++

Redis版本是6.0.10.对gcc环境的版本有一定的要求,要升级gcc的版本

sudo yum install centos-release-scl
sudo yum install devtoolset-7-gcc*
scl enable devtoolset-7 bash
gcc -v # 查看版本升级

# 在解压的目录中
make && make isntall

redis 默认目录

/usr/local/bin

#启动redis服务
redis-server ../redis.conf  # 修改好配置文件后,指定路径
#连接redis服务
redis-cli -p 6379

在 Redis 中,HMSET 是一个用于设置 hash 中多个字段的命令,但它已经在 Redis 3.2 版本中被弃用,并在后续版本中完全移除。取而代之的是使用 HSET 命令结合多个字段-值对来一次性设置多个字段。由于 HMSET 已经不再可用

redis 对一次写入的数据大小没有限制,但是根据Redis的设计,键(key)的最大长度是512 MB。因此,在使用HSET命令时,需要确保键(key)的长度不超过512 MB

重启redis服务也不会导致数据丢失,只要内存没有影响

Redis 基本命令

服务管理

$ redis-server restart  # 重启 redis-server 服务
$ redis-cli -h 127.0.0.1 -p 6379 shutdown # 关闭 redis-server 服务, 无密码
$ redis-cli -h 127.0.0.1 -p 6379 -a password shutdown  # 关闭 redis-server 服务,有密码
$ /user/local/redis/bin/redis-server /etc/redis/redis.conf #启动redis服务

Redis-keys

set key1 value1 # 增加键值
get key1 # 查看 key1 的值
strlen key1 # 查看 key1 的值的字符串长度
append key1 # 对 key1 的值追加
# 注意:字符串需要使用双引号,

info memory # 进入到redis shell 使用,打印所有磁盘占用情况

TYPE key  # 打印键类型
flushall # 清空所有数据
exists kye1 # key1是否存在
move key1 1  # 移除当前库1的key1的数据
expire key1 15 # 设置key1的过期时间为15s
ttl key1 # 查看key1的剩余生命时间

$ redis-cli CLIENT LIST
# 这将返回一个包含所有客户端信息的列表。请注意,这个列表不会告诉你哪些客户端属于哪个用户
# 因为Redis不跟踪这种信息。如果你需要这种级别的用户管理,你可能需要在应用程序层面实现用户认证和会话跟踪。  


127.0.0.1:6379> ping  #查看当前连接是否正常,正常返回PONG
PONG
127.0.0.1:6379> clear  #清楚当前控制台(为了更好的看到下面输入的命令)
127.0.0.1:6379> keys *  #查看当前库里所有的key
1) "db"
127.0.0.1:6379> FLUSHALL  #清空所有库的内容
OK
127.0.0.1:6379> set name dingdada  #添加一个key为‘name’ value为‘dingdada’的数据
OK
127.0.0.1:6379> get name  #查询key为‘n    ame’的value值
"dingdada"
127.0.0.1:6379> EXISTS name  #判断当前key是否存在
(integer) 1
127.0.0.1:6379> move name 1  #移除当前库1的key为‘name‘的数据
(integer) 1
127.0.0.1:6379> FLUSHALL  #再次清空所有库的内容
OK

## 多加几条数据 下面测试设置key的过期时间
127.0.0.1:6379> set name dingdada
OK
127.0.0.1:6379> set name1 dingdada1
OK
127.0.0.1:6379> set name2 dingdada2
OK
127.0.0.1:6379> EXPIRE name 3  # 设置key为’name‘的数据过期时间为15秒 单位seconds
(integer) 1
127.0.0.1:6379> ttl name  # 查看当前key为’name‘的剩余生命周期时间
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) 0
127.0.0.1:6379> ttl name  # 如若返回-2,证明key已过期
(integer) -2
127.0.0.1:6379> get name  # 再次查询即为空
(nil)
127.0.0.1:6379> type name1
string

String 字符串

127.0.0.1:6379> set name dingdada  #插入一个key为‘name’值为‘dingdada’的数据
OK
127.0.0.1:6379> get name  #获取key为‘name’的数据
"dingdada"
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> keys *  #查看当前库的所有数据
1) "name"
127.0.0.1:6379> EXISTS name  #判断key为‘name’的数据存在不存在,存在返回1
(integer) 1
127.0.0.1:6379> EXISTS name1  #不存在返回0
(integer) 0
127.0.0.1:6379> APPEND name1 dingdada1  #追加到key为‘name’的数据后拼接值为‘dingdada1’,如果key存在类似于java中字符串‘+’,不存在则新增一个,类似于Redis中的set name1 dingdada1 ,并且返回该数据的总长度
(integer) 9
127.0.0.1:6379> get name1
"dingdada1"
127.0.0.1:6379> STRLEN name1  #查看key为‘name1’的字符串长度
(integer) 9
127.0.0.1:6379> APPEND name1 ,dingdada2  #追加,key存在的话,拼接‘+’,返回总长度
(integer) 19
127.0.0.1:6379> STRLEN name1
(integer) 19
127.0.0.1:6379> get name1
"dingdada1,dingdada2"
127.0.0.1:6379> set key1 "hello world!"  #注意点:插入的数据中如果有空格的数据,请用“”双引号,否则会报错!
OK
127.0.0.1:6379> set key1 hello world!  #报错,因为在Redis中空格就是分隔符,相当于该参数已结束
(error) ERR syntax error
127.0.0.1:6379> set key1 hello,world!  #逗号是可以的
OK
127.0.0.1:6379> set num 0  #插入一个初始值为0的数据
OK
127.0.0.1:6379> get num
"0"
127.0.0.1:6379> incr num  #指定key为‘num’的数据自增1,返回结果  相当于java中 i++
(integer) 1
127.0.0.1:6379> get num  #一般用来做文章浏览量、点赞数、收藏数等功能
"1"
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
127.0.0.1:6379> get num
"3"
127.0.0.1:6379> decr num  #指定key为‘num’的数据自减1,返回结果  相当于java中 i--
(integer) 2
127.0.0.1:6379> decr num
(integer) 1
127.0.0.1:6379> decr num
(integer) 0
127.0.0.1:6379> decr num  #可以一直减为负数~
(integer) -1
127.0.0.1:6379> decr num  #一般用来做文章取消点赞、取消收藏等功能
(integer) -2
127.0.0.1:6379> decr num
(integer) -3
127.0.0.1:6379> INCRBY num 10  #后面跟上by  指定key为‘num’的数据自增‘参数(10)’,返回结果
(integer) 7
127.0.0.1:6379> INCRBY num 10
(integer) 17
127.0.0.1:6379> DECRBY num 3  #后面跟上by  指定key为‘num’的数据自减‘参数(3)’,返回结果
(integer) 14
127.0.0.1:6379> DECRBY num 3
(integer) 11
#截取
127.0.0.1:6379> set key1 "hello world!"
OK
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 4  #截取字符串,相当于java中的subString,下标从0开始,不会改变原有数据
"hello"
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 -1  #0至-1相当于 get key1,效果一致,获取整条数据
"hello world!"
#替换
127.0.0.1:6379> set key2 "hello,,,world!"
OK
127.0.0.1:6379> get key2
"hello,,,world!"
127.0.0.1:6379> SETRANGE key2 5 888  #此语句跟java中replace有点类似,下标也是从0开始,但是有区别:java中是指定替换字符,Redis中是从指定位置开始替换,替换的数据根据你所需替换的长度一致,返回值是替换后的长度
(integer) 14
127.0.0.1:6379> get key2
"hello888world!"
127.0.0.1:6379> SETRANGE key2 5 67  #该处只替换了两位
(integer) 14
127.0.0.1:6379> get key2
"hello678world!"
#设置过期时间,跟Expire的区别是前者设置已存在的key的过期时间,而setex是在创建的时候设置过期时间
127.0.0.1:6379> setex name1 15  dingdada  #新建一个key为‘name1’,值为‘dingdada’,过期时间为15秒的字符串数据
OK
127.0.0.1:6379> ttl name1  #查看key为‘name1’的key的过期时间
(integer) 6
127.0.0.1:6379> ttl name1
(integer) 5
127.0.0.1:6379> ttl name1
(integer) 3
127.0.0.1:6379> ttl name1
(integer) 1
127.0.0.1:6379> ttl name1
(integer) 0
127.0.0.1:6379> ttl name1  #返回为-2时证明该key已过期,即不存在
(integer) -2
#不存在设置
127.0.0.1:6379> setnx name2 dingdada2  #如果key为‘name2’不存在,新增数据,返回值1证明成功
(integer) 1
127.0.0.1:6379> get name2
"dingdada2"
127.0.0.1:6379> keys *
1) "name2"
127.0.0.1:6379> setnx name2 "dingdada3"  #如果key为‘name2’的已存在,设置失败,返回值0,也就是说这个跟set的区别是:set会替换原有的值,而setnx不会,存在即不设置,确保了数据误操作~
(integer) 0
127.0.0.1:6379> get name2
"dingdada2"
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  #插入多条数据
OK
127.0.0.1:6379> keys *  #查询所有数据
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3  #查询key为‘k1’,‘k2’,‘k3’的数据
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> MSETNX k1 v1 k4 v4  #msetnx是一个原子性的操作,在一定程度上保证了事务!要么都成功,要么都失败!相当于if中的条件&&(与)
(integer) 0
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> MSETNX k5 v5 k4 v4  #全部成功
(integer) 1
127.0.0.1:6379> keys *
1) "k2"
2) "k4"
3) "k3"
4) "k5"
5) "k1"
#这里其实本质上还是字符串,但是我们讲其key巧妙的设计了一下。
##mset student:1:name  student 相当于类名,1 相当于id,name 相当于属性
#如果所需数据全部这样设计,那么我们在java的业务代码中,就不需要关注太多的key
#只需要找到student类,下面哪个id,需要哪个属性即可,减少了代码的繁琐,在一定程度上可以理解为这个一个类的对象!
127.0.0.1:6379> mset student:1:name dingdada student:1:age 22  #新增一个key为‘student:1:name’,value为‘dingdada ’。。等数据
OK
127.0.0.1:6379> keys *  #查看所有的key
1) "student:1:age"
2) "student:1:name"
127.0.0.1:6379> mget student:1:age student:1:name  #获取数据
1) "22"
2) "dingdada"

##getset操作
127.0.0.1:6379> getset name1 dingdada1  #先get再set,先获取key,如果没有,set值进去,返回的是get的值
(nil)
127.0.0.1:6379> get name1
"dingdada1"
127.0.0.1:6379> getset name1 dingdada2  ##先获取key,如果有,set(替换)最新的值进去,返回的是get的值
"dingdada1"
127.0.0.1:6379> get name1  #替换成功
"dingdada2"

# 可以灵活的表示字符串、整数、浮点数3种值。Redis会自动的识别这3种值。

List 列表

#lpush 左插入
127.0.0.1:6379> lpush list v1  #新增一个集合
(integer) 1
127.0.0.1:6379> lpush list v2
(integer) 2
127.0.0.1:6379> lpush list v3
(integer) 3
#lrange 查询集合
127.0.0.1:6379> LRANGE list 0 -1  #查询list的所有元素值
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> lpush list1 v1 v2 v3 v4 v5  #批量添加集合元素
(integer) 5
127.0.0.1:6379> LRANGE list1 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
###这里大家有没有注意到,先进去的会到后面,也就是我们的lpush的意思是左插入,l--left
#rpush 右插入
127.0.0.1:6379> LRANGE list 0 1  #指定查询列表中的元素,从下标零开始,1结束,两个元素
1) "v3"
2) "v2"
127.0.0.1:6379> LRANGE list 0 0  #指定查询列表中的唯一元素
1) "v3"
127.0.0.1:6379> rpush list rv0  #右插入,跟lpush相反,这里添加进去元素是在尾部!
(integer) 4
127.0.0.1:6379> lrange list 0 -1  #查看集合所有元素
1) "v3"
2) "v2"
3) "v1"
4) "rv0"
##联想:这里我们是不是可以做一个,保存的记录值(如:账号密码的记录),
每次都使用lpush,老的数据永远在后面,我们每次获取 0 0 位置的元素,是不是相当于更新了
数据操作,但是数据记录还在?想要查询记录即可获取集合所有元素!
#lpop 左移除
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
127.0.0.1:6379> lpop list  #从头部开始移除第一个元素
"v5"
##################
#rpop 右移除
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> rpop list
"v1"
127.0.0.1:6379> LRANGE list 0 -1  #从尾部开始移除第一个元素
1) "v4"
2) "v3"
3) "v2"

#lindex 查询指定下标元素
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
127.0.0.1:6379> lindex list 1  #获取指定下标位置集合的元素,下标从0开始计数
"v3"
127.0.0.1:6379> lindex list 0  #相当于java中的indexof
"v4"
#llen  获取集合长度
127.0.0.1:6379> llen list  #获取指定集合的元素长度,相当于java中的length或者size
(integer) 3

# lrem 根据value移除指定的值
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
127.0.0.1:6379> lrem list 1 v2  #移除集合list中的元素是v2的元素1个
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
127.0.0.1:6379> lrem list 0 v3 #移除集合list中的元素是v2的元素1个,这里的0和1效果是一致的
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
127.0.0.1:6379> lpush list  v3 v2 v2 v2
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "v2"
2) "v2"
3) "v2"
4) "v3"
5) "v4"
127.0.0.1:6379> lrem list 3 v2  #移除集合list中元素为v2 的‘3’个,这里的参数数量,如果实际中集合元素数量不达标,不会报错,全部移除后返回成功移除后的数量值
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v4"

#ltrim 截取元素
127.0.0.1:6379> lpush list v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> ltrim list 1 2  #通过下标截取指定的长度,这个list已经被改变了,只剩下我们所指定截取后的元素
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v2"
################
#rpoplpush 移除指定集合中最后一个元素到一个新的集合中
127.0.0.1:6379> lpush list v1 v2 v3 v4 v5
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
127.0.0.1:6379> rpoplpush list newlist  #移除list集合中的最后一个元素到新的集合newlist中,返回值是移除的最后一个元素值
"v1"
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379> LRANGE newlist 0 -1  #确实存在该newlist集合并且有刚刚移除的元素,证明成功
1) "v1"


#lset 更新
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379> 
127.0.0.1:6379> lset list 1 newV5  #更新list集合中下标为‘1’的元素为‘newV5’
OK
127.0.0.1:6379> LRANGE list 0 -1  #查看证明更新成功
1) "v5"
2) "newV5"
3) "v3"
4) "v2"
##注意点:
127.0.0.1:6379> lset list1 0 vvvv  #如果指定的‘集合’不存在,报错
(error) ERR no such key
127.0.0.1:6379> lset list 8 vvv  #如果集合存在,但是指定的‘下标’不存在,报错
(error) ERR index out of range
########################
#linsert
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "newV5"
3) "v3"
4) "v2"
127.0.0.1:6379> LINSERT list after v3 insertv3  #在集合中的‘v3’元素 ‘(after)之后’ 加上一个元素
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "newV5"
3) "v3"
4) "insertv3"
5) "v2"
127.0.0.1:6379> LINSERT list before v3 insertv3  #在集合中的‘v3’元素 ‘(before)之前’ 加上一个元素
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "newV5"
3) "insertv3"
4) "v3"
5) "insertv3"
6) "v2"


# 实际上是一个链表,before Node after , left,right 都可以插入值
# 如果key 不存在,创建新的链表
# 如果key存在,新增内容
# 如果移除了所有值,空链表,也代表不存在!
# 在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~
# 消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)!

Set 集合

#set中所有的元素都是唯一的不重复的!
127.0.0.1:6379> sadd set1 ding da mian tiao  #添加set集合(可批量可单个,写法一致,不再赘述)
(integer) 4
127.0.0.1:6379> SMEMBERS set1  #查看set中所有元素
1) "mian"
2) "da"
3) "tiao"
4) "ding"
127.0.0.1:6379> SISMEMBER set1 da  #判断某个值在不在set中,在返回1
(integer) 1
127.0.0.1:6379> SISMEMBER set1 da1  #不在返回0
(integer) 0
127.0.0.1:6379> SCARD set1  #查看集合的长度,相当于size、length
(integer) 4
127.0.0.1:6379> srem set1 da  #移除set中指定的元素
(integer) 1
127.0.0.1:6379> SMEMBERS set1  #移除成功
1) "mian"
2) "tiao"
3) "ding"
######################
127.0.0.1:6379> sadd myset 1 2 3 4 5 6 7  #在set中添加7个元素
(integer) 7
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
127.0.0.1:6379> SRANDMEMBER myset 1  #随机抽取myset中1个元素返回
1) "4"
127.0.0.1:6379> SRANDMEMBER myset 1  #随机抽取myset中1个元素返回
1) "1"
127.0.0.1:6379> SRANDMEMBER myset 1  #随机抽取myset中1个元素返回
1) "5"
127.0.0.1:6379> SRANDMEMBER myset  #不填后参数,默认抽1个值,但是下面返回不会带序号值
"3"
127.0.0.1:6379> SRANDMEMBER myset 3  #随机抽取myset中3个元素返回
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> SRANDMEMBER myset 3  #随机抽取myset中3个元素返回
1) "6"
2) "3"
3) "5"
######################
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
127.0.0.1:6379> spop myset  #随机删除1个元素,不指定参数值即删除1个
"2"
127.0.0.1:6379> spop myset 1  #随机删除1个元素
1) "7"
127.0.0.1:6379> spop myset 2  #随机删除2个元素
1) "3"
2) "5"
127.0.0.1:6379> SMEMBERS myset  #查询删除后的结果
1) "1"
2) "4"
3) "6"
127.0.0.1:6379> smove myset myset2 1  #移动指定set中的指定元素到新的set中
(integer) 1
127.0.0.1:6379> SMEMBERS myset  #查询原来的set集合
1) "4"
2) "6"
127.0.0.1:6379> SMEMBERS myset2  #查询新的set集合,如果新的set存在,即往后加,如果不存在,则自动创建set并且加入进去
1) "1"


127.0.0.1:6379> sadd myset1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> sadd myset2 3 4 5 6 7
(integer) 5
127.0.0.1:6379> SMEMBERS myset1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> SMEMBERS myset2
1) "3"
2) "4"
3) "5"
4) "6"
5) "7"
127.0.0.1:6379> SDIFF myset1 myset2  #查询指定的set之间的差集,可以是多个set
1) "1"
2) "2"
127.0.0.1:6379> SINTER myset1 myset2  #查询指定的set之间的交集,可以是多个set
1) "3"
2) "4"
3) "5"
127.0.0.1:6379> sunion myset1 myset2  #查询指定的set之间的并集,可以是多个set
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"

# 总结:可实现共同好友、共同关注等需求。

Hash 哈希

127.0.0.1:6379> hset myhash name dingdada age 23  #添加hash,可多个,即批量
(integer) 2
127.0.0.1:6379> hget myhash name  #获取hash中key是name的值
"dingdada"
127.0.0.1:6379> hget myhash age  #获取hash中key是age的值
"23"
127.0.0.1:6379> hgetall myhash  #获取hash中所有的值,包含key
1) "name"
2) "dingdada"
3) "age"
4) "23"
127.0.0.1:6379> hset myhash del test  #添加
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
3) "age"
4) "23"
5) "del"
6) "test"
127.0.0.1:6379> hdel myhash del age  #删除指定hash中的key(可多个),key删除后对应的value也会被删除
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
127.0.0.1:6379> hlen myhash  #获取指定hash的长度,相当于length、size
(integer) 1
127.0.0.1:6379> HEXISTS myhash name  #判断key是否存在于指定的hash,存在返回1
(integer) 1
127.0.0.1:6379> HEXISTS myhash age  #判断key是否存在于指定的hash,不存在返回0
(integer) 0

127.0.0.1:6379> hset myhash age 23 high 173
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
3) "age"
4) "23"
5) "high"
6) "173"
127.0.0.1:6379> hkeys myhash  #获取指定hash中的所有key
1) "name"
2) "age"
3) "high"
127.0.0.1:6379> hvals myhash   #获取指定hash中的所有value
1) "dingdada"
2) "23"
3) "173"
127.0.0.1:6379> hincrby myhash age 2  #让hash中age的value指定+2(自增)
(integer) 25
127.0.0.1:6379> hincrby myhash age -1  #让hash中age的value指定-1(自减)
(integer) 24
127.0.0.1:6379> hsetnx myhash nokey novalue  #添加不存在就新增返回新增成功的数量(只能单个增加哦)
(integer) 1 
127.0.0.1:6379> hsetnx myhash name miaotiao  #添加存在则失败返回0
(integer) 0
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
3) "age"
4) "24"
5) "high"
6) "173"
7) "nokey"
8) "novalue"

# 总结:比String更加适合存对象~

zSet 有序集合

127.0.0.1:6379> zadd myzset 1 one 2 two 3 three  #添加zset值,可多个
(integer) 3
127.0.0.1:6379> ZRANGE myzset 0 -1  #查询所有的值
1) "one"
2) "two"
3) "three"
#-inf 负无穷  +inf 正无穷
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf  #将zset的值根据key来从小到大排序并输出
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> ZRANGEBYSCORE myzset 0 1  #只查询key<=1的值并且排序从小到大
1) "one"
127.0.0.1:6379> ZREVRANGE myzset 1 -1  #从大到小排序输出
1) "two"
2) "one"
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf withscores  #查询指定zset的所有值,包含序号的值
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"

127.0.0.1:6379> zadd myset 1 v1 2 v2 3 v3 4 v4
(integer) 4
127.0.0.1:6379> ZRANGE myset 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> zrem myset v3  #移除指定的元素,可多个
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1
1) "v1"
2) "v2"
3) "v4"
127.0.0.1:6379> zcard myset  #查看zset的元素个数,相当于长度,size。
(integer) 3
127.0.0.1:6379> zcount myset 0 100  #查询指定区间内的元素个数
(integer) 3
127.0.0.1:6379> zcount myset 0 2  #查询指定区间内的元素个数
(integer) 2

# 总结:成绩表排序,工资表排序,年龄排序等需求可以用zset来实现!

特殊类型

地理、基数、位Geospatial:

地理位置城市经纬度查询: 经纬度查询

注意点1:两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!

注意点2:有效的经度从-180度到180度。

注意点3:有效的纬度从-85.05112878度到85.05112878度。注意点4:m 为米。km 为千米。mi 为英里。ft 为英尺。127.0.0.1:6379> geoadd city 118.8921 31.32751 nanjing 197.30794 31.79322

#当经纬度其中一个或者两个超过界限值,报错,信息如下:
(error) ERR syntax error. Try GEOADD key [x1] [y1] [name1] [x2] [y2] [name2] ...
#添加城市经纬度 语法格式: geoadd key 经度 纬度 name +++可多个添加
#添加成功后返回添加成功的数量值
127.0.0.1:6379> geoadd city 118.8921 31.32751 nanjing 117.30794 31.79322 hefei 102.82147 24.88554 kunming 91.13775 29.65262 lasa 116.23128 40.22077 beijing 106.54041 29.40268 chongqing  
(integer) 6
127.0.0.1:6379> ZRANGE city 0 -1  #注意:geo的查看方式和zset的命令是一致的,
#由此可知,geo本质上还是个集合,不过Redis官方对其进行了二次封装
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
127.0.0.1:6379> geopos city nanjing  #查看看指定城市的经纬度信息
1) 1) "118.89209836721420288"
   2) "31.32750976275760735"
127.0.0.1:6379> geopos city nanjing beijing  #查看看多个城市的经纬度信息
1) 1) "118.89209836721420288"
   2) "31.32750976275760735"
2) 1) "116.23128265142440796"
   2) "40.22076905438526495"
127.0.0.1:6379> geodist city nanjing beijing   #计算南京到北京之间的距离,默认返回单位是m
"1017743.1413"
127.0.0.1:6379> geodist city nanjing beijing km  #km  千米
"1017.7431"
127.0.0.1:6379> geodist city nanjing beijing mi  #mi  英里
"632.3978"
127.0.0.1:6379> geodist city nanjing beijing ft  #ft  英尺
"3339052.3010"


127.0.0.1:6379&gt; ZRANGE city 0 -1  #查看城市
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#查看指定位置的1000公里范围内有哪些城市
127.0.0.1:6379&gt; georadius city 120 38 1000 km  
1) "beijing"
2) "hefei"
3) "nanjing"
127.0.0.1:6379&gt; georadius city 120 38 400 km  #查看指定位置的400公里范围内有哪些城市
(empty array)
127.0.0.1:6379&gt; georadius city 120 38 550 km  #查看指定位置的550公里范围内有哪些城市
1) "beijing"
#查看指定位置的550公里范围内有哪些城市,withcoord指定返回城市的name
127.0.0.1:6379&gt; georadius city 120 38 1000 km withcoord
1) 1) "beijing"
   2) 1) "116.23128265142440796"
      2) "40.22076905438526495"
2) 1) "hefei"
   2) 1) "117.30793744325637817"
      2) "31.79321915080526395"
3) 1) "nanjing"
   2) 1) "118.89209836721420288"
      2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,withdist指定返回城市的’经纬度‘值
127.0.0.1:6379&gt; georadius city 120 38 1000 km withcoord withdist
1) 1) "beijing"
   2) "408.3496"
   3) 1) "116.23128265142440796"
      2) "40.22076905438526495"
2) 1) "hefei"
   2) "732.6371"
   3) 1) "117.30793744325637817"
      2) "31.79321915080526395"
3) 1) "nanjing"
   2) "749.0265"
   3) 1) "118.89209836721420288"
      2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,withhash指定返回城市的’经纬度‘的hash值
#如果两个城市的hash值越’像‘,证明城市距离越近!
127.0.0.1:6379&gt; georadius city 120 38 1000 km withcoord withdist withhash
1) 1) "beijing"
   2) "408.3496"
   3) (integer) 4069896088584598
   4) 1) "116.23128265142440796"
      2) "40.22076905438526495"
2) 1) "hefei"
   2) "732.6371"
   3) (integer) 4052763834193093
   4) 1) "117.30793744325637817"
      2) "31.79321915080526395"
3) 1) "nanjing"
   2) "749.0265"
   3) (integer) 4054278565840695
   4) 1) "118.89209836721420288"
      2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,count num 指定返回’num‘个城市数据量
127.0.0.1:6379&gt; georadius city 120 38 1000 km withcoord withdist withhash count 2
1) 1) "beijing"
   2) "408.3496"
   3) (integer) 4069896088584598
   4) 1) "116.23128265142440796"
      2) "40.22076905438526495"
2) 1) "hefei"
   2) "732.6371"
   3) (integer) 4052763834193093
   4) 1) "117.30793744325637817"
      2) "31.79321915080526395"

#查询南京 500公里范围有哪些城市
127.0.0.1:6379&gt; georadiusbymember city nanjing 500 km
1) "hefei"
2) "nanjing"
#查询重庆 1500公里范围有哪些城市
127.0.0.1:6379&gt; georadiusbymember city chongqing 1500 km
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#返回北京和南京的经纬度的 hash值
127.0.0.1:6379&gt; geohash city beijing nanjing
1) "wx4sucvncn0"
2) "wtsd1qyxfx0"
#查看所有城市name
127.0.0.1:6379&gt; ZRANGE city 0 -1
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#根据geo中的name删除g元素
127.0.0.1:6379&gt; ZREM city lasa
(integer) 1
#删除成功
127.0.0.1:6379&gt; ZRANGE city 0 -1
1) "kunming"
2) "chongqing"
3) "hefei"
4) "nanjing"
5) "beijing"

总结:实际需求中,我们可以用来查询附近的人、计算两人之间的距离等。

当然,那些所需的经纬度我们肯定要结合java代码来一次导入,手动查询和录入太过于浪费时间!

Hyperloglog: 基数

再数学层面上:两个数据集中不重复的元素~但是再Redis中,可能会有一定的误差性。 官方给出的误差率是0.81%

127.0.0.1:6379&gt; pfadd dataList 1 2 3 4 5 6 7  #添加数据集
(integer) 1
127.0.0.1:6379&gt; pfcount dataList  #统计数据集中的元素
(integer) 7
127.0.0.1:6379&gt; pfadd dataList1 4 5 6 7 8 9 10  #添加数据集
(integer) 1
127.0.0.1:6379&gt; pfcount dataList1  #统计数据集中的元素
(integer) 7
#将dataList 和dataList1  两个数据集合并成一个新的 newdata数据集,并且自动去重
127.0.0.1:6379&gt; pfmerge newdata dataList dataList1  
OK
127.0.0.1:6379&gt; pfcount newdata
(integer) 10

总结:如果在实际业务中,允许一定的误差值,我们可以使用基数统计来计算~效率非常高!

比如:网站的访问量,就可以利用Hyperloglog来进行计算统计!Bitmap: 位存储 Bitmap 位图,数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态

127.0.0.1:6379&gt; setbit login 1 1   #添加周一已登陆 为1
(integer) 0
127.0.0.1:6379&gt; setbit login 2 1
(integer) 0
127.0.0.1:6379&gt; setbit login 3 1
(integer) 0
127.0.0.1:6379&gt; setbit login 4 0  #添加周四已登陆 为0
(integer) 0
127.0.0.1:6379&gt; setbit login 5 0
(integer) 0
127.0.0.1:6379&gt; setbit login 6 1
(integer) 0
127.0.0.1:6379&gt; setbit login 7 0
(integer) 0
127.0.0.1:6379&gt; getbit login 1  #获取周一是否登录
(integer) 1
127.0.0.1:6379&gt; getbit login 4  #获取周四是否登陆
(integer) 0
127.0.0.1:6379&gt; bitcount login  #统计这周登陆的天数
(integer) 4

总结:实际需求中,可能需要我们统计用户的登陆信息,员工的打卡信息等等

只要是事务的只有两个状态的,我们都可以用Bitmap来进行操作

事务和乐观锁

事务

  1. 原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做
  2. 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的
  3. 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
  4. 持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响

在Redis事务没有没有隔离级别的概念(好像数据库之间的数据是隔离的?是这种隔离吗)

在Redis单条命令式保证原子性的,但是事务不保证原子性

127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> set name dingyongjun  #添加数据
QUEUED
127.0.0.1:6379> set age 26  #添加数据
QUEUED
127.0.0.1:6379> set high 172  #添加数据
QUEUED
127.0.0.1:6379> exec  执行事务
1) OK
2) OK
3) OK
127.0.0.1:6379> get name  #获取数据成功,证明事务执行成功
"dingyongjun"
127.0.0.1:6379> get age
"26"
#############
127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> set name dingyongjun  #添加数据
QUEUED
127.0.0.1:6379> set age 26  #添加数据
QUEUED
127.0.0.1:6379> discard  #放弃事务
OK
127.0.0.1:6379> get name  #不会执行事务里面的添加操作
(nil)

# 编译时异常,代码有问题,或者命令有问题,所有的命令都不会被执行
127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> set name dingyongjun  #添加数据
QUEUED
127.0.0.1:6379> set age 23  #添加数据
QUEUED
127.0.0.1:6379> getset name  #输入一个错误的命令,这时候已经报错了,但是这个还是进入了事务的队列当中
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set high 173  #添加数据
QUEUED
127.0.0.1:6379> exec  #执行事务,报错,并且所有的命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name  #获取数据为空,证明没有执行
(nil)

# 运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令可以正常执行
127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> set name dingyongjun  #添加字符串数据
QUEUED
127.0.0.1:6379> incr name  #对字符串数据进行自增操作
QUEUED
127.0.0.1:6379> set age 23  #添加数据
QUEUED
127.0.0.1:6379> get age  #获取数据
QUEUED 
127.0.0.1:6379> exec  #执行事务。虽然对字符串数据进行自增操作报错了,但是其他的命令还是可以正常执行的
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "23"
127.0.0.1:6379> get age  #获取数据成功
"23"

# 总结:由以上可以得出结论,Redis是支持单条命令事务的,但是事务并不能保证原子性!

乐观锁

  1. 当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。
  2. 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题

在Redis是可以实现乐观锁的

watch 监视
127.0.0.1:6379> set money 100  #添加金钱100
OK
127.0.0.1:6379> set cost 0  #添加花费0
OK
127.0.0.1:6379> watch money  #监控金钱
OK
127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> DECRBY money 30  #金钱-30
QUEUED
127.0.0.1:6379> incrby cost 30  #花费+30
QUEUED
127.0.0.1:6379> exec  #执行事务,成功!这时候数据没有发生变动才可以成功
1) (integer) 70
2) (integer) 30
多线程测试 watch
#线程1
127.0.0.1:6379> set money 100  #添加金钱100
OK
127.0.0.1:6379> set cost 0  #添加花费0
OK
127.0.0.1:6379> watch money  #开启监视(乐观锁)
OK 
127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> DECRBY money 20  #金钱-20
QUEUED
127.0.0.1:6379> INCRBY cost 20   #花费+20
QUEUED
#这里先不要执行,先执行线程2来修改被监视的值
127.0.0.1:6379> exec  #执行报错,因为我们监视了money这个值,如果事务要对这个值进行操作前
#监视器会判断这个值是否正常,如果发生改变,事务执行失败!
(nil)
#线程2,这个在事务执行前操作执行
127.0.0.1:6379> INCRBY money 20  #金钱+20
(integer) 120

总结:乐观锁和悲观锁的区别。
悲观锁: 什么时候都会出问题,所以一直监视着,没有执行当前步骤完成前,不让任何线程执行,十分浪费性能!一般不使用!
乐观锁: 只有更新数据的时候去判断一下,在此期间是否有人修改过被监视的这个数据,没有的话正常执行事务,反之执行失败

redis.config 配置文件

  • Redis配置对大小写不敏感
  • 搭建Redis集群时,可以使用includes包含其他配置文件

网络

bind 127.0.0.1 # 绑定的ip 
protected-mode yes # 保护模式 
port 6379 # 端口设置

GENERAL

daemonize yes # 以守护进程的方式运行,默认是 no,我们需要自己开启为yes
pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个 pid 文件! 
# 日志 
# Specify the server verbosity level. 
# This can be one of:
# debug (a lot of information, useful for development/testing) 
# 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 notice 
logfile "" # 日志的文件位置名 
databases 16 # 数据库的数量,默认是 16 个数据库 
always-show-logo yes # 是否总是显示LOGO

快照(RDB)

持久化,在规定的时间内,执行了多少次操作则会持久化到文件 .rdb .aof文件
Redis是内存数据库,如果没有持久化,那么数据断电即失

# 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作 
save 900 1 
# 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作 
save 300 10 
# 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作 
save 60 10000 
# 我们之后学习持久化,会自己定义这个测试!

SECURITY

设置密码:

  1. 配置文件设置:# requirepass foobared这一行,将其前面的注释符号 # 去除;将 foobared 更换成自定义的密码,比如 mypassword;
  2. 命令行设置:
127.0.0.1:6379> config get requirepass  #获取Redis的密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456"  #设置Redis的密码为123456
OK
# Ctrl+C 退出当前连接
[root@dyjcomputer bin]# redis-cli -p 6379  #重新连接
127.0.0.1:6379> ping  #测试ping,失败,所有的命令都显示无权限
(error) NOAUTH Authentication required.  
127.0.0.1:6379> set k1 v1  #失败,所有的命令都显示无权限
(error) NOAUTH Authentication required.  
127.0.0.1:6379> auth 123456  #auth + 密码  登陆上去
OK 
127.0.0.1:6379> ping  #正常
PONG
127.0.0.1:6379> config get requirepass  #获取密码,正常
1) "requirepass"
2) "123456"

# 使用密码
$ redis-cli -h host -p port -a password # 会有密码不安全警告,取消有两种方式
# 1、
$ redis-cli -a password --no-auth-warning 
# 2、
$ redis-cli -h host -p -port
$ auth password  # 进来之后再输入

root@77e5c14c7de9:/data # redis-cli -a 123456  # -a 属性是密码,没有密码就不用填写
127.0.0.1:6379> auth 123456 # 同样可以输入密码

限制CLIENTS

maxclients 10000   #设置能连接上redis的最大客户端的数量 
maxmemory <bytes>  #redis 配置最大的内存容量 
maxmemory-policy noeviction  #内存到达上限之后的处理策略 
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
2、allkeys-lru : 删除lru算法的key 
3、volatile-random:随机删除即将过期key 
4、allkeys-random:随机删除 
5、volatile-ttl : 删除即将过期的 
6、noeviction : 永不过期,返回错误

APPEND ONLY 模式 aof配置(持久化保存)

appendonly no  #默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用! 
appendfilename "appendonly.aof"  #持久化的文件的名字 
# appendfsync always # 每次修改都会 sync。消耗性能 
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据! 
# appendfsync no  #不执行 sync,这个时候操作系统自己同步数据,速度最快!

日志

默认redis是没有启动日志,需要在配置文件redis.conf中设置

#logfile "/usr/local/redis5/redis_log.log" redis.conf文件中

配置完,重启Redis的过程,将会把启动过程中的日志信息写入日志文件(包括启动错误日志信息,运行日志,bgsave备份日志等等 )

redis 慢日志

slowlog-log-slower-than

设定执行时间,单位是毫秒,执行时长超过该时间的命令将会被记入log。-1表示不记录slow log; 0强制记录所有命令。

slowlog-max-len

slow log的长度。最小值为0。如果日志队列已超出最大长度,则最早的记录会被从队列中清除。

可以通过编辑redis.conf文件配置以上两个参数。对运行中的redis, 可以通过config get, config set命令动态改变上述两个参数

\1. 列出所有 slow log

$ 127.0.0.1:6379> slowlog get
(empty list or set)

\2. 列出最近10条 slow log

slowlog get 10

说明:slow log是记录在内存中的,所以即使你记录所有的命令(将slowlog-log-slower-than设为0),对性能的影响也很小。

Redis 持久化

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能

RDB(Redis DataBase)

/usr/local/bin/dump.rdb 文件就是持久化保存的文件

在 SNAPSHOTTING 配置

save 60 5  # 60s内进行5次操作,即写入rdb文件中进行持久化保存

触发条件:

  1. 达到保存条件,如 60s 操作 5 次
  2. flushall
  3. shutdown
恢复rdb文件

1、只需将备份的rdb文件放在我们的redis启动目录即可,Redis启动的时候会自动检查dump.rdb文件并恢复其中的数据

2、查找文件位置的命令:

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"  # 如果在这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据
优缺点
  1. 优点

    1. 适合大规模的数据恢复
    2. 对数据的完整性要求不高
  2. 缺点

    1. 需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了
    2. fork进程的时候,会占用一定的内容空间!
总结

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。

这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置

在生产环境我们会将这个文件进行备份

AOF(Append Only File)

Redis默认使用的是RDB模式,所以需要手动开启AOF模式

手动开启 AOF

在配置文件 APPEND ONLY MODE 部分,找到 appebdonly no 位置,改为 yes

重启服务

可以看到多出 /usr/local/bin/appendonly.aof 文件

打开appendonly.aof文件,里面存储的就是先前操作的命令

修复 aof 文件

如果 aof 损坏,重启服务会失败

使用redis-check-aof文件来进行修复

redis-check-aof --fix appendonly.aof  #修复appendonly.aof文件

虽然错误的内容少了,但是正确的也有一定的丢失。所以这个修复无法做到百分百修复

AOF重写规则

aof 默认的就是文件的无限追加,在配置文件中可以设置文件的大小

# redis.config
# appendfsync always # 每次修改都会 sync。消耗性能 
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据! # appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!

appendfilename "appendonly.aof" # 持久化的文件的名字
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下, rdb完全够用!

auto-aof-rewrite-percentage 100  # 写入百分比
auto-aof-rewrite-min-size 64mb  # 写入的文件最大值是多少,一般在实际工作中我们会将其设置为5gb左右!
优缺点

优点:

  1. 每一次修改都同步,文件的完整性会更加好!
  2. 每秒同步一次,最多会丢失一秒的数据!
  3. 从不同步,效率最高的!

缺点:

  1. 相对于数据文件来说,aof 远远大于 rdb,修复的速度也比 rdb慢
  2. Aof 运行效率也要比 rdb 慢,所以我们redis默认的配置就是rdb持久化!

总结

  1. RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储

  2. AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF 命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。

  3. 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化

  4. 同时开启两种持久化方式

    1. 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
    2. RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段
  5. 性能建议

    1. 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则
    2. 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值
    3. 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构

发布订阅

Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。
Redis客户端可以订阅任意数量的频道

这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等

订阅端

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> SUBSCRIBE dingdada  #订阅名字为 dingdada 的频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "dingdada"
3) (integer) 1
#等待推送的信息
1) "message"  #消息
2) "dingdada"  #来自哪个频道的消息
3) "hello world\xef\xbc\x81"  # 消息的具体内容
1) "message"
2) "dingdada"
3) "my name is dyj\x81"

发送端

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> PUBLISH dingdada "hello world!"  #发送消息到dingdada 频道
(integer) 1
127.0.0.1:6379> PUBLISH dingdada "my name is dyj"  #发送消息到dingdada 频道
(integer) 1
# PSUBSCRIBE # 订阅指定频道  
PSUBSCRIBE + 频道 # 订阅给定的模式,可多个

# PUBLISH # 发送消息至指定频道
PUBLISH + 频道 +消息  #将信息 message 发送到指定的频道 channel

# PUNSUBSCRIBE 退订
# 指示客户端退订指定模式,若果没有提供模式则退出所有模式。

# SUBSCRIBE 订阅,同上
# UNSUBSCRIBE 退订,同上

总结

Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

远程连接

修改配置文件redis.config

# 1、
bind 127.0.0.1 ::1  # 注释掉

# 2、
# redis3.2版本以上的,保护模式修改成no
protected-mode yes

使用 go 连接

https://blog.csdn.net/corruptwww/article/details/126047077

Go 批量写入 HASH

package redis_util

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)
func WriteRedis(RD_DSN string, RD_PWD string, data map[string]int) (err error) {
    c, err := redis.Dial("tcp", RD_DSN,
        redis.DialPassword(RD_PWD),
    )
    if err != nil {
        return err
    }
    defer c.Close()

    dataArgs := make([]interface{}, 0, len(data)*2)
    dataArgs = append(dataArgs, "thread_info")
    
    // 数量不大的情况的一次性插入:
    // for k, v := range data {
    //     dataArgs = append(dataArgs, k, fmt.Sprintf("%d", v))
    // }

    // fmt.Println("dataArgs is already")

    // _, err = c.Do("HSET", dataArgs...)
    // if err != nil {
    //     fmt.Println("Error:", err)
    // }

    // 数量很大使用分批插入,可以测试分批大小为多少是最优:
    i := 0
    for k, v := range data {
        dataArgs = append(dataArgs, k, fmt.Sprintf("%d", v))
        i++
        if i % 100000 == 0 {
            fmt.Println("dataArgs is already")
            _, err = c.Do("HSET", dataArgs...)
            if err != nil {
                fmt.Println("Error:", err)
            }
            dataArgs = dataArgs[:1] // 重置dataArgs,只保留 "thread_info"
        }
    }

    if len(dataArgs) > 1 {
        _, err = c.Do("HSET", dataArgs...)
        if err != nil {
            fmt.Println("Error:", err)
        }
    }

    return nil
}

问题

内存使用问题

问题:设置了 maxmemory 但是 used_memory 依旧不会改变

127.0.0.1:6379> info memory
# Memory
used_memory:171388424
used_memory_human:163.45M
used_memory_rss:178491392
used_memory_rss_human:170.22M
used_memory_peak:171409472
used_memory_peak_human:163.47M
used_memory_peak_perc:99.99%
used_memory_overhead:830656
used_memory_startup:810048
used_memory_dataset:170557768
used_memory_dataset_perc:99.99%
allocator_allocated:171411480
allocator_active:171687936
allocator_resident:176910336
total_system_memory:3861655552
total_system_memory_human:3.60G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:524288000
maxmemory_human:500.00M
maxmemory_policy:noeviction
allocator_frag_ratio:1.00
allocator_frag_bytes:276456
allocator_rss_ratio:1.03
allocator_rss_bytes:5222400
rss_overhead_ratio:1.01
rss_overhead_bytes:1581056
mem_fragmentation_ratio:1.04
mem_fragmentation_bytes:7143984
mem_not_counted_for_evict:0
mem_replication_backlog:0
mem_clients_slaves:0
mem_clients_normal:20504
mem_aof_buffer:0
mem_allocator:jemalloc-5.1.0
active_defrag_running:0
lazyfree_pending_objects:0
lazyfreed_objects:0

文章作者: Nico
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Nico !
  目录