- 一、Redis概述
- 二、Redis的安装
- 三、基本指令
- 四、Redis中的持久化机制
- 五、Redis集群的搭建
- 六、分布式锁
- 七、Redis整合SpringBoot
- 八、Redis做缓存![在这里插入图片描述](https://img-blog.csdnimg.cn/25e365ba511c451395e5c19052b246a9.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDgwOTMzNw==,size_16,color_FFFFFF,t_70)
一、Redis概述
1.1 、NoSQL(非关系型数据库)分类
1.1.1 键值(Key-Value)
- 这一类数据库主要会使用到一个哈希表.
- 这个表中有一个特定的键和一个指针指向特定的数据。
- Key/value模型 优势在于简单、易部署。
- 但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。
1.1.2 列存储数据库
这部分数据库通常是用来应对分布式存储的海量数据。
键仍然存在,但是它们的特点是指 向了多个列。这些列是由列家族来安排的。
如:Cassandra, HBase, Riak.
1.1.3 文档型数据库
- 文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似 BSON====>JSON {“id”:”21”,name:”xa”,} {“id”:”22”,name:”xa”,age:23} 事务支持不友好
- collection document
该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可 以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。
如:CouchDB, MongoDb. 国内也有文档型数据库SequoiaDB,已经开源。
1.1.4 图形(Graph)数据库 图片 音频 视频 文件服务器
- 图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。
- NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。许多NoSQL数据库都有REST式的数据接口或者查询API。
如:Neo4J, InfoGrid, Infinite Graph.
1.2 Redis 简介
Redis 开源,
基于内存键值存储=>拥有持久化机制(磁盘)。
用于(1)数据库缓存 (2)消息中间件
特点:
Redis 高性能key/value内存型数据库
Redis 支持丰富的数据类型如(String,list,set,zset,hash)
Redis 支持持久化
Redis 单线程,单进程 效率高 (不支持并发操作)
Redis与memcahed对比:
共同点:
底层都是使用C语言编写,都是基于key-value 内存存储
不同点:
Memcahed支持的数据类型,比较简单(String,Object)
Redis支持的数据类型比较丰富(String,List,set,zset,hash)
Memcahed默认一个值的最大存储不能超过1M
Redis一个值的最大存储1G
Memcahed中存储的数据不能持久化,一旦断电,立即丢失
Redis中存储的数据可以持久化
Memcahed 多线程,支持并发访问 数据安全 锁
Redis 单线程,并发时串行执行,将单进程单线程效率发挥到最大
Memcahced自身不支持集群环境 (使用中间件)
Redis从3.0版本之后自身开始提供集群环境支持
二、Redis的安装
2.1 单节点安装
1 下载redis
2.将下载redis资料包上传到Linux中将压缩包在linux系统中解压
3.解压完,解压目录中执行make即可(需要yum install -y gcc来支持编译)make MALLOC=libc
4.编译完成后make install PREFIX=/usr/redis安装
5.启动redis服务 ./redis-server /opt/install/redis/bin/redis.conf 启动成功看到如下画面
6.redis的服务的默认端口是6379
7、客户端连接redis服务,使用 ./redis-cli –p 6379 连接redis的服务端
8.关闭服务
[root@localhost bin]# ./redis-cli shutdown
- 核心配置修改
2.2 集群安装
三、基本指令
3.1 String类型的操作
- 命令说明
keys */ flushAll
set 设置一个key/value
get 根据key获得对应的value
mset 一次设置多个key value
mget 一次获得多个key的value
getset 获得原始key的值,同时设置新值
strlen 获得对应key存储value的长度
append 为对应key的value追加内容
getrange 截取value的内容 getrange key 0 7 (不改变原值)
setex 设置一个key存活的有效期(秒)
psetex 设置一个key存活的有效期(豪秒)
setnx 存在不做任何操作,不存在添加
msetnx 可以同时设置多个key,只有有一个存在都不保存
decr (不存在则创建) 进行数值类型的-1操作
Decrby (不存在则创建) 根据提供的数据进行减法操作
Incr (不存在则创建) 进行数值类型的+1操作
incrby (不存在则创建) 根据提供的数据进行加法操作
Incrbyfloat (保留17位) 根据提供的数据加入浮点数
3.2 List类型的操作
- 命令说明
lpush 将某个值加入到一个key列表头部
rpush 将某个值加入到一个key列表末尾
lpushx 同lpush,但是必须要保证这个key存在
rpushx 同rpush,但是必须要保证这个key存在
lpop 返回和移除列表的最左第一个元素
rpop 返回和移除列表的最右第一个元素
lrange 获取某一个下标区间内的元素 (0,-1 所有 最左 最上)
llen 获取列表元素个数
lset 设置某一个指定索引的值(索引必须存在)
lindex 获取某一个指定索引位置的元素
lrem 删除重复元素
ltrim 保留列表中特定区间内的元素
linsert 在某一个元素之前,之后插入新元素
3.3 SET类型的操作
sadd 为集合添加元素
smembers 显示集合中所有元素 无序
scard 返回集合中元素的个数
spop 随机返回一个元素
srem 从集合中删除一个元素
sismember 判断一个集合中是否含有这个元素
srandmember 随机返回元素
smove 从一个集合中向另一个集合移动元素
sdiff 去掉第一个集合中其它集合含有的相同元素
sinter 求交集
sunion 求和集
3.4 ZSET类型的操作
- 3 4 20 不是下标,是分数
zadd 添加一个有序集合元素
zcard 返回集合的元素个数
zrange 返回一个范围内的元素
zrangebyscore 按照分数查找一个范围内的元素
zrank 返回排名
zrevrank 倒序排名
zscore 显示某一个元素的分数
zrem 移除某一个元素
zincrby 给某个特定元素加分
3.5 HASH类型的操作
hset 设置一个key/value对 hset key id 1/
hget 获得一个key对应的value
hgetall 获得所有的key/value对
hdel 删除某一个key/value对
hexists 判断一个key是否存在
hkeys 获得所有的key
hvals 获得所有的value
hmset 设置多个key/value
hmget 获得多个key的value
hsetnx 设置一个不存在的key的值
hincrby 为value进行加法运算
hincrbyfloat 为value加入浮点值
四、Redis中的持久化机制
4.1 快照持久化
-
a) 快照持久化也是redis中的默认开启的持久化方案, 根据redis.conf中的配置,快照将被写入dbfilename指定的文件里面(默认是dump.rdb文件中)
-
b)根据redis.conf中的配置,快照将保存在dir选项指定的路径上
-
创建快照的几种方式
1、客户端使用BGSAVE命令: 来创建一个快照,
当接收到客户端的BGSAVE命令时
redis会调用fork¹来创建一个子进程,然后子进程负责将快照写入磁盘中,
而父进程则继续处理命令请求。
名词解释 : fork当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,
在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,
父子进程共享相同内存,直到父进程对内存进行了写之后,
对被写入的内存的共享才会结束服务。
2、客户端使用SAVE命令;来创建一个快照, 都可以生成快照。即dump.rdb 文件
接收到SAVE命令的redis服务器,在快照创建完毕之前,将不再响应任何其他的命令.
注意:创建完毕之前,redis处于阻塞状态,无法对外服务
3、当redis通过shutdown指令接收到关闭服务器的请求时,会执行一个save命令,阻塞所有的客户
端, 不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器
>shutdown 客户端窗口执行命令。 等价 [root@localhost bin]# ./redis-cli shutdown
4、redis 配置自定义 save 策略
时间 请求数 执行save
缺点:都会丢数据。
4.2 AOF 持久化机制
- 在redis的默认配置中AOF持久化机制 是没有开启的
- AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,
- 因此只要redis从头到尾执行一次AOF文件所包含的所有写命令,
- 就可以恢复AOF文件的记录的数据集.
- 开启AOF持久化机制,需要修改redis.conf的配置文件,
通过修改redis.conf配置中appendonly yes来开启AOF持久化
通过appendfilename指定日志文件名字(默认为:appendonly.aof) 存放位置为快照存放位置
通过appendfsync指定日志记录频率
- AOF 日志记录频率的选项
选项 同步频率
always 每个redis写命令都要同步写入硬盘,严重降低redis速度
everysec 每秒执行一次同步显式的将多个写命令同步到磁盘
no 由操作系统决定何时同步
- 为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据(推荐使用这种方式)
4.3 AOF文件的重写
-
AOF 的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写机制
-
重写 aof 文件的两种方式
执行BGREWRITEAOF命令
配置redis.conf中的auto-aof-rewrite-percentage选项
4.3.1 BgReWriteAOF命令
- 收到此命令redis将使用与快照类似的方式将内存中的数据 以命令的方式保存到临时文件中,最后替换原来的文件。
具体过程如下:
1、redis调用fork ,现在有父子两个进程子进程根据内存中的数据进性类似快照的操作。.
2、子进程往临时文件中,写入重建数据库状态的命令。
3、父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
4、当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
5、父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
- 注意 : 重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件这点和快照有点类似。(AOF重写过程完成后会删除旧的AOF文件,删除一个体积达几十GB大的旧的AOF文件可能会导致系统随时挂起 )
4.3.2配置redis.conf中的auto-aof-rewrite-percentage选项
- AOF重写也可以使用auto-aof-rewrite-percentage 100 和auto-aof-rewrite-min-size 64mb来自动执行BGREWRITEAOF.
- 说明: 如果设置auto-aof-rewrite-percentage值为100和auto-aof-rewrite-min-size 64mb,并且启用的AOF持久化时,那么当AOF文件体积大于64M,并且AOF文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-percentage设置为更大
4.4 两种持久化方案的总结
- AOF持久化既可以将丢失的数据的时间降低到1秒(甚至不丢失任何数据)
- 两种持久化方案既可以同时使用,又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定
- 无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)
- 注意 :
这个问题实际上并没有这么简单,因为redis会不断将执行的写命令记录到AOF文件中,所以随着redis运行,AOF文件的体积会不断增大,在极端情况下甚至会用完整个硬盘, - 还有redis重启重新执行AOF文件记录的所有写命令的来还原数据集,AOF文件体积非常大,会导致redis执行恢复时间过长
五、Redis集群的搭建
- Redis在3.0后开始支持Cluster(模式)模式,目前redis的集群支持节点的自动发现,
支持slave-master选举和容错,支持在线分片(sharding)等特性 - Redis的集群架构图
- .Redis的集群细节
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议,优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的节点检测失效时才生效.(一般基数节点)
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
- redis容错架构图
- 如果半数以上master节点与master节点通信超过(cluster-node-timeout),认为当前master节点挂掉
- 当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误
- 注意:
如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,
也可以理解成进群的slot映射[0-16383]不完成时进入fail状态.
如果进群超过半数以上master挂掉,无论是否有slave集群进入fail状态
-
判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数.
-
搭建集群至少需要三个主节点,三个从节点,至少需要6个节点
六、分布式锁
- 定义:保证同一时间只能有一个客户端对共享资源进行操作
- 要求
1、不会发生死锁。 即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续
其他客户端能加锁
2、具有容错性。 只要大部分的Redis节点正常运行,客户端就可以加锁和解锁
3、解铃还须系铃人。 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了
分布式锁实现大致分为三种, Redis、Zookeeper、数据库
6.1 分布式锁场景一
- 向 Redis 中添加一个 lockKey 锁标志位, 如果添加成功则能够继续向下执行扣减优惠券数量操作, 最后再释放此标志位
6.2 分布式锁场景二加expire
- 场景一基于 setnx 命令实现分布式锁的可能发生死锁
- 线程1在成功获取锁后, 执行流程时异常结束, 没有执行释放锁操作, 这样就会 产生死锁
- 解决死锁(如果方法执行异常导致的线程被回收, 那么可以将解锁操作放到 finally 块中)
- 如果获得锁的线程在执行中, 服务被强制停止或服务器宕机, 锁依然不会得到释放.
对 Redis 的 锁标志位加上过期时间 就能很好的防止死锁问题
6.3 场景三
- 处理如果在客户端加锁成功后, 还没有设置过期时间时宕机
- 对锁标志位 & 添加过期时间命令 保证一个原子性, 要么一起成功, 要么一起失败
- 从 Redis 2.6.12 版本起, 提供了可选的 字符串 set 复合命令
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
可选参数如下:
EX: 设置超时时间,单位是秒
PX: 设置超时时间,单位是毫秒
NX: IF NOT EXIST 的缩写,只有 KEY不存在的前提下 才会设置值
XX: IF EXIST 的缩写,只有在 KEY存在的前提下 才会设置值
6.4 场景四 verify value
- 创建辨别客户端身份的唯一值了, 将加锁及解锁归一化,
- 代码相当于我们添加锁标志位时, 同时为每个客户端设置了 uuid 作为锁标志位的 val, 解锁时需要判断锁的 val 是否和自己客户端的相同, 辨别成功才会释放锁
使用setnx、getset、expire、del这4个redis命令实现
setnx 是『SET if Not eXists』(如果不存在,则 SET)的简写。 命令格式:SETNX key value;使用:只在键 key 不存在的情况下,将键 key 的值设置为 value 。若键 key 已经存在, 则 SETNX 命令不做任何动作。返回值:命令在设置成功时返回 1 ,设置失败时返回 0 。
getset 命令格式:GETSET key value,将键 key 的值设为 value ,并返回键 key 在被设置之前的旧的value。返回值:如果键 key 没有旧值, 也即是说, 键 key 在被设置之前并不存在, 那么命令返回 nil 。当键 key 存在但不是字符串类型时,命令返回一个错误。
expire 命令格式:EXPIRE key seconds,使用:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。返回值:设置成功返回 1 。 当 key 不存在或者不能为 key 设置生存时间时(比如在低于
del 命令格式:DEL key [key …],使用:删除给定的一个或多个 key ,不存在的 key 会被忽略。返回值:被删除 key 的数量。
A尝试去获取锁lockkey,通过setnx(lockkey,currenttime+timeout)命令,对lockkey进行setnx,将value值设置为当前时间+锁超时时间;
如果返回值为1,说明redis服务器中还没有lockkey,也就是没有其他用户拥有这个锁,A就能获取锁成功;
在进行相关业务执行之前,先执行expire(lockkey),对lockkey设置有效期,防止死锁。因为如果不设置有效期的话,lockkey将一直存在于redis中,其他用户尝试获取锁时,执行到setnx(lockkey,currenttime+timeout)时,将不能成功获取到该锁;
执行相关业务;
释放锁,A完成相关业务之后,要释放拥有的锁,也就是删除redis中该锁的内容,del(lockkey),接下来的用户才能进行重新设置锁新值。
七、Redis整合SpringBoot
7.1、 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>1.5.7.RELEASE</version>
</dependency>
7.2、key–value
@Autowired
StringRedisTemplate stringRedisTemplate;
stringRedisTemplate.opsForValue().set("name","zhangsan");
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println(name); //zhangsan
//存活100秒 :ttl id 指令查看剩余存活秒速
stringRedisTemplate.opsForValue().set("id","1",100, TimeUnit.SECONDS);
7.3、批量删除
//
List<String> strings = Arrays.asList("id", "name");
stringRedisTemplate.delete(strings);
7.4、 为key设置超时时间
//为已存在的key,设置超时时间
stringRedisTemplate.expire("name",100, TimeUnit.SECONDS);
stringRedisTemplate.getexpire("name");//100
7.5、list
Long aLong = stringRedisTemplate.opsForList().leftPush("names", "xiaohei");
stringRedisTemplate.opsForList().leftPushAll("names","xiaohei","xiaohei1","xiaohei2","xiaohei3","xiaohei4","xiaohei5");
List<String> names = stringRedisTemplate.opsForList().range("names", 0, -1);
for (String name : names) {
System.out.println(name);
}
7.6、Set 集合 存取
Long add = stringRedisTemplate.opsForSet().add("s1", "s2", "s3");
Set<String> s1 = stringRedisTemplate.opsForSet().members("s1");
for (String s : s1) {
System.out.println(s);
}
DataType s11 = stringRedisTemplate.type("s1");
if (s11.equals(DataType.SET)){
System.out.println("this is set");
}