Redis

Redis


为什么要用NoSQL

用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长

这个时候我们就需要使用NoSQL数据库,可以很好的处理以上情况


什么是NoSQL

NoSQL=Not Only SQL

关系型数据库:表格,行,列

泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代,尤其是超大规模的高并发的社区

NoSQL在当今大数据环境下发展的十分迅速

很多的数据类型用户的个人信息,社交网络,地理位置,这些数据的存储不需要一个固定的格式,不需要多余的操作就可以横向发展


NoSQL特点

  1. 方便扩展(数据之间没有关系,很好扩展)
  2. 大数据量高性能(Redis 一秒写8万次,读11万次,是一种细粒度的缓存,性能会比较高)
  3. 数据类型是多样型的 (不需要事先设计数据库,随取随用,如果是数据量非常大的表,很多人无法设计)
  4. 传统的RDBMS和NoSQL
传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存储在单独的表中
- 数据操作,数据定义语言
- 严格的一致性
- 基础的事务
- ......
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE  (异地多活)
- 高性能,高可用,高可扩展性

大数据时代的3V:主要是描述问题的

  1. 海量Volume
  2. 多样Variety
  3. 实时Velocity

大数据时代的3高:主要是对程序的要求

  1. 高并发
  2. 高可用
  3. 高扩展

真正在生产环境中的实践:NoSQL+RDBMS一起使用才是最强的!


NoSQL的四大分类

KV键值对

  • 新浪:Redis
  • 美团:Redis+Tair
  • 阿里、百度:Redis+Memcache

文档型数据库(Bson格式,和json一样):

  • MongoDB(必须掌握)
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • MongoDB是一个介于关系型与非关系型数据库中间的产品,也属于NoSQL中功能最丰富,最像RDBMS的
  • ConthDB

列存储数据库:

  • HBase
  • 分布式文件系统

图关系数据库

  • 不是存图形,存的是关系,比如:朋友圈社交网络,广告推荐!
  • Neo4j,InfoGrid

Redis入门

概述

Redis是什么?

Redis(Remote Dictionary Server),即远程字典服务

C语言写的,支持网络,基于内存

免费和开源,也被称之为结构化数据库

Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-server(主从)同步

Redis能做什么?

  1. 内存存储、持久化、内存中是断电即失,所以说持久化很重要(RDB、AOF)
  2. 效率高,可以用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(浏览量)

Redis特性:

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

官网:

CRUG网站 (redis.cn)


Redis安装

  1. 下载安装包
  2. 解压安装包
  3. 进入解压后的文件,可以看到redis.conf
  4. 备份redis.conf
  5. 基本的环境安装
1. yum -y install gcc-c++
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
# gcc -v    查看版本
2. make     #编译
3. make install   #安装
4. cd /usr/local/bin     #即可看到redis的目录
5. 将配置文件拷贝到新建的yconfig中
6. redis默认不是后台启动的,需要修改配置文件   222行改成yes
7. 启动redis服务 通过指定的配置文件启动  redis-server yconfig/redis.conf
8. 连接数据库 redis-cli -p 6379
9. 退出连接 shutdown  exit

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

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

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

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

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


测试性能

Redis-benchmark是一个官方自带的压力测试工具

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

简单测试一下:

# 测试:100个并发连接 100000请求
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000

基础知识

Redis默认有16个数据库,默认使用的是第0个

可以使用select切换数据库

127.0.0.1:6379> select 3     #切换数据库
OK
127.0.0.1:6379[3]> DBSIZE    #查看DB大小
(integer) 0
127.0.0.1:6379[3]>

不同数据库存储的内容不互通


  • ==查看==当前库中的所有key **key ***
127.0.0.1:6379> keys *
1) "myhash:{tag}:__rand_int__"
2) "counter:{tag}:__rand_int__"
3) "key:{tag}:__rand_int__"
4) "mylist:{tag}"
5) "name"

  • ==清除==当前库中的所有key flushdb
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>

  • ==清除所有库==的所有key flushall
127.0.0.1:6379> flushall
OK
127.0.0.1:6379>


小知识

Redis是单线程的

官方表示,redis是基于内存操作的,cpu不是redis的性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,即使用单线程

Redis是由C语言编写的,官方提供的数据为10万+的QPS(每秒查询率),完全不比同样使用key-value的Memcache差!

Redis为什么单线程还这么快?

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

误区2:多线程(CPU上下文切换)一定比单线程效率高

首先在速度上。CPU>内存>硬盘

核心:Redis是将所有数据放在内存中的,所以说使用单线程去操作,效率就是最高的,多线程(上下文会切换)是一个耗时的操作

对于内存系统来说,如果没有上下文切换,效率就是最高的,多次读写都是在一个cpu上的,在内存情况下,这个就是最佳方案


