Redis系列

前言

Redis是一款开源的、高性能的键-值存储。它常被称作是一款数据结构服务器。redis的键值开源包括字符串(String)、哈希(hash)、列表(list)、集合(set)和有序集合(zset)等基本数据类型。
为了获得优异的性能,redis采用了内存中数据集的方式。同时redis支持数据的持久化,可以每隔一段时间将数据转存到磁盘上(RDB),也可以在日志尾部追加每一条操作命令(AOF)。

介绍

 Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串哈希表列表集合有序集合位图hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区

目录

  安装

  基本数据结构

  持久化原理
  持久化配置
  redis实现分布式锁


安装

https://www.redis.net.cn/tutorial/3503.html

1、Docker方式
# 拉取 redis 镜像
> docker pull redis
# 运行 redis 容器
> docker run --name myredis -d -p6379:6379 redis
# 执行容器中的 redis-cli,可以直接使用命令行操作 redis
> docker exec -it myredis redis-cli...
2、直接安装方式

# mac
> brew install redis
# ubuntu
> apt-get install redis
# redhat
> yum install redis
# 运行客户端
> redis-cli

redis配置

参数说明

redis.conf 配置项说明如下:

1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

    daemonize no

2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

    pidfile /var/run/redis.pid

3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

    port 6379

4. 绑定的主机地址

    bind 127.0.0.1

5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

    timeout 300

6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

    loglevel verbose

7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

    logfile stdout

8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

    databases 16

9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

    save <seconds> <changes>

    Redis默认配置文件中提供了三个条件:

    save 900 1

    save 300 10

    save 60 10000

    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

    rdbcompression yes

11. 指定本地数据库文件名,默认值为dump.rdb

    dbfilename dump.rdb

12. 指定本地数据库存放目录

    dir ./

13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

    slaveof <masterip> <masterport>

14. 当master服务设置了密码保护时,slav服务连接master的密码

    masterauth <master-password>

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

    requirepass foobared

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

    maxclients 128

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

    maxmemory <bytes>

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

    appendonly no

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

     appendfilename appendonly.aof

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

    appendfsync everysec

21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

     vm-enabled no

22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

     vm-swap-file /tmp/redis.swap

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

     vm-max-memory 0

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

     vm-page-size 32

25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

     vm-pages 134217728

26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

     vm-max-threads 4

27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

    glueoutputbuf yes

28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

    hash-max-zipmap-entries 64

    hash-max-zipmap-value 512

29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

    activerehashing yes

30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

    include /path/to/local.conf

连接

本地:redis-cli

远程:redis-cli -h host -p port -a password

切换到指定的数据库

select 0~15

命令帮助手册

http://redisdoc.com/bitmap/setbit.html?highlight=setbit

基本数据结构

String

注意:一个键最大能存储512MB。

# 设置一个指定key的值
set key value
# 获取指定key的值
get key

# 设置多个key和value
mset key1 value1 key2 value2...

# 获取多个key对应的值
mget key1 key2 key3
# 返回key的值对应开始到结束位置的子串,包含end getrange key start end  

# 将给定key的值设置新的value,并返回旧的值,没有旧值则返回nil
getset key value

# 给key设置值,并设置过期时间,秒为单位
setex key second value

# 给key设置值,并设置过期时间,毫秒为单位
psetex key millinseconds value

# 当key不存在的时候设置value
setnx key value

# 设置多个key和value,当key不存在时
msetnx key1 value1 key2 value2...

# 返回key对应值的长度
strlen key

# 将key对应的数字加1
incr key

# 对应的值增加指定增量
incrby key increment

# 对应的值增加指定浮点数增量
incrbyfloat key increment

# 对应的值减1
decr key

# 对应的值减指定的减量值
decrby key decrement

# 如果key已经存在并是字符串,在末尾添加value
append key value

例子:

> set name allen
OK
> get name
“allen”
获取字符串的长度
> strlen name
5
获取子串
> getrange name 1 3
lle
追加子串
> append name cool
(integer) 9
> get name
"allencool"


> exists name
(integer) 1
> del name
(integer) 1
> get name
(nil)
>mset name1 boy name2 girl name3 unknown
>mget name1 name2 name3 
"boy" 
"girl" 
"unknown"


# 5s 后过期,等价于 set+expire
>setex name 5 codehole
//等5秒
>get name “codehole“
>get name (nil)
# 如果 name 不存在就执行 set 创建
>setnx name codehole
(integer) 1 
> get name "codehole"

List

# 在列表key的尾部插入多个value
rpush key v1 v2 v3

# 在列表尾部移除value并返回 rpop key

# 在列表的头部依次插入多个value,最后一个元素插入在列表第一个
lpush key v1 v2 v3

# 在列表头部移除value并返回
lpop key

