Redis学习笔记

redis学习

第一章 nosql相关介绍

1.1 单机mysql的演进

90年代,一个网站的的访问量不会太大,单个数据库部署在一个服务器就可以使用,但是随着用户的增多,网站会出现一下问题:

1)数据量增加到一定程度,单机数据库就放不下了

2)数据的索引(B+数数据类型),一个机器也存不下

3)访问量变大后(高并发),服务的性能承载不了

img

此时需要向前引进

1.2 Memcached(缓存)+mysql+垂直拆分(读写分离)

一个网站百分之80都是在读的状态,每次去查询数据库的话十分的麻烦,为了减轻数据库的负担,我们可以使用缓存来保证读的效率,可以使用垂直拆分来让每台服务器的mysql各司其职,一台专门写数据然后同步到其他服务器的数据库,缓存从提供读的服务器拿数据存入缓存区供用户读操作,从而提高效率

image-20210712110355957

整体优化的过程如下:

1)优化数据库的表结构和索引(当一张表的数据量已经特别大时候,表结构已经不好进行修改)

2)文件缓存,通过IO流获取比每次都访问数据库效率略高,但是当访问量爆炸增长时,IO流也承受不了

3)MemCached,当时最热门的技术,通过数据库和数据访问层之间增加一层缓存,第一次访问数据库的时候,把结果放在缓存中,后续的查询先检查缓存,若有直接拿缓存的数据,没有再从数据库查询

1.3 分库分表+水平拆分+集群

image-20210712110919578

1.4 如今的年代

如今信息量井喷式增长,各种各样的出现(定位数据,音乐数据,图片数据等),在大数据背景下,单一的关系型数据库(RDBMS)无法满足大量数据的要求,此时nosql数据库出现就是为了解决这些问题

image-20210712111249020

1.5 为什么要用nosql

用户的个人信息,地理位置,社交网络,用户产生的数据,用户的日志信息都在爆发式增长,此时我们需要用nosql来处理这些数据

因为nosql有一下特点:

1.方便扩展(数据之间没有关系,很好扩展)

2.大数据量高性能(redis每秒可以读11万次,写8万次,缓存记录级别是细粒度的缓存,性能比较高)

3.数据类型是多样的(不用设计表结构,随取随用)

4.和关系型数据库对比

传统的RDMBS
1.结构化组织
2.数据和关系都存在单独的表中
3.操作 (通过sql语句来执行操作)
4.严格的一致性
5.基础的事务

nosql
1.不仅仅是数据
2.没有固定的查询语句
3.键值对存储,列存储,文档存储,图形数据库
4.最终一致性(不用保证整个过程都是一致的,只需要结果是一直的就行)
5.CAP定理和BASE
5.高性能、高可用、高扩展

大数据时代的3V和3高

3V:描述数据的问题

海量的(Velume)多样的(Variety)实时的(Velocity)

3高:对程序的要求

高并发、高可扩、高性能

1.6 nosql的分类

1)KV键值对

  • 新浪 redis
  • 美团 redis+Tair
  • 阿里、百度 redis+memecache

2)文档型数据库

  • mengoDB
    • mengoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • mengoDB是一个介于关系型数据库和非关系数据库中间的数据库产品,mengoDB在非关系数据库中功能丰富,最像关系型数据库。
  • ConthDB

3)列存储数据库

  • HBase
  • 分布式文件系统

4)图关系数据库

​ 存放社交关系的数据库

image-20210712140405598

第二章 redis学习

2.1 基本介绍

什么是redis

redis是一种远程字典服务,由C语言编写的一款开源、支持网络、可基于内存亦可持久化的日志型、kv数据库,并提供了多种语言的api。

和memcached一样,为了保证效率,数据都是缓存在内存中,区别的是redis会周期性的把更新的数据写入磁盘中或者把修改操作写入追加的记录文件。并且再次基础下实现了master-slave(主从)同步。

redis能干什么?

1.内存存储,持久化,内存是断电即失的,所以需要持久化(ROB\AOF)

2.高效率、用于高速缓冲

3.发布订阅系统

4.地图信息分析

5.计时器、计数器(浏览量)

特性

1.多样的数据类型

2.持久化

3.集群

4.事务

window下安装redis

1)解压压缩包

2)双击启动服务

3)各个文件的作用

image-20210713101553091

image-20210713101619724

linux下安装redis

1)把安装包放在opt目录下

image-20210713101719668

2)解压压缩包

tar -zxvf redis-5.0.7.tar.gz 

3)安装c环境

redis是由c语言编写,安装gcc

yum install gcc-c++

4)编译

make

5)安装

make install

6)把redis配置文件移动到redis安装目录/usr/local/bin/mconfig下

rm redis.conf /usr/local/bin/mconfig
  1. 设置redis为守护进程

配置redis.conf

image-20210713134553221

8)启动redis

在/usr/local/bin目录下启动redis

redis-server /usr/local/bin/mconfig/redis.conf

9)启动redis客户端

redis-cli

10)关闭redis

在客户端下shutdown 后exit

redis性能测试

redis-benchmark:redis官方提供的性能测试工具

具体参数如下:

image-20210713135346846

简单测试:100个并发连接100000个请求

redis-benchmark -h localhost -p 6379 -c 100 -n 100000

image-20210713135659565

基础知识
1)redis有16个数据库

image-20210713135827220

2)切换

通过select n进行数据库的切换

image-20210713140020531

3)dbsize可以看当前数据库的大小(key 的数量)

image-20210713140126526

4)不同的数据库是不通的
5)keys * 查看当前数据库的key值
6)flushdb:清空当前数据库的键值对
7) flushall:清空所有数据库的键值对
8)redis是单线程的

redis是基于内存操作的,所以redis的性能瓶颈不是cpu,而且机器内存和网络带宽

9)为什么redis是单线程还那么快

误区1:高性能的服务器一定是多线程的

误区2:多线程的效率一定比单线程块(多线程会涉及上下文切换划去大量时间)

核心:redis把所有的数据放在内存中,所有说使用单线程效率最高,而多线程涉及上下文切换,耗费时间,对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都在一个cpu上,在内存存储数据情况下,单线程就是最佳的方案

2.2 五大基本数据类型

2.2.1 redis-key 的一些常用指令
1)查看当前数据库所有的key
#命令:keys *

image-20210713143930341

2)移除一个key

移除一个key包括(value)到指定的数据库

move  key   dbname

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQfZXrEu-1651225465237)(https://gitee.com/yueguang87/cloudimage/raw/master/img/image-20210713144250226.png)]

3)删除一个key(包括value)
del  name

image-20210713144512512

4)判断key是否存在
exits  keyname

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99ZBvQWC-1651225465239)(C:\Users\10360\AppData\Roaming\Typora\typora-user-images\image-20210713144657439.png)]

5)查看value的数据类型
type  keyname
6)设置键值对过期时间
expire key  second(秒)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oaKH5wN2-1651225465239)(https://gitee.com/yueguang87/cloudimage/raw/master/img/image-20210713145048085.png)]

7)ttl指令

Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:

  1. 当前key没有设置过期时间,所以会返回-1.

    127.0.0.1:6379[1]> ttl key2
    (integer) -1
    
  2. 当前key有设置过期时间,而且key已经过期,所以会返回-2.

    127.0.0.1:6379[1]> ttl key3
    (integer) -2
    
  3. 当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.

    127.0.0.1:6379[1]> expire key4 100
    (integer) 1
    127.0.0.1:6379[1]> ttl key4
    (integer) 81
    

关于重命名RENAMERENAMENX

  1. RENAME key newkey修改 key 的名称

    127.0.0.1:6379[1]> rename key2 key5
    OK
    
  2. RENAMENX key newkey仅当 newkey 不存在时,将 key 改名为 newkey 。

    127.0.0.1:6379[1]> renamenx key1 key5
    (integer) 0
    
2.2.2 String类型

常用指令和实例

1)追加指令
127.0.0.1:6379[1]> set name "hello"
OK
127.0.0.1:6379[1]> append name ",world"
(integer) 11
127.0.0.1:6379[1]> get name
"hello,world"

image-20210713154311210

2)增1或减1
#减1:decr key
#加1:incr key

127.0.0.1:6379[1]> set number 1
OK
127.0.0.1:6379[1]> incr number
(integer) 2
127.0.0.1:6379[1]> get number
"2"
127.0.0.1:6379[1]> decr number
(integer) 1
127.0.0.1:6379[1]> get number
"1"
3)指定数量相加/相减
127.0.0.1:6379[1]> get number
"1"
127.0.0.1:6379[1]> incrby number 5
(integer) 6
127.0.0.1:6379[1]> decrby number 5
(integer) 1

4)为数值加上浮点数数值
127.0.0.1:6379[1]> incrbyfloat number 0.5
"1.5"
5)获取value的长度
127.0.0.1:6379[1]> strlen number
(integer) 3
127.0.0.1:6379[1]> get number
"1.5"
6)根据起止位置获取字符串内容
#getrange key  start end
127.0.0.1:6379[1]> get name
"hello,world"
127.0.0.1:6379[1]> getrange name 0 5
"hello,"