五大数据类型

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库缓存消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-key

127.0.0.1:6379> keys *                    #查看所有的key
(empty array)
127.0.0.1:6379> set name yzy              #给key赋值
OK
127.0.0.1:6379> get name                  #查看此key的value
"yzy"
127.0.0.1:6379> exists name               #判断这个key是否存在,结果为1则是存在
(integer) 1
127.0.0.1:6379> exists name1              #结果为0则不存在
(integer) 0
127.0.0.1:6379> move name 1               #移除当前的key
(integer) 1
127.0.0.1:6379> expire name 10            #设置key的过期时间,单位是秒
(integer) 0
127.0.0.1:6379> ttl name                  #查看当前key的剩余时间
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>


127.0.0.1:6379> set name yzy
OK
127.0.0.1:6379> type name                 #查看当前key的类型
string



String(字符串)

127.0.0.1:6379> set key1 v1                     #设置值
OK
127.0.0.1:6379> get key1                        #获得值
"v1"
127.0.0.1:6379> exists key1                     #判断key1是否存在
(integer) 1
127.0.0.1:6379> append key1 "hello"             #追加key1的值,若当前key不存在,则自动创建并赋值,相当于set key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1                     #获取字符串的长度
(integer) 7
127.0.0.1:6379> append key1 ",yanzeyang"
(integer) 17
127.0.0.1:6379> get key1
"v1hello,yanzeyang"
127.0.0.1:6379>
############################################################################################################
127.0.0.1:6379> set a 0                         #设置key的值为0
OK
127.0.0.1:6379> get a
"0"
127.0.0.1:6379> incr a                          #自增1  (1是默认的) 浏览量+1
(integer) 1
127.0.0.1:6379> get a
"1"
127.0.0.1:6379> incr a
(integer) 2
127.0.0.1:6379> get a
"2"
127.0.0.1:6379> decr a                          #自减1   (1是默认的)  浏览量-1
(integer) 1
127.0.0.1:6379> get a
"1"
127.0.0.1:6379> incrby a 10                     #设置步长,指定增量
(integer) 11
127.0.0.1:6379> decrby a 9                      #设置步长,指定减量
(integer) 2

############################################################################################################
#字符串范围 range

127.0.0.1:6379> set key1 "Hello,yanzeyang"      #设置key1的值
OK
127.0.0.1:6379> get key1
"Hello,yanzeyang"
127.0.0.1:6379> getrange key1 0 4               #截取从0~4
"Hello"
127.0.0.1:6379> getrange key1 2 7
"llo,ya"
127.0.0.1:6379> getrange key1 0 -1              #截取全部的字符串   相等于get key
"Hello,yanzeyang"

#替换 setrange
127.0.0.1:6379> set key2 abcdefg                
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 2 xxx              #将key2中的值里的第二位开始,后面三位替换为xxx
(integer) 7
127.0.0.1:6379> get key2
"abxxxfg"
############################################################################################################
# setex (set with expire)     #设置过期时间
# setnx (set if not exist)    #不存在再设置(常用于分布式锁中)
127.0.0.1:6379> setex key3 30 "hello"           #设置key3的值为hello,并设置30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 25
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis"             #如果mykey不存在,则创建value为redis
(integer) 1
127.0.0.1:6379> keys *
1) "a"
2) "mykey"
3) "key1"
4) "name"
5) "key2"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "mongodb"         #如果mykey存在,则创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"

############################################################################################################
# mset     #批量创建
# mget     #批量获取
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3       #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3                #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379>

127.0.0.1:6379> msetnx k1 v1 k2 v2 k3 55 k4 v4
(integer) 0
127.0.0.1:6379> mget k1 k2 k3 k4         #这时发现k4并没有创建值,但是前面说nx应该为不存在则创建,这是为什么?
1) "v1"                                  #因为msetnx是一个原子性操作,要么一起成功,要么一起失败
2) "v2"
3) "v3"
4) (nil)

#对象
set user:1 {name:yanzeyang,age:25}  #设置一个user:1 对象 值为json字符来保存一个对象
#这里的key是一个很巧妙的设计   user:{id}:{filed}
 127.0.0.1:6379> mset user:1:name yanzeyang user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "yanzeyang"
2) "2"
127.0.0.1:6379>
############################################################################################################
getset  #先get再set

127.0.0.1:6379> getset db redis             #如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb           #如果存在值,则获取值,并设置新的值 (好好理解这句话!!!)
"redis"
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379>

String类型的使用场景:value除了是字符串还可以是数字

  • 计数器
  • 统计多单位的数量
  • 对象缓存存储

List (列表)

在redis里面,可以把list当成栈、队列、阻塞队列

