Redis初级篇

Redis

视频地址:https://www.bilibili.com/video/BV1Rv41177Af?p=38

资料地址:https://pan.baidu.com/s/1GxYRq5UkZHKhk3KB0nOioQ q7vj

在这里插入图片描述

概述

Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API的非关系型数据库。

与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。

Github 源码:https://github.com/antirez/redis

Redis 官网:https://redis.io/

支持的数据类型

共支持5种数据类型

  • String(字符串)

格式: set key value

string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

string类型是Redis最基本的数据类型,一个键最大能存储512MB。

  • Hash(哈希)

格式: hmset name key1 value1 key2 value2

Redis hash 是一个键值(key=>value)对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

  • List(列表)

格式: lpush name value

  • Set(集合)

格式: sadd name value

  • zset(sorted set:有序集合)

格式: zadd name score value

Redis的优缺点

优点

  • 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
  • 支持数据持久化,支持AOF和RDB两种持久化方式。
  • 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
  • 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。

缺点

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
  • Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
  • 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
  • Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。

Redis持久化

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

Redis 提供了两种持久化方式:RDB(默认) 和AOF

redis通讯协议RESP

RESP 的特点:实现简单、快速解析、可读性好

Redis 有哪些架构模式?讲讲各自的特点

单机版 主从复制 哨兵 集群(proxy 型) 集群(直连型)

一致性哈希算法和哈希槽

https://www.cnblogs.com/lpfuture/p/5796398.html

http://www.jasontec.cn/articles/2020/04/11/1586586130767.html

Redis常用命令

Keys pattern

*表示区配所有

以bit开头的

查看Exists key是否存在

Set

设置 key 对应的值为 string 类型的 value。

setnx

设置 key 对应的值为 string 类型的 value。如果 key 已经存在,返回 0,nx 是 not exist 的意思。

删除某个key

第一次返回1 删除了 第二次返回0

Expire 设置过期时间(单位秒)

TTL查看剩下多少时间

返回负数则key失效,key不存在了

Setex

设置 key 对应的值为 string 类型的 value,并指定此键值对应的有效期。

Mset

一次设置多个 key 的值,成功返回 ok 表示所有的值都设置了,失败返回 0 表示没有任何值被设置。

Getset

设置 key 的值,并返回 key 的旧值。

Mget

一次获取多个 key 的值,如果对应 key 不存在,则对应返回 nil。

Incr

对 key 的值做加加操作,并返回新的值。注意 incr 一个不是 int 的 value 会返回错误,incr 一个不存在的 key,则设置 key 为 1

incrby

同 incr 类似,加指定值 ,key 不存在时候会设置 key,并认为原来的 value 是 0

Decr

对 key 的值做的是减减操作,decr 一个不存在 key,则设置 key 为-1

Decrby

同 decr,减指定值。

Append

给指定 key 的字符串值追加 value,返回新字符串值的长度。

Strlen

取指定 key 的 value 值的长度。

persist xxx(取消过期时间)

选择数据库(0-15库)

Select 0 //选择数据库

move age 1//把age 移动到1库

Randomkey随机返回一个key

Rename重命名

Type 返回数据类型

Redis分布式锁及实现

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!

Redis异步队列使用及缺点

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

缺点:

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

能不能生产一次消费多次呢?

使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

缓存穿透?如何避免?缓存雪崩?如何避免

缓存穿透

一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

如何避免?

1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。

2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。

如何避免?

1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期

3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

Redis 为什么要用做缓存

主要从“高性能”和“高并发”这两点来看待这个问题

高性能:假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

为什么要用 Redis 而不用 map/guava 做缓存?

缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。

Redis为什么这么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路 I/O 复用模型,非阻塞 IO;

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

Redis使用

1、Github下载地址:https://github.com/MicrosoftArchive/redis/releases
2、百度网盘下载地址 https://pan.baidu.com/s/1z1_OdNVbtgyEjiktqgB83g 密码:kdfq

快速安装构建

Linux安装

(1)安装C语言的编译环境

yum install centos-release-scl scl-utils-build
yum install -y devtoolset-8-toolchain
scl enable devtoolset-8 bash

测试gcc版本

gcc --version

(2)下载redis-6.2.1.tar.gz

解压命令

tar -zxvf redis-6.2.1.tar.gz

解压后进入目录cd redis-6.2.1

在redis-6.2.1目录下再次执行make命令