image-20210713155145178

7)给value的指定地方换内容
setrange key  offset  value

image-20210713155336572

8)将指定key的value值改为新值,并返回key的旧值
127.0.0.1:6379[1]> get name
"hello,world"
127.0.0.1:6379[1]> getset name  "world,hello"
"hello,world"
127.0.0.1:6379[1]> get name
"world,hello"
9)仅当key不存在的时候才能set
127.0.0.1:6379[1]> setnx name "hello"
(integer) 0
127.0.0.1:6379[1]> get name
"world,hello"
10)set键值对并设置过期时间
127.0.0.1:6379[1]> setex key6 100 value6
OK
127.0.0.1:6379[1]> ttl key6
(integer) 94
11)批量set键值对
127.0.0.1:6379[1]> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379[1]> keys *
1) "key1"
2) "key5"
3) "k1"
4) "k3"
5) "name"
6) "k2"
7) "number"
12) 批量设置键值对时,仅当参数中所有key都不存在才成功(原子性操作,要么一起成功,要么一起失败)
127.0.0.1:6379[1]> msetnx k1 v1 k10 v10
(integer) 0
127.0.0.1:6379[1]> get key10
(nil)
12)批量获取多个value的值
127.0.0.1:6379[1]> keys *
1) "key1"
2) "key5"
3) "k1"
4) "k3"
5) "name"
6) "k2"
7) "number"
127.0.0.1:6379[1]> mget key1 k2 k3
1) "value1"
2) "v2"
3) "v3"
12)以毫秒为单位设置key的存在时间
#psetex k  毫秒值 v
127.0.0.1:6379[1]> psetex key1 100000 value1
OK
127.0.0.1:6379[1]> ttl key1
(integer) 94

string类型的使用场景:value除了是字符串还可以是数字,场景举例:

  • 计数器
  • 统计多单位的数量,比如粉丝数
  • 对象存储缓存

tip:当存储对象的时候可以用以下方法,不用存储json字符串的形式,把基本属性作为key,真实值为value进行存储

127.0.0.1:6379> mset user:1:name "zhangsan" user:1:age 18
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "18"

2.2.3 list类型
1)基本介绍

redis列表是简单的字符串列表,按照插入顺序排序,数据是可重复的,你可以从列表的左边或者右边添加元素,我们可以通过规则定义变为队列、栈、双端队列等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gvSBrHn9-1651225465241)(C:\Users\10360\AppData\Roaming\Typora\typora-user-images\image-20210713172351721.png)]

正如图中Redis中list是可以进行双端操作的,所有命令也分为两种LXXX和RLLL类

2)常用指令

1)lpush/rpush 从左边/右边向列表中push值

127.0.0.1:6379[1]> lpush mylist v1
(integer) 1
127.0.0.1:6379[1]> lpush mylist v2
(integer) 2
127.0.0.1:6379[1]> lpush mylist v3
(integer) 3
127.0.0.1:6379[1]> rpush mylist1 v1
(integer) 1
127.0.0.1:6379[1]> rpush mylist1 v2
(integer) 2
127.0.0.1:6379[1]> rpush mylist1 v3
(integer) 3

2)获取指定范围的list元素

#lrange k 开始索引 结束索引
127.0.0.1:6379[1]> lrange mylist 0 3
1) "v3"
2) "v2"
3) "v1"

3)向已存在的列名中的头部push值

127.0.0.1:6379> lrange mylist 0 2
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> lpushx mylist v4
(integer) 4
127.0.0.1:6379> lrange mylist 0 3
1) "v4"
2) "v3"
3) "v2"
4) "v1"

4)在指定元素列表的前/后插入值

#linsert k before  具体value  要插入的value
127.0.0.1:6379> linsert mylist before v3 v3.3
(integer) 5
127.0.0.1:6379> lrange mylist 0 4
1) "v4"
2) "v3.3"
3) "v3"
4) "v2"
5) "v1"

5)查看列表长度

127.0.0.1:6379> llen mylist
(integer) 5

6)通过索引获取列表元素

127.0.0.1:6379> lrange mylist 0 4
1) "v4"
2) "v3.3"
3) "v3"
4) "v2"
5) "v1"
127.0.0.1:6379> lindex mylist 4
"v1"

7)通过索引为元素设值

127.0.0.1:6379> lset mylist 4 v4new
OK
127.0.0.1:6379> lrange mylist 0 4
1) "v4"
2) "v3.3"
3) "v3"
4) "v2"
5) "v4new"

8)从最左边/最右边移除值 并返回

#lpop k 
127.0.0.1:6379> lrange mylist 0 4
1) "v4"
2) "v3.3"
3) "v3"
4) "v2"
5) "v4new"
127.0.0.1:6379> lpop mylist
"v4"

# rpop k

127.0.0.1:6379> lrange mylist 0 4
1) "v3.3"
2) "v3"
3) "v2"
4) "v4new"
127.0.0.1:6379> rpop mylist
"v4new"
127.0.0.1:6379> lrange mylist 0 4
1) "v3.3"
2) "v3"
3) "v2"

9)将列表的最右边的最后一个值弹出,并返回,放在另一个列表的头部

127.0.0.1:6379> lrange mylist 0 10
1) "v3.3"
2) "v3"
3) "v2"
127.0.0.1:6379> lrange mylist1 0 10
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> rpoplpush mylist1 mylist
"v3"
127.0.0.1:6379> lrange mylist 0 10
1) "v3"
2) "v3.3"
3) "v3"
4) "v2"

10)通过下标截取指定范围内的列表元素(其它元素剔除)

#ltrim k index1 index2
127.0.0.1:6379> lrange mylist 0 10
1) "v7"
2) "v6"
3) "v5"
4) "v4"
5) "v3"
6) "v3"
7) "v3.3"
127.0.0.1:6379> ltrim mylist 0 4
OK
127.0.0.1:6379> lrange mylist 0 10
1) "v7"
2) "v6"
3) "v5"
4) "v4"
5) "v3"

11)删除指定value的列表元素(因为list允许重复,需要指定删除的个数)

#lrem k count value
127.0.0.1:6379> lrange mylist 0 5
1) "v7"
2) "v6"
3) "v5"
4) "v4"
5) "v3"
127.0.0.1:6379> lrem mylist 1 v3
(integer) 1
127.0.0.1:6379> lrange mylist 0 5
1) "v7"
2) "v6"
3) "v5"
4) "v4"

12)移除并获取列表的第一个/最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素位置

#blpop 	k  second 这里以blpop为例 如果从右边拿改为brpop即可
127.0.0.1:6379> lrange  mylist 0 10
1) "v7"
2) "v6"
3) "v5"
4) "v4"
127.0.0.1:6379> blpop mylist 10
1) "mylist"
2) "v7"
127.0.0.1:6379> blpop mylist 10
1) "mylist"
2) "v6"
127.0.0.1:6379> blpop mylist 10
1) "mylist"
2) "v5"
127.0.0.1:6379> blpop mylist 10
1) "mylist"
2) "v4"
127.0.0.1:6379> blpop mylist 10
(nil)
(10.06s)

13) 把列表的最右边的元素弹到新的列表中,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

brpoplpush 资源k 目标k  时间

小结:

  • list是一个链表,before node after ,left,right都可以插入值
  • 如果key不存在,则创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有元素,空链表也就代表不存在
  • 在两边插入或者改动值,效率最高,修改中间元素效率较低

应用:

消息排队、消息队列、栈

2.2.4 set类型
1)基本介绍

redis的set是string类型的无序集合,集合成员是唯一的,这就意味着集合中不能出现重复的元素。

redis中集合是通过哈希表实现的,所有添加、删除、查查找的复杂度都是O(1)

2)常用指令
  • 添加成员

    #sadd k v
    
    

127.0.0.1:6379[3]> sadd k1 v1
(integer) 1





- 获取元素数量

