Redis

105 阅读18分钟

一.Redis概念

1.1 Redis介绍以及使用场景

Redis是一种运行速度很快,并发性能很强的nosql(非关系型数据库)

NoSQL数据库和传统数据库相比的优势:

NoSQL数据库无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。

Redis的常用使用场景:

  • 缓存,毫无疑问这是Redis当今最为人熟知的使用场景。在提升服务器性能方面非常有效;一 些频繁被访问的数据,经常被访问的数据如果放在关系型数据库,每次查询的开销都会很大,而放在redis中,因为redis 是放在内存中的可以很高效的访问

  • 排行榜,在使用传统的关系型数据库(mysql oracle 等)来做这个事儿,非常的麻烦,而利 用Redis的SortSet(有序集合)数据结构能够简单的搞定; 计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问 数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场 景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压 力;

  • 好友关系,利用集合的一些命令,比如求交集、并集、差集等。可以方便搞定一些共同好 友、共同爱好之类的功能;

  • 简单消息队列,除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制, 比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB压力,完全 可以用List来完成异步解耦;

  • Session共享,以jsp为例,默认Session是保存在服务器的文件中,如果是集群服务,同一个 用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用Redis保存Session后,无论 用户落在那台机器上都能够获取到对应的Session信息。

1.2 Redis/Memcache/MongoDB对比

这几个都是nosql型数据库的代表

Redis和Memcache的对比:

Redis和Memcache都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。

memcache 数据结构单一(kv类型),redis 更丰富一些,还提供 list,set, hash 等数据结构的存储,有 效的减少网络 IO 的次数

虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value交换到磁盘

存储数据安全–memcache挂掉后,数据没了(没有持久化机制);redis可以定期保存到磁盘(持久化)

灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过RBD或AOF恢复

Redis和MongoDB对比

redis和mongodb并不是竞争关系,更多的是一种协作共存的关系

mongodb本质上还是硬盘数据库,在复杂查询时仍然会有大量的资源消耗,而且在处理复杂逻辑 时仍然要不可避免地进行多次查询。

这时就需要redis或Memcache这样的内存数据库来作为中间层进行缓存和加速。

比如在某些复杂页面的场景中,整个页面的内容如果都从mongodb中查询,可能要几十个查询语句,耗时很长。如果需求允许,则可以把整个页面的对象缓存至redis中,定期更新。这样 mongodb和redis就能很好地协作起来

1.3 分布式数据库CAP原理

CAP介绍

传统的关系型数据库事务具备ACID: A:原子性 C:一致性 I:独立性 D:持久性 分布式数据库的CAP:

C(Consistency):强一致性 “all nodes see the same data at the same time”,即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致,这就是分布式的一致性。一致性的问题在并发系统 中不可避免,对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问 题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。

A(Availability):高可用性 可用性指“Reads and writes always succeed”,即服务一直可用,而且要是正常的响应时间。好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问 超时等用户体验不好的情况。

P(Partition tolerance):分区容错性 即分布式系统在遇到某节点或网络分区故障时,仍然能够对外提供满足一致性或可用性的服务。 分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转 正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器 还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。

在分布式环境下,为了保证系统可用性,通常都采取了复制的方式,避免一个节点损坏,导致系统不可用。那么就出现了每个节点上的数据出现了很多个副本的情况,而数据从一个节点复制到另外的节点时需要时间和要求网络畅通的,所以,当P发生时,也就是无法向某个节点复制数据时,这时候你有两个选择: 选择可用性 A,此时,那个失去联系的节点依然可以向系统提供服务,不过它的数据就不能保证是同步的了(失去了C属性)。选择一致性C,为了保证数据库的一致性,我们必须等待失去联系的节点恢复过来,在这个过程中,那个节点是不允许对外提供服务的,这时候系统处于不可用状态(失去了A属性)。 ,某个节点负责写入数据,然后将数据同步到其它节点,其它节点提供读取的服务,当两个节点出现通信问题时,你就面临着选择A(继续提供服务,但是数据不保证准确),C(用户处于等待状态,一直等到数据同步完成)。

CAP总结:

  1. 分区是常态,不可避免,三者不可共存
  2. 一致性高,可用性低||一致性低,可用性高
  3. 因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大 类:

CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。

CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。

AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

二.Redis操作指令

启动:

cd /usr/local/bin redis-server /opt/redis-5.0.4/redis.conf

关闭:

单实例关闭:

redis-cli shutdown

多实例关闭:

redis-cli -p 6379 shutdown

检测6379端口是否在监听:

netstat -lntp | grep 6379

检测后台进程是否存在:

ps -ef|grep redis

连接redis:

redis-cli 
ping //测试是否连接成功

输出HelloWorld:

# 保存数据 
set k1 china   
# 获取数据
get kl

测试性能:

redis-benchmark

切换数据库(默认16个):

vim /opt/redis-5.0.4/redis.conf //查看数据库

127.0.0.1:6379> get k1          //查询k1
"china"
 127.0.0.1:6379> select 16             //切换到16号数据库
(error) ERR DB index is out of range    //数据库的下标超出了范围
127.0.0.1:6379> select 15               
OK
 127.0.0.1:6379[15]> get k1              
(nil)
 127.0.0.1:6379[15]> select 0            
OK
 127.0.0.1:6379> get k1                  

数据库键的数量:

dbsize

清空数据库:

flushdb //清空当前库
flushall //清空所有库

模糊查询(key):

查询所有的键
keys *
模糊查询k开头,后面随便多少个字符
keys k*
模糊查询e为最后一位,前面随便多少个字符
keys *e
双 * 模式,匹配任意多个字符:查询包含k的键
keys *k*
模糊查询k字头,并且匹配一个字符
keys k?
你只记得第一个字母是k,他的长度是3
 keys k??
记得其他字母,第二个字母可能是a或e
 keys r[ae]dis

判断某个key是否存在:exists key

127.0.0.1:6379> exists k1
 (integer) 1   # 存在
127.0.0.1:6379> exists y1
 (integer) 0   # 不存在 

移动(剪切,粘贴)键到库:move key db

127.0.0.1:6379> move x1 8   # 将x1移动到8号库
(integer) 1   # 移动成功    
127.0.0.1:6379> exists x1   # 查看当前库中是否存在x1
 (integer) 0   # 不存在(因为已经移走了)
127.0.0.1:6379> select 8    
# 切换8号库
OK
 127.0.0.1:6379[8]> keys *   # 查看当前库中的所有键
1) "x1"

查看键还有多久过期:ttl key

127.0.0.1:6379[8]> ttl x1  
(integer) -1   # 永不过期

为键设置过期时间:expire key

127.0.0.1:6379[8]> set k1 v1        
OK
 127.0.0.1:6379[8]> ttl k1           
(integer) -1    
# 永不过期
127.0.0.1:6379[8]> expire k1 10     
(integer) 1     
# 设置成功
127.0.0.1:6379[8]> get k1           
"v1"
 127.0.0.1:6379[8]> ttl k1           
(integer) 2     
# 还有2秒过期
127.0.0.1:6379[8]> get k1  
(nil)
 127.0.0.1:6379[8]> keys *           
# 保存k1
 # 查看k1的过期时间
# 获取k1
 # 设置k1的过期时间为10秒(10秒后自动销毁)