make install

(3)安装成功的目录为usr/local/bin

查看默认安装目录:

redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何

redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲

redis-check-dump:修复有问题的dump.rdb文件

redis-sentinel:Redis集群使用

redis-server:Redis服务器启动命令

redis-cli:客户端,操作入口

前台启动(不推荐)

前台启动不推荐,命令行窗口不能关闭,否则服务器停止

在这里插入图片描述

后台启动推荐

(1)备份redis.conf

cp  /opt/redis-6.2.1/redis.conf  /myredis

(2)配置文件中后台启动设置daemonize no改成yes

(3)redis启动

redis-server /myredis/redis.conf

(4)用客户端访问

redis-cli

注意:多个端口也可以:redis-cli -p6379

测试验证:ping
在这里插入图片描述

(5)Redis关闭

单实例关闭:redis-cli shutdown

在这里插入图片描述

也可以进入终端后再关闭(shutdown)

在这里插入图片描述

多实例关闭,指定端口关闭:redis-cli -p 6379 shutdown

Windows安装

  1. cmd文件目录,输入如下命令
redis-server.exe redis.windows.conf 

可以把Redis配置环境变量后可以采用如下命令即可启动redis
在这里插入图片描述

redis-server.exe
  1. 另起cmd运行
redis-cli.exe -h 127.0.0.1 -p 6379
  1. 设置键值对
set myKey abc
  1. 取出键值对
get myKey

在这里插入图片描述

常用指令

卸载服务:redis-server --service-uninstall
开启服务:redis-server --service-start
停止服务:redis-server --service-stop

常用五大数据类型

Redis键(Key)

keys * :查看当前库所有key (匹配:keys *1)

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

type key:查看你的key是什么类型

del key : 删除指定的key数据

unlink key:根据value选择非阻塞删除

仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。

expire key 10:10秒钟:为给定的key设置过期时间

ttl key :查看还有多少秒过期,-1表示永不过期,-2表示已过期

select:命令切换数据库

dbsize:查看当前数据库的key的数量

flushdb:清空当前库

flushall:通杀全部库

Redis字符串

String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。

String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M。

常用命令

set <key><value>:添加键值对

get <key>:查询对应键值

append <key><value>:将给定的 追加到原值的末尾

strlen <key>获得值的长度

setnx <key><value>只有在 key 不存在时 设置 key 的值

增长:

incr <key>:将 key 中储存的数字值增1,只能对数字值操作,如果为空,新增值为1

decr <key>:将 key 中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1

incrby / decrby <key><步长>:将 key 中储存的数字值增减。自定义步长。

批量操作:

mset <key1><value1><key2><value2> ..... :同时设置一个或多个 key-value对

mget <key1><key2><key3> .....:同时获取一个或多个 value

msetnx <key1><value1><key2><value2> ..... :同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。(次命令具有原子性,有一个失败则都失败

范围操作:

getrange <key><起始位置><结束位置>:获得值的范围,类似java中的substring,前包,后包

setrange <key><起始位置><value>:用 覆写所储存的字符串值,从<起始位置>开始

其它:

setex <key><过期时间><value>:设置键值的同时,设置过期时间,单位秒。

getset <key><value>:以新换旧,设置了新值同时获得旧值。

Redis的list

单键多值

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

在这里插入图片描述

常用命令

lpush/rpush <key><value1><value2><value3> .... :从左边/右边插入一个或多个值。

在这里插入图片描述

在这里插入图片描述

lrange <key><start><stop>:按照索引下标获得元素(从左到右)

在这里插入图片描述

在这里插入图片描述

lpop/rpop <key>:从左边/右边吐出一个值,值在键在,值光键亡

在这里插入图片描述

值取完后就为空了

在这里插入图片描述

rpoplpush <key1><key2>:从列表右边吐出一个值,插到列表左边

在这里插入图片描述

lindex <key><index>:按照索引下标获得元素(从左到右)

在这里插入图片描述

llen <key>:获得列表长度

在这里插入图片描述

linsert <key> before <value><newvalue>:在的后面插入的插入值

在这里插入图片描述

lrem <key><n><value>:从左边删除n个value(从左到右)

在这里插入图片描述

lset<key><index><value>:将列表key下标为index的值替换成value

在这里插入图片描述

Redis的Set

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)

一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变

常用命令

