Redis

1)Redis概述

  1. 在传统的Web应用中,广泛使用的是关系型数据库,因为那时候基本上访问量和并发量不高,而在后来,随着访问量和并发量的提升,使用关系型数据库的Web应用多多少少都开始在性能上出现了一些瓶颈,而瓶颈的源头一般是在磁盘的I/O上。
  2. 为了克服这一问题,NoSQL非关系型数据库应运而生,它同时具备了高性能、可扩展性强、高可用等优点。
  3. Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI编码、 C语言编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库。
  4. 它具备如下特性:
  • 基于内存运行,性能高效
  • 支持分布式,理论上可以无限扩展
  • key-value的形式存储数据
  • 开源的、使用ANSI编码、C语言编写、支持网络、基于内存存储数据、可持久化的Key-Value数据库,并提供多种语言的操作API

2)Redis的应用场景有哪些?

    Redis 的应用场景包括:缓存系统、计数器、消息队列系统、排行榜、实时热点系统,等等。

3)Redis的数据类型

    Redis提供的数据类型主要分为5种自有类型和一种自定义类型。

    5种自有类型包括:**String字符串类型、hash哈希类型、List链表类型、Set集合类型、ZSet顺序集合类型**。

    自定义类型是Custom Data Type,是用户自行创建的一种数据类型。

3.1,String类型:

    它是一个二进制安全的字符串,意味着它不仅能够存储字符串、还能存储图片、视频等多种类型, 最大长度支持512M。

3.2,hash哈希类型:

    该类型是由**field和关联的value**组成的map。其中,field和value都是字符串类型的。

3.3,List链表类型:

    该类型是一个插入顺序排序的字符串元素集合,,**基于双向链表实现**。

3.4,Set集合类型:

    Set类型是一种无顺序集合, 它和List类型最大的区别是:集合中的元素没有顺序, 且元素是唯一的。

3.5,ZSet顺序集合类型:

    ZSet也叫Sorted Set,是一种有序集合类型,每个元素都会**关联一个double类型的分数权值(score)**,通过这个权值来为集合中的成员进行从小到大的排序。

    ZSet与Set类型一样,其底层也是通过哈希表实现的,也是String类型元素的集合,且不允许重复的成员。

    注意,ZSet有序集合的成员虽然是唯一的,但分数权值(score)是可以重复的。

4)Redis的一些指令:

4.1,通用指令:

keys          #查看所有key

type key    #返回key存储的类型

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

del key     #删除key

move key 1    #将key移动到1数据库

expire key 3    #设置key的生命周期为3秒

pexpire key 3    #设置key的生命周期为3毫秒

ttl key     #查看key的剩余存活时间(秒)

pttl key    #查看key的剩余存活时间(毫秒)

perisist key    #把该key设置为永久有效

4.2,String字符串类型的操作

1,设置一个key:

set  key  value  [ex 秒数]  [px 毫秒数]  [nx/xx] 

    # 如果ex和px同时写,则以后面的有效期为准
    # nx:如果key不存在则建立
    # xx:如果key存在则修改其值
    # 也可以直接使用 setnx / setex 命令

set KEY_NAME VALUE    #SET命令用于设置给定key的值。如果key已经存储其他值,SET就覆写旧值,且无视类型。

示例:

set javastack 666   #设置一个key,key为"javastack",值为"666"

2,

get key        #取值

mset key1 value1 key2 value2     #一次设置多个值

mget key1 key2     #一次获取多个值

append key value     #把value追加到当前key的原值上

getset key newvalue    #获取并返回旧值,在设置新值

strlen key    #获取当前key的value值的长度

4.3,hash哈希类型的操作

    hash是一个string类型的、由**field和关联的value**组成的map;

    hash特别适用于存储对象,将一个对象存储在hash类型中会占用更少的内存,并且可以方便的存取整个对象。
hset myhash field value        #设置myhash的field为value
hsetnx myhash field value        #myhash不存在该field的情况下,设置field为value

#设置成功,返回1;如果给定字段已经存在且没有操作被执行,返回0。

举例:
redis 127.0.0.1:6379> HSETNX myhash field1 "foo"
(integer) 1
redis 127.0.0.1:6379> HSETNX myhash field1 "bar"
(integer) 0
redis 127.0.0.1:6379> HGET myhash field1
"foo"
hmset myhash field1 value1 field2 value2        #在myhash中,同时设置多个field