所有的list命令都是以l开头的!

############################################################################################################
127.0.0.1:6379> lpush list one        #将一个值或者多个值,插入到列表头部(左)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1       #通过区间,获取具体的值
1) "three"
2) "two"
127.0.0.1:6379> rpush list yzy       #将一个值或者多个值,插入到列表尾部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "yzy"
127.0.0.1:6379>
############################################################################################################
#  pop移除
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "yzy"
127.0.0.1:6379> lpop list                   #移除列表的第一个元素
"three"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "yzy"
127.0.0.1:6379> rpop list                   #移除列表的最后一个元素
"yzy"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
############################################################################################################
# lindex
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0                #通过下标获取list中的值,起始下标从0开始
"two"
127.0.0.1:6379> lindex list 1
"one"
############################################################################################################
#  lrem  移除指定的值
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 2 three           #移除列表中指定个数的value,精确匹配
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
############################################################################################################
# trim  修剪,截取
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist hello1
(integer) 2
127.0.0.1:6379> rpush mylist hello2
(integer) 3
127.0.0.1:6379> rpush mylist hello3
(integer) 4
127.0.0.1:6379> ltrim mylist 2 3          #通过下标截取指定的长度,这个list已经被改变了,只剩下截取的元素了
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"
2) "hello3"
############################################################################################################
# rpoplpush 移除列表最后一个元素,并将它移动到新的列表中
127.0.0.1:6379> lpush list hello
(integer) 1
127.0.0.1:6379> lpush list hello1
(integer) 2
127.0.0.1:6379> lpush list hello2
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello1"
3) "hello"
127.0.0.1:6379> rpoplpush list mylist     #移除列表的最后一个元素,并将它移动到新列表中
"hello"
127.0.0.1:6379> lrange list 0 -1          #查看原来的列表,最后一个元素已经不见了
1) "hello2"
2) "hello1"
127.0.0.1:6379> lrange mylist 0 -1        #查看目标的列表,确实存在该值
1) "hello"
############################################################################################################
# lset    #将列表中指定下标的值,替换成另一个值,相当于更新操作
127.0.0.1:6379> exists list             #判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item        #如果不存在,当更新时,会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "value1"
127.0.0.1:6379> lset list 0 item       #如果存在,则会更新对应下标的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
############################################################################################################
# linsert  将某个具体的值插入到列表中某一个具体的元素的前面或者后面
127.0.0.1:6379> lpush list hello
(integer) 1
127.0.0.1:6379> rpush list world
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "world"
127.0.0.1:6379> linsert list before "world" "111"      #在world的前面插入111
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "111"
3) "world"
127.0.0.1:6379> linsert list after "hello" "222"      #在hello的后面插入222
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "222"
3) "111"
4) "world"

小结

  • 实际上是一个链表,在before、after、left、right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有的值,空链表,也代表不存在
  • 在两边插入或者改动值,效率最高,中间元素相对来说效率会低一点

Set (集合)

set是无序不重复集合,同样,set类型的命令都是以s开头的!