sadd <key><value1><value2> ..... :将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略

smembers <key>:取出该集合的所有值

在这里插入图片描述

sismember <key><value>:判断集合是否为含有该值,有1,没有0

在这里插入图片描述

scard<key>:返回该集合的元素个数

在这里插入图片描述

srem <key><value1><value2> .... :删除集合中的某个元素

在这里插入图片描述

spop <key>:随机从该集合中吐出一个值

在这里插入图片描述

srandmember <key><n>:随机从该集合中取出n个值,不会从集合中删除

在这里插入图片描述

smove <source><destination>value:把集合中一个值从一个集合移动到另一个集合

在这里插入图片描述

sinter <key1><key2>:返回两个集合的交集元素

sunion <key1><key2>:返回两个集合的并集元素

sdiff <key1><key2>:返回两个集合的差集元素(key1中的,不包含key2中的)

在这里插入图片描述

Redis的hash

Redis hash 是一个键值对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

类似Java里面的Map<String,Object>,可以存储的格式有如下三种

第一种:

问题】:每次修改用户的某个属性需要先反序列化改好后再序列化回去。开销较大

格式】:user : {id=1,name=zhangsan,age=20}

在这里插入图片描述

第二种:

问题】:用户ID数据冗余

格式】:

user:id1
user:namezhangsan
user:age20

在这里插入图片描述

第三种:

问题】:通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题

格式】:

id1
usernamezhangsan
age20

在这里插入图片描述

常用命令

hset <key><field><value>给集合中的 键赋值

hget <key1><field>从集合取出 value

hmset <key1><field1><value1><field2><value2>... 批量设置hash的值

hexists<key1><field>查看哈希表 key 中,给定域 field 是否存在

hkeys <key>列出该hash集合的所有field

hvals <key>列出该hash集合的所有value

hincrby <key><field><increment>为哈希表 key 中的域 field 的值加上增量 1 -1

hsetnx <key><field><value>将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在

在这里插入图片描述

Redis的Zset

Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。

不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。

因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。

常用命令

zadd <key><score1><value1><score2><value2>…:将一个或多个 member 元素及其 score 值加入到有序集 key 当中

在这里插入图片描述

zrange <key><start><stop> [WITHSCORES] :返回有序集 key 中,下标在 之间的元素

带WITHSCORES,可以让分数一起和值返回到结果集

在这里插入图片描述

zrangebyscore key minmax [withscores] [limit offset count]:返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列

在这里插入图片描述

zrevrangebyscore key maxmin [withscores] [limit offset count] :同上,改为从大到小排列

在这里插入图片描述

zincrby <key><increment><value> :为元素的score加上增量

在这里插入图片描述

zrem <key><value>:删除该集合下,指定值的元素

在这里插入图片描述

zcount <key><min><max>:统计该集合,分数区间内的元素个数

在这里插入图片描述

zrank <key><value>:返回该值在集合中的排名,从0开始

在这里插入图片描述

Redis的发布和订阅

在这里插入图片描述

1、 打开一个客户端订阅channel1

SUBSCRIBE channel1

在这里插入图片描述

2、打开另一个客户端,给channel1发布消息hello

publish channel1 hello

在这里插入图片描述

返回的1是订阅者数量

3、打开第一个客户端可以看到发送的消息

在这里插入图片描述

注:发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息

Redis6的新数据类型

Bitmaps

简介

现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成, 但实际在计算机存储时将其用二进制表示, “abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011,如下图

在这里插入图片描述

合理地使用操作位能够有效地提高内存使用率和开发效率

Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:

(1) Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作

(2) Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量

在这里插入图片描述

使用

setbit<key><offset><value>:设置Bitmaps中某个偏移量的值(0或1)

在这里插入图片描述

getbit<key><offset>:获取Bitmaps中某个偏移量的值

在这里插入图片描述

bitcount<key>[start end]: 统计字符串从start字节到end字节比特值为1的数量

在这里插入图片描述

start和end代表起始和结束字节数, 下面操作计算用户id在第1个字节到第3个字节之间的独立访问用户数, 对应的用户id是11, 15, 19

举例: K1 【01000001 01000000 00000000 00100001】,对应【0,1,2,3】

bitcount K1 1 2 : 统计下标1、2字节组中bit=1的个数,即01000000 00000000

–》bitcount K1 1 2   --》1