hget myhash field        #获取myhash中指定的field的value

hmget myhash field1 field2        #在myhash中一次获取多个field的value

hexists myhash field        #在myhash中,判断指定的field是否存在

hlen myhash        #获取myhash中的field的数量

hkeys myhash        #返回hash所有的field

hvals myhash        #返回hash所有的value

hgetall myhash        #获取某个hash中全部的field及value

hdel myhash field        #删除指定的field

4.4,List链表类型的操作

    list类型其实就是一个 **每个子元素都是一个string类型的双向链表** 的这么一个容器,每个子元素的链表的最大长度是2^32。list既可以用做栈,也可以用做队列。
lpush key value        #把值插入到链表key的头部

rpush key value        #把值插入到链表key的尾部

示例:

redis 127.0.0.1:6379> LPUSH runoobkey redis
(integer) 1
redis 127.0.0.1:6379> LPUSH runoobkey mongodb
(integer) 2
redis 127.0.0.1:6379> LPUSH runoobkey mysql
(integer) 3
redis 127.0.0.1:6379> LRANGE runoobkey 0 10

1) "mysql"
2) "mongodb"
3) "redis"
    上述我们使用了 **LPUSH** 将三个值插入了**key为 runoobkey** 的链表当中。
lpop key         #返回并删除链表key的头部元素

rpop key         #返回并删除链表key的尾部元素
  
lindex key index        #返回链表key中的index索引上的元素值

llen key        #计算链表key的长度

lrange key start stop        #返回链表key中[start, stop]中的元素

4.5,Set集合类型的操作

    Set集合的底层数据结构是通过哈希表实现的,所以添加,删除,查找的时间复杂度都是 O(1)。

    Set集合特点:无序性、唯一性。

    **添加元素:**sadd 命令将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略、不会添加。
sadd key value1        #添加一个元素,往集合key里面添加元素value1

sadd key value1 value2        #添加多个元素,往集合key里面添加元素value1和value2

smembers key        #获取集合key中的所有元素

spop key        #返回并删除集合key中的1个随机元素(可以做抽奖,不会重复抽到某人)

srandmember key        #在集合key中,随机取一个元素

scard key        #返回集合key的长度

sismember key value        #判断集合key中是否存在某个值

srem key value        #删除集合key中的指定元素

示例:

redis 127.0.0.1:6379> SADD myset "hello"
(integer) 1
redis 127.0.0.1:6379> SADD myset "foo"
(integer) 1
redis 127.0.0.1:6379> SADD myset "hello"
(integer) 0
redis 127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "foo"

4.6,ZSet顺序集合类型的操作

    ZSet集合是在Set集合的基础上,为每个元素都增加了一个顺序属性,即**一个double类型的分数权值(score)**,这一属性(分数权值)在添加、修改元素的时候可以指定,ZSet会通过这个分数权值来为集合中的成员进行从小到大的排序。

    与Set类型一样,ZSet的底层也是通过哈希表实现的。

    **添加元素**:Zadd 命令用于将一个或多个成员元素及其分数值加入到有序集当中。如果某个成员已经是有序集的成员,那么**更新这个成员的分数权值,并通过重新插入这个成员元素,来保证该成员在正确的位置上**。
zadd key score1 value1        #添加一个元素,往集合key里面添加元素value1,value1的分数权值为score1

zadd key score1 value1 score2 value2    #添加多个元素,往集合key里面添加元素value1和value2,它们的分数权值分别为score1和score2

zrange key start stop [withscores]        #把集合key排序后,返回名次[start,stop]的元素,默认是升序排列,withscores是把score也打印出来

zrank key member        #在集合key中,查询member的排名(升序,从0名开始)
  
zrevrank key member        #在集合key中,查询member的排名(降序,从0名开始)
  
zcard key        #返回集合key的长度
  
zrem key value1        #删除集合中的一个元素value1

zrem key value1 value2        #删除集合中的多个元素value1和value2

示例:

redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 1 "uno"
(integer) 1
redis> zadd myzset 2 "two" 3 "three"
(integer) 2
redis> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
redis>