############################################################################################################
127.0.0.1:6379> sadd myset hello                  # myset集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset yanzeyang
(integer) 1
127.0.0.1:6379> sadd myset love yanzeyang
(integer) 1
127.0.0.1:6379> smembers myset                    # 查看集合myset中的所有值
1) "yanzeyang"
2) "hello"
3) "love"
127.0.0.1:6379> sismember myset hello             # 判断一个值是不是在集合myset中
(integer) 1                                       # 返回 1  代表在集合中
127.0.0.1:6379> sismember myset yzy
(integer) 0                                       # 返回 0  代表不在集合中
############################################################################################################
127.0.0.1:6379> scard myset                       # 查看集合中有多少个元素
(integer) 3
127.0.0.1:6379> sadd myset yanzeyang              # 添加与集合中同样的值时,
(integer) 0                                       # 返回为0,因为set不可重复读
127.0.0.1:6379> sadd myset yanzeyang1             # 重新添加一个元素
(integer) 1                                       # 返回为1,说明新元素添加到集合myset中了
127.0.0.1:6379> scard myset                       # 再次查看集合中有多少个元素
(integer) 4
127.0.0.1:6379> smembers myset                    #查看集合myset中的所有值
1) "yanzeyang"
2) "hello"
3) "love"
4) "yanzeyang1"
############################################################################################################
#  srem   移除集合中的指定元素
127.0.0.1:6379> srem myset hello                  # 移除myset集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> smembers myset
1) "yanzeyang"
2) "love"
3) "yanzeyang1"
############################################################################################################
# 小游戏   抽取随机数
127.0.0.1:6379> smembers myset
1) "yanzeyang"
2) "love"
3) "yanzeyang1"
127.0.0.1:6379> srandmember myset
"yanzeyang1"
127.0.0.1:6379> srandmember myset
"yanzeyang1"
127.0.0.1:6379> srandmember myset
"yanzeyang"
############################################################################################################
# 删除指定的key   删除随机的key
127.0.0.1:6379> smembers myset            # 查看集合中的所有元素
1) "yanzeyang"
2) "love"
3) "yanzeyang1"
127.0.0.1:6379> spop myset                # 随机弹出一个元素
"love"
127.0.0.1:6379> spop myset                # 再随机弹出一个元素
"yanzeyang1"
127.0.0.1:6379> smembers myset            # 留下随机的一个元素
1) "yanzeyang"
############################################################################################################
# 将一个指定的元素移动到另一个集合中
127.0.0.1:6379> sadd myset yan
(integer) 1
127.0.0.1:6379> sadd myset ze
(integer) 1
127.0.0.1:6379> sadd myset yang
(integer) 1
127.0.0.1:6379> sadd myset yanzeyang
(integer) 1
127.0.0.1:6379> sadd myset2 wangqing
(integer) 1
127.0.0.1:6379> smembers myset
1) "yanzeyang"
2) "ze"
3) "yan"
4) "yang"
127.0.0.1:6379> smove myset myset2 "yanzeyang"    # 将一个指定的元素移动到另一个集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "ze"
2) "yan"
3) "yang"
127.0.0.1:6379> smembers myset2
1) "yanzeyang"
2) "wangqing"
############################################################################################################
# 交集:sinter  (intersection)
# 并集:sunion  (union)
# 差集:sdiff   (different)
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2
1) "b"
2) "a"
127.0.0.1:6379> sinter key1 key2
1) "c"
127.0.0.1:6379> sunion key1 key2
1) "d"
2) "a"
3) "c"
4) "e"
5) "b"

小结

微博 A用户将所有关注的人放在一个set集合中,将他的粉丝也放在一个集合中,共同关注,共同爱好,推荐好友,二度好友(六度分割理论)


Hash(哈希)

Map集合,key-Map 相当于 key- 这个值是一个map集合 这个地方自己理解一下,我尽可能的表达清楚

hash本质和string没有任何区别,还是一个简单的key-value

############################################################################################################
127.0.0.1:6379> hset myhash v yanzeyang             # set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash v
"yanzeyang"
127.0.0.1:6379> hmset myhash v1 yanzeyang v2 lnn v3 mg  # set多个 key-value
OK
127.0.0.1:6379> hmget myhash v1 v2 v3               # 获取多个字段值
1) "yanzeyang"
2) "lnn"
3) "mg"
127.0.0.1:6379> hgetall myhash                      # 获取全部的数据
1) "v"
2) "yanzeyang"
3) "v1"
4) "yanzeyang"
5) "v2"
6) "lnn"
7) "v3"
8) "mg"
############################################################################################################
# hdel 删除指定的字段值
127.0.0.1:6379> hdel myhash v1                      # 删除myhash中指定的key以及所对应的值
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "v"
2) "yanzeyang"
3) "v2"
4) "lnn"
5) "v3"
6) "mg"
############################################################################################################
# hlen  获取hash字符长度
127.0.0.1:6379> hmset myhash v1 yzy v2 lnn v3 mg
OK
127.0.0.1:6379> hlen myhash                         # 获取hash表的字段数量
(integer) 3
127.0.0.1:6379> hgetall myhash
1) "v1"
2) "yzy"
3) "v2"
4) "lnn"
5) "v3"
6) "mg"
127.0.0.1:6379> hmget myhash v1 v2 v3
1) "yzy"
2) "lnn"
3) "mg"
############################################################################################################
# hexists  判断hash中的指定字段是否存在
127.0.0.1:6379> hexists myhash v1
(integer) 1                                        # 返回 1  则存在
127.0.0.1:6379> hexists myhash v5
(integer) 0
############################################################################################################
# hkeys 只获取hash中所有的key
127.0.0.1:6379> hkeys myhash                       # 只获取hash中所有的key
1) "v1"
2) "v2"
3) "v3"
# hvals 只获取hash中所有的value
127.0.0.1:6379> hvals myhash                       # 只获取hash中所有的value
1) "yzy"
2) "lnn"
3) "mg"
############################################################################################################
# hincrby    指定自增   若要减少 可用负增长
127.0.0.1:6379> hset myhash v1 1                   #这里自己看吧,和string一样
(integer) 1
127.0.0.1:6379> hget myhash v1
"3"
127.0.0.1:6379> hincrby myhash v1 2
(integer) 5
127.0.0.1:6379> hincrby myhash v1 2
(integer) 7
127.0.0.1:6379> hincrby myhash v1 -1              # 注意! 没有decr,若要减少,用hincrby的负增长!!!
(integer) 6
############################################################################################################
# hsetnx 判断是否存在,如果不存在,就创建
127.0.0.1:6379> hget myhash v1
"6"
127.0.0.1:6379> hsetnx myhash v1 8              # 这里v1存在,并且有值,所以我赋值的8无法写入
(integer) 0
127.0.0.1:6379> hsetnx myhash v2 3              # 这里v2不存在,所以赋值成功
(integer) 1
127.0.0.1:6379> hmget myhash v2
1) "3"
127.0.0.1:6379> hgetall myhash
1) "v1"
2) "6"
3) "v2"
4) "3"
############################################################################################################