# 查看k1的过期时间
# 从内存中销毁了
(empty list or set

查看键的数据类型:type key

127.0.0.1:6379[8]> type k1
 string          # k1的数据类型是会string字符串

三.五大数据类型

字符串String

set/get/del/append/strlen

image.png

image.png incr/decr/incrby/decrby:加减操作,操作的必须是数字类型

image.png getrange/setrange:查询范围是多少多少下的值,类似between...and...

image.png setex:添加数据的同时设置生命周期

image.png setnx:添加数据的时候判断是否已经存在,防止已存在的数据被覆盖掉

image.png mset:一次添加多条数据 mget:一次获取多条数据 msetnx:添加多条数据是判断这些数据是否存在 image.png

getset:先get后set 先取出后设置

列表List

使用push和pop,类似机枪AK47:push,压子弹,pop,射击出子弹

lpush/rpush/lrange

l:left 自左向右→添加 (从上往下添加)

r:right 自右向左←添加(从下往上添加)

image.png

lpop/rpop:移除第一个元素(上左下右)

image.png lindex:根据下标查询元素(从左向右,自上而下)

image.png llen:返回集合长度

image.png

lrem:删除n个valu

image.png ltrim:截取指定范围的值,除了指定范围内的 剩余的全扔掉

image.png

image.png rpoplpush:从一个集合搞一个元素到另一个集合中(右出一个,左进一个)

image.png

lset:改变某个下标的某个值

image.png

image.png insert:插入元素(指定某个元素之前/之后)

image.png

性能总结:类似添加火车皮一样,头尾操作效率高,中间操作效率惨;

集合set

和java中的set特点类似,不允许重复

sadd/smembers/sismember:添加/查看/判断是否存在

image.png 注意:1和0可不是下标,而是布尔。1:true存在,2:false不存在

scard:获得集合中的元素个数

image.png srem(srem key value):删除集合中的元素

image.png srandmember(srandmember 整数):从集合中随机获取几个元素

image.png spop:随机出栈(移除)

image.png

smove:移动元素:将key1某个值赋值给key2

image.png

数学集合类 交集:sinter 并集:sunion 差集:sdiff

image.png

image.png

哈希hash

类似java里面的Map KV模式不变,但V是一个键值对

hset/hget/hmset/hmget/hgetall/hdel:添加/得到/多添加/多得到/得到全部/删除属性

image.png

hlen:返回元素的属性个数

image.png

hexists:判断元素是否存在某个属性

image.png

hkeys/hvals:获得属性的所有key/获得属性的所有value

image.png

hincrby/hincrbyfloat:自增(整数)/自增(小数)

image.png

hsetnx:添加的时候,先判断是否存在

image.png

有序集合zset

真实需求:

充10元可享vip1;

充20元可享vip2;

充30元可享vip3;

以此类推...

zadd/zrange (withscores):添加/查询

image.png

zrangebyscore:

模糊查询 ( : 不包含 limit:跳过几个截取几个

image.png

zrem:删除元素

image.png zcard/zcount/zrank/zscore:集合长度/范围内元素个数/得元素下标/通过值得分数

image.png

zrevrank:逆序找下标(从下向上)

image.png

zrevrange:逆序查询

image.png

zrevrangebyscore:逆序范围查找

image.png

四.持久化(重点)

RDB

Redis DataBase 在指定的时间间隔内,将内存中的数据集的快照写入磁盘; 默认保存在/usr/local/bin中,文件名dump.rdb;

自动备份

redis是内存数据库,当我们每次用完redis,关闭linux时,按道理来说,内存释放,redis中的数据也会随之消失 为什么我们再次启动redis的时候,昨天的数据还在,并没有消失呢? 正是因为,每次关机时,redis会自动将数据备份到一个文件中 :/usr/local/bin/dump.rdb 接下来我们就来全方位的认识自动备份机制

  1. 默认的自动备份策略不利于我们测试,所以修改redis.conf文件中的自动备份策略

image.png

当然如果你只是用Redis的缓存功能,不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。可以直接一个空字符串来实现停用:save ""

  1. 使用shutdown模拟关机 ,关机之前和关机之后,对比dump.rdb文件的更新时间

注意:当我们使用shutdown命令,redis会自动将数据库备份,所以,dump.rdb文件创建时间更新了

  1. 开机启动redis,我们要在120秒内保存10条数据,再查看dump.rdb文件的更新时间(开两个终端 窗口,方便查看)

  2. 120秒内保存10条数据这一动作触发了备份指令,目前,dump.rdb文件中保存了10条数据,将 dump.rdb拷贝一份dump10.rdb,此时两个文件中都保存10条数据

  3. 既然有数据已经备份了,那我们就肆无忌惮的将数据全部删除flushall,再次shutdown关机

  4. 再次启动redis,发现数据真的消失了,并没有按照我们所想的 将dump.rdb文件中的内容恢复到 redis中。为什么?

  5. 将dump.rdb文件删除,将dump10.rdb重命名为dump.rdb

  6. 启动redis服务,登录redis,数据10条,全部恢复!

手动备份

之前自动备份,必须更改好多数据,例如上边,我们改变了十多条数据,才会自动备份;

现在,我只保存一条数据,就想立刻备份,应该怎么做?

每次操作完成,执行命令 save 就会立刻备份

优势and劣势

优:适合大规模数据恢复,对数据完整性和一致行要求不高;

劣:一定间隔备份一次,意外down掉,就失去最后一次快照的所有修改

AOF

Append Only File

以日志的形式记录每个写操作;

将redis执行过的写指令全部记录下来(读操作不记录);

只许追加文件,不可以改写文件;

redis在启动之初会读取该文件从头到尾执行一遍,这样来重新构建数据;

开启AOF

  1. 为了避免失误,最好将redis.conf总配置文件备份一下,然后再修改内容如下:

image.png 2. 重新启动redis,以新配置文件启动

image.png

  1. 连接redis,加数据,删库,退出

  2. 查看当前文件夹多一个aof文件,看看文件中的内容,保存的都是 写 操作

文件中最后一句要删除,否则数据恢复不了

编辑这个文件,最后要 :wq! 强制执行

  1. 只需要重新连接,数据恢复成功

RDB和AOF的关系

共存,且AOF优先

因为AOF比RDB数据保存的完整性更高

修复AOF文件,杀光不符合redis语法规范的代码

image.png

总结

RDB:只用作后备用途,建议15分钟备份一次就好

AOF:

在最恶劣的情况下,也只丢失不超过2秒的数据,数据完整性比较高,但代价太大,会带来持续的IO

对硬盘的大小要求也高,默认64mb太小了,企业级最少都是5G以上;

五.事务

事务指可以一次执行多个命令,是一个命令组,一个事务中,所有命令都会序列化(排队),不会被插队; 一个队列中,一次性,顺序性,排他性的执行一系列命令

事务三特性

隔离性:所有命令都会按照顺序执行,事务在执行的过程中,不会被其他客户端送来的命令 打断

没有隔离级别:队列中的命令没有提交之前都不会被实际的执行,不存在“事务中查询要看到 事务里的更新,事务外查询不能看到”这个头疼的问题

不保证原子性:冤有头债有主,如果一个命令失败,但是别的命令可能会执行成功,没有回滚

如何开启事务:

开启:multi

入队:queued

执行:Lexec

事务特点介绍:

一起生

开启事务,加入队列,一起执行,并成功

image.png

一起死

放弃之前的操作,恢复到原来的值

image.png

一粒老鼠屎坏一锅汤

一句报错,全部取消,恢复到原来的值

image.png

冤有头债有主

追究责任,谁的错,找谁去

image.png

watch监控

测试:模拟收入与支出

正常情况下

image.png

特殊情况下

image.png

unwatch:取消watch命令对所有key的操作 一旦执行了exec命令,那么之前加的所有监控自动失效!

六.订阅发布

进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。例如:微信订阅号

订阅一个或多个频道

image.png

image.png

七.主从复制

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

主从复制是为了达成高可用

为了避免单点Redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据是同步的。

主从复制特性:

配从不配主:只能由从节点选择主节点 主节点无法选择从节点

读写分离:主节点只负责写 从节点只负责读 从节点无法写入数据

血脉相传(用继承中的传递性减轻服务器负担)

141为主节点 142和143为从节点

image.png

主节点死亡

当主节点1因为某些原因挂掉 如果选出新的主节点2后,主节点1再次回归,则主节点1成为"光杆司令"即没有任何子节点

复制原理

image.png

全量复制:Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份slave接收到数据 文件后,存盘,并加载到内存中;(步骤1234)

增量复制:Slave初始化后,开始正常工作时主服务器发生的写操作同步到从服务器的过程;(步骤56)

Redis主从同步策略:主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。

当然,如果有需要,slave 在任何时候都可以发起全量同步

redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步

哨兵模式(****)

哨兵模式:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务 器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级 为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求.

简单来说,哨兵模式就相当于选几个次节点或者一个次节点让它们监视主节点和所有的子节点,当主节点挂掉后,自动从其他子节点中选一个升级为主节点,然后新选的主节点代替之前主节点的任务.

指令:sentinel monitor 被监控主机名(自定义) ip port 票数

image.png

启动服务的顺序:主Redis --> 从Redis --> Sentinel1/2/3

image.png

将1号老大挂掉,后台自动发起激烈的投票,选出新的老大

image.png

如果之前的主节点再次回归,刚回来的时候会显示它还是master,但是过了几秒就会被哨兵检测到,自动改为slave 成为次节点.