5)RedisTemplate的各种操作

    Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有的Redis原生的api。

RedisTemplate中定义了对5种数据结构的操作方法:

redisTemplate.opsForValue();//操作字符串

redisTemplate.opsForHash();//操作hash

redisTemplate.opsForList();//操作list

redisTemplate.opsForSet();//操作set

redisTemplate.opsForZSet();//操作有序set

示例:

前提,注入RedisTemplate:

@Autowired
private RedisTemplate<String,String> redisTemplate;

5.1,String字符串相关操作(最常用的):

    //存入元素:
    redisTemplate.opsForValue().set("key1","value1");  
    redisTemplate.opsForValue().set("key2","value2");  
    redisTemplate.opsForValue().set("key3","value3");  
    redisTemplate.opsForValue().set("key4","value4");  

    //取出元素:
    String result1=redisTemplate.opsForValue().get("key1").toString();  
    String result2=redisTemplate.opsForValue().get("key2").toString();  
    String result3=redisTemplate.opsForValue().get("key3").toString();  

    System.out.println("缓存结果为:result:"+result1+"  "+result2+"   "+result3);

运行结果:

缓存结果为:result:value1  value2  value3

5.2,Hash哈希相关操作:

    Map<String,String> map=new HashMap<>();  
    map.put("key1","value1");  
    map.put("key2","value2");  
    map.put("key3","value3");  
    map.put("key4","value4");  
    map.put("key5","value5");  

    //存入元素:hash集合名叫做"map1"
    redisTemplate.opsForHash().putAll("map1",map);  

    //取出元素:
    //获取hash集合"map1"
    Map<String,String> resultMap= redisTemplate.opsForHash().entries("map1");  
    System.out.println("resultMap: "+resultMap);

    //获取hash集合"map1"的所有value:
    List<String> reslutMapValueList=redisTemplate.opsForHash().values("map1"); 
    System.out.println("reslutMapValueList: "+reslutMapValueList);

    //获取hash集合"map1"的所有field:
    Set<String> resultMapFieldSet=redisTemplate.opsForHash().keys("map1");  
    System.out.println("resultMapFieldSet: "+resultMapFieldSet);

    //获取hash集合"map1"中的field为"key1"的value:
    String value=(String)redisTemplate.opsForHash().get("map1","key1");  
    System.out.println("value: "+value);

运行结果为:

resultMap: {key3=value3, key2=value2, key1=value1, key5=value5, key4=value4} 

reslutMapValueList: [value1, value2, value5, value3, value4] 

resultMapFieldSet: [key1, key2, key5, key3, key4] 

value: value1

5.3,List链表相关操作:

//存入元素

    //在key为"list"的链表中,在头部存入三个元素"a"、"b"、"c"
    redisTemplate.opsForList().leftPush("list","a");  
    redisTemplate.opsForList().leftPush("list","b");  
    redisTemplate.opsForList().leftPush("list","c");   

//取出元素  xxxPop()--弹出元素

    //在key为"list"的链表中,弹出头部的那个元素:
    Object popValue = redisTemplate.opsForList().leftPop("list");  
    System.out.print("弹出头部的那个元素是: " + popValue);

运行结果:

弹出头部的那个元素是: c

5.4,Set集合相关操作:

    //存入元素:将元素"22" "33" "44"存入Set集合"set1"中;
    SetOperations<String, String> set = redisTemplate.opsForSet();
    set.add("set1","22");  
    set.add("set1","33");  
    set.add("set1","44");  

    //取出元素:获取Set集合"set1"中的所有元素  
    Set<String> resultSet =redisTemplate.opsForSet().members("set1"); 
    System.out.println("resultSet: "+resultSet);

打印结果为:

resultSet: ["33", "44", "22"]

5.5,ZSet有序集合相关操作:

//存入元素:

    //add(K key, V value, double score)
    //在key为"zSet1"的集合中,存入元素"A""B""C""D",分数分别为1 3 2 5
    redisTemplate.opsForZSet().add("zSet1","A",1);  
    redisTemplate.opsForZSet().add("zSet1","B",3);  
    redisTemplate.opsForZSet().add("zSet1","C",2);  
    redisTemplate.opsForZSet().add("zSet1","D",5);