小结

hash适合存储经常变动的信息,user name age,尤其是用户信息,更适合于对象的存储,String更加适合字符串存储


Zset(有序集合)

在set的基础上增加了一个值 set k1 v1 k1 v2 zset k1 score v1

############################################################################################################
127.0.0.1:6379> zadd myset 1 one                    # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three            # 添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
############################################################################################################
# zrangebyscore  排序(从小到大)   zrevrange 排序(从大到小)
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 800 magou
(integer) 1
127.0.0.1:6379> zadd salary 8000 yzy
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf    # 显示全部的用户 从小到大
1) "magou"
2) "xiaohong"
3) "yzy"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores      # 显示全部的用户,从小到大,并且附带值
1) "magou"
2) "800"
3) "xiaohong"
4) "2500"
5) "yzy"
6) "8000"
127.0.0.1:6379> ZREVRANGE salary  0 -1
1) "yzy"
2) "xiaohong"
############################################################################################################
# zrem 移除
127.0.0.1:6379> zrange salary 0 -1
1) "magou"
2) "xiaohong"
3) "yzy"
127.0.0.1:6379> zrem salary magou                 # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiaohong"
2) "yzy"
127.0.0.1:6379> zcard salary                      # 获取有序集合中的个数
(integer) 2
############################################################################################################
# zcount 获取指定区间的成员数量
127.0.0.1:6379> zadd myset 1 hello 2 world 3 yanzeyang
(integer) 3
127.0.0.1:6379> zrange myset 0 -1
1) "hello"
2) "world"
3) "yanzeyang"
127.0.0.1:6379> zcount myset 1 3                  # 获取指定区间的成员数量 1~3之间有3个
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
############################################################################################################

*** 小结***

  • 其余的一些api,如果有需要,直接查询官方文档

事务

Redis单条命令保证原子性,但是事务不保证原子性

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

所有的命令在事务中,并没有直接被执行,只有发起执行命令时才会执行 Exec

Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按顺序执行!

特性:一次性、顺序性、排他性 执行一系列的命令

Redis事务:

  • 开启事务( multi )
  • 命令入队( … )
  • 执行事务( exec )

正常执行事务

127.0.0.1:6379> multi                  # 开启事务
OK
127.0.0.1:6379> set k1 v1              # 以下为命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> mget k1 k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec                  # 执行事务
1) OK
2) OK
3) 1) "v1"
   2) "v2"
4) OK

事务在执行结束之后就没了,如果需要再次执行,需重新建立事务


放弃事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k4
(nil)

编译型异常

(代码有问题,命令有错),事务中的所有命令都不会被执行!

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> getset k1               # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> exec                    # 执行事务时报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1                  # 所有的命令都不会被执行
(nil)


运行时异常

(命令没有错误,但是运行时有错误),执行命令时,其他命令都可以正常执行

127.0.0.1:6379> set k1 "yanzeyang"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1                   # 命令没有报错,但是运行时有问题,因为字符串没办法自增1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec                      # 运行时只有运行有错误的那一条命令不执行,不影响其他命令的执行
1) (error) ERR value is not an integer or out of range
2) OK
3) "v2"
# 这个案例很好的证明了redis的事务不保证原子性!       多理解 我写的很详细

监控 Watch

悲观锁:

  • 很悲观,认为无论什么时候都会出问题,无论做什么都会加锁 很影响性能

乐观锁:

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

Redis的监视测试

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money             # 监视money对象
OK
127.0.0.1:6379> multi                   # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做redis的乐观锁操作!

127.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
127.0.0.1:6379> exec                    # 执行之前 另外一个线程修改了money的值,这时会导致事务执行失败
(nil)

失败了之后怎么办? 有方法解决

127.0.0.1:6379> unwatch                 # 当发现事务执行失败时,先解锁
OK
127.0.0.1:6379> watch money             # 获取最新的值,再次加锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec                   # 此时当监视的值没有发生变化,则事务执行成功,如果发生变化,重复以上操作即可
1) (integer) 2060
2) (integer) 40

Redis.conf 详解