bitcount K1 1 3 : 统计下标1、2字节组中bit=1的个数,即01000000 00000000 00100001

–》bitcount K1 1 3  --》3

bitcount K1 0 -2 : 统计下标0到下标倒数第2,字节组中bit=1的个数,即01000001 01000000 00000000

–》bitcount K1 0 -2  --》3

注意:redis的setbit设置或清除的是bit位置,而bitcount计算的是byte位置。

bitop and(or/not/xor) <destkey> [key…]:bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。

实例:计算出两天都访问过网站的用户数量和任意一天都访问过网站的用户数量

2020-11-04 日访问网站的userid=1,2,5,9。

setbit unique:users:20201104 1 1
setbit unique:users:20201104 2 1
setbit unique:users:20201104 5 1
setbit unique:users:20201104 9 1

2020-11-03 日访问网站的userid=0,1,4,9。

setbit unique:users:20201103 0 1
setbit unique:users:20201103 1 1
setbit unique:users:20201103 4 1
setbit unique:users:20201103 9 1
  1. 计算出两天都访问过网站的用户数量
bitop and unique:users:and:20201104_03 unique:users:20201103 unique:users:20201104
bitcount unique:users:and:20201104_03

在这里插入图片描述

  1. 计算出任意一天都访问过网站的用户数量(例如月活跃就是类似这种) , 可以使用or求并集
bitop or unique:users:or:20201104_03 unique:users:20201103 unique:users:20201104
bitcount unique:users:or:20201104_03

在这里插入图片描述

总结:bitmaps只是用来对数据进行统计,并不是用来存储数据的。bitmaps中的值只能是0或者1,常用于对数据的是或者否来进行统计,统计其个数,可以节省内存空间

HyperLogLog

简介

这种求集合中不重复元素个数的问题称为基数问题。

解决基数问题有很多种方案:

(1)数据存储在MySQL表中,使用distinct count计算不重复个数

(2)使用Redis提供的hash、set、bitmaps等数据结构来处理

以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。

能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

使用

pfadd <key>< element> [element ...]:添加指定元素到 HyperLogLog 中

在这里插入图片描述

pfcount<key> [key ...]: 计算program的基数

在这里插入图片描述

pfmerge<destkey><sourcekey> [sourcekey ...: 将两个set合并后放到一个新的set中,然后统计其中的基数个数

在这里插入图片描述

总结:主要用于统计基数个数(即去重后的基数个数),在set数量比较大的时候可以节省内存

Geospatial

简介

Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

使用

geoadd<key>< longitude><latitude><member> [longitude latitude member...] : 添加地理位置(经度,纬度,名称)

在这里插入图片描述

geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing

geopos <key><member> [member...]: 获得指定地区的坐标值

在这里插入图片描述

geodist<key><member1><member2> [m|km|ft|mi ]: 获取两个位置之间的直线距离

单位:

m 表示单位为米[默认值]。

km 表示单位为千米。

mi 表示单位为英里。

ft 表示单位为英尺。

如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位

在这里插入图片描述

georadius<key>< longitude><latitude>radius m|km|ft|mi : 以给定的经纬度为中心,找出某一半径内的元素

在这里插入图片描述

Jedis操作Redis

使用案例

简单连接案例

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
public class JedisDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.179.128", 6379);
        //测试
        String value = jedis.ping();
        System.out.println(value);
    }
}
结果:
PONG

Process finished with exit code 

测试连接API

(1)测试连接和获取keys

    @Test
    public void getKeys() {
        Jedis jedis = new Jedis("192.168.179.128", 6379);
        Set<String> keys = jedis.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }
    }
结果:
k3
program
user:1001
k1
k2   

(2)测试添加

  • API-String
	@Test
    public void setKeys() {
        Jedis jedis = new Jedis("192.168.179.128", 6379);
        jedis.select(1);
        jedis.set("name", "Mike");
        String name = jedis.get("name");
        System.out.println(name);

        Set<String> keys = jedis.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }
    }