6)Redis可持久化:

    Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制。

    redis是内存型的非关系型数据库,所以数据安全必须考虑,redis支持将数据持久化到磁盘。

    Redis 的持久化机制有两种,**一种是RDB快照,一种是 AOF 日志**。

6.1)RDB快照机制

    Redis利用操作系统的多进程机制,来支持实现RDB快照持久化。

1)原理:

    1>  在某些时刻,Redis通过fork产生一个子进程;

    2>  子进程会根据父进程的内存生成一个临时的快照文件(父进程的副本),该快照文件将有着和父进程当前时刻相同的数据。父进程负责继续处理外界请求,子进程负责生成临时的快照文件。

    3>  子进程生成临时的快照文件之后,用当前的临时文件替换掉原来的RDB文件,然后子进程退出。

2)触发机制

    RDB持久化触发机制分为:**手动触发**和**自动触发**

手动触发:

    `save命令`:会阻塞当前服务器,直到RDB完成为止,如果数据量大的话会造成长时间的阻塞,线上环境一般禁止使用该命令。

    `bgsave命令`:就是background save,执行bgsave命令时,Redis主进程会fork一个子进程来完成RDB的过程,完成后自动结束。**所以Redis主进程阻塞时间只有fork阶段的那一下**。相对于save命令,阻塞时间很短。

    PS:fork,又译作派生、分支,是计算机程序设计中的分叉函数。fork()函数会产生一个和当前进程完全一样的新进程 。

自动触发:

    **场景一:配置redis.conf**,触发配置文件中定义的触发规则之后,自动执行RDB。

    比如:
# 当在规定的时间内,Redis发生了写操作的个数满足条件,会触发发生BGSAVE命令。
# save <seconds> <changes>

# 当用户设置了多个save的选项配置,只要其中任一条满足,Redis都会触发一次BGSAVE操作
save 900 1 
save 300 10 
save 60 10000

# 以上配置的含义:900秒之内至少一次写操作、300秒之内至少发生10次写操作、60秒之内发生至少10000次写操作,只要满足任一条件,均会触发bgsave
    **场景二:执行shutdown命令关闭服务器时**,如果没有开启AOF持久化功能,那么会自动执行一次bgsave。

    **场景三:主从同步**(slave和master建立同步机制)。

3)细节

    1> RDB机制不适于实时性持久化,但其数据体量小,执行速度快,适合做数据版本控制,适合做数据的定时备份,用于灾难恢复。

    2> RDB快照机制是某个时间点的一次全量数据备份,是二进制文件。

6.2)AOF日志机制

    AOF日志机制是持续不断新增数据的备份机制,AOF日志是基于写命令存储的可读的txt文本文件。

1)原理
    1> Redis将每一次的数据写操作 执行成功之后,将该数据都再去写入到一个aof文件中,

    2> Redis重启时,只要从头到尾执行一次aof文件,即可恢复数据;

    3> 也可以将aof文件复制到别的服务器,做数据移植;

    4> 注意:在服务器重启时,如果要恢复数据,如果此时rdb文件和aof文件同时存在,以AOF为准;

2)触发机制:
    和RDB类似,AOF触发机制也分为:**手动触发**和**自动触发**

3)细节
    1> AOF日志会在运行中持续增大,因此体积较大,在Redis服务器重启时,又由于AOF文件中存储的是一条条命令,且体积庞大,所以服务器重启后需要重新执行一遍所有的命令,加载数据的时间会比较长。

    2> 由于AOF日志是txt文本文件,且体积可能比较庞大,所以需要定期对AOF日志进行重写瘦身。

    3> 目前AOF是Redis持久化的主流方式。

6.3)总结与补充:

    1.1> RDB快照机制是某个时间点的一次全量数据备份,是二进制文件。

    1.2> AOF日志是持续不断新增数据的备份机制,是基于写命令存储的可读的txt文本文件。

    2.1> Redis服务器加载RDB文件的速度比加载AOF文件要快很多,因为RDB文件中直接存储的是内存数据。

    2.2> 而AOF文件中存储的是一条条命令,服务器重启后,需要重新执行一遍所有的命令,时间比较漫长。

    3.1> RDB无法做到实时持久化,比如说如果在两次bgsave的中间的时候服务器宕机了,则会丢失该区间内的新增的数据,所以RDB机制不适用于实时性要求较高的场景。

    3.2> AOF机制数据同步的时间间隔小,数据更安全,比RDB机制更擅长做实时的持久化。

    4> 目前AOF是Redis持久化的主流方式。

    5> AOF只是追加写日志文件,速度比RDB要快,对服务器性能影响较小;

    6> AOF重演命令式的恢复数据,在服务器重启加载数据时,速度显然比RDB要慢。