启动的时候,通过配置文件来启动

单位

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

1.配置文件unit单位对大小写不敏感


包含

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


网络

127.0.0.1 #绑定的IP
protected-mode yes   #保护模式
port 6379    #端口设置

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

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


通用 General

daemonize yes #以守护进程的方式运行,默认是no  需要手动开启,如果不开启,一退出,进程就结束了
pidfile /var/run/redis_6379.pid   #如果以后台方式运行,我们就需要指定一个pid进程文件
loglevel notice # 默认为notice
logfile ""   #日志的文件位置名
databases 16    #redis默认有16个数据库
always-show-logo yes  #默认开启logo(不重要)

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

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

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

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

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


快照 SNAPSHOTTING

持久化,在规定的时间内执行了多少次操作,则会持久化到文件 .rdb .aof

redis是内存数据库,如果没有持久化,数据断电即失
# 如果900秒内,至少有一个key进行了修改,就进行持久化操作
# 如果300秒内,至少有10个key进行了修改,就进行持久化操作
# 如果60秒内,至少有一万个key进行了修改,就进行持久化操作
stop-writes-on-bgsave-error yes  #持久化如果出错,是否还需要继续工作
rdbcompression yes  #是否压缩rdb文件,需要消耗一些cpu资源 (可关闭)
rdbchecksum yes  #保存rdb文件的时候,进行错误的校验检查
dir ./   #rdb文件保存的目录

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

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

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

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

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


主从复制 REPLICATION


安全 SECURITY

可以在这里设置密码,默认没有密码

127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456"
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> auth 123456
OK

限制 CLIENTS

maxclients 10000   #设置能连接上redis的最大客户端的数量为一万
maxmemory <bytes>   #redis配置最大的内存容量,默认为字节
maxmemory-policy noeviction   #内存到达上限的处理策略
六种策略如下:
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
2、allkeys-lru : 删除lru算法的key   
3、volatile-random:随机删除即将过期key   
4、allkeys-random:随机删除   
5、volatile-ttl : 删除即将过期的   
6、noeviction : 永不过期,返回错误

aof配置 APPEND ONLY模式

appendonly no   #默认不开启aof模式,默认使用rdb方式持久化,在所有情况下,rdb完全够用了
appendfilename "appendonly.aof"    #持久化的文件名字

# appendfsync always    #每次修改都会同步,消耗性能,一般不用
appendfsync everysec    #每秒执行一次sync,可能会丢失这一秒的数据
# appendfsync no        #不执行sync,这个时候操作系统自己同步数据,速度是最快的,一般也不用

具体的配置在持久化中会写到


Redis持久化

RDB持久化

Redis DataBase

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以将内存中的数据写入到硬盘中的这一过程,就称为Redis的持久化!

什么是RDB?

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

在指定的时间间隔内将内存中当前进程的数据生成快照保存到硬盘(因此也称作快照持久化),用二进制压缩存储,当Redis重新启动时,可以读取快照文件恢复数据

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

RDB保存的文件是dump.rdb

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

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

修改完配置文件之后,在redis里面输入save即可

触发机制:

  1. save的规则满足情况下,会自动触发RDB
  2. 执行flushall命令时,也会触发
  3. 退出redis,也会产生rdb文件

备份就会自动生产一个.rdb文件

如何恢复rdb文件

  1. 只需要将rdb文件放到redis的启动目录下即可,redis启动的时候会自动检查.rdb文件,恢复其中的数据
  2. 查看需要存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"   #如果在这个目录下存在dump.rdb文件,那么启动时会自动恢复数据

优点:

  • 高性能的持久化实现:创建一个子进程来执行持久化,先将数据写入临时文件,持久化过程结束后,再用这个临时文件替换上次持久化好的文件
  • 过程中主进程不做任何IO操作
  • 比较适合大规模数据恢复,且对数据完整性要求不是非常高的场合

缺点:

  • 意外宕机时,丢失最后一次持久化的所有数据

AOF持久化

Append Only File

将所有命令都记录下来,类似于history,恢复的时候就把这个文件全部再执行一遍

什么是AOF?

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

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

AOF保存的是appendonly.aof文件

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

默认是不开启的,如果需要开启:

appendonly yes     #改成yes

当同时存在是,aof会优先于rdb

如果aof文件有错误,这时redis是启动不了的,需要修复配置文件,redis提供了一个工具:redis-check-aof --fix appendonly.aof

[root@yzy bin]# redis-check-aof appendonly.aof

优点:

  • 可以灵活设置持久化方式
  • 出现意外宕机时,仅可能丢失1秒的数据

缺点:

  • 持久化文件的体积通常会大于RDB方式
  • 执行fsync策略时的速度可能会比RDB方式慢