```bash
#scard k
127.0.0.1:6379[3]> scard k1
(integer) 1
  • 获得集合中所有的成员

    127.0.0.1:6379[3]> smembers k1
    1) "v1"
    
  • 随机返回集合中n个成员

    127.0.0.1:6379[3]> smembers k1
    1) "v4"
    2) "v1"
    3) "v2"
    4) "v3"
    127.0.0.1:6379[3]> srandmember k1 2
    1) "v4"
    2) "v3"
    127.0.0.1:6379[3]> srandmember k1 2
    1) "v2"
    2) "v3"
    127.0.0.1:6379[3]> srandmember k1 2
    1) "v1"
    2) "v2"
    
  • 随机移除并返回集合中n个成员

    # spop k count
    127.0.0.1:6379[3]> smembers k1
    1) "v4"
    2) "v1"
    3) "v2"
    4) "v3"
    127.0.0.1:6379[3]> spop k1 2
    1) "v3"
    2) "v1"
    127.0.0.1:6379[3]> smembers k1
    1) "v4"
    2) "v2"
    
  • 将set集合中的元素移动到另一个set集合中

    #smove 资源k 目标k value
    127.0.0.1:6379[3]> smembers k1
    1) "v4"
    2) "v2"
    127.0.0.1:6379[3]> smembers k2
    1) "v1"
    2) "v3"
    127.0.0.1:6379[3]> smove k1 k2 v4
    (integer) 1
    127.0.0.1:6379[3]> smembers k1
    1) "v2"
    127.0.0.1:6379[3]> smembers k2
    1) "v4"
    2) "v1"
    3) "v3"
    
  • 返回所有集合的差集

    #sdiff set1 set2 返回的是set1中set2没有的值  以set1为标准
    127.0.0.1:6379[3]> smembers k1
    1) "v1"
    2) "v4"
    3) "v2"
    4) "v3"
    127.0.0.1:6379[3]> smembers k2
    1) "v4"
    2) "v1"
    3) "v3"
    127.0.0.1:6379[3]> sdiff k1 k2
    1) "v2"
    
  • 在sdiff的基础上,把差集放在目标集合中

    #sdiffstore 目标set3  资源set1  资源set2
    127.0.0.1:6379[3]> smembers k1
    1) "v1"
    2) "v4"
    3) "v2"
    4) "v3"
    127.0.0.1:6379[3]> smembers k2
    1) "v4"
    2) "v1"
    3) "v3"
    127.0.0.1:6379[3]> sdiffstore k3 k1 k2
    (integer) 1
    127.0.0.1:6379[3]> smembers k3
    1) "v2"
    
  • 返回所有集合的交集

    #sinter set1 set2
    127.0.0.1:6379[3]> smembers k1
    1) "v1"
    2) "v4"
    3) "v2"
    4) "v3"
    127.0.0.1:6379[3]> smembers k2
    1) "v4"
    2) "v1"
    3) "v3"
    127.0.0.1:6379[3]> sinter k1 k2
    1) "v4"
    2) "v1"
    3) "v3"
    
  • 在sinter的基础上,把结果放在集合中

    #sinterstore 目标set 资源set1 资源set2
    127.0.0.1:6379[3]> sinter k1 k2
    1) "v4"
    2) "v1"
    3) "v3"
    127.0.0.1:6379[3]> sinterstore k4 k1 k2
    (integer) 3
    127.0.0.1:6379[3]> smembers k4
    1) "v1"
    2) "v4"
    3) "v3"
    
  • 返回所有集合的并集

    #sunion set1  set2
    127.0.0.1:6379[3]> smembers k1
    1) "v1"
    2) "v4"
    3) "v2"
    4) "v3"
    127.0.0.1:6379[3]> smembers k2
    1) "v4"
    2) "v1"
    3) "v3"
    127.0.0.1:6379[3]> sunion k1 k2
    1) "v3"
    2) "v4"
    3) "v2"
    4) "v1"
    
  • 在sunion的基础上,把结果保存到集合中

    #sunionstore 目标1set 资源set1 资源set2
    127.0.0.1:6379[3]> sunionstore k5 k1 k2
    (integer) 4
    127.0.0.1:6379[3]> smembers k5
    1) "v3"
    2) "v4"
    3) "v2"
    4) "v1"
    

tip:set常用来做共同关注,共同好友等

2.2.5 hash 类型
1)基本介绍

redis的hash 是一个String类型的field和value的映射表,hash特别适合存储对象,相当于map

2)常用命令
  • 添加一个元素

    #hset key key value
    127.0.0.1:6379[15]> hset k1 k1 v1
    (integer) 1
    
  • 添加多个元素

    #hmset k   k1 v1 k2 v2
    127.0.0.1:6379[15]> hmset k1 k2 v2 k3 v3 k4 v4
    OK
    
  • 只有map中的k不存在的时候,才新增成功(如果存在,则新增不了)

    #hsetnx  k  k v
    127.0.0.1:6379[15]> hsetnx k1 k1 k1_new
    (integer) 0
    
  • 查看哈希表key中,指定的key是否存在

    #hexists k  k
    127.0.0.1:6379[15]> hexists k1 k1
    (integer) 1
    
  • 获取存储在哈希表中指定字段的值

    #hget k  k
    127.0.0.1:6379[15]> hget k1 k1
    "v1"
    
  • 获取多个存储在哈希表中指定字段的值

    127.0.0.1:6379[15]> hmget k1 k1 k2 k3
    1) "v1"
    2) "v2"
    3) "v3"
    
  • 获取在哈希表key的所有字段和值

    #hgetall k
    127.0.0.1:6379[15]> hgetall k1
    1) "k1"
    2) "v1"
    3) "k2"
    4) "v2"
    5) "k3"
    6) "v3"
    7) "k4"
    8) "v4"
    
  • 获取哈希表中所有的key

    #hkeys k
    127.0.0.1:6379[15]> hkeys k1
    1) "k1"
    2) "k2"
    3) "k3"
    4) "k4"
    
  • 获取哈希表中所有的value

    #hvals k
    127.0.0.1:6379[15]> hvals k1
    1) "v1"
    2) "v2"
    3) "v3"
    4) "v4"
    
  • 获取哈希表中key的数量

    #hlen k
    127.0.0.1:6379[15]> hlen k1
    (integer) 4
    
  • 删除哈希表一个或多个元素

    #hdel k  k1 k2
    127.0.0.1:6379[15]> hdel k1 k1 k2
    (integer) 2
    127.0.0.1:6379[15]> hkeys k1
    1) "k3"
    2) "k4"
    
  • 为哈希表指定k的value增加(减少)指定的值

    #hincrby  k   k  count
    127.0.0.1:6379[15]> hset k2 k1 1
    (integer) 1
    127.0.0.1:6379[15]> hincrby k2 k1 10
    (integer) 11
    127.0.0.1:6379[15]> hget k2 k1
    "11"
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzqUKpbV-1651225465242)(C:\Users\10360\AppData\Roaming\Typora\typora-user-images\image-20210714075819881.png)]

  • 为哈希表key中的指定字段的浮点数加载增量

    #hincrbyfloat k k  float
    127.0.0.1:6379[15]> hincrbyfloat k2 k1 0.5
    "11.5"
    127.0.0.1:6379[15]> hget k2 k1
    "11.5"
    
  • 迭代哈希表中键值对

HSCAN key cursor [MATCH pattern] [COUNT count]
2.2.6 zset类型

1)基本介绍

zset是有序集合,和set不同的是,zset的每个元素都关联一个double类型的分数(score),redis正是通过分数来为集合中的成员进行从小到大的排序。

如果score相同,则通过字典排序

有序集合的成员是唯一的,但是分数可以相同

2)常用命令

  • 添加成员

    127.0.0.1:6379[5]> zadd k1 10 v1 20 v2 30 v3 40 v4
    (integer) 4
    
  
- 计算在有序集合中指定区间score的成员数

  ```bash
  #zcount key  min max
  127.0.0.1:6379[5]> zcount k1 20 40
  (integer) 3
  • 有序集合对指定成员的分数加上增量n

    127.0.0.1:6379[5]> zincrby k1 20 v1
    "30"
    
  • 返回有序集合中,成员的分数值

    127.0.0.1:6379[5]> zscore k1 v1
    "30"
    
  