# 获取列表的长度
llen key

# 获取列表指定索引开始到结束位置的value
lrange key start end

# 移除列表指定value,count指定value个数
lrem key count value

# 获取列表指定索引的值
lindex key index

# 在指定元素的前面或者后面插入值,pivot指定已存在的value
linsert key befor|after pivot value

# 通过指定索引的位置更新value
lset key index value

# 修剪列表指定开始和结束位置的元素,不在范围内的元素会被删除
ltrim key start end


例子:

队列/堆栈 链表可以从表头和表尾追加和移除元素,结合使用rpush/rpop/lpush/lpop四条指令,可以将链表作为队列或堆栈使用,左向右向进行都可以
# 右进左出
> rpush name go
(integer) 1
> rpush ireader java python
(integer) 3
> lpop name
"go"
> lpop name
"java"
> lpop name
"python"
# 左进右出
> lpush name go java python
(integer) 3
> rpop name
"go"
...


Hash

# 设置多个field和value的映射表
hmset key field1 value2 field2 value2

# 获取指定hash key下多个field字段的value
hmget key field1 field2


# 获取key下所有的field和value hgetall key

# 设置一个一个字段映射
hset key field value

# 获取一个字段对应的值
hget key field1

# 获取key下所有的字段
hkeys key

# 获取key下所有的value
hvals key

# 删除key下指定的字段, 可以多个字段
hdel key field1 field2

# 查看key下指定字段是否存在
hexists key field

# 给hash对象指定key的值指定增量
hincrby key field increment

# 给hash对象指定key的值指定浮点增量
hincrbyfloat key field increment
# 获取hash key下的字段数量
hlen key

# field不存在时设置value
hsetnx key field value
#可以通过hget定位具体key对应的value.
#可以通过hmget获取多个key对应的value.
#可以使用hgetall获取所有的键值对.
#可以使用hkeys和hvals分别获取所有的key列表和value列表。
#这些操作和Java语言的Map接口是类似的。    
>hset name go fast 
(integer) 1 
> hmset name java fast python slow 
OK
> hmset name go fast java fast python slow
OK
> hget name go
"fast"
> hmget name go python
1) "fast"
2) "slow"



> hgetall name
1) "go"
2) "fast"
3) "java"
4) "fast"
5) "python"
6) "slow"
> hkeys name
1) "go"
2) "java"
3) "python"
> hvals name
1) "fast"
2) "fast"
3) "slow"

Set

# 集合中添加多个成员,重复的成员只保留一个
sadd key m1 m2 m3

# 获取集合中的元素个数
scard key

# 返回两个集合中的差集,相当于key1 - key2 key1中的元素,如果key1与key2的位置互换,则返回内容有变化
sdiff key1 key2

# 返回集合中的交集
sinter key1 key2

# 元素m是否是集合中的成员
sismember key m

# 返回集合中所有的成员
smembers key

# 随机移除集合中的任意一个元素
spop key

# 随机返回集合中指定个数的元素,默认一个,超过集合长度则返回所有元素
srandmember key count

# 移除集合中指定的元素,一个或多个
srem key m1 m2

# 多个集合的并集
sunion s1 s2

例子:

set 结构可以用来存储活动中奖的用户 ID,因为有去重功能,可以保证同一个用户不会中奖两次。

> sadd books python
(integer) 1
> sadd books python # 重复
(integer) 0
> sadd books java golang
(integer) 2
> smembers books # 注意顺序,和插入的并不一致, 因为 set 是无序的
1) "java"
2) "python"
3) "golang“
> scard books # 获取长度相当于 count()
(integer) 3


Zset

# 有序集合中添加成员和分数,已存在成员则更新分数
zadd key score1 m1 score2 m2

# 有序集合中成员个数
zcard key


> zadd books 9.0 "think in java"
(integer) 1
> zadd books 8.9 "java concurrency"
(integer) 1
> zadd books 8.6 "java cookbook"
(integer) 1

> zrange books 0 -1 # 按 score 排序列出,以 0 表示有序集第一个成员以,-1 表    示最后一个成员
1) "java cookbook"
2) "java concurrency"
3) "think in java"
> zrevrange books 0 -1 # 按 score 逆序列出,参数区间为排名范围
1) "think in java"
2) "java concurrency"
3) "java cookbook"...

过期时间
Redis 所有的数据结构都可以设置过期时间,时间到了,Redis 会自动删除相应的对象。需要注意的是过期是以对象为单位,比如一个 hash 结构的过期是整个 hash 对象的过期,而不是其中的某个子 key。 还有一个需要特别注意的地方是如果一个字符串已经设置了过期时间,然后你调用了 set 方法修改了它,它的过期时间会消失。