rewrite重写

aof默认就是文件的无限追加,文件会越来越大

如果aof文件大于64MB,fork一个新的进程来将文件重写


扩展

  1. RDB持久化方式能够在指定的时间间隔内对数据进行快照存储
  2. AOF持久化方式记录每次对服务器的写操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件的末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
  3. 当只做缓存时,如果希望数据只在服务器运行的时候存在,就不需要任何持久化
  4. 同时开启两种持久化方式
  • 在这种情况下,当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集更完整
  • RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,但是也不建议只使用AOF,因为RDB更适合用户备份数据库(AOF不断在变化不好备份),快速重启,且不会有AOF可能潜在的Bug
  1. 性能建议
  • 因为RDB文件只用做后备用途,建议只在Salve上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则
  • 如果开启了AOF,好处是在最恶劣情况下也只会丢失不超过两秒的数据,只需要启动的时候加载AOF文件即可,代价一是带来的持续的IO,二是AOF的rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的,只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64MB太小了,可以设置到5G以上
  • 如果关闭了AOF,仅靠主从复制实现高可用也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动,代价是如果主从同时挂掉,会丢失十几分钟的数据,启动脚本也要比较两个主从中的RDB文件,载入较新的那个,微博就是这种架构

Redis发布订阅

Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息

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

订阅/发布消息图:

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

下图展示了频道channel1,以及订阅这三个频道的三个客户端——client2 client5和cilent1之间的关系

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

当有新消息通过publish命令发送给频道channel1时,这个消息就会被发送给订阅它的三个客户端

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

命令:

这些命令被广泛用于构建即时通信应用,比如网络聊天室和实时广播,实时提醒等

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

  1. subscribe channel 订阅一个频道
  2. publish channel message 将信息发送到指定的频道
订阅端:
127.0.0.1:6379> subscribe yanzeyang            #订阅一个频道,频道名字叫 yanzeyang
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yanzeyang"
3) (integer) 1                                 # 等待推送信息
发送端:
127.0.0.1:6379> publish yanzeyang "hello ni zhen shuai"    # 发布者发布消息到频道
(integer) 1
127.0.0.1:6379>
订阅端:
127.0.0.1:6379> subscribe yanzeyang
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yanzeyang"
3) (integer) 1
1) "message"                                    # 消息
2) "yanzeyang"                                  # 来自哪个频道的消息
3) "hello ni zhen shuai"                        # 消息的具体内容

Redis主从复制

概念:

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点(master/leader),后者称为从节点(salve/follwer),数据的复制是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主

默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点

可以理解为,一个爹可以多个儿,但一个儿不可能有多个爹(干爹当我没说)

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载,尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
  4. 高可用基础(集群):除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,至少需要一主二从。原因如下:

  1. 从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大
  2. 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器的内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis服务器最大使用内存不应该超过20G

​ 电商网站上的商品,一般都是一次上传,无数次浏览,也就是“多读少写”

架构如下:

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

环境配置

只配置从库,不需要配置主库

127.0.0.1:6379> info replication   # 主机  不用动
# Replication
role:master               # 角色,默认为master
connected_slaves:0        # 没有从机
master_replid:6e220393d0697342020ce2ad6bfccf2f84131318
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
一主多从:在从节点修改配置文件并且进入redis 输入 slaveof+主IP

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

将#去掉,replicaof+主机IP即可

细节:

主机可以写,从机不能写只能读,主机中的所有信息和数据,都被会从机保存

如果主机断开连接,从机依旧连接到主机,但是没有写操作,当主机重新连接回到集群,从机依旧可以直接获取到主机写的信息

复制的原理:

Slave启动成功连接到Master后发送一个sync同步命令

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

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

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

但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

主从从:

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

79是80的主,80是81的主,但是80的信息依旧是从 当主写入数据 81和81都可以读!

如果没有主节点了,这个时候可以手动选择一个主节点出来

在其中一个主节点,进入redis 输入 slaveof no one 让自己变成主机,其他的节点可以手动连接到最新的这个主节点

此时如果原先的主节点修复成功,要么让他也成为从节点,要么重新配置,维持原样

(以上我写的内容需要自己手动配置一下才能理解!)


Redis哨兵模式(自动选举master)

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用,不推荐!因此,优先考虑哨兵模式(Sentinel),来解决这个问题

哨兵可以后台监控主机是否故障,如果故障了根据投票数,自动将从服务器转换为主服务器

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个单独的进程,作为进程,它会独立运行,其原理是==哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例==

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

这里的哨兵有两个作用:

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

然而一个哨兵进程对Redis服务器进行监控,会有单点故障的问题,因此,可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,就形成了多哨兵模式,如下图:

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

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

测试:

目前的状态是一主二从

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