- 返回有序集合中指定成员的索引

  ```bash
  127.0.0.1:6379[5]> zrank k1 v1
  (integer) 1
  127.0.0.1:6379[5]> zrank k1 v2
  (integer) 0
  • 通过索引区间返回有序集合指定区间的成员

    #zrange  k  start  end
    127.0.0.1:6379[5]> zrange k1 0 1
    1) "v2"
    2) "v1"
    
  • 从小到大排序,带score返回

    #zrangebyscore  k   min  max  withscores     (-inf  +inf  代表负无穷到正无穷)
    127.0.0.1:6379[5]> zrangebyscore k1 10 30  withscores
    1) "v2"
    2) "20"
    3) "v1"
    4) "30"
    5) "v3"
    6) "30"
    
  • 从大到小排序,带score返回(其实是根据索引排序返回)

    #zrevrange k start stop  withscores
    127.0.0.1:6379[5]> zrevrange k1 0 -1 withscores
    1) "v4"
    2) "40"
    3) "v3"
    4) "30"
    5) "v1"
    6) "30"
    7) "v2"
    8) "20"
    
  • 移除有序集合中一个/多个成员

    #zrem k v
    127.0.0.1:6379[5]> zrem k1 v1 v2
    (integer) 2
    
  • 移除有序集合中给定字典区间的成员(根据字典顺序)

    #zremrangebylex  k  - +
    127.0.0.1:6379[5]> zremrangebylex k1 - +
    (integer) 2
    
  • 移除有序集合中给定的排名区间的成员(根据排名顺序)

    #zremrangebyrank k min max
    127.0.0.1:6379[5]> zremrangebyrank k1 0 100
    (integer) 3
    
  • 移除有序集合中给定的分数区间的所有成员

    #zremrangebyscore k min max
    127.0.0.1:6379[5]> zremrangebyscore k1 10 20
    (integer) 2
    
  • 返回有序集合中指定分数去区间内的成员,分数从高到低排序

    #zrevrangebyscores k  max min  withscores
    127.0.0.1:6379[5]> zrevrangebyscore k1 100 0  withscores
    1) "v4"
    2) "40"
    3) "v3"
    4) "30"
    
  • 返回有序集合中指定字典区间的成员,按字典顺序排序

    #zrevrangebylex k  min max  这一块要redis官网  [符号不能省略
    127.0.0.1:6379[5]> zrevrangebylex k2 [c [aa
    1) "c"
    2) "b"
    
  • 计算给定的一个或多个有序集的交集并将结果集存储到新的有序集合key中,numkeys表示参与运算的集合的个数,scores为交集元素的scores和

    #zinterstore 目标set  numkeys  资源1zset 资源2zset
    127.0.0.1:6379[5]> zadd myset1 10 a 20 b
    (integer) 2
    127.0.0.1:6379[5]> zadd myset2 20 b 30 c
    (integer) 2
    127.0.0.1:6379[5]> zinterstore myset3 2 myset1 myset2
    (integer) 1
    127.0.0.1:6379[5]> zrange myset3 0 -1 withscores
    1) "b"
    2) "40"
    
  • 计算给定的一个或多个有续集的并集并将结果存储到新的有序集合key中,numkeys表示参与运算的集合的个数,scores为交集元素的scores和

#zunionstore 目标set  numkeys  资源1zset 资源2zset
127.0.0.1:6379[5]> zrange myset1 0 -1
1) "a"
2) "b"
127.0.0.1:6379[5]> zrange myset2 0 -1
1) "b"
2) "c"
127.0.0.1:6379[5]> zunionstore myset4 2 myset1 myset2
(integer) 3
127.0.0.1:6379[5]> zrange myset4 0 -1
1) "a"
2) "c"
3) "b"
127.0.0.1:6379[5]> zrange myset4 0 -1 withscores
1) "a"
2) "10"
3) "c"
4) "30"
5) "b"
6) "40"
  • 迭代有序集合中的元素

    ZSCAN key cursor [MATCH pattern\] [COUNT count]
    

2.3 三大特殊数据类型

2.3.1 geospatial类型
1)基本介绍

geosptial的底层是zset类型,zset类型的命令对geosptial也有作用

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。
2)常见命令
  • 添加地理位置

    #我们可以把地理位置的经纬度存在redis中,然后进行各种操作
    #geoadd k  纬度  经度  地点名
    127.0.0.1:6379[7]> geoadd china:city 116.35 40.40 beijing
    (integer) 1
    
  • 获取集合中一个/多个成员的坐标

    #geopos k  地点名1  地点名2
    127.0.0.1:6379[7]> geopos china:city beijing
    1) 1) "116.35000258684158"
       2) "40.399999187562123"
    
  • 返回两个给定位置之间的距离,默认以m为单位

    #geodist k  地点名1 地点名2 距离单位
    127.0.0.1:6379[7]> geodist china:city beijing zhengzhou km
    "672.8330"
    
  • 以给定的经纬度为中心,设定一个半径,返回指定数量的半径内区域的地点

    #georadius k  中心纬度 中心经度 距离 单位 withcoord(展示经纬度) withdist(展示距离中心位置) count number(显示的数量)
    127.0.0.1:6379[7]> georadius china:city 116.35 40.40 1000 km withcoord withdist withdist count 1
    1) 1) "beijing"
       2) "0.0002"
       3) 1) "116.35000258684158"
          2) "40.399999187562123"
    
  • 以集合中存在的地点为中心,展示半径内的地点信息

    #georadiusbymember k  成员  距离 单位 withcoord withdist count 数量
    127.0.0.1:6379[7]> georadiusbymember china:city zhengzhou 2000 km withcoord withdist count 10
    1) 1) "zhengzhou"
       2) "0.0000"
       3) 1) "113.62000197172165"
          2) "34.749999265106908"
    2) 1) "beijing"
       2) "672.8330"
       3) 1) "116.35000258684158"
          2) "40.399999187562123"
    
  • 返回一个或多个位置的geohash,使用geohash位置52点整数编码(用hash值表示经纬度)

    #geohash k  v
    127.0.0.1:6379[7]> geohash china:city zhengzhou
    1) "ww0v9qp93v0"
    
2.3.2 hyperloglog类型

1)基本介绍

HyperLogLog不是集合,不会存储元素本身,而是添加的时候计数不重复元素的一种算法,不是redis独有的。当添加量非常大时,存在0.81%标准误差。HyperLogLog最大只占12KB内存,可以计算近264元素的基数。

2)基本指令

  • 添加基数

    #pfadd  k  v1  v2 v3....
    127.0.0.1:6379[7]> pfadd test1 a b c d e f a a a
    (integer) 1
    
  • 获得基数

    #pfcount k
    127.0.0.1:6379[7]> pfcount test1
    (integer) 6
    
  • 基数合并

    #pfmerge  目标k  资源1k  资源2k
    127.0.0.1:6379[7]> pfadd test2 c d e f g h i j k
    (integer) 1
    127.0.0.1:6379[7]> pfmerge test3 test1 test2
    OK
    127.0.0.1:6379[7]> pfcount test3
    (integer) 11
    

3)应用场景

访问量,在线人数,搜索次数,注册人数

2.3.3 bitmap类型
1)基本介绍

redis提供的bitmap可以实现对位的操作,bitmap本身不是一种数据结构,实际上是字符串,但是可以对字符串的位进行操作。

可以把bitmaps详细成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps叫做偏移量。单个bitmaps的最大长度为2的32位比特,512M

2)命令
  • 添加元素
#以下案例是统计周一到周日的签到信息 0表示未签到 1表示签到
#setbit k offset 0|1

127.0.0.1:6379[7]> setbit sign 0 0
(integer) 0
127.0.0.1:6379[7]> setbit sign 1 1
(integer) 0
127.0.0.1:6379[7]> setbit sign 2 1
(integer) 0
127.0.0.1:6379[7]> setbit sign 3 1
(integer) 0
127.0.0.1:6379[7]> setbit sign 4 1
(integer) 0
127.0.0.1:6379[7]> setbit sign 5 1
(integer) 0
127.0.0.1:6379[7]> setbit sign 6 1
(integer) 0
  • 获取值

    # getbit k  offset
    127.0.0.1:6379[7]> getbit sign 4
    (integer) 1
    
  • 统计数量

    #bitcount k  start  end
    127.0.0.1:6379[7]> bitcount sign
    (integer) 6
    

2.4 事务

1)基本介绍

redis事务本质:一组命令的集合。把命令先放在队列中,然后通过执行命令去执行。

事务中每条命令都会被排序,执行过程中按顺序执行,不允许其它命令进行干扰。

redis事务特性

  • 一次性
  • 顺序性
  • 排他性

redis事务没有隔离级别的概念

redis单条命令保证原子性,事务不保证原子性

2)redis事务的操作过程
  • 开启事务multi
  • 命令入队(编写各个命令)
  • 执行事务(exec)

事务加入队列时并没有被执行,直到提交时才会开始执行(exex)一次性完成。

3)实战操作
  • 正常事务操作
127.0.0.1:6379> multi   #开启事务
OK
127.0.0.1:6379> set name zhangsan  #命令入队
QUEUED
127.0.0.1:6379> get name  #命令入队
QUEUED
127.0.0.1:6379> lpush list zhangsan wangwu  #命令入队
QUEUED
127.0.0.1:6379> exec  #执行事务 一次性执行完所有命令
1) OK
2) "zhangsan"
3) (integer) 2


  • 取消事务操作

    127.0.0.1:6379> multi   #开启事务
    OK
    127.0.0.1:6379> set name wangchenyang #命令入列
    QUEUED
    127.0.0.1:6379> lpush list lisi wangwu  #命令入列
    QUEUED
    127.0.0.1:6379> discard  #取消事务 
    OK
    127.0.0.1:6379> exec
    (error) ERR EXEC without MULTI  #事务未被执行
    127.0.0.1:6379> get name  #set name wangchenyang  未被执行
    (nil)
    
    
  • 事务错误

    1)代码语法错误(编译异常),所有的命令都不执行

    127.0.0.1:6379> multi  #开始事务
    OK
    127.0.0.1:6379> set name wang
    QUEUED
    127.0.0.1:6379> setget name 123  #故意语法错误
    (error) ERR unknown command `setget`, with args beginning with: `name`, `123`, 
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors. #编译错误,事务无法执行
    
    
    

    2)代码逻辑错误(运行时异常),除了出错的命令,其它的命令都可以正常执行

    127.0.0.1:6379> set name wang    #设置value为字符串
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> lpush list lisi
    QUEUED
    127.0.0.1:6379> incr name  #字符串无法相加,运行命令会报错
    QUEUED
    127.0.0.1:6379> exec
    1) (integer) 1
    2) (error) ERR value is not an integer or out of range  #事务被执行了,没问题的命令正常运行
    127.0.0.1:6379> lrange 0 -1
    (error) ERR wrong number of arguments for 'lrange' command
    127.0.0.1:6379> lrange list 0 -1
    1) "lisi"
    
    
    
    

2.5 监控

1)基本介绍

  • 悲观锁

    很悲观,认为什么都可能发生,无论做什么都会加锁

  • 乐观锁

    很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据

    mysql是通过version字段判断,先获取version,更新的时候再比较version

2)redis的监控功能相当于version

举例:

正常情况

127.0.0.1:6379> set money 100    # 有100块钱
OK
127.0.0.1:6379> set out 0  #输出目前是0
OK
127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> decrby money 10 #花掉10块
QUEUED
127.0.0.1:6379> incrby out 10  #输出加10块
QUEUED
127.0.0.1:6379> exec
1) (integer) 90
2) (integer) 10

非正常情况(在事务执行之前,其它的线程更新了money值)

比如

线程1开启事务还未执行

127.0.0.1:6379[7]> set money 100
OK
127.0.0.1:6379[7]> set out 0
OK
127.0.0.1:6379[7]> multi
OK
127.0.0.1:6379[7]> decrby money 10
QUEUED
127.0.0.1:6379[7]> incrby out 10
QUEUED

线程2修改了money

127.0.0.1:6379[7]> set monty 80
OK

此时线程1执行exec命令 得到结果90肯定是不对的

127.0.0.1:6379[7]> exec
127.0.0.1:6379[7]> get money
"90"

乐观锁处理

1.先开启事务,命令入队,但是还未执行事务操作

7.0.0.1:6379> watch money  #相当于乐观锁,观察money更新状态,如果执行事务之前,和检测时的值不同则开锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED

2.其他线程更改了money值

127.0.0.1:6379> set money 1000
OK

3.原线程执行事务会失败,已经被上锁

127.0.0.1:6379> exec
(nil

4.解锁,重新事务操作

127.0.0.1:6379> unwatch   #解锁
OK
127.0.0.1:6379> multi   #开启事务
OK
127.0.0.1:6379> decrby money 10  #花10块
QUEUED
127.0.0.1:6379> incrby out 10  #支出10块
QUEUED
127.0.0.1:6379> exec  #执行事务
1) (integer) 990
2) (integer) 10

第三章 java应用redis

3.1 jedis

1)基本介绍

jedis是redis官方推荐使用的java连接redis的客户端。

2)使用

  • 导包

    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.6.1</version>
            </dependency>
    
  • 使用

 Jedis jedis = new Jedis("localhost",6379);  //声明jedis对象,添加主机和端口连接redis
        Transaction multi = jedis.multi(); //开启事务,声明一个操作对象
        try {
            multi.set("name","lisi");//指令
            multi.get("name");//指令
            multi.exec();//执行事务
        }catch (Exception e){
            multi.discard(); //取消事务
            System.out.println(e);
        }finally {
            System.out.println(jedis.get("name"));
            jedis.close();//关闭连接
        }
  • 常用api

    redis的命令操作,jedis是通过方法来实现的,根据平时的命令选择方法对redis进行操作

3.2 springboot整合redis

1)基本介绍

在springboot2.x版本后,原先的jedis被替换为了lettuce

2)jedis和lettuce的区别

  • jedis

    采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全,使用jedis pool连接池,更像bio模式

  • lettuce

    采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,更像nio模式

3)源码分析

@Bean
@ConditionalOnMissingBean(name = "redisTemplate") 
// 我们可以自己定义一个redisTemplate来替换这个默认的!
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化!(默认的序列化是jdk序列化)
// 两个泛型都是 Object, Object 的类型,我们后使用需要强制转换 <String, Object>
    RedisTemplate<Object, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}
@Bean
@ConditionalOnMissingBean // 由于 String 是redis中最常使用的类型,所以说单独提出来了一个bean!
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

springboot配置redis

1.根据springboot的自动装配的原理,整合一个组件进行配置时,一定会有一个自动配置类xxxAutoConfiguration,并且一定在factories中一定可以找到这个类的完全限定名。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ML9dklui-1651225465243)(https://gitee.com/yueguang87/cloudimage/raw/master/img/image-20210715151339681.png)]

2.进入这个自动配置类中

image-20210715153150520

3.redis的相关配置项都是在RedisProperties.class中

image-20210715153332875

4.在application.properties中配置redis的配置项

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWrM6kon-1651225465244)(C:\Users\10360\AppData\Roaming\Typora\typora-user-images\image-20210715153741009.png)]

5.测试

 @Test
    public void testRedis(){
        redisTemplate.opsForValue().set("name","wangchenyang");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

6.关于序列化

redis默认的序列化是通过jdk序列化器,所以有时回出现乱码的情况

我们重新定义redisTemplate对redis对象设置不同的序列化

  @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

第四章 redis配置文件

1)基本介绍

我们启动redis的时候是基于redis的配置文件启动的

2)配置详解

我们按照配置文件模块详解各个模块的主要配置、

  • 单位

    配置文件对大小写不敏感

  • includes

    包含,一个配置文件可以包含多个配置文件的路径,启动的时候读取多个配置文件的内容

  • network

bind 127.0.0.1	#绑定ip   不注释掉代表只能在本机访问 如果需要远程访问 需要设置ip 搭建集群的时候最好注释掉
protected-mode yes		#保护模式  改为no才可以被远程访问
prot 6379		#端口号  配置集群的时候需要修改
  • general

    通用配置

    daemonize  yes  #守护进程,默认是no,yes代表以守护进程的方式运行
    pidfile /var/run/redis_6379.pid #如果以后台方式运行,我们需要指定一个pid进程文件
    #日志级别
    # Specify the server verbosity level.
    # This can be one of:
    # debug (a lot of information, useful for development/testing)
    # verbose (many rarely useful info, but not a mess like the debug level)
    # notice (moderately verbose, what you want in production probably)生产环境使用
    # warning (only very important / critical messages are logged)
    loglevel notice #日志级别
    logfile '' #文件名。
    database 16 #数据库数量,默认是16
    always-show-logo yes #默认是yes 是否显示logo
    
  • snapshotting

    快照,对rdb持久化操作

save 900 1	  #如果900s内,至少有 1 个key进行修改,我们就进行持久化操作
save 300 10   #如果300s内,至少有 10 个key进行修改,我们就进行持久化操作
save 60 10000 #如果60s内,至少有 10000ey进行修改,我们就进行持久化操作
stop-writes-on-bgsave-error yes #持久化出错后是否还会继续工作,默认是yes
rdbcompression yes #是否压缩我们的rdb文件,默认是yes,会损耗我们的cpu
rdbchecksum yes #保存rdb文件的时候,是否开启进行错误的检查校验
dir ./ #文件保存的目录
  • replication

    主从复制中会使用这个参数

  • security

    #安全,默认情况下redis是没有密码的,我们可以来设置密码
    127.0.0.1:6379> config get requirepass
    1) "requirepass"
    2) ""		#默认为空的
    #配置文件设置
    # requirepass foobareds
    requirepass 123456
    #通过命令行的模式添加一个密码
    127.0.0.1:6379> CONFIG SET requirepass "123456"#设置redis密码
    OK
    127.0.0.1:6379> CONFIG get requirepass	#获取密码
    1) "requirepass"
    2) "123456"
    127.0.0.1:6379> auth 123456		#使用密码登录
    OK
    127.0.0.1:6379> ping
    PONG
    
  • clients

#客户端限制 可是设置最大的客户端连接数
# maxclients 10000 		#默认最大客户端连接数量
# maxmemory <bytes>		#设置Redis的最大内存容量 
# maxmemory-policy noeviction#设置一些策略防止内存溢出
	#移除一些过期的key。。。。。
    #1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
    #2、allkeys-lru : 删除lru算法的key   
    #3、volatile-random:随机删除即将过期key   
    #4、allkeys-random:随机删除   
    #5、volatile-ttl : 删除即将过期的   
    #6、noeviction : 永不过期,返回错误

  • append only mode
#aof的一些配置
appendonly no  #默认不开启aof模式,默认通过rdb方式持久化,在大部分情况下,rdb就足够了
appendfilename "appendonly.aof" #持久化文件的名字
# appendfsync always	#每次修改都会同步,速度比较慢
appendfsync everysec	 #每秒执行一次sync,可能会丢掉这一秒的数据
#appendfsync no   不同步,这个时候操作系统自己同步数据,速度最快

第五章 redis持久化

redis为什么要持久化?因为redis是内存数据库,如果不将内存中的数据持久化到磁盘中,一旦服务器退出,服务器的数据库状态也会消失。所以需要持久化

redis持久化的两种方式aof和rdb

5.1 rdb持久化

1)基本原理

在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时将快照文件直接读到内存中。

redis会单独创建(fork)一个子进程来进行持久化,先将数据写到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何io操作的,这就确保了极高的性能。如果需要大规模数据的恢复,且对于数据恢复的完整性要求不是非常敏感,那rdb方式要比aof方式更加的高效。rdb的缺点是最后一次持久化后的数据可能丢失。

功能核心函数rdbSave(生成RDB文件)和rdbLoad(将文件加载内存)两个函数

image-20210716171442648

reb保存的文件是dump.rdb

# The filename where to dump the DB
dbfilename dump.rdb

2)rdb持久化的触发规则

  • 配置文件配置save规则,当save规则被满足的时候,rdb会进行持久化操作
  • 执行flushall命令,也会触发我们的rdb规则
  • 当我们退出redis的时候,也会生出rdb文件
  • 被焚毁时会自动生成一个dump.db

3)rdb是如何恢复数据的?

只需要把rdb文件放在我们的redis启动目录即可,redis启动的时候会自动检查durm.rdb恢复其中的数据

4)rdb的优点和缺点

  • 优点

    如果对数据的完整性要求不高,适合大规模的数据恢复

  • 缺点

    需要一定的时间间隔进行持久化,如果redis意外宕机,最后一次修改的数据就没了

5.2 aof持久化

append only file 追加文件

将我们的命令都记录到一个文件中,恢复的时候把这个文件再执行一遍。

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

1)什么是aof

快照功能(RDB)并不是非常耐久,如果redis因为某些故障而停机,服务器将丢失最近写入,以及未保存到快照中的那些数据。

append only file yes表示启动aof

默认是不开启的,需要手工配置后重启redis就会生效

如果aof文件有错位,此时redis是启动不起来的,我们需要修复这个aof文件

redis提供了一个工具来修复aof文件 redis-check-aof --fix

2)aof的优缺点

  • 优点

    1.每次修改都会同步,文件的完整性更好

    2.每秒同步一次,可能只会丢失一秒钟的数据

    3.从不同步,效率最高

  • 缺点

    1.相对于数据文件来说,aof文件比rdb文件大的多

    2.aof恢复数据的速度很慢,所以redis默认的持久化就是rdb持久化

3)扩展

1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式

  • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
  • RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。

5、性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
  • 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

第六章 redis发布订阅

redis发布订阅(pub/sub)是一种消息通信模式:发送者发送信息,订阅者接受消息。微信、微博、 关注系统!

redis客户端可以订阅任意数量的频道。

命令:

#psubscribe pattern  订阅一个或多个符合给定模式的频道。

#punsubscribe pattern 退订一个多多个符合给定模式的频道

#pubsub subcommand argument 查看订阅与发布系统状态

#publish channel message 向指定频道发布消息

#subscreibe channel 订阅一个或多个频道

#unsubscribe channel  退订一个或多个频道

代码:

订阅者订阅频道 一直等待消息

127.0.0.1:6379> SUBSCRIBE wcy
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "wcy"
3) (integer) 1
1) "message"
2) "wcy"
3) "hello"

发布者发布消息

127.0.0.1:6379> PUBLISH wcy hello
(integer) 1

第七章 redis进阶

7.1 主从复制

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

默认情况下,每台redis服务器都是主节点,且一个主节点可以有多个从节点,但一个从节点只能有一个主节点。

主从复制的作用如下:

1)数据冗余:主从复制实现了数据的热备份,是持久化外的一种数据冗余方式。

2)故障恢复:当主节点出现问题,可以从节点提供服务,实现快读的故障恢复,实际上是一种服务的冗余

3)负载均衡:当主从复制的基础上,配合读写分离,可以由主节点提供写的服务,从节点提供读的服务,分担服务负载,尤其是在写少读多的情况下,通过多个从节点分担读负载,可以大大提高redis服务器的并发量。

  1. 高可用基石:除了上述作用外,主从复制还是哨兵和集群能够实施的基础,因此主从复制的redis是高可用的基础。

一般来说,将redis用于工程项目组,只使用一台redis服务是万万不行的。原因如下:

1)从结构上,单个redis服务器会发生单点故障,并且一台redis服务器需要处理所有的请求负载,压力较大。

2)从容量上,单个redis服务器的内存容量有限,就算一台redis服务器内存容量为256G,也不能将所有的内存作为redis存储内存,一般来说,单台redis的最大使用内存不应该超过20G

电商网站上的商品,一般都是一次上传,多次浏览。对于这种场景,我们可以使用一下架构

image-20210716100258769

主从复制!读写分离!80%都是读的情况,减缓服务器的压力,架构中经常使用一主二从架构,只要在公司中,主从复制是必须要使用的。

总结:

1.单台服务器难以负载大量的请求

2.单台服务器故障率高,系统崩坏几率大

3.单台服务器的内存容量有限。

7.1.1主从复制环境搭建

我们只配置从机,不用配置主机

1)查看当前主从复制情况

127.0.0.1:6379> info replication
# Replication
role:master  //主分支
connected_slaves:0 //从复制为零
master_replid:9999fa515882c7ebd556d39c59537e2e4c978621
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

2)复制三个配置文件,修改配置文件内容

  • 修改端口
  • 修改pid文件名字
  • 修改log文件名字
  • 修改dump.rdb名字
[root@bogon mconfig]# cp redis.conf  redis_6380.conf
[root@bogon mconfig]# cp redis.conf  redis_6381.conf
[root@bogon mconfig]# cp redis.conf  redis_6382.conf

3)开启三个客户端启动三个服务

7.1.2 主从复制实现

注意:默认情况下,每个redis服务器都是主节点,我们一般情况下配置从机就可以了

认老大模式进行主从机配置

1)配置从机的两个方式和区别

  • 通过命令行配置

    #查看当前本机配置情况
    127.0.0.1:6380> info replication
    # Replication
    role:master
    connected_slaves:0
    master_replid:14e92f4f4c83fa39beb744a2f02abe3d998aacd5
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:0
    second_repl_offset:-1
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0
    
    
    #找老大,配置从机 
    127.0.0.1:6380> slaveof 127.0.0.1 6379
    OK
    127.0.0.1:6380> info replication
    # Replication
    role:slave    #角色成为从机
    master_host:127.0.0.1  #主机信息
    master_port:6379
    master_link_status:up
    master_last_io_seconds_ago:2
    master_sync_in_progress:0
    slave_repl_offset:0
    slave_priority:100
    slave_read_only:1
    connected_slaves:0
    master_replid:096211c9b9e726615ba81cb3611760628b57271c
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:0
    second_repl_offset:-1
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:1
    repl_backlog_histlen:0
    
    #通过命令行配置,只在当前有效,重启后重新回到默认配置
    
  • 通过配置文件,让从机配置永久生效

    # Master-Replica replication. Use replicaof to make a Redis instance a copy of
    # another Redis server. A few things to understand ASAP about Redis replication.
    #
    #   +------------------+      +---------------+
    #   |      Master      | ---> |    Replica    |
    #   | (receive writes) |      |  (exact copy) |
    #   +------------------+      +---------------+
    #
    # 1) Redis replication is asynchronous, but you can configure a master to
    #    stop accepting writes if it appears to be not connected with at least
    #    a given number of replicas.
    # 2) Redis replicas are able to perform a partial resynchronization with the
    #    master if the replication link is lost for a relatively small amount of
    #    time. You may want to configure the replication backlog size (see the next
    #    sections of this file) with a sensible value depending on your needs.
    # 3) Replication is automatic and does not need user intervention. After a
    #    network partition replicas automatically try to reconnect to masters
    #    and resynchronize with them.
    #
    # replicaof <masterip> <masterport> 
    replicaof 172.0.0.1 6379 #添加这行代码
    # If the master is password protected (using the "requirepass" configuration
    # directive below) it is possible to tell the replica to authenticate before
    # starting the replication synchronization process, otherwise the master will
    # refuse the replica request.
    
    
7.1.3 主从复制规则
#1.从机只能读,不能写,主机可以读但是多用于写
#2.当主机断电宕机后,默认情况下,从机的角色是不会改变,集群中只是失去了写的操作,当主机恢复后,又会连接上从机恢复原状
#3.当从机宕机后,若不是配置文件配置的从机,再次启动后是无法获取主机的数据的,若此时重新配置成从机,又可以获得主机的所有数据。
#4.默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机
#1)从机手动执行命令slaveof no one 这样执行后从机都会变成主机
#2)使用哨兵模式(自动选举)
7.2.4 复制原理

slave 启动成功后连接到master后会发送一个sync同步命令

master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件给slave,并完成一次完全同步

全量复制:slave服务在接受的数据文件后将其存盘并加载到内存中

增量复制:master继续将新的所有收集到的修改命令一次传给slave,完成同步

但是只要重新连接master,一次全量复制将被自动执行,我们的数据一定可以在从机中看到

7.2 哨兵模式

1)基本介绍

主从切换的方式是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多适合,我们优先考虑哨兵模式。redis从2.8开始正式提供了sentinel(哨兵)架构来解决这个问题。能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

哨兵模式是一个特殊的模式,首先redis提供了哨兵的命令,哨兵是一个独立的进程,座位进程,它会独立运行。其原理是哨兵通过发送命令,等待redis服务器响应(如果没有响应,说明该台redis服务器已经宕机)从而监控运行的多个redis实例。

2)哨兵的作用

  • 通过发送命令,让redis服务器返回监控其运行状态,包括主服务器和从服务器
  • 当哨兵监测到master宕机,会自动将slave切换到master,通过发布订阅模式通知其它的从服务器,修改配置文件,切换主机的时候

一个哨兵进程对redis服务器进程监控可能会出现问题,为此,我们可以使用多个哨兵进程监控,各个哨兵之间互相监控,可以把哨兵模式当成主从复制的升级版

假设主服务器宕机,哨兵1会监测到这个结果,系统并不会马上进行failover(故障转移)过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也监测到主服务器不可用,并且数量达到一定值时,那么哨兵之间会进行一次投票。投票的结果由一个哨兵发起,进程failover故障转移操作,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机。这个过程成为客观下线。

3)实战

  • 配置哨兵配置文件sentinel.conf

    #配置文件名字不能错
    sentinel monitor myredis 127.0.0.1 6379 1
    
  • 启动哨兵模式

[root@bogon bin]# redis-sentinel /usr/local/bin/mconfig/sentinel.conf  #命令
3391:X 16 Jul 2021 15:15:19.511 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3391:X 16 Jul 2021 15:15:19.512 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=3391, just started
3391:X 16 Jul 2021 15:15:19.512 # Configuration loaded
3391:X 16 Jul 2021 15:15:19.512 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 5.0.7 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 3391
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

3391:X 16 Jul 2021 15:15:19.512 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
3391:X 16 Jul 2021 15:15:19.513 # Sentinel ID is 660649d8624de3b35570102e0140ee4b0c89267b
3391:X 16 Jul 2021 15:15:19.513 # +monitor master myredis 127.0.0.1 6379 quorum 1 #从节点
3391:X 16 Jul 2021 15:15:19.513 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379     #从节点
3391:X 16 Jul 2021 15:15:19.516 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379     #从节点

4) 哨兵模式优缺点

优点:

1.哨兵集群,基于主从复制模式,所有主从复制的优点,它都有

2.主从可以切换,故障可以转移,系统的可用性更好

3.哨兵模式是主从模式的升级,手动到自动,更加健壮

缺点:

1.redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦

2.实现哨兵模式的配置其实是很麻烦的,里边有很多配置项

# Example sentinel.conf
#哨兵sentinel实例运行的端口 默认26379

port 26379


#哨兵sentinel的工作目录

dir /tmp


#哨兵sentinel监控的redis主节点的 ip port

master-name  #可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。

quorum #当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了

sentinel monitor <master-name> <ip> <redis-port> <quorum>

sentinel monitor mymaster 127.0.0.1 6379 1


#当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码

#设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码

sentinel auth-pass <master-name> <password>

sentinel auth-pass mymaster MySUPER--secret-0123passw0rd


#指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒

sentinel down-after-milliseconds <master-name> <milliseconds>

sentinel down-after-milliseconds mymaster 30000


#这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,

#这个数字越小,完成failover所需的时间就越长,

#但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。

#可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。


sentinel parallel-syncs <master-name> <numslaves>

sentinel parallel-syncs mymaster 1


#故障转移的超时时间 failover-timeout 可以用在以下这些方面:

#1. 同一个sentinel对同一个master两次failover之间的间隔时间。

#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。

#3.当想要取消一个正在进行的failover所需要的时间。

#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了

#默认三分钟

sentinel failover-timeout <master-name> <milliseconds>

sentinel failover-timeout mymaster 180000


SCRIPTS EXECUTION

#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。

#对于脚本的运行结果有以下规则:

#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10

#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。

#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。

#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,

#一个是事件的类型,

#一个是事件的描述。

#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。

#通知脚本

sentinel notification-script <master-name> <script-path>

sentinel notification-script mymaster /var/redis/notify.sh


#客户端重新配置主节点参数脚本

#当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。

#以下参数将会在调用脚本时传给脚本:

<master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>

#目前<state>总是“failover”,

<role>是“leader”或者“observer”中的一个。

#参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的

#这个脚本应该是通用的,能被多次调用,不是针对性的。

sentinel client-reconfig-script <master-name> <script-path>

sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

7.3 redis缓存穿透、击穿、雪崩

1)基本认识

  • 缓存穿透:key对应的数据在数据源中并不存在,每次针对此key的请求从缓存取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,此时如果缓存还是数据库都没有,黑客利用此漏洞进行攻击可能压垮数据库
  • 缓存击穿:key对应的数据存在,但在redis中以及过期了,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端db加载数据并回设到缓存,此时这个高并发的请求可能会瞬间把后端db压垮。
  • 缓存雪崩:当缓存服务器重启或者大量缓存集合在某一个时间段失效,这样在失效的时候,也会给后端系统带来很大压力。

2)解决方案

  • 缓存穿透解决方案

    1)布隆过滤器

    对所有可能查询的参数以hash的形式存储,以便快速确定是否存在这个值,在控制层先进行校验,校验不通过直接打回,减轻了存储系统的压力

    image-20220429163542303

    (2)缓存空对象

    一次请求若缓存和数据库中都没有,就在缓存中放一个空对象用于处理后续的请求

    image-20210716163510540

缺点:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高,解决这个缺点的方式就是设置过期时间

  • 缓存击穿解决方法

    相对于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的那一刻,同时有大量的请求,这些请求都会击穿到db,造成瞬时db请求量大,压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,打算其它的key依然可以使用缓存响应

    案例:热搜排行榜,一个热点新闻被同时大量访问就可能导致缓存击穿

    1)设置热点数据永不过期

    这样就不会出现热点数据过期的现象,但是redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用空间

    2)加互斥锁(分布式锁)

    在访问key之前,采用setnx(set if not exists)来设置另一个key来锁住当前key的访问,访问结束后再删除该短期key,保证同一时刻只有一个key来访问

  • 缓存雪崩

    大量的key设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬间db请求量大,压力骤增,引起雪崩。(redis宕机)

    产生雪崩的原因之一:比如在写文本的时候,马上就到双11零点,很快迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时,那么到了凌晨一点钟,缓存过期,此后对这批商品的访问都落到了数据库,对于数据库而言,就会产生周期性的压力波峰,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5r7X0YQa-1651225465247)(https://gitee.com/yueguang87/cloudimage/raw/master/img/image-20210716165145838.png)]

    解决方案:

    1)redis高可用

    这个思想的含义是,既然redis可能挂掉,可以多加几台redis服务器

    2)限流降级

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

    3)数据预热

    数据预热的含义就是在正式部署之前,先测试一遍,把大量的可能访问的数据放在缓存中,在即将发送大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点均匀起来

面试常见问题

1)使用keys扫出指定模式key列表会有什么问题

由于Redis是单线程,keys指令在运行时会导致线程阻塞一段时间,线上服务会停顿
可以使用scan无阻塞地提取,但会有一定的重复概率,需要在客户端做一次去重,所花时间比keys要长。

2)如何使用Redis做异步队列?

一般使用list结构,rpush生产消息,lpop消费消息。无消息时,适应sleep()重试或使用blpop阻塞直到有消息到来

3)sortedset 就是zset

延迟队列首先是个消息队列,其次是个带延迟功能的消息队列,你这么理解就对了。相对于普通消息队列,延迟队列中的消息除了消息本身外,还要有一个重要元素就是说明这条消息应该何时被消费掉!也就说在指定时间消费掉指定消息。

如何Redis实现延时队列

使用sortedset,拿时间戳作为score,消息内容作为key调用add来生间消息,消费者用zrangebyscore指令获取N次之前的数据轮询进行处理

第八章 总结

  1. 什么是redis

    redis是一种非关系型数据库,数据存放在内存中,读写速度非常快,所以redis常用来做缓存,也经常用来做分布式锁,此外,redis也支持事务、持久化、LUA脚本、LRU驱动事件、多种集群方案。

  2. 为什么要用redis做缓存

    高性能和高并发来看待这个问题。

    高性能:我们利用redis缓存数据的特点,第一次读数据后把数据存放在缓存中,下次取数据就直接在缓存中拿,减少了系统的开销,提高了性能

    高并发:直接操作缓存能够承受的请求是远远大于数据库的。我们使用缓存存放一些数据,这样用户的一部分请求走的是缓存而不是数据库,减少了数据库的压力

  3. redis的优点?

    优点:

    ​ (1)读写性能优异,Redis读的速度是11万每秒,写的速度是8.1万每秒

    ​ (2)支持数据持久化,AOF和RDB两种持久化

    ​ (3)支持事务,Redis的所有操作都是原子性,同时redis还支持对几个操作合并后的原子性执行。

    ​ (4)数据结构丰富:除了支持String类型的value,还支持hash、set、zset、list等数据结构

    ​ (5)支持主从复制,主机将自动将数据同步到从机,可以进行读写分离。

    缺点:

    ​ (1)数据库容量有限,不能作海量数据的高性能读写

    ​ (2)Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分部分读写请求失败,需要等待集合重启或者手动切换前端的Ip才能恢复。

    ​ (3)主机宕机,宕机前后部分数据未同步到从机,切换ip后还会引起数据不一致的问题,降低了系统的可用性。

    ​ (4)Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

  4. 使用redis的好处

    ​ (1)读写速度快,因为数据在内存中

    ​ (2)支持丰富的数据类型。

    ​ (3)支持事务,操作都是原子性,对数据的更改要么全部执行、要么全部不执行

    ​ (4)丰富的特性:可用于缓存、消息,按key设置过期时间,过期后会自动删除。

  5. redis为什么这么快

    ​ (1)完全基于内存

    ​ (2)数据结构简单,对数据操作也简单

    ​ (3)采用单线程,避免了上下文切换的时间

  6. redis的数据类型

    ​ (1)String

    ​ 简单的键值对缓存

    ​ (2)list 重复有序

    ​ 粉丝列表、文章的评论列表等

    ​ (3)set 不可重复无序的

    ​ 去交集、并集、差集操作,共同关注等

    ​ (4)zset 有序

    ​ 去重且可以排序、获取排名前几的用户

    ​ (5)hash 键值对

  7. redis的应用场景

    ​ (1)计数器

    ​ 可以对String类型的value进行自增自减操作,从而实现计数器的功能。

    ​ (2)缓存

    ​ 将热点数据放在内存中,设置内存的最大使用量以及淘汰机制保证缓存的命中率

    ​ (3)会话缓存

    ​ 可以使用Redis来统一存储多台应用服务器的会话信息,当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用 服务器,从而更容易实现高可用性以及可伸缩性。

    ​ (4)全页缓存

    ​ 除基本的会话token之外,Redis还提供了很简单FPC平台

    ​ (5)查找表

    ​ (6)消息队列

    ​ (7)分布式锁实现

    ​ (8)其它

  8. 持久化

    什么是redis持久化?就是把数据存到磁盘中,防止数据丢失。

  9. Redis的持久化方案

    redis的持久化方案有两种,RDB和AOF

    RDB:redis的默认持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件未dump.rdb,通过配置文件的save参数来定义快照的周期

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UjhPDt5F-1651225465247)(https://gitee.com/yueguang87/cloudimage/raw/master/img/image-20210820203802743.png)]

    优点:

    1.只有一个文件,方便持久化

    2.容灾性好,一个文件可以保存到安全的磁盘

    3.性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所有是IO最大化,使用单独的子进程进行持久化,主进程不进行IO操作,保证了redis的高 性能。

    4.相对于数据集大时,比AOF的启动效率高。

    缺点:

    数据安全性低,RDB间隔一段时间进行持久化,如果持久化期间redis发送故障,会发送数据丢失。

​ AOF持久化:

​ AOF持久化则是将redis执行的每次的写操作记录到单独的日志文件中,当重启redis会重新将持久化的日志中文件恢复数据。

image-20210820204309080

​ 优点:

​ 1.数据安全,aof持久化可配置appendfsync属性,每进行一次命令操作就记录到aof文件中一次

​ 2.通过append模式写文件,即使中途服务器宕机,也可以通过redis-check-aof工具解决数据一致性问题。

​ 3.AOF的rewrite模式,AOF文件没被rewrite之前,可以删除其中的没写指令

​ 缺点:

​ 1.AOF文件比RDB文件大,恢复速度慢

​ 2.数据集比较大的时候,比rdb效率要慢。

​ 总结

​ 1.两种方式同时开启,数据恢复redis会优先选择aof恢复

​ 2.两种持久化的优缺点是什么?

​ aof文件比rdb文件更像频率高,优先使用aof还原数据

​ aof比rdb更安全

​ rdb的性能比aof好

​ 3.如何选择合适的持久化方式。

​ 更加业务场景选择,如果对数据的完整度要求高,可以选择aof方式,如果想达到绝对的数据安全性,可以考虑两个都打开,不推荐只使用AOF方式,因为 定时生成rdb快照非常便于数据库的备份,并且rdb恢复数据的速度也比aof要快得多

​ 如果你只是用redis作缓存作用,可以不开启持久化。

  1. Redis的持久化数据和缓存是怎么扩容的

    如果redis被当作缓存使用,使用一致性哈希实现动态扩容缩容。

    如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。

  2. redis的过期键的删除策略。

    redis是k-v数据库,可以设置key 的过期时间,redis的过期策略就是当redis中缓存的key过期了,如何处理?

    (1)定时过期:每个设置过期时间的key都会创建一个定时器,到过期时间就会立即清除。优点:对内存优化。缺点:会占用大量的cpu资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

    (2)惰性过期:只有当访问一个key时,才会判断该key 是否过期,过期则删除。该策略会最大化节省cpu资源,对内存非常不友好,极端情况下可能出现大量的过期key没有被再次访问,占用大量的内存。

    (3)定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已经过期的key,该策略是前两者的折中方案,通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得cpu和内存资源达到最大的平衡效果。(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

	Redis中同时使用了惰性过期和定期过期两种过期策略。
  1. redis的过期时间和永久有效如何设置

    expire命令和persist命令

    我们知道expire来设置key的过期时间,那么对过期的时间是如何处理的呢?

    除了缓存服务器自带的缓存失效策略之外,还可以根据自己的业务需求自定义缓存淘汰,常见的策略如下:

    (1)定时处理过期的缓存

    (2)当有请求过来时,判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

  2. redis的内存淘汰策略有哪些?

    (1)全局的键空间选择性移除

    (2)设置过期时间的键空间选择性移除

  3. redis的内存用完了会发送什么?

    如果达到内存上限,redis的写命令会返回错误信息或者配置内存淘汰机制,刷掉老的数据。

  4. redis如何作内存优化

  5. Redis线程模型

    Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。

    • 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
    • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

    虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。

  6. 事务

    什么是事务?

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

    redis事务的概念

    • Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
    • 总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

    redis事务的三个阶段

    ​ 1.开启事务

    ​ 2.命令入队

    ​ 3.执行事务

    redis的事务命令

    Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

    Redis会将一个事务中的所有命令序列化,然后按顺序执行。

    1. redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
    2. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行
    3. 如果在一个事务中出现运行错误,那么正确的命令会被执行
    • WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
    • MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
    • EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
    • 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
    • UNWATCH命令可以取消watch对所有key的监控。

    redis事务支持隔离性吗?

    • Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的

    redis事务保证原子性吗?支持回滚吗?

    • Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败(指的命令逻辑有问题,程序报错),其余的命令仍会被执行。
  7. 集群方案

    哨兵模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B40z7Vz6-1651225465248)(https://gitee.com/yueguang87/cloudimage/raw/master/img/image-20210821171921719.png)]

哨兵的功能:

(1)集群监控,负责监控redis的master和slave进程是否正常工作

(2)消息通知:如果redis实例有故障,哨兵负责发送消息作为报警通知管理员

(3)故障转移:如果master node节点挂了,会自动转移到slave node上

(4)配置中心:如果故障转移了,通知客户端新的master地址。

哨兵用于实现redis的高可用,本身也是分布式的,作为一个哨兵集群去使用,互相协同工作

  • 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。

哨兵的核心知识:

  • 哨兵至少需要 3 个实例,来保证自己的健壮性。
  • 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
  • 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
  1. 缓存异常

    什么是redis穿透?

    • 就是用户请求透过redis去请求mysql服务器,导致mysql压力过载。但一个web服务里,极容易出现瓶颈的就是mysql,所以才让redis去分担mysql 的压力,所以这种问题是万万要避免的
    • 解决方法:
      1. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
      2. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
      3. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

    什么是redis雪崩?

    • 就是redis服务由于负载过大而宕机,导致mysql的负载过大也宕机,最终整个系统瘫痪
    • 解决方法:
      1. redis集群,将原来一个人干的工作,分发给多个人干
      2. 缓存预热(关闭外网访问,先开启mysql,通过预热脚本将热点数据写入缓存中,启动缓存。开启外网服务)
      3. 数据不要设置相同的生存时间,不然过期时,redis压力会大

    什么是redis击穿?

    • 高并发下,由于一个key失效,而导致多个线程去mysql查同一业务数据并存到redis(并发下,存了多份数据),而一段时间后,多份数据同时失效。导致压力骤增
    • 解决方法:
      1. 分级缓存(缓存两份数据,第二份数据生存时间长一点作为备份,第一份数据用于被请求命中,如果第二份数据被命中说明第一份数据已经过期,要去mysql请求数据重新缓存两份数据)
      2. 计划任务(假如数据生存时间为30分钟,计划任务就20分钟执行一次更新缓存数据)

    缓存预热

    • 缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
    • 解决方案
      1. 直接写个缓存刷新页面,上线时手工操作一下;
      2. 数据量不大,可以在项目启动的时候自动进行加载;
      3. 定时刷新缓存;

    缓存降级

    • 当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
    • 缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
    • 在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
      1. 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
      2. 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
      3. 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
      4. 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
    • 服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

    热点数据和冷数据

    • 热点数据,缓存才有价值
    • 对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存
    • 对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。
    • 数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。
    • 那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。

    缓存热点key

    • 缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

    解决方案

    • 对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值