结果:
Mike
name 
/**jedis.mset("str1","v1","str2","v2","str3","v3");
System.out.println(jedis.mget("str1","str2","str3"));**/
  • API-list
        jedis.lpush("mylist", "zhangsan", "lisi", "wangwu");
        List<String> list = jedis.lrange("mylist", 0, -1);
        for (String element : list) {
            System.out.println(element);
        }
  • API-hash
		jedis.hset("hash1","userName","lisi");
        System.out.println(jedis.hget("hash1","userName"));
        Map<String,String> map = new HashMap<String,String>();
        map.put("telphone","13810169999");
        map.put("address","atguigu");
        map.put("email","abc@163.com");
        jedis.hmset("hash2",map);
        List<String> result = jedis.hmget("hash2", "telphone","email");
        for (String element : result) {
            System.out.println(element);
        }
  • API-set
		jedis.sadd("orders", "order01","order02","order03","order04");
        Set<String> smembers = jedis.smembers("orders");
        for (String order : smembers) {
            System.out.println(order);
        }
        jedis.srem("orders", "order02");
  • API-zset
        HashMap<String, Double> map = new HashMap<String, Double>();
        map.put("java", 100d);
        map.put("c++", 200d);
        map.put("php", 300d);
        map.put("python", 400d);
        jedis.zadd("program", map);
        Set<Tuple> program = jedis.zrangeWithScores("program", 0, -1);
        for (Tuple tuple : program) {
            System.out.println(tuple.getElement() + ":" + tuple.getScore());
        }

综合案例:完成一个手机验证码功能

要求:

1、输入手机号,点击发送后随机生成6位数字码,2分钟有效

2、输入验证码,点击验证,返回成功或失败

3、每个手机号每天只能输入3次

分析:

  • 点击发送后生成6位的随机数字码,并跟手机号绑定,两分钟后过期
  • 点击验证后把手机号和验证码进行比对,如果正确则返回验证正确,如果不对则返回验证失败
  • 手机号在发送生成验证码前要查看当前手机号在24小时内是否发送超过3次,如果超过了则提示超过次数
public class GenerateCode {
    Jedis jedis = null;
    
    @Before
    public void getConnect() {
        jedis = new Jedis("192.168.179.128", 6379);
        jedis.select(2);
    }

    public void generateCode(String phone) {
        getConnect();
        Random random = new Random();
        String verifyCode = "";
        for (int j = 0; j < 6; j++) {
            int i = random.nextInt(10);
            verifyCode += i;
        }
        jedis.setex(phone, 120, verifyCode);
        System.out.println("手机号" + phone + "生成的验证码是:" + jedis.get(phone));
        //把该手机号在当天的使用次数再加上1
        if (jedis.get(phone + "times") == null) {
            //如果从来没有使用过,则设置使用次数为1
            jedis.setex(phone + "times", 24 * 60 * 60, "1");
            System.out.println("手机号" + phone + "今天第一次使用");
        } else {
            //如果已经使用过,则把使用次数加1
            jedis.incr(phone + "times");
        }
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入收到的验证码:");
        String inputVerifyCode = sc.nextLine();
        System.out.println("输入的验证码是:" + inputVerifyCode);
        verifyCode(phone, inputVerifyCode);
    }

    public void verifyCode(String phone, String inputVerifyCode) {
        getConnect();
        String verifyCode = jedis.get(phone);
        if (inputVerifyCode.equalsIgnoreCase(verifyCode)) {
            System.out.println("验证成功");
        } else if (verifyCode == null) {
            System.out.println("验证失败,验证码已过期");
        } else {
            System.out.println("验证失败,验证码不匹配");
        }
    }

    @Test
    public void phoneToGetCode() {
        getConnect();
        //从控制台获取输入的手机号
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入手机号:");
        String phone = sc.nextLine();
        System.out.println("输入的手机号是:" + phone);
        String phonetimes = jedis.get(phone + "times");
        //如果手机号在当天次数小于3则打印次数,否则超过次数
        if (phonetimes == null) {
            //该手机号在当天从未使用过,可以生成验证码
            generateCode(phone);
        } else if (Integer.parseInt(phonetimes) < 3) {
            //该手机号使用过,但没有超过3次
            System.out.println("该手机号今天已经使用的次数:" + phonetimes);
            generateCode(phone);
        } else {
            //该手机号使用次数超过3次
            System.out.println("手机号" + phone + "今天超过使用次数");
        }
    }
}
//测试结果:
/**
请输入手机号:
输入的手机号是:123456
手机号123456生成的验证码是:300248
手机号123456今天第一次使用
请输入收到的验证码:
输入的验证码是:300248
验证成功
**/

与springboot整合

  1. 导入依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
  1. 配置类