6.4)解决方案:

1> 混合持久化:

    Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——**混合持久化**。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。

    这里的 AOF 日志不再是全量的日志,而是在RDB持久化开始到RDB持久化结束,这段时间内发生的增量数据由于并没有被写入RDB文件,所以将其写入AOF 日志文件,通常这部分 AOF 日志很小。

    在 Redis 重启的时候,可以先加载RDB文件的内容,然后再加载增量AOF日志。

2> 主AOF,从RDB:

    另外,可以使用下面这种方式。Master主服务器使用AOF,Slave从服务器使用RDB快照。

    master需要首先确保数据完整性,它作为数据备份的第一选择;

    slave提供只读服务或仅作为备机,它的主要目的就是快速响应客户端的读请求或容灾切换。

    至于具体使用哪种持久化方式,就看大家根据场景选择,没有最好,只有最合适。

7)Redis有哪些架构模式?

7.1)详情见:

    **单机模式**、**主从模式**(redis2.8版本之前的模式)、**哨兵模式**(redis2.8及之后的模式)、**集群模式**(redis3.0版本之后)

    详情见,[https://blog.csdn.net/qq_44750696/article/details/123565124](https://blog.csdn.net/qq_44750696/article/details/123565124)

7.2) Redis的哨兵机制.

1)介绍:

    因为Redis没有master主节点自动选举功能,所以在Redis的主从结构中,主节点挂掉后,需要人工的将一个从节点升级为主节点;

    而人工明显很费劲,所以,使用哨兵机制;

哨兵机制:

    由哨兵来监控整个Redis系统的运行;

    哨兵它的主要功能是:监控master主节点和slave从节点是否正常运行;

    当master主节点出现故障时,哨兵节点自动的将一个slave从节点 升级为 master主节点。(PS:至于选择哪个从节点,完全是随机的,因为都一样。)

2)哨兵机制的主要功能如下:

1) 集群监控,
    负责监控 master节点 和 slave从节点 是否正常工作。

2) 消息通知,
    如果某个redis节点发生故障了,那么哨兵将会发送报警,通知给管理员。

3) 故障迁移,
    也就是自动上位,如果一个master主节点挂掉了,哨兵会将失效Master主节点的其中一个Slave从节点升级为新的Master主节点。并让失效Master的其他Slave改为连接新的Master;

    当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址。

3)哨兵集群:

    为了保证哨兵的可用性,可以使用多个哨兵进行“全方位监控”。

    此时哨兵不仅会监控master和slave,同时还会互相监控;

    这种方式称为哨兵集群。

8)Redis实现分布式锁:

8.1)使用的命令介绍:

(1)SETNX
    `SETNX key val`:
  •           当且仅当key不存在时,set一个key为val的字符串,返回1;
    
  •           若key存在,则什么都不做,返回0。
    

(2)expire
    `expire key timeout`:为key设置一个超时时间,单位为second,超过这个时间key会失效。

(3)delete
    `delete key`:删除key。

    在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

8.2)条件:

    设置一个key,所有线程的key都是这同一个key;

8.3)流程:

1、线程1先执行setnx方法,申请获取锁资源,
    setnx返回的结果为1,所以线程1获取到了锁资源,开始处理请求,此时并通过expire方法设置一个过期时间;

2、key未超时的时候:
    **2.1、如果,线程1还未释放锁资源**: 其他的线程尝试获取锁资源,先执行setnx方法,由于所有线程的key都是这同一个key,所以setnx返回的结果为0,判定为获取锁资源失败。

    **2.2、如果,线程1释放了锁资源: **其他的线程们开始抢夺锁资源,抢到了锁资源的那个线程开始执行处理(并设置过期时间),其他的线程则继续等待;

3、key超时的时候:
    强制迫使线程1释放锁资源,然后其他的线程们开始抢夺锁资源...