一、入门
1. 什么是NoSql
NoSQL一词最早出现于1998年,是Carlo Strozzi开发的一个轻量、开源、不提供SQL功能的关系数据库。2009年,Last.fm的Johan Oskarsson发起了一次关于分布式开源数据库的讨论,来自Rackspace的Eric Evans再次提出了NoSQL的概念,这时的NoSQL主要指非关系型、分布式、不提供ACID的数据库设计模式。它不同于传统的关系数据库,两者存在许多显著的不同点,其中最重要的是NoSQL不使用SQL作为查询语言。其数据存储可以不需要固定的表格模式。
2. Redis简介
Redis是使用c语言开发的一个高性能键值数据库。常用于分布式系统中的缓存、电商秒杀、排行榜、访问量统计、分布式会话共享等高并发应用场景。Redis可以通过一些键值类型来存储数据。其数据类型包括字符类型、散列类型、列表类型、集合类型、有序集合类型。
3. 安装Redis
访问Redis官网https://redis.io/download下载最新的版本 。
解压并编译安装
$ tar xzf redis-5.0.3.tar.gz
$ cd redis-5.0.3
$ make install
Redis官网并没有提供windows版本,但可以前往https://github.com/tporadowski/redis/releases下载windows的个人编译版本(注意:并不是最新的版本)。
4. 启动服务
4.1 前端启动
在redis的src目录有一个redis-server文件,用于启动一个redis服务。
redis的默认端口为6379,当客户端需要连接到redis服务时,就通过服务端的IP地址以及这个端口进行连接。也可以修改这个默认端口。在redis的根目录下有一个redis.conf文件,它是redis的核心配置文件,redis的所有配置信息都在此文件中。如果需要修改端口,我们在配置文件中找到port配置,并将6379改为其他的端口号。
修改完后需要重新启动redis服务,需要注意的是,在使用redis-server启动服务时需要指定redis.conf文件的绝对路径,否则redis将以默认的配置启动一个服务实例。
前端启动的模式我们可以在终端看到redis的启动信息和相关的操作日志,但此时如果关闭了终端或者使用control+c将会立即停止redis服务。
4.2 后端启动
所谓后端启动,就是以一个独立的进程来运行一个redis服务。首先修改redis.conf文件,找到daemonize选项并设置为yes,如下图:
保存退出后重新启动redis服务,此时redis将以后台进程的方式启动服务。终端没有显示相关的启动信息,并且启动完成后,终端可以继续执行其他的操作。
5. 客户端连接
5.1 Redis客户端
在redis的src目录下有一个redis-cli命令,这个就是官方提供的redis客户端,可以使用它来连接和操作redis。当然,这仅仅只是一个命令行的客户端程序,在实际的开发中会有不同的平台语言,因此官网也提供了对各种语言的客户端实现,在实际的项目开发中使用不同语言的客户端来操作redis。例如官网提供了一个Java的客户端Jedis。
1)使用redis-cli
可以使用使用官方自带的redis-cli客户端来连接redis服务。参数-h为连接redis服务器的IP地址,-p为redis的端口号。连接完成后就可以对redis进行操作了,我们使用简单的set和get命令来进行存储和访问操作。
2)退出客户端
如果想要退出客户端的连接只需要在连接的状态下输入quit或者exit即可。
3)身份认证
默认连接Redis时是不需要认证密码的,我们可以为其设置一个连接的认证密码。首先在redis.conf中找到requirepass配置项,取消注释并设置一个密码。
保存后重启服务,在连接客户端时加上-a参数并输入配置的密码。
连接时也可以不指定密码也可以正常连接,但在操作Redis时候会提示一个错误,要求输入认证密码。这时使用auth命令来输入密码即可。
如果设置了认证密码,在关闭客户端时也同样需要指定。
5.2 可视化客户端
也可以使用第三方的redis的可视化客户端RDM(redis-desktop-manager),它同时提供了各种系统平台的编译版本,安装后即可使用。下载地址:
点击左上角的Connect to Redis Server,在弹出的窗口中填写相关的Name(连接名称)、Address(连接地址)、端口号以及认证密码(Auth),点击OK即可。
这里我们看到连接redis后默认有16个库(0 ~ 15),这是redis默认的配置,可以在redis.conf中可以找到相应的选项并修改默认数量。
当我们使用客户端连接redis时,默认选择的是index为0的数据库,然而也可以使用select命令选择其他数据库。例如选择index为15的数据库,如下操作:
6. 停止服务
如果使用前端启动redis,可以使用control+c或者kill命令来杀掉进程的方式关闭redis(注意:control+c并不能停止后端启动的redis),但这些方式都是强制性的关闭redis,由于redis保存的数据先会存储在内存,如果此时强制关闭,将导致redis还没将数据持久化到文件中就退出,可能会照成部分的数据丢失。因此,应该使用正常的退出方式来停止redis服务,正常退出redis同样使用redis-cli客户端。
上面的命令表示关闭本机端口为6379的redis服务。
二、基础
1. 数据类型及常用API
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及sorted set(zset:有序集合)。
1.1 string(字符串)
String 是 redis 最基本的类型,一个 key 对应一个 value。它是二进制安全的,可以包含任何数据,如jpg图片或者序列化的对象。
1)SET
语法:set key value
赋值操作。
2)GET
语法:get key
取值操作。
3)GETSET
语法:getset key value
取值并赋值。
4)MSET
语法:mset key value [key value ...]
同时设置多个键值。
5)MGET
语法:mget key [key ...]
获取多个键值。
6)DEL
语法:del key [key ...]
删除一个或多个键值对。
7)INCR
语法:incr key
当存储的字符串是整数时,让当前键值递增,并返回递增或增加后的值。
8)INCRBY
语法:incrby key increment*
当存储的字符串是整数时,让当前键值增加指定的数值,并返回递增或增加后的值。
9)DECR
语法:decr key
让当前键值递减,并返回递减或减少后的值。
10)DECRBY
语法:decrby key decrement
让当前键值减少指定的数值,并返回递减或减少后的值。
11)APPEND
语法:append key value
向键值的末尾追加value。如果键不存在则将该键的值设置为value,即相当于 SET key value。返回值是追加后字符串的总长度。
12)获取字符串长度(STRLEN)
STRLEN命令返回键值的长度,如果键不存在则返回0。
1.2 hash(哈希)
hash是一个string类型的field和value的映射表,而field只能是String类型,hash特别适合用于存储对象。
1)HSET
语法:HSET key field value
HSET一次只能设置一个字段值。HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0。
2)HMSET
语法:HMSET key field value [field value ...]
HMSET和HSET作用一样,只不过一次可以设置多个字段值。
3)HSETNX
语法:HSETNX key field value
当字段不存在时赋值,类似HSET。区别在于如果字段存在,该命令不执行任何操作。
例如:hsetnx user name zing
说明:如果user中不存在name字段则设置name的值为zing,否则不做任何操作。
4)HGET
语法:HGET key field
HGET一次只能获取一个字段值。
5)HMGET
语法:HMGET key field [field ...]
HMGET一次可以获取多个字段值。
6)HGETALL
语法:HGETALL key
获取所有字段值。
7)HDEL
语法:HDEL key field [field...]
可以删除一个或多个字段,返回值是被删除的字段个数。
8)HINCRBY
语法:HINCRBY key field increment
为某个字段增加数值。
9)HEXISTS
语法:HEXISTS key field
判断字段是否存在,存在则返回1,否则返回0。
10)HKEYS
语法:HKEYS key
获取所有的字段名。
11)HVALS
语法:HVALS key
获取所有字段的值。
12)HLEN
语法:HLEN key
获取字段数量。
1.3 list(列表)
Redis的list是采用来链表来存储的,所以对于Redis的list数据类型的操作,是操作list的两端数据来操作的。
1)LPUSH
语法:LPUSH key value [value ...]
向列表左边添加元素。
2)RPUSH
语法:RPUSH key value [value ...]
向列表右边添加元素。
3)LRANGE
语法:LRANGE key start stop
LRANGE命令是列表类型最常用的命令之一,用于获取列表中的某一片段,将返回start到stop之间的所有元素(包含两端的元素),索引从0开始。索引可以是负数,如:-1代表最后边的一个元素。
4)LPOP
语法:LPOP key
LPOP命令从列表左边弹出一个元素,会分两步完成:第一步是将列表左边的元素从列表中移除。第二步是返回被移除的元素值。
5)RPOP
语法:RPOP key
RPOP命令从列表右边弹出一个元素,步骤与LPOP类似,第一步是将列表右边的元素从列表中移除。第二步是返回被移除的元素值。
6)LLEN
语法:LLEN key
获取列表中元素的个数
7)LREM
语法:LREM key count value
LREM命令会删除列表中前count个值为value的元素,返回实际删除的元素个数。根据count值的不同,该命令的执行方式会有所不同:
当count>0时, LREM会从列表左边开始删除。
当count<0时, LREM会从列表右边开始删除。
当count=0时,LREM删除所有值为value的元素。
8)LINDEX
语法:LINDEX key index
获得指定索引的元素值。
9)LSET
语法:LSET key index value
设置指定索引的元素值。
10)LTRIM
语法:LTRIM key start stop
只保留列表的指定片段
11)LINSERT
语法:LINSERT key BEFORE|AFTER pivot value
LINSERT首先会在列表中从左到右查找值为pivot的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面。
12)RPOPLPUSH
语法:RPOPLPUSH source destination
将一个列表的最后一个元素转移到另一个列表的最前面
1.4 set(集合)
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
1)SADD
语法:SADD key member [member ...]
增加一个或多个元素。
2)SREM
语法:SREM key member [member ...]
移除一个或多个元素。
3)SMEMBERS
语法:SMEMBERS key
获得集合中的所有元素。
4)SISMEMBER
语法:SISMEMBER key member
判断元素是否存在集合中。存在返回1,否则返回0。
5)SDIFF
语法:SDIFF key [key ...]
查找属于集合A并且不属于集合B的元素。(差集运算)
6)SINTER
语法:SINTER key [key ...]
查找属于集合A且属于集合B的元素。(交集运算)
7)SUNION
语法:SUNION key [key ...]
查找属于集合A或者属于集合B的元素。(合并运算)
8)SCARD
语法:SCARD key
获取集合中元素的个数。
9)SPOP
语法:SPOP key [count]
从集合中弹出一个或多个元素,由count指定。如果不指定count,默认弹出一个。由于集合是无序的,所有SPOP命令会从集合中随机选择一个元素弹出。
1.5 zset(有序集合)
zset又称sorted set,称之为有序集合,可排序的,但是唯一。和set的不同支出在于zet会给集合中的元素添加一个分数,然后通过这个分数进行排序。
1)ZADD
语法:ZADD key score member [score member ...]
向有序集合中加入一个或多个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。返回值是新加入到集合中的元素个数,不包含之前已经存在的元素。
2)ZSCORE
语法:ZSCORE key member
获取元素的分数。
3)ZREM
语法:ZREM key member [member ...]
移除有序集合中的一个或多个成员,不存在的成员将被忽略。
4)ZRANGE
语法:ZRANGE key start stop [WITHSCORES]
按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)。如果需要获得元素的分数可以在命令尾部加上WITHSCORES参数。
5)ZREVRANGE
语法:ZREVRANGE key start stop [WITHSCORES]
按照元素分数从大到小的顺序返回索引从start到stop之间的所有元素(包含两端的元素)。如果需要获得元素的分数的可以在命令尾部加上WITHSCORES参数。
6)ZRANK
语法:ZRANK key member
获取元素排名(从小到大)。
7)ZREVRANK
语法:ZREVRANK key member
获取元素排名(从大到小)。
8)ZRANGEBYSCORE
语法:ZRANGEBYSCORE key min max WITHSCORES
获得指定分数范围的元素。
9)ZINCRBY
语法:ZINCRBY key increment member
增加某个元素的分数,并返回更改后的分数。
10)ZCARD
语法:ZCARD key
获取集合元素的数量。
11)ZCOUNT
语法:ZCOUNT key min max
获取指定分数范围内的元素个数。
12)ZREMRANGEBYRANK
语法:ZREMRANGEBYRANK key start stop
按照排名范围删除元素。
13)ZREMRANGEBYSCORE
语法:ZREMRANGEBYSCORE key min max
按照分数范围删除元素。
2. Redis键(Keys)
Redis键是二进制安全的,这意味着你可以使用任何二进制序列作为键,从像”foo” 这样的字符串到一个 JPEG文件的内容。空字符串也是合法的键。
2.1 键的一些设计规则
-
不要使用太长的键。例如,不要使用一个1024字节的键,不仅是因为占用内存,而且在数据集中查找key时需要多次耗时的key比较。
-
不要使用太短的key。例如,user:1001比u1001更具有实际意义,相对于key本身以及value对象来说,增加的空间微乎其微。当然,短的键会消耗少的内存,需要找到平衡点。
-
规范一种模式 (schema)。用冒号或者下横线来连接多单词字段,例如:”user:1000”或者"user_1000"。
2.2 Key的常用API
1)KEYS
语法:keys pattern
返回指定pattern的所有key
2)EXISTS
语法:exists key
判断一个key是否存在。存在返回后1,否则返回0。
3)RENAME
语法:rename key newkey
重命名key
4)TYPE
语法:type key
根据key返回value的类型。
5)EXPIRE
语法:expire key seconds
设置key的生存时间。Redis的数据是缓存在内存中的,然后很多时候数据一般都会设置一个过期时间(即到期后销毁数据,从而释放更多的内存)。过期时间默认以秒为单位,默认值为-1,表示永不过期。
也可以在设值的时候指定过期时间(秒)
6)TTL
语法:ttl key
查看key剩余的过期时间。
7)PERSIST
语法:persist key
清除key的过期时间。
8)PEXPIRE
语法:pexpire key
以毫秒为单位设置key的过期时间。
也可以在设值的时候指定过期的时间(毫秒)
3. 持久化
3.1 简介
Redis是一个支持持久化的内存数据库,可以将内存中的数据同步到磁盘保证持久化。我们知道Redis会将数据缓存在内存中,如果没有持久化,在服务器关闭或重启之后数据会丢失。为了保证数据的安全以及效率,Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。而Redis提供了RDB和AOF两种持久化策略。
3.2 RDB
Redis默认是会以快照RDB的形式将数据持久化到磁盘的一个dump.rdb二进 制文件。当Redis决定要持久化时,会 fork 一个子进程将数据写到磁盘上一个临时的RDB文件中,当子进程完成写操作后,将原来的RDB替换掉。而Redis会在满足某些条件后会进行持久化,并且可以对其进行配置。
配置RDB
在redis.conf文件中找到“Save the DB on disk”的配置,我们可以根据需要来修改这Redis的RDB持久化策略。
说明:
save 900 1(如果在900秒之内有1次操作,则执行快照保存)
save 300 10(如果在300秒内有10次操作,则执行快照保存)
save 60 10000(如果在1分钟之内有10000个次操作,则执行快照保存)
SAVE和BGSAVE
我们可以在客户端直接使用SAVE或者BGSAVE命令立即将Redis的数据持久化到RDB文件中。他们两者的区别在于BGSAVE命令会fork一个子进程在后台进行持久化,主进程可以继续处理客户端发送的命令(非阻塞)。而SAVE命令需要等待Redis持久化完成后才可以继续处理客户端发送的命令(阻塞)。
RDB优点
RDB非常适合用于数据备份, 可以在当天内每小时备份一次,或者每个月的每天都进行备份。 如果遇到断电或者宕机等其他一些灾难情况,可以随时将数据集还原。
RDB缺点
如果对数据的完整性和安全性要求非常高,要求每一次的操作数据都能持久化到文件中,这时RDB就不太适合了。因为RDB是按照时间范围的操作次数为条件促发持久化,如果未满足这些触发条件,Redis并不会将数据保存到文件,导致数据丢失。例如:save 60 10000,如果在1分钟之内有9000次的操作,如果此时服务器异常退出或宕机,由于未满足条件,将导致丢失这1分钟的数据。
3.3 AOF
AOF持久化可以记录每个写操作,将Redis执行过的所有写指令(读操作不记录)保存到appendonly.aof文件中,并且只允许追加文件而不可以改写文件。在Redis启动的时候会读取该文件重新构建缓存数据。在打开AOF持久化机制之后,Redis每当接收到一条写命令,会先写入系统缓存,然后每隔一定时间(默认是每秒钟)fsync一次(写入到指定文件)。
启用AOF
AOF持久化默认是关闭的,如果要启用AOF,需要在redis.conf配置文件中启用该功能,将appendonly no设置为appendonly yes。
所有写操作默认保存在appendonly.aof文件中,可以自行修改保存的路径和文件名。
同步策略
AOF提供了三种同步策略:
-
always(每次写操作就执行一次fsync)
-
everysec(每秒执行一次fsync,默认)
-
no(不执行fsync)
AOF重写
AOF会记录Redis所有的写操作命令,但这种方式会造成一个问题,就是随着时间的推移,大量频繁的操作将导致AOF文件体积的急剧增长,对系统会造成影响。为了解决以上的问题, Redis就需要对AOF文件进行重写。重写的过程会创建一个新的AOF文件来代替原有的AOF文件, 而新AOF文件和原有AOF 文件保存的数据状态是一致的,但新文件的体积将变得尽可能地小。以下两种方式会触发AOF重写。
1)手动出发
在客户端直接向Redis发送BGREWRITEAOF命令,这个命令会通过移除AOF文件中的冗余命令来重写(rewrite)AOF文件。
2)自动触发
其实在启用了AOF之后(appendonly yes),Redis会依据redis.conf配置文件中的auto-aof-rewrite-percentage选项和auto-aof-rewrite-min-size选项来自动执行BGREWRITEAOF命令。
说明:
例如设置了auto-aof-rewrite-percentage为100和auto-aof-rewrite-min-size为64mb,那么当AOF文件的体积大于64MB时,并且AOF文件的体积比上一次重写之后的体积大一倍(100%)的,Redis将执行BGREWRITEAOF命令进行重写。
AOF优点
AOF弥补了RDB按照时间范围的操作次数为条件的缺点,即使在默认的策略中发生故障,最多也只会丢失一秒钟的数据,更大程度的保证了数据的安全性。
AOF缺点
AOF会保存每一次的写操作,这将导致AOF文件的体积通常要大于RDB文件。如果选用always策略,则表示每一次操作都会记录到AOF文件中,从性能的角度上来说会低于RDB。当然,使用默认的everysec策略进行持久化性能还是非常可观的。
3.4 混合持久化
在实际应用中,通常会同时使用RDB和AOF两种持久化来找到一个最佳的平衡点,即能保证性能的同时最大程度保证数据的安全。因此需要RDB和AOF两者同时进行合理的设置和调整。而从Redis 4.0开始,官方提供了一种更加方便的混合持久化配置。
未启用混合持久化
在未启用混合持久化之前,如果我们往Redis写入一条记时,RDB文件会保存操作的键值数据,AOF文件则保存的是写操作的指令,我们可以分别查看一下这两个文件的内容。
使用cat命令查看RDB文件
然而显示的内容并不太直观也不易理解,因此可以借助Redis提供的redis-check-rdb工具进行查看。
RDB文件中会保存Redis的相关信息以及存储的keys数量和相关的活期时间。接下来我们继续查看AOF文件的内容,直接使用cat命名进行查看。
结果显示AOF文件中保存的是相关的操作指令。
启用混合持久化
要使用混合持久化,除了在redis.conf文件中启用AOF(将appendonly设置为yes),还需要将aof-use-rdb-preamble设置为yes。
设置完重新启用Redis服务。启用了混合持久化之后,使用BGREWRITEAOF命令执行一次AOF重写,同时向Redis插入一条新的数据。
然后再次使用cat命令再次查看AOF文件,这时会发现启用混合持久化之后的AOF文件内容和未启用时的AOF文件内容不一样。这是因为此时产生的AOF文件是一个RDB-AOF的混合文件,Redis会基于某种协议将此文件的前半部分存储RDB的数据,后半部分存储的是AOF的操作命令。
4. 事务
Redis事务可以一次执行多个命令(批量命令操作),并且是一个单独的隔离操作。事务中的所有命令都会按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
4.1 事务操作
Redis事务主要由MULTI 、 EXEC、DISCARD、WATCH和UNWATCH这些基础命令构成。
1)MULTI
语法:MULTI
用于标记事务的开始,后续客户端执行的命令都将被存入一个命令队列,直到执行EXEC时,这些命令才会被执行。
2)EXEC
语法:EXEC
执行命令队列中的所有命令,但如果在启用一个事务之前执行了WATCH命令,那么只有当WATCH所监控的keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,并返回所有命令的执行结果,否则EXEC将放弃当前事务中的所有命令。
3)DISCARD
语法:DISCARD
取消事务队列中的所有命令,并将当前连接的状态恢复为非事务状态。如果WATCH命令被使用,会自动执行UNWATCH取消监视的所有keys。
4)WATCH
语法:WATCH key[key...]
WATCH命令类似于关系型数据库的乐观锁,可以在启用事务之前监视某些keys的变化。在MULTI命令执行之前,可以指定需要监视的keys,在执行EXEC之前,如果被监控的keys发生修改,EXEC将放弃执行该队列中的所有指令。并且WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。
首先打开一个客户端,并使用watch命令监视user:1001的key,接着使用multi启用事务。
然后打开第二个客户端,并修改key为user:1001的value为user01。
最后回到第一个客户端再次对key为user:1001的value修改为user001,并执行exec命令。由于user:1001这个key被第一个客户端所监视,而这个key在启用事务前被第二个客户端修改了,因此当第一个客户端启用事务后再对其进行修改时这是无效的,Redis将放弃队列中的所有指令,返回了(nil)。
5)UNWATCH
语法:WATCH key[key...]
取消当前事务中指定监控的keys。如果执行了EXEC或DISCARD命令,则无需再手工执行该命令了,因为在此之后UNWATCH命令会自动执行,事务中所有的keys都将自动取消监控。
4.2 原子性
在关系型数据库中的原子性代表一系列不可分割的操作,要么全部执行成功,要么全部不执行。如果执行过程中产生了错误或者异常,那么事务将会自动回滚。而在Redis的事务中是否具备原子性呢?我们看看以下两种情况,并得出相关的结论。
错误指令
在使用multi命令开启事务之后,然后输入一些命令,其中包含一个错误的命令。
从结果来看似乎有点符合我们对事务的理解。但仔细想想,这只是在输入命令的时候产生语法的错误,Redis对其进行了校验,报错之后Redis就放弃了这个事务。因此得出的结论是:Redis在启用事务输入操作命令时是原子操作,它会对命任何一个命令进行语法检查,当输入有误时,Redis会清空队列并放弃事务。
运行时错误
如果输入的命令都正确,而在执行这些命令时产生了错误,Redis是否会取消所有命令并放弃事务呢?看下面的例子。
当执行到第二条命令时产生了错误(用户名不是一个整型数值,并不能自增),但是前面和后面的命令都执行成功。并不会因为执行了一个错误的命令而回退所有已经执行成功的命令并放弃整个事务。因此得出的结论是:Redis在执行命令队列时并不是原子性的,通俗点说就是Redis本身并不支持事务的回滚机制。