127.0.0.1:6379> set name allen
OK 
127.0.0.1:6379> expire name 600 
(integer) 1 
127.0.0.1:6379> ttl name (integer) 
597 
127.0.0.1:6379> set name allen
OK 
127.0.0.1:6379> ttl name 
(integer) -1


持久化原理


RDB和AOF两种持久化机制的介绍

RDB持久化机制,对redis中的数据执行周期性的持久化

AOF机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集

如果我们想要redis仅仅作为纯内存的缓存来用,那么可以禁止RDB和AOF所有的持久化机制

通过RDB或AOF,都可以将redis内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云,云服务

如果redis挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动redis,redis就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务

如果同时使用RDB和AOF两种持久化机制,那么在redis重启的时候,会使用AOF来重新构建数据,因为AOF中的数据更加完整

RDB持久化机制的优点、缺点

RDB持久化机制的优点
(1)RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说Amazon的S3云服务上去,在国内可以是阿里云的ODPS分布式存储上,以预定好的备份策略来定期备份redis中的数据

(2)RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可

(3)相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速

RDB持久化机制的缺点

(1)如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据

(2)RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒


AOF持久化机制的优点、缺点

AOF持久化机制的优点
(1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据

(2)AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复

(3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。

(4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据。


AOF持久化机制的缺点
(1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大

(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的

(3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似AOF这种较为复杂的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有bug。不过AOF就是为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

RDB和AOF到底该如何选择

(1)不要仅仅使用RDB,因为那样会导致你丢失很多数据

(2)也不要仅仅使用AOF,因为那样有两个问题,第一,你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快; 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug

(3)综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复

持久化配置


配置RDB持久化机制

1、如何配置RDB持久化机制
redis.conf文件,也就是/etc/redis/6379.conf,去配置持久化

save 60 1000
每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照,这个操作也被称之为snapshotting,快照。也可以手动调用save或者bgsave命令,同步或异步执行rdb快照生成

save可以设置多个,就是多个snapshotting检查点,每到一个检查点,就会去check一下,是否有指定的key数量发生了变更,如果有,就生成一个新的dump.rdb文件

2、RDB持久化机制的工作流程
(1)redis根据配置自己尝试去生成rdb快照文件
(2)fork一个子进程出来
(3)子进程尝试将数据dump到临时的rdb快照文件中
(4)完成rdb快照文件的生成之后,就替换之前的旧的快照文件
dump.rdb,每次生成一个新的快照,都会覆盖之前的老快照

配置AOF持久化机制

1、AOF持久化的配置AOF持久化,默认是关闭的,默认是打开RDB持久化。appendonly yes,可以打开AOF持久化机制。
2、三种策略可以选择,
(1)一种是每次写入一条数据就执行一次fsync; 
(2)一种是每隔一秒执行一次fsync
(3)一种是不主动执行fsyncalways: 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低
3、 redis每次接收到一条写命令,就会写入日志文件中,当然是先写入os cache的,然后每隔一定时间再fsync一下而且即使AOF和RDB都开启了,redis重启的时候,也是优先通过AOF进行数据恢复的。
4、AOF破损文件的修复如果redis在append数据到AOF文件时,机器宕机了,可能会导致AOF文件破损用redis-check-aof --fix命令来修复破损的AOF文件。

AOF和RDB同时工作
(1)如果RDB在执行snapshotting操作,那么redis不会执行AOF rewrite; 如果redis再执行AOF rewrite,那么就不会执行RDB snapshotting
(2)如果RDB在执行snapshotting,此时用户执行BGREWRITEAOF命令,那么等RDB快照生成之后,才会去执行AOF rewrite
(3)同时有RDB snapshot文件和AOF日志文件,那么redis重启的时候,会优先使用AOF进行数据恢复,因为其中的日志更完整

 


redis实现分布式锁

分布式锁本质上要实现的目标就是在 Redis 里面占一个“茅坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就只好放弃或者稍后再试。 占坑一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占坑。先来先占, 用完了,再调用 del 指令释放茅坑。
>setnx lock:codehole true 
... do something critical ... 
> del lock:codehole

但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样就会陷入死锁,锁永远得不到释放。
于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也可以保证 5 秒之后锁会自动释放。

但是以上逻辑还有问题。如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现问题。
>setnx lock:codehole true
expire lock:codehole 5 ... do something critical ... 
> del lock:codehole
为了解决这个问题,Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底解决了分布式锁的乱象。。 > set lock true ex 5 nx
OK 
... do something critical ... 
> del lock

redis应用

统计在线人数
https://www.jianshu.com/p/135b8b83bf88


golang操作redis
https://www.cnblogs.com/wdliu/p/9330278.html










原文地址:https://www.cnblogs.com/mituxiaoshutong/p/9968285.html