#Redis服务器地址
spring.redis.host=192.168.179.128
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
  1. redis配置类
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
//key序列化方式
        template.setKeySerializer(redisSerializer);
//value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}
  1. 控制器类
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping
    public String testRedis() {
        //设置值到redis
        redisTemplate.opsForValue().set("name","lucy");
        //从redis获取值
        String name = (String)redisTemplate.opsForValue().get("name");
        return name;
    }
}

测试结果:

在这里插入图片描述

复合案例

配置文件

redis.host=127.0.0.1
redis.port=6379
redis.password=123456
redis.timeout=100000
redis.maxIdle=100
redis.maxActive=300
redis.maxWait=1000
redis.testOnBorrow=true
public class RedisUtil {
    public RedisUtil() {
    }

    // 以下配置可用可不用
    private static Jedis jedisxuan;// redis实例
    private static String host;// 地址
    private static String port;// 端口
    private static String password;// 授权密码
    private static String timeout;// 超时时间:单位ms
    private static String maxIdle;// 最大空闲数:空闲链接数大于maxIdle时,将进行回收
    private static String maxActive;// 最大连接数:能够同时建立的"最大链接个数"
    private static String maxWait;// 最大等待时间:单位ms
    private static String testOnBorrow;// 在获取连接时,是否验证有效性

