1)Redis概述
在传统的Web应用中,广泛使用的是关系型数据库,因为那时候基本上访问量和并发量不高,而在后来,随着访问量和并发量的提升,使用关系型数据库的Web应用多多少少都开始在性能上出现了一些瓶颈,而瓶颈的源头一般是在磁盘的I/O上。
为了克服这一问题,NoSQL非关系型数据库应运而生,它同时具备了高性能、可扩展性强、高可用等优点。
Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI编码、 C语言编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库。
它具备如下特性:
-
基于内存运行,性能高效
-
支持分布式,理论上可以无限扩展
-
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版本之后)
详情见,Redis架构模式演进_Morning sunshine的博客-CSDN博客
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释放锁资源,然后其他的线程们开始抢夺锁资源...