vim /usr/local/bin/yconfig/sentinel.conf

# sentinel monitor 被监控的名称  被监控的主机IP 被监控的主机端口  1
sentinel monitor myredis 127.0.0.1 6379  1

后面这个1,代表有1个哨兵检测到主节点失效就进行选举

2.启动哨兵

redis-sentinel yconfig/sentinel.conf
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 4.0.8 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 30557
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

当主机宕机之后,哨兵会自动选举新的主,当原主恢复后,成为新主的从


Redis的缓存穿透与雪崩

服务的高可用问题

Redis缓存的使用,极大的提高了应用程序的性能和效率,特别是数据查询方面,但同时,也带来了一些问题,最要害的问题,就是数据一致性问题,严格意义上讲,这个问题无解,如果对数据的一致性要求很高,那就不能用缓存

另外还有一些典型问题,缓存穿透,缓存雪崩和缓存击穿,目前,也有比较流行的解决方案

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

缓存穿透

概念:

缓存穿透的概念很简单,用户想要查询一个数据,发现Redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库,这会给持久层数据库带来非常大的压力,这个时候就相当于出现了缓存穿透

解决方案

布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的存储压力

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

缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源

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

但是这种方法会存在两个问题:

1.如果空值能够被存储起来,这就意味着存储需要更多的空间存储更多的键,因为这当中可能会有很多空值的键

2.即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有所影响


缓存击穿

概述

这里需要注意缓存穿透和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库

在某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且会写缓存,会导致数据库瞬间压力过大

解决方案

设置热点数据永不过期

从缓存层面来看,没有设置过期时间,所以不会产生热点key过期后产生的问题

加互斥锁

分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转到了分布式锁,因此对分布式锁的考验很大


缓存雪崩

概念

是指在某一个时间段,缓存集中过期失效或者Redis宕机

产生雪崩的原因之一,比如双十一,这波商品时间比较集中的放入了缓存,假设缓存一个小时,那么到了凌晨1点,这批商品的缓存就都过期了,而对这批商品的访问查询,都落到了数据库上,对于数据库而言,会产生周期性的压力波峰,于是所有请求都会到达存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况

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

其实集中过期,并不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或者断网。因为自然形成的缓存雪崩,一定是在某个时间段内集中创建缓存,这个时候,数据库也是可以顶住压力的,无非就是对数据库产生周期性的压力而已,而缓存服务节点的宕机,对数据库服务器造成的压力的不可预知的,很有可能瞬间把数据库压垮

解决方案

Redis高可用

含义就是,既然Redis有可能挂掉,那就多增加几台Redis,搭建集群

限流降级

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

数据预热

含义就是在正式部署之前,先把可能的数据预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
也有比较流行的解决方案

[外链图片转存中…(img-6gor0JIg-1655300827463)]

缓存穿透

概念:

缓存穿透的概念很简单,用户想要查询一个数据,发现Redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库,这会给持久层数据库带来非常大的压力,这个时候就相当于出现了缓存穿透

解决方案

布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的存储压力

[外链图片转存中…(img-VGI6koPr-1655300827463)]

缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源

[外链图片转存中…(img-p8psp6Bw-1655300827463)]

但是这种方法会存在两个问题:

1.如果空值能够被存储起来,这就意味着存储需要更多的空间存储更多的键,因为这当中可能会有很多空值的键

2.即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有所影响


缓存击穿

概述

这里需要注意缓存穿透和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库

在某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且会写缓存,会导致数据库瞬间压力过大

解决方案

设置热点数据永不过期

从缓存层面来看,没有设置过期时间,所以不会产生热点key过期后产生的问题

加互斥锁

分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转到了分布式锁,因此对分布式锁的考验很大


缓存雪崩

概念

是指在某一个时间段,缓存集中过期失效或者Redis宕机

产生雪崩的原因之一,比如双十一,这波商品时间比较集中的放入了缓存,假设缓存一个小时,那么到了凌晨1点,这批商品的缓存就都过期了,而对这批商品的访问查询,都落到了数据库上,对于数据库而言,会产生周期性的压力波峰,于是所有请求都会到达存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况

[外链图片转存中…(img-eQQYgbtO-1655300827463)]

其实集中过期,并不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或者断网。因为自然形成的缓存雪崩,一定是在某个时间段内集中创建缓存,这个时候,数据库也是可以顶住压力的,无非就是对数据库产生周期性的压力而已,而缓存服务节点的宕机,对数据库服务器造成的压力的不可预知的,很有可能瞬间把数据库压垮

解决方案

Redis高可用

含义就是,既然Redis有可能挂掉,那就多增加几台Redis,搭建集群

限流降级

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

数据预热

含义就是在正式部署之前,先把可能的数据预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值