    // 静态代码块
    static {
        // 加载properties配置文件
        Properties properties = new Properties();
        InputStream is = RedisUtil.class.getClassLoader().getResourceAsStream(
                "redis.properties");
        try {
            properties.load(is);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        host = properties.getProperty("redis.host");
        port = properties.getProperty("redis.port");
        password = properties.getProperty("redis.password");
        timeout = properties.getProperty("redis.timeout");
        maxIdle = properties.getProperty("redis.maxIdle");
        maxActive = properties.getProperty("redis.maxActive");
        maxWait = properties.getProperty("redis.maxWait");
        testOnBorrow = properties.getProperty("redis.testOnBorrow");
        // 得到Jedis实例并且设置配置
        jedisxuan = new Jedis(host, Integer.parseInt(port),
                Integer.parseInt(timeout));
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public static boolean set(final String key, String value) {
        boolean result = false;
        try {
            jedisxuan.set(key, value);
            result = true;
        } catch (Exception e) {
            System.out.println("set cache error");
        }
        return result;
    }

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public static Object get(final String key) {
        Object result = null;
        result = jedisxuan.get(key);
        return result;
    }

    /**
     * 删除key对应的value
     *
     * @param key
     */
    public static void remove(final String key) {
        if (key != null && key.length() >= 1 && !key.equals("")
                && jedisxuan.exists(key)) {
            jedisxuan.del(key);
        }
    }

    /**
     * 判断缓存中是否有key对应的value
     *
     * @param key
     * @return
     */
    public static boolean exists(final String key) {
        return jedisxuan.exists(key);
    }

    /**
     * 写入缓存(规定缓存时间)
     *
     * @param key
     * @param value
     * @param expireSecond
     * @return
     */
    public static boolean set(final String key, String value, Long expireSecond) {
        boolean result = false;
        try {
            // NX代表不存在才set,EX代表秒,NX代表毫秒
            String set = jedisxuan.set(key, value, new SetParams().px(expireSecond));
            System.out.println("set:" + set);
            result = true;
        } catch (Exception e) {
            System.out.println("set cache error");
        }
        return result;
    }

测试类

public static void main(String[] args) {

    // 写入一个缓存
    boolean flag = RedisUtil.set("x", "轩");
    if (flag) {
        // 读取缓存
        System.out.println("成功写入缓存");
        System.out.println("正在读取缓存......");
        String xuan = String.valueOf(RedisUtil.get("x"));
        System.out.println("你读取的缓存为:" + xuan);
    } else {
        System.out.println("写入缓存失败");
    }

    //写入一个带时间的缓存 30秒消失
    //可以自己去验证是否正确
  	boolean flag1 = RedisUtil.set("xuan", "关注我博客~", Long.parseLong("30"));
    if (flag) {
        System.out.println("写入成功");
    }
}

测试结果

在这里插入图片描述

Redis事务操作

事务的使用

Redis事务定义

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

事务命令

  • Multi、Exec、discard

**Multi:**Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行

**Exec:**输入Exec后,Redis会将之前的命令队列中的命令依次执行

**discard:**组队的过程中可以通过discard来放弃组队

在这里插入图片描述

案例

  • 组队成功,提交成功

在这里插入图片描述

  • 组队阶段报错,提交失败

    在这里插入图片描述

  • 组队成功,提交有成功有失败情况

在这里插入图片描述

事务的错误处理

  • 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消

在这里插入图片描述

  • 如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚

    在这里插入图片描述

事务冲突的问题

想想一个场景:有很多人有你的账户,同时去参加双十一抢购。

一个请求想给金额减8000

一个请求想给金额减5000

一个请求想给金额减1000

在这里插入图片描述

问题:出现金额为负数的场景

悲观锁

在这里插入图片描述

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁表锁等,读锁写锁等,都是在做操作之前先上锁。

乐观锁

在这里插入图片描述

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

watch指令

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务**执行之前这个(**或这些) key 被其他命令所改动,那么事务将被打断。

watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。

https://blog.csdn.net/weixin_43597208/article/details/118481680

unwatch指令

取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。

事务应用-秒杀案例

实现单线程情况下场景

场景

10个商品由多个用户来秒杀,只允许10个用户来成功秒杀,不允许出现商品数量少于0,也不允许用户重复秒杀

实现图

在这里插入图片描述

核心代码

	public static boolean doSecKill(String uid,String prodid) throws IOException {
		//1 uid和prodid非空判断
		if(uid == null || prodid == null) {
			return false;
		}

		//2 连接redis
		//Jedis jedis = new Jedis("192.168.179.128",6379);
		//通过连接池得到jedis对象
		JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis = jedisPoolInstance.getResource();

		//3 拼接key
		// 3.1 库存key
		String kcKey = "sk:"+prodid+":qt";
		// 3.2 秒杀成功用户key
		String userKey = "sk:"+prodid+":user";

		//4 获取库存,如果库存null,秒杀还没有开始
		String kc = jedis.get(kcKey);
		if(kc == null) {
			System.out.println("秒杀还没有开始,请等待");
			jedis.close();
			return false;
		}

		// 5 判断用户是否重复秒杀操作
		if(jedis.sismember(userKey, uid)) {
			System.out.println("已经秒杀成功了,不能重复秒杀");
			jedis.close();
			return false;
		}

		//6 判断如果商品数量,库存数量小于1,秒杀结束
		if(Integer.parseInt(kc)<=0) {
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		}

		//7.1 库存-1
		jedis.decr(kcKey);
		//7.2 把秒杀成功用户添加清单里面
		jedis.sadd(userKey,uid);

		System.out.println("秒杀成功了..");
		jedis.close();
		return true;
	}

测试结果:单线程没有出现问题

在这里插入图片描述

在这里插入图片描述

多线程测试出现的问题

测试出现超卖问题

采用ab工具模拟多线程

联网安装工具

yum install httpd-tools

vi postfile 模拟表单提交参数,以&符号结尾;存放当前目录。

内容:prodid=0101&

测试命令

ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded http://192.168.124.9:8080/Seckill/doseckill

测试结果:出现了超卖问题

在这里插入图片描述

在这里插入图片描述

利用乐观锁解决超卖问题

在这里插入图片描述

核心代码

		//监视库存
		jedis.watch(kcKey);
		//7 秒杀过程
		//使用事务
		Transaction multi = jedis.multi();

		//组队操作
		multi.decr(kcKey);
		multi.sadd(userKey,uid);

		//执行
		List<Object> results = multi.exec();

		if(results == null || results.size()==0) {
			System.out.println("秒杀失败了....");
			jedis.close();
			return false;
        }

测试结果:解决了超卖问题

在这里插入图片描述

测试出现连接超时问题

在这里插入图片描述

用连接池来解决连接超时问题

节省每次连接redis服务带来的消耗,把连接好的实例反复利用。

通过参数管理连接的行为

链接池参数

  • MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted;

  • maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;

  • MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;

  • testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;

public class JedisPoolUtil {
    private static volatile JedisPool jedisPool = null;

    private JedisPoolUtil() {
    }

    public static JedisPool getJedisPoolInstance() {
        if (null == jedisPool) {
            synchronized (JedisPoolUtil.class) {
                if (null == jedisPool) {
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(200);
                    poolConfig.setMaxIdle(32);
                    poolConfig.setMaxWaitMillis(100 * 1000);
                    poolConfig.setBlockWhenExhausted(true);
                    poolConfig.setTestOnBorrow(true);  // ping  PONG

                    jedisPool = new JedisPool(poolConfig, "192.168.179.128", 6379, 60000);
                }
            }
        }
        return jedisPool;
    }

    public static void release(JedisPool jedisPool, Jedis jedis) {
        if (null != jedis) {
            jedisPool.returnResource(jedis);
        }
    }
}

//获取redis连接池
/*JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis = jedisPoolInstance.getResource();*/
测试出现库存遗留问题

库存设为200,压力测试下有2000数据,并发100,理论上是不会有剩余库存,却发现还是有库存

ab -n 2000 -c 100 -k -p ~/postfile -T application/x-www-form-urlencoded http://192.168.124.9:8080/Seckill/doseckill

在这里插入图片描述

乐观锁造成了库存遗留问题

用LUA脚本解决库存遗留问题

LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。

redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题

Redis持久化

Redis 提供了2个不同形式的持久化方式。

  • RDB(Redis DataBase)

  • AOF(Append Of File)

RDB

定义及原理

定义

指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里

实现原理

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。

Fork

  • Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
  • 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术
  • 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程

RDB持久化流程

在这里插入图片描述

使用

配置文件redis.conf中有dump.rdb

优势

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高更适合使用
  • 节省磁盘空间
  • 恢复速度快

劣势

  • Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  • 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
  • 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改

总结:RDB是redis中的一种持久化方式,过程是通过一个fork的子进程建立一个临时文件,等持久化完了再把临时文件替换之前的文件,过程中的技术叫做写时复制技术,RDB的优势是周期性对数据进行持久化操作,适用于大规模的数据恢复,缺点是最后一额持久化的过程中可能会造成数据的丢失

RDB备份数据
  • 修改redis.conf文件中的save,即20秒内有3个key修改则自动保存
# save 3600 1
# save 300 100
# save 60 10000
save 20 3
  • 添加数据
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> set b 2
OK
127.0.0.1:6379> set c 3
OK
127.0.0.1:6379> set d 4
OK
  • 备份此时的dump.rdb,关闭redis服务器,再把dump.rdb替换为之前备份的,再启动服务器,可以恢复之前备份的数据
127.0.0.1:6379> keys *
1) "c"
2) "a"
3) "b"

AOF

日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

注:AOF默认不开启

appendonly no改为yes

注:AOFRDB同时开启,redis听谁的?

AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

异常恢复

如果appendonly.aof文件异常,会导致redis服务启动报错,可以采用redis-check-aof文件来修复

  • 修改默认的appendonly no,改为yes
  • 如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof–fix appendonly.aof进行恢复
  • 备份被写坏的AOF文件
  • 恢复:重启redis,然后重新加载

AOF同步频率设置

appendfsync always

始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好

appendfsync everysec

每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。

appendfsync no

redis不主动进行同步,把同步时机交给操作系统。

Rewrite压缩

1. 是什么:

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

2. 重写原理,如何实现重写

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。

no-appendfsync-on-rewrite:

如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)

如果 no-appendfsync-on-rewrite=no, 还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)

3. 触发机制,何时重写

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。

auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)

auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。

例如:文件达到70MB开始重写,降到50MB,下次什么时候开始重写?100MB

系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,

如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。

优势

  • 备份机制更稳健,丢失数据概率更低。
  • 可读的日志文本,通过操作AOF稳健,可以处理误操作。

劣势

  • 比起RDB占用更多的磁盘空间。
  • 恢复备份速度要慢。
  • 每次读写都同步的话,有一定的性能压力。
  • 存在个别Bug,造成恢复不能。

用哪一个好

官方推荐两个都启用。

如果对数据不敏感,可以选单独用RDB。

不建议单独用 AOF,因为可能会出现Bug。

如果只是做纯内存缓存,可以都不用。

redis的使用场景

在这里插入图片描述

参考内容

https://zhuanlan.zhihu.com/p/118561398 我和面试官的博弈:Redis 篇

http://blog.itpub.net/31545684/viewspace-2213990/ 分享30道Redis面试题,面试官能问到的我都找到了

https://blog.csdn.net/weixin_47920113/article/details/108281671 Redis面试题

https://www.cnblogs.com/javazhiyin/p/13839357.html Redis 常见面试题(2020最新版)

下篇—中级使用篇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值