Redis入门

Redis

本文使用的为Redis6.2.6版本

注:一到七章是基础,八到十二是进阶和面试会问的内容

一、背景

1、简单介绍Redis

数据库分为SQL数据库和NOSQL数据库,NOSQL的NO不是not(不)的意思而是not only的意思,也就是不只是SQL的数据库。

Redis就是一种NOSQL型数据库

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

2、数据库的发展历史

  1. 单机数据库时代

    90年代,一个基本的网站访问量较小,单个数据库可以顶住当时的访问量

    在那个时候,都会去使用静态网页html,因为服务器压力不会太大

    缺点:

    • 数据量太大,一台机子放不下了
    • 当数据量达到300万以上,就需要建立索引,MySQL索引,B+树。数据量一大,电脑内存也放不下了
    • 在当时的数据库单机时代,读写一体,服务器承受不了

    如果说以上三个条件满足了至少一个,那么就需要做出改变了

    DAL是数据访问层

    img

  2. Memcached(缓存)+ MySQL + 垂直拆分(实现读写分离)

    网站在大部分情况下都是在读,当用户在界面中按下一个button,就会对数据库发送一个查询请求,如果说,当一个或者多个用户都在发送一个相同的请求,而这个请求每次都要查询数据库,这很耗费性能,这时候就需要减轻数据库的压力,可以使用缓存来保证效率。

    发展:优化数据结构和索引----->文件缓存(IO操作)----->Memcached(当时的热门技术)

    img

  3. 分库分表 + 水平拆分 + MySQL集群

    技术与业务在发展的同时对人开始有越来越高的要求

    本质都是在解决数据库的读和写问题

    以MySQL的存储引擎为例

    MyISAM:这个存储引擎支持表锁,并且不支持事务的ACID,影响操作,在高并发下会出现严重的锁问题
    InnoDB:行锁+表锁,支持事务的ACID
    慢慢的由于数据量的增大,慢慢的开始使用分库分表解决写压力!在当时MySQL还推出了一个 表分区 的概念,但是并没有多少的公司愿意去使用

    MySQL集群的解决方案,已经在当时解决了大部分的需求

    img

  4. 目前的现状

    2010-2020,过了十年,世界发生了巨大变化,从按键手机到智能手机,定位,也成为了一种数据

    MySQL等关系型数据库开始出现性能瓶颈!大数据,数据量很多,变化很快

    MySQL可以用来存储一些比较大的文件,博客,图片!数据库表很大,执行IO操作的效率就会很低下,如果有一种数据库专门用来处理这种数据,就可以用来缓解MySQL的压力(如何处理这些问题)。因此诞生了NOSQL。

    img

3、为什么要用NOSQL

随着时代的发展,用户个人信息,社交网络,地理位置,用户自己产生的信息数据,等等一系列的弹性数据爆发式增长,关系型数据的传统的表结构已经承载不了了,这时候就需要使用NoSQL数据库来解决,NoSQL可以很好处理以上情况

NOSQL不是非SQL,而是not only sql,不仅仅是SQL,也就是我们常说的非关系型数据库

web2.0的诞生,传统的关系型数据库已经很难对付web2.0时代!特别是指大规模高并发社区!会出现很多问题,NoSQL在大数据时代发展的十分迅速,尤其是Redis

很多的数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式,不需要太多操作就可以实现横向拓展,就比如Redis,它是使用类似于Java的Map<String, Object>来实现存储,键值对的形式存储,这只是NoSQL的解决方式之一

4、NOSQL的特点

  1. 方便扩展(数据之间没有联系可以快速拓展)

  2. 大数据量高性能,Redis可以支持8w的并发量(写操作),11w访问量(读操作),NoSQL的缓存记录级,是一种细粒度的缓存,性能比较高

  3. 数据类型多样性(不需要事先设计数据库,随取随用,数据量过大就无法设计)

5、传统的关系数据库管理系统(Relational Database Management System:RDBMS)和NoSQL的区别

传统DBMS

  • 结构化
  • SQL
  • 数据和关系存在于单独的表中 row(行) column(列)
  • 数据操作,数据定义语言
  • 严格的一致性
  • 基础的事务

NOSQL

  • 不仅仅是数据
  • 没有固定的查询语言
  • 键值对存储,列存储,文档存储,图形数据库
  • 最终一致性
  • CAP定律和BASE理论

6、大数据时代的3V和三高

3V:

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

三高:

  1. 高并发
  2. 高可用(随时水平拆分,机器不够了,随时扩展)
  3. 高性能(保证用户体验和性能)

目前实践中用的最多的是NOSQL+传统SQL的组合来实现业务

7、从阿里巴巴架构的发展窥探技术发展的路线

img

目前淘宝等网站的各种数据的存储方式或技术:

  1. 商品的基本信息

    如:名称、价格、商家信息

    发展为使用MySQL ,摒弃Oracle也就是去IOE化(IOE:IBM、Oracle、EMC存储设备)

  2. 商品描述

    如:评论,文本信息多

    使用mongodb

  3. 图片

    分布式文件系统 FastDFS

    淘宝:TFS

    Google:GFS

    Hadoop:HDFS

    阿里云:OSS

  4. 商品关键字搜索

    搜索引擎一般用:solr或elasticsearch

    淘宝:使用ISearch,ISearch作者是阿里的多隆

  5. 商品热门波段信息

    Redis + Tair + Memcached

  6. 商品交易或支付接口

    第三方应用接口

8、四种NOSQL(NOSQL的分类)

KV键值对

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里,百度:Redis + Memcached

文档型数据库(使用了Bson,Binary Json,也就是二进制Json)

  • MongoDB,需要掌握,它是一种基于分布式文件存储的数据库,由C++编写,主要用来处理大量的文档。它是一种介于关系型数据库和非关系型数据库之间的一种中间产品,功能丰富,而且MongoDB是NoSQL中最像关系型数据库的产品(介于NOSQL和传统SQL之间的数据库)
  • ConthDB

列存储数据库

  • HBASE
  • 分布式文件系统

图形关系数据库

PS:它不是存图片的!它存放的是关系,就好比一个人的社交圈,可以动态扩充

  • Neo4j
  • InfoGrid

不同NOSQL的对比:

img

二、Redis入门

1、什么是Redis

Redis(Remote Directory Server),中文译为远程字典服务,免费开源,由C语言编写,支持网络,可基于内存也可持久化的日志型,KV键值对数据库,并且提供多种语言的API,是当下NoSQL中最热门的技术之一!被人们称之为结构化数据库!

Redis支持目前常用的大部分语言,如:

img

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

注:Redis是单线程的,因为Redis主要是基于内存进行工作的,从单线程提升到多线程对于Redis的工作效率来说提升不大,但是6.0之后的版本也开始使用多线程了

2、Redis有什么用

  1. 内存存储,持久化,因为内存断电即失,并且Redis支持两种持久化方式,RDB / AOF
  2. 效率高,可用于高速缓存
  3. 消息发布订阅(消息队列)
  4. 地图信息分析
  5. 计数器(eg:微博浏览量)

3、Redis的特性

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

4、在Linux上安装

为什么在Linux上而不是Windows上呢,因为官方建议在Linux上使用Redis,且Windows版本已经很久都没有更新了,非常不推荐使用。

Redis的默认端口号为6379

  1. 在Redis官网上下载最新版本的Redis

  2. 上传到linux服务器的/opt目录下

  3. 解压redis压缩包

  4. 进入解压文件中,可以看到redis.conf,这是redis的配置文件

  5. 安装c和c++环境

    命令:yum -y install gcc

    ​ yum -y install gcc-c++

  6. 执行make命令,自动配置安装

  7. 执行make install命令,确认是否已经安装相关程序,相关程序默认安装在/usr/local/bin目录下

  8. 复制(cp命令)一份配置文件到任意位置,原配置文件留作备份,方便回复到原来的配置

    将配置文件的后台运行项改为yes

  9. 指定我们修改后的配置文件打开redis服务

    命令如:redis-server xiafanconfig/redis.conf,前面为redis服务,后面是我们指定目录下的配置文件

  10. 测试

    使用redis客户端测试,具体如下:

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

  11. 如何退出redis

    只需使用shutdown命令并exit退出(当然,也可以查看redis的进程ID使用kill命令直接杀死进程,但还是推荐使用shutdown)

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

5、性能测试

使用redis-benchmark测试性能,测试时会根据设定的并发数和请求数为依据来对各个基本指令进行测试,返回测试结果

如,我想测试100个并发,每个并发有100000个请求,则命令为:redis-benchmark -h localhost -p 6379 -c 100 -n 100000

测试过程:

  1. 开启redis服务

    命令:redis-server redis.conf

  2. 使用benchmark测试

    命令:redis-benchmark -h localhost -p 6379 -c 100 -n 100000

6、Redis的基本知识(基本命令)

  1. 从redis.conf这个配置文件中可以看出,redis默认拥有16个数据库database

  2. 可以用select+数据库编号命令切换数据库(数据库编号是从0-15)

    如:select 2(切换到第三个数据库)

  3. 用dbsize命令查看当前数据库存在数据的情况

  4. 清空Redis

    • flushdb:清空目前数据库
    • flushall:清空所有数据库

三、Redis的数据类型

官网原话的翻译:

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

额外补充:Redis-Key的基本命令

  1. key *:查看所有的key
  2. set A B:设置一个键值对,键名为A值为B
  3. exists A:判断键名为A的键值对是否存在,返回1为存在,返回0为不存在
  4. del A:删除键名为A的键值对
  5. move A 数据库编号(0-15):移动当前数据库的对应键名A的键值对到指定的数据库中
  6. expire A B:设置键值对A的存活时间为B,B的单位是秒,如expire name 10,即键名为name的键值对的存活时间为10秒,10秒后自动销毁
  7. ttl A:查看键值对A的剩余存活时间,若<=0则已销毁
  8. type A:查看键名为A的键值对的值的类型
  9. get A:得到键名为A的值

1、五种基本数据类型

  1. String字符串

    string类型的常见使用场景:

    • 计数器
    • 统计单位数量,如:粉丝数、浏览量等
    • 对象缓存存储
    • 有过期时间的验证码

    这里补充除了以上基本命令以外也很常用的命令:

    • append A 字符串B:向键名为A的字符串型键值对的值追加字符串B,比如原来的字符串是hello,我可以追加world,就会拼接为helloword,如:append str1 world
    • strlen A:查看键名为A的字符串型键值对的值的字符长度
    • incr A:使键名为A的键值对的值自增,相当于我们编程语言的i++的作用,可以用于我们对浏览量的操作
    • decr A:使键名为A的键值对的值自减,相当于我们编程语言的i–的作用
    • incrby A B:使键名为A的键值对的值按步长B自增,相当于我们编程语言的i+=B的作用,比如incrby view 10,则每次自增都会+10而不是+1,也就是i=i+10
    • decrby A B:使键名为A的键值对的值按步长B自减,相当于我们编程语言的i-=B的作用,比如decrby view 10,则每次自增都会-10而不是-1,也就是i=i-10
    • getrange A B C:获取键名为A的键值对的值从B-C的部分(子串),注意,我们学编程的要自己知道,字符串中字符的下标肯定是从0开始算起的,如getrange name 0 3。特殊的,如果C是-1,那么获取从B到字符串结束的所有值,如:getrange name 0 -1,可以获取整个字符串(注意,B-C是闭区间的)
    • setrange A B C:将键名为A的键值对从B开始(包括B)的子串替换为C,具体替换多少个字符由C的长度决定,如果C的长度超过原来B—字符串尾部的长度,那么从B开始的字符串将会被完全替换且自动延长字符串直到C可以完全放入其中
    • setnx A B:类似set,但是有所不同,setnx是set if not exist的意思,也就是不存在才会创建,否则会创建失败,防止后来创建的键值对覆盖之前的键值对,在分布式锁中时常需要用到,比较重要。返回1则创建成果,返回0为创建失败
    • mset A1 B1 A2 B2 A3 B3…:一次性存储多个键值对,AnBn为一对,以此类推
    • msetnx A1 B1 A2 B2 A3 B3…:一次性存储多个键值对,AnBn为一对,以此类推,和setnx相似,也是不存在才会创建,但还有一点,那就是只要存在一个键值对已存在,那么其他未存在的键值对也不会创建,也就是该命令是原子性的,要么一整个成功,要么一整个失败
    • mget A1 A2 A3…:一次性查看多个键值对的值,An都是键值对的键名
    • getset A B:先查看键名为A的键值对原来的值,然后把键名为A的键值对的值设置为B

    那么如何设置对象呢,下面以简单的User用户对象为例,有两种设置方法

    • 用json的形式设置:set user:1 {name:xiafan,age:18},本质上其实就是键值对的嵌套,这里要注意,两个踩坑点,1后面没有空格会报错,xiafan后面没有逗号也会报错
    • 利用redis中“:”符号的使用,该符号可以表示层次结构,所以我们也可以这样设置:mset user:1:name xiafan user:1:age 18,也可以起到一样的作用
    • 区别:第一种产生的键是user:1,第二种产生的是user:1:name和user:1:age,
  2. List列表

    注意:列表是允许相同元素存在的,所以一个列表可能存在多个相同值

    我们会发现以下这些命令很多前面都会带有L这个字母,这个L有时候指的是left有时候指的是list,我觉得很多时候两种理解都是可以的,不必太过纠结这个L到底是什么意思,并且很多命令其实是有重复功能的,可以根据自己的习惯来使用,或者查看完成同样任务的命令的效率如何,按性能来选择

    这个数据类型还是很灵活的,根据需要可以当成栈、队列、各种变种队列来用

    • Lpush A B1 B2 B3 B4…:向列表A中左插入元素B1、B2、B3、B4…(L是left左的意思)
    • Rpush A B1 B2 B3 B4…:向列表A中右插入元素B1、B2、B3、B4…(R是right右的意思)
    • Lpop A n:移除列表A最左边的n个元素(L是left左的意思),n可以不填,不填则默认n=1,也就是只弹出最左边的第一个元素
    • Rpop A n:移除列表A最右边的n个元素(R是right右的意思),n可以不填,不填则默认n=1,也就是只弹出最右边的第一个元素
    • Lrange A a b:获取列表A从左往右第a-b个元素(从0开始计数)(L是left左的意思),同样的如果b是-1,说明是从a到最后一个元素
    • Lindex A n:获取列表A从左往右的第n+1个元素(从0开始计数,所以第一个其实是lindex A 0)(L是left左的意思)
    • Llen A:返回A的元素个数
    • Lrem A n B:移除列表A中值为B的n个元素,移除优先级按添加时间从后往前
    • Ltrim A a b:只保留a-b的元素,其他元素删除
    • RpopLpush A B:把列表A最右的元素移除并插入到列表B的最左端
    • exists A:查看列表A是否存在
    • Lset A n B:将列表A中下标为n-1的位置的值替换为B
    • Linsert A before|after a1 a2:在列表A中,在a1前面或者后面插入a2
  3. Set集合(无序集合)

    注意:集合不允许相同元素存在的,所以一个集合中的某个元素的值是唯一存在的

    • sadd A a1 a2 a3…:向集合A中添加a1、a2、a3…等元素
    • smembers A:查询集合A中的所有元素
    • sismember A a:查询集合A中是否存在元素a
    • scard A:查询集合A中存在几个元素
    • srem A a1 a2 a3…:移除集合A中值为a1、a2、a3…的元素
    • srandmember A n:从集合A中随机取出n个元素
    • smove A B a:把集合A中的元素A移动到集合B中
    • sdiff A B:查询集合A减集合B的差集(注意,sdiff A B和sdiff B A不一样)
    • sinter A B:查询集合A和集合B的交集(比如可以用来求平台的共同关注或共同好友等)
    • sunion A B:查询集合A和集合B的并集
  4. Hash哈希

    注意:hash数据类型很适合存储对象,因为它本身就是由键值对组成的

    • hset A a1 b1 a2 b2…:向哈希A中放入键值对a1、b1,键值对a2、b2…
    • hget A a1:获得哈希A中键名为a1的值
    • hgetall A:获得哈希A中的所有键值对
    • hdel A a1:删除哈希A中键名为a1的键值对
    • hlen A:查询哈希A中键值对的数量
    • hexists A a1:查询哈希A中键名为a1的键值对是否存在
    • hkeys A:获取哈希A中所有的键名
    • hvals A:获取哈希A中所有的值
    • hsetnx A a1 b1:类似setnx,不再赘述了
    • hincrby A a1 n:如果键名为a1的值为数字,那么该命令可以使哈希A中键名为a1的值增加n(n可以为负数)
  5. Zset有序集合

    • zadd A n a1:向有序集合A中添加a1元素,且a1元素的权重为n
    • zrange A a b:查询有序集合A中a到b的元素
    • zrangebyscore A a b (withscores):将有序集合A按权值范围a到b排序,比如-inf +inf就是从小到大排序(-inf是负无穷,+inf是正无穷),如果权值不在a到b中的元素不参与排序,withscores决定是否显示权值,如果不添加则只显示元素名
    • zrem A a:删除有序集合A中的元素a
    • zcount A a b:查询有序集合A中权值在a到b的元素个数(注意,a到b是闭区间)

    应用场景:由于这是有序的集合,所以可以应用于需要排序或者需要设置权重的场景,比如成绩表、工资表、设置消息权重、依据某个数据进行排序(比如xx的top n榜)等

2、三种特殊数据类型

  1. geospatial

    这是一个特殊的,和地理位置有关的数据类型,存储某地的经度纬度,可以应用于如:附近的人、外卖配送等领域

    • geoadd,语法:geoadd A 经度1 纬度1 地理位置1 经度2 纬度2 地理位置2…:向geoA中放入地理位置信息,注意,A可以是单独的值也可以,还要注意范围:经度是-180到+180,纬度是-85.05到+85.05,如果超出的话就会报错
    • getpos,语法:getpos A 地理位置1 地理位置2…:返回geoA中查询的地理位置的经纬度
    • getdist,语法:getdist A 地理位置1 地理位置2 (单位):单位可有可无,默认为m,也可以设置为km、mi(英里)、ft(英尺),计算两地之间的距离
    • getradius,语法:getradius A 经度a 纬度b 半径n [单位] [WITHCOORD(搜寻到的目标的经纬度)] [WITHDIST(直线距离)] [count]:查看以经纬度ab的位置半径为n的geoA中的地理位置,单位可有可无默认为m,WITHCOORD参数可以显示查询到的经纬度,WITHDIST参数可以查询两地的直线距离,count可以限制可以查询出的数据个数
    • georadiusbymember,基本语法:georadiusbymember A a 半径n [单位] [WITHCOORD(搜寻到的目标的经纬度)] [WITHDIST(直线距离)]:查看以地理位置a(a在A中)的半径为n的圈内包含的geoA的元素
    • geohash,语法:geohash geoA a1 a2…:返回geoA中a1、a2…元素的哈希hash值,本质上是将二维的经纬度转换为一维的字符串来表示,这里的字符串是十一位的,理论上字符串长度越长进度越高

    geospatial数据类型实际上是对Zset进行封装来使用的,所以Zset的操作在geo这里都可以使用,如zrem,zrange等

  2. hyperloglog

    hyperloglog是用来统计基数的,那么什么是基数,这里的基数是不重复元素的意思,也就是统计不重复元素的个数,这么听起来好像Set数据类型也可以完成这样的任务,但是hyperloglog有它自己的优点。

    优点:hyperloglog最大的优点就是非常节省内存,如果项目所存的不重复元素个数非常多的话,那么hyperloglog肯定是首选,它所占用的内存是固定的,占用12KB

    缺点:使用set和hashmap基本上可以保持不会出错,而hyperloglog可能会有略微的错误率,这个错误率在千分之一级别,如果允许这样的错误率使用它是最好的,如果要最大程度保持数据正确率则使用set等好一点

    应用场景:比如统计网页的UV(单独访问数,同个用户多次访问算一次访问)

    • pfadd A a1 a2 a3…:向hyperloglogA加入元素a1、a2、a3…
    • pfcount A1 A2…:统计A1、A2…等hyperloglog数据结构总体的统计基数结果
    • pfmerge A1 B1 B2…:合并hyperloglogB1、hyperloglogB2…等到hyperloglogA1
  3. bitmaps

    这里的bit是位的意思,这个数据结构是按位存储的。

    应用场景:比如用户是否在线、用户是否活跃、是否已登录、是否打卡、健康码是否为绿、查看员工是否全勤等

    bitmap就是位图,这个学过操作系统的朋友应该很熟悉,我们磁盘存储机制中也会使用位示图来表示磁盘块是否被占用

    • setbit A 位偏移量n 0|1:设置bitmapsA中位置为n的值为0或1
    • getbit A 位偏移量n:获取bitmapsA中位置为n的值
    • bitcount A:统计bitmapsA中值为1的位的个数

四、Redis的事务(也可以称为Redis的批处理)

我们在MySQL中也已经学过事务transaction这个概念了,在MySQL中我们一直强调事务是有ACID四个特定的,但是到Redis这里就又不一样了。Redis只能保证单条命令是原子性的,但不能保证整个事务是原子性的,而且Redis也没有隔离的概念。Redis的事务具有一次性执行、顺序执行、排他性这三个特性。这里我们可以联想到我们所说的,Redis在6.0版本之前其实都是单线程的,这也就说明它的事务极有可能在底层是串行调度的,所以并不会被干扰。

Redis的事务操作非常简单,只有三个操作,开启事务multi、执行事务exec和取消事务discard

输入multi命令代表事务开启,然后输入一系列命令加入预执行列表,最后选择执行事务或取消事务,一般结构如下:

multi

命令1
命令2
命令3

exec|discard

当事务中的命令存在错误的时候,分为两种:

  1. 命令语法错误,无法通过编译(类似Java的编译错误),此时事务会一整个都执行失败
  2. 命令语法没有错误,但是执行的时候会出现错误(类似Java的执行错误),此时事务会正常执行,只有执行错误的命令不会执行,其他正确的命令照常执行

五、Redis中的乐观锁

关于乐观锁和悲观锁,在MySQL中也好,在mybatis-plus也好,都提到过,这里再简单介绍一遍。

乐观锁就是“乐观”的锁,认为不会出现问题,不需要对数据进行上锁处理,更新的时候判断一下在这个过程中是否数据已经被修改过了。

在MySQL中我们是通过增加一个version字段来实现乐观锁的,在更新操作中自增version。

悲观锁就是“悲观”的锁,认为每一次都会出现问题,所以对数据的操作每一次都需要上锁。

一般来说,应用的比较多的是乐观锁,因为只需要多增加对一个字段的操作而已,而悲观锁需要频繁的上锁解锁会造成资源浪费,效率低下。

在Redis中,使用watch关键字实现乐观锁,起到监控(watch)的作用,如:watch num,可以监控num这个数据。而解锁则可以直接使用unwatch即可,常用于秒杀系统。

六、Redis和Java的融合——Jedis

Jedis就是把Redis的操作封装成一个可以被Java操作的类,并且可以连接指定的Redis服务。

jedis封装的api和我们学过的Redis原生操作非常非常的相似,我们只需要导入jedis依赖就好,还要看过第三大章节可以迅速上手操作。这里主要补充说一下jedis如何操作事务:

这里用一段代码演示

public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.1.107", 6379);
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", "xiafan");
        jsonObject.put("age", "18");
        jsonObject.put("sex", "man");
        Transaction multi = jedis.multi(); //  开启事务
        String user = jsonObject.toJSONString();
        try {
            multi.set("user1", user);
            multi.set("user2", user);
            multi.exec();
        } catch (Exception e) {
            multi.discard(); // 出现问题,放弃事务
            e.printStackTrace();
        } finally {
            System.out.println(jedis.mget("user1", "user2"));
            jedis.close(); // 关闭连接
        }
    }
}

七、Redis和springboot整合

springboot整合Redis有两个封装好的依赖,一个是我们已经提到的jedis,另一个是lettuce。

值得一提的是,springboot2.x的某些版本的redis依赖是只有lettuce而没有jedis的,我现在实操用的是2.6.2版本,会发现jedis又回来了,读者需要看一下自己的版本是两个都存在还是只有lettuce。

jedis:采用直连,模拟多个线程操作会出现安全问题。为避免此问题,需要使用Jedis Pool连接池!类似于BIO模式

lettuce:采用netty网络框架,对象可以在多个线程中被共享,完美避免线程安全问题,减少线程数据,类似于NIO模式

先配置Redis:

spring.redis.host=127.0.0.1
spring.redis.port=6379

接下来是springboot精髓的地方,就是任何整合的对象都会给它一个template,一个模板,任何通过模板对象来操作。

演示一下操作:

opsForxxx,这个xxx就是指定我们操作的数据结构,也就是我们学过的那八个数据结构,这里只用一个作为演示

@Test
void contextLoads() {
    ValueOperations ops = redisTemplate.opsForValue();
    redisTemplate.opsForGeo();
    ops.set("k1", "v1");
    Object o = ops.get("k1");
    System.out.println(o);
}

以上是简单的使用演示。

springboot中的RedisAutoConfiguration下的redisTemplate方法可以自定义覆盖:

原RedisAutoConfiguration为:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

推荐自定义redisTemplate方法为:

@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
  throws UnknownHostException {
  // 为了开发方便,可以直接使用<String, Object>
  RedisTemplate<String, Object> template = new RedisTemplate<>();

  // 序列化配置
  Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
  template.setDefaultSerializer(serializer);
  template.setConnectionFactory(redisConnectionFactory);
  ObjectMapper om = new ObjectMapper();
  om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
  serializer.setObjectMapper(om);
  StringRedisSerializer srs = new StringRedisSerializer();
  // 对于String和Hash类型的Key,可以采用String的序列化方式
  template.setKeySerializer(srs);
  template.setHashKeySerializer(srs);
  // String和Hash类型的value可以使用json的方式进行序列化
  template.setValueSerializer(serializer);
  template.setHashValueSerializer(serializer);
  template.afterPropertiesSet();
  return template;
}

像我们操作JDBC、mybatis等技术一样,我们也可以自己定义一个redis的util工具类,来帮助我们使用redis,具体如何封装按照自己的操作习惯和公司业务来定义。

八、详解Redis的配置文件redis.conf

以下内容均是从配置文件中读出来的信息。

1、基本设定

​ 1.配置文件对大小写不敏感

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

​ 2.Redis对单位的定义

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

​ 3.Redis服务的开启需要附带配置文件

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

4.配置文件可以有多个

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

2、网络配置

​ 1.Redis绑定的ip地址,可以根据需要进行修改

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

​ 2.是否开启保护模式,默认为yes

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

​ 3.端口号,默认为6379

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

3、通用配置

​ 1.是否以守护进程方式运行,默认为no,守护进程就是在Linux系统中后台运行的意思

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

​ 2.配置pid文件,pid文件一般都位于/var/run目录下

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

​ 3.配置日志

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

​ 4.日志文件的位置

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

​ 5.默认的数据库数量,编号从0开始,连接时默认打开的为0号数据库

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

​ 6.是否显示Redis的logo(没什么用…想开就开想关就关吧)

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

4、持久化规则(默认使用rdb持久化,而不是aof持久化,aof持久化需要开启)

​ 1.默认的有三个等级的持久化规则,我们也可以自己设定

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

​ 第一个等级是如果3600秒内有1个key被改变就执行持久化

​ 第二个等级是如果300秒内有100个key被改变就执行持久化

​ 第三个等级是如果60秒内有10000个key被改变就执行持久化

​ 2.持久化出错后是否保持工作,默认保持

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

​ 3.是否压缩rdb文件,默认开启

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

​ 4.保存rdb文件时,是否进行错误校验,默认开启

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

​ 5.rdb文件保存的目录,默认为当前目录

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

​ 6.rdb文件默认命名

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

7、主从复制的配置(从机的永久性配置)

​ 1.配置所属主机的ip地址和端口

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

​ 2.配置主机密码(如果主机有密码的话)

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

8、安全配置

​ 1.密码配置,Redis默认是无密码的,需要自己设置

​ 可以在配置文件设置,但更常用的是在命令行设置

​ 配置文件设置:

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

​ 写在指向处,格式为:requirepass 密码

​ 命令行设置:

  • config set requirepass 密码,设置密码

  • 可以通过config get requirepass,获取当前密码

  • 设置密码后,以后登录需要使用:auth 密码,才可以登录

  • 如果要取消密码,则把密码置为空,命令:config set requirepass “”,即可

  • 客户端配置

    1.最大客户端连接数

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

    默认最大客户端连接数为10000

    2.最大内存容量

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

    3.内存达到上限之后的处理策略

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

    Redis拥有以下六种策略选择:

    其实学过操作系统对如下策略都不会陌生

    1. volatile-lru(默认值,最常用):从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。

    2. volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越小越优先被淘汰。

    3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。

    4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。

    5. allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。

    6. no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。

9、Append Only模式,开启aof持久化

​ 1.是否开启aof模式,默认不开启

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

​ 2.持久化文件名,默认为appendonly.aof

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

​ 3.设置同步sync频率,默认为每秒同步一次

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

​ always等级为每次操作都同步一次,很耗费资源

​ everysec等级为默认使用的,为每秒同步一次,我们最多丢失一秒的数据

​ no等级为不同步,由操作系统自行同步,资源利用率最高

九、Redis的持久化(rdb和aof)

Redis持久化在面试和工作中都是经常会涉及到的,这里也需要了解一下

由于Redis是内存数据库,如果不持久化,Redis的数据是存储在内存中的,如果服务器进程退出或机器断电,数据就会丢失,所以持久化是必须的

1、rdb持久化(默认为rdb)

文件名默认为:dump.rdb,默认路径在redis服务开启的同级目录中,有意思的是rdb其实是redis database的缩写

rdb文件是以二进制格式存储数据的

rdb持久化的原理:redis的rdb持久化机制原理是redis会单独创建(folk)一个和主进程一模一样的子进程来进行持久化操作,这个子进程的所有数据局(变量,环境变量,程序计数器等)都跟原进程一样。当触发rdb操作时,redis会先将要持久化的数据写到一个临时文件中,等持久化结束之后再将这个临时文件替换上一次持久化好的文件,整个过程都由子进程完成,主进程不再进行任何io操作,从而保证了极高的性能。
我们知道,redis是一个单线程的高性能缓存key-value数据库。这里说的单线程实际上是指在接收网络请求时是单线程处理的,如下图三个客户端redis-cli同时发送命令过来时redis会将这多个同时过来的命令进行排队,然后按照排队顺序一个个往下执行,而将排队的命令取出来进行处理或持久化的过程中就不一定是单线程完成了,因此单线程的概念是对客户端网络请求处理时而言的,整个redis的工作过程并非完全是单线程完成的。

如下图所示:

img

这里先说明一下什么时候会触发rdb持久化的机制:

  1. shutdown的时候(默认无开启aof时)会自动触发(bgsave)

  2. redis.conf配置文件中自带的自动触发设置,(bgsave)也可以自己在配置文件设置,详细已在第八章说明

  3. 手动执行bgsave或save命令,会触发,这里讲一下bgsave和save的区别

    • bgsave:异步
      • 机制:fork一个子进程,在持久化过程中不影响主进程的操作,客户端可以正常连接Redis,等fork子进程持久化完成后,通知主进程,并结束子进程
      • 返回:立刻返回OK
    • save:同步
      • 机制:持久化时,先阻塞主进程,此时客户端无法连接Redis,等持久化完成后,主进程才开始重新工作,客户端也可以连接
      • 返回:持久化完成后再返回OK

    可见,在线上维护中,如果需要手动持久化,使用bgsave更合适

  4. 除了以上三个触发机制之外,flushall命令也会触发rdb机制

我们再深入探讨rdb的机制,提出三个问题:

  1. rdb持久化是什么时候开始fork子进程的

    当持久化开始时即fork一个子进程,在持久化操作结束后,也会自动释放这个进程。口说无凭,如何验证?这里不进行实机操作,但是可以给予一个思路,可以通过jedis一次性放入百万级别的数据,然后在redis客户端执行bgsave,之后查看Redis相关进程的情况,就可以发现会出现一个redis-rdb-bgsave的进程,这就是我们fork出来的子进程了

  2. rdb究竟是如何持久化的

    rdb持久化不是直接覆盖原来的持久化文件,我想这是因为如果持久化失败会影响到原来的数据,所以rdb持久化是先产生一个临时文件来保存要持久化的内容的,然后再将原来的持久化文件替换成临时文件。如何验证?当持久化的时候,我们可以查看dump.rdb这一级目录的文件,会发现多了一个temp开头的rdb后缀文件,这就是它产生的临时文件

  3. 主进程在持久化的时候不参与IO操作,如何验证

    我们手动save,而不是bgsave,此时主进程会被阻塞,其他客户端的IO请求都会被阻塞,直到持久化完成,所以在主进程使用rdb持久化是非常非常不明智的,会直接导致其他客户端无法对Redis数据库进程操作,这也就是我们主从复制中,rdb用于从机而不用于主机的原因

2、aof持久化(需要在配置文件中手动开启)

文件名默认为:appendonly.aof,默认路径在redis服务开启的同级目录中,aof是append only file的缩写

需要开启aof持久化时,只需要把redis.conf配置文件中的appendonly参数改为yes即可

我们在数据库理论基础中就学过了数据库的日志系统,简单来说就是,使用日志记录我们的操作,当需要持久化的时候就执行日志中的操作即可,日志不止可以用来持久化,也可以用来恢复数据。

aof持久化的原理:在aof机制中redis操作日志以追加的方式写入文件,其中读操作不做记录。

为什么需要aof:因为rdb方式在快照配置中假设默认60s查看触发一次rdb,但是在这60s内假设突然发生了宕机,那么后果就是在这60s内产生的redis数据都没有被持久化下来,从而造成数据的丢失,为了弥补这种意外情况发生导致数据丢失的问题,因此就出现了aof,而aof由于每秒操作因此极端情况下丢失的数据极少。

触发条件:

在配置文件中默认是每秒触发一次

文件内容大体如下:

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

我们解读一下这个文件:

*n代表下一个命令的操作数有多少个,比如select 0有两个,set k1 v1有三个,以此类推

$n代表下一个操作数的长度是多少,比如sleect是6,set是3,以此类推

select 0是aof文件自带的,因为默认进入Redis的数据库就是0号数据库

aof的重写机制:

  1. 什么时候会触发重写机制

    由于aof的原理决定了aof文件随着Redis的使用必然会越来越大(因为记录的命令越来越多),当触发了配置文件中aof文件的大小阈值(默认是64M)时,就会启动重写机制

  2. 重写机制的原理

    其实就是用占用内存更短的命令来替代原来的命令,减少空间的浪费,通常重写后aof文件会较原来小很多。

    aof重写并不需要对原有AOF文件进行任何的读取,写入,分析等操作,这个功能是通过读取服务器当前的数据库状态来实现的。

    比如:

    lpush list “A”

    lpop list “A”

    lpush list “B”

    lpop list “B”

    lpush list “C”

    lpop list “C”

    lpush list “D” “E”

    lpush list “F”

    本来需要存储这8条完整的命令,但重写之后则会压缩成这1条命令:lpush list “D” “E” “F”

    那么这里有一个问题,如果写入命令带有的参数太多怎么办,此时可能会导致客户端输入缓冲区的溢出,所以Redis设定了一个阈值,redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD,这个值一般是64

    重写流程图:

    img

  3. aof文件可能会损坏,我们可以使用redis-check-aof文件进行修复

3、rdb和aof如何同时工作

Redis中aof的优先级高于rdb

具体流程如下:

img

那么会有一个问题:如果我原来的数据存在rdb文件,但是我现在需要换成aof持久化,原来的文件怎么样才能不丢失?

解决:先不开启aof机制,直接连接Redis数据库,使得rdb文件被读入到Redis内存中,然后使用命令config set appendonly yes动态开启aof机制,这样生成的aof文件就会把当前内存中的数据存储进去了,接下来关闭Redis客户端,然后到配置文件中查看aof是否开启了,如果没有开启再手动修改成yes即可(目的是把内存作为一个中介,将rdb文件中的数据存储到aof文件中去)

4、rdb和aof的优缺点和比较

  • rdb的优点:

    • 适合大规模数据修复
    • 对数据精度要求不高
  • rdb的缺点:

    • 在持久化的时候需要一定的时间间隔,如果在一定的间隔时间内服务器意外宕机,那么就会丢失最后一次持久化的数据
    • 因为RDB持久化是需要fork出一份子进程进行IO操作的,也就是说,在原本的进程当中再复制出一个一模一样的进程作为子进程在内存中运行,内存的承载就会变为原来的两倍
  • aof的优点:

    • 它支持 同步记录异步记录,可在配置文件中操作,如下:

      appendfsync always       # 同步记录,客户端中一有写操作,即刻记录,数据的完整性好,但是性能较差
      appendfsync everysec     # 异步记录,每秒记录一次,但是服务器如果在这一秒之内宕机,这一秒的数据就会丢失
      appendfsync no           # 不记录
      
  • aof的缺点:

    • 从恢复数据的角度来说,AOF所恢复的数据量一定是比RDB来得大的,从恢复数据的时间的角度来说,AOF的时间也是大于RDB的

比较:

rdb:数据丢失多,性能快,适合大数据导入

aof:数据丢失少,性能慢,不适合大数据导入

如果rdb和aof都设置为每秒执行一次持久化,二者有什么区别?

  1. rdb会folk子进程进行持久化导致开销会很大,相当于又创建了一个redis进程
  2. aof的持久化是不会fork子进程的因此性能开销更小

十、Redis的发布订阅

Redis的发布订阅(publish/subscribe)是一种消息通信模式,发送者(publish)发送消息,订阅者(subscribe)接收消息

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

img

如图,有三个客户端订阅了一个Channel1

img

当Channel1的后台发送了一个数据到Channel1的频道中,这三个订阅了Channel1的客户端就会同时收到这个数据

img

命令:

img

这些都是用来实现数据通信的命令,现实中的场景可以是网络聊天室,广播等

演示:

订阅者:

127.0.0.1:6379> SUBSCRIBE xiafan   # 订阅一个频道叫xiafan
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "xiafan"
3) (integer) 1
# 一旦开始订阅,会立即占用当前进程去监听自己所订阅的那个Channel
1) "message" #消息
2) "xiafan" #频道
3) "Hello!I love Java!!" #消息具体内容
1) "message"
2) "xiafan"
3) "Hello!I love Redis!!"

发布者:

127.0.0.1:6379> PUBLISH xiafan "Hello!I love Java!!"  # 往频道xiafan中发布一条消息
(integer) 1
127.0.0.1:6379> PUBLISH xiafan "Hello!I love Redis!!"
(integer) 1

原理:

Redis是C语言编写,在实现消息的发布和订阅的时候,Redis将其封装在一个后缀名为点c的文件中,pubsub.c

img

通过subscribe和publish命令实现消息的发布和订阅,当用户订阅了一个频道之后,redis-server里面维护了一个字典,字典里有很多个Channel(频道),字典的值就是一个链表,链表中保存的是订阅了这个频道的用户,使用publish命令往频道发送数据之后,redis-server使用此频道作为key,去遍历这个指定的value链表,将信息依次发送过去。

类似下图:

img

使用场景(纯Redis使用的场景一般较为简单):

  1. 实时消息系统
  2. 即时聊天IM
  3. 订阅关注系统

对于复杂场景:可以使用消息中间件来做,如:RabbitMQ,RocketMQ,kafka等中间件技术

十一、Redis的主从复制

1、什么是Redis主从复制

Redis的主从复制,实际上就是将一台Redis服务器的数据,复制到其他Redis的服务器。前者被称为服务器的主节点(master/leader),后者就是从节点(slave/follower),数据的复制只能由主节点到从节点,Master以写为主,slave以读为主

实际上每台服务器都是一个主节点,一个主节点可以有零个或多个从节点,并且每一个从节点只能有一个主节点

2、主从复制有什么用

1、主从复制可以实现数据备份,这是除了持久化的另一种实现数据保存的方式

2、实现故障快速修复,因为要实现主从复制必然需要多台服务器,一旦主节点挂掉,从节点可以代替主节点提供服务

3、主从复制配合读写分离,可以实现分担服务器负载,如果在生产环境中读的操作远大于写的操作时,可以通过多个从节点进行分担负载,以提高Redis的并发量

4、主从复制也是Redis集群搭建和哨兵机制的基础

将Redis运用于项目中,是不会只使用一台服务器进行搭建的,因为:

  • 如果是单个Redis,那么单个服务器将会独自完成来自客户端的读写操作,负载较大,而且只有一台Redis服务器,太容易挂掉了,一不小心宕掉了就很麻烦

  • 单个服务器,内存容量是有上限的,不管这台Redis服务器的内存再怎么大,也不可能完全用来存储内存,并且单台Redis服务器的内存占用一般不会超过20个G

下图为主从复制例子:

img

主从复制,读写分离,在大部分情况下,很多人浏览网页更多的则是读操作,这是架构中常常会用到的一种模式,用来缓解读压力

3、在单服务器上实现主从复制所需的配置文件修改

因为Redis默认启动时本身就是一个master,所以在进行主从复制的搭建时,只需要配置从库即可,查询当前库的状态信息,可以使用下面这个命令

需要几台从机就增加几个配置文件,我们以一主二从为例:

那么一共需要三个配置文件,配置文件需要修改自己特定的部分为:

1、搭建主从复制,不同的服务器就需要不同的端口号

2、pidfile的文件名称

3、logfile 日志文件名称

4、Redis默认RDB持久化,所以dump.rdb的名称也是需要修改的

修改完毕后即可启动Redis服务

4、使用命令配置从机(暂时性配置)

  1. 使用info replication查看当前Redis服务的状态

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

  2. 配置从机

    主机是不需要配置的,因为Redis服务打开时默认是主机,只需要配置从机。

    使用slaveof命令配置从机即可:slaveof ip地址 端口

    如:slaveof 127.0.0.1 6379

    在这里插入图片描述

    说明:

    # Replication
    role:slave                              # 当前角色 从机
    master_host:127.0.0.1                   # 主机信息 127.0.0.1
    master_port:6379                        # 主机端口号 6379
    master_link_status:up                   # 主机状态 在线
    
  3. 查看主机情况

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

    可以看到此时多了两个从机

这里是使用命令行方式进行演示,使用配置文件配置则是永久性配置,详细看第八章

5、主从复制的机制

  1. 从机会自动保存主机中的所有信息和数据,主机负责写入,从机负责读出

  2. 当主机断开的时候,从机原来保有的数据依旧存在,依然可以负责读操作

  3. 如果是命令行方式配置的临时性从机,如果从机断开再连接时是不会自动把原来的主机当主机的,而是默认把自己当主机,而命令行配置则不会,所以工作环境中还是推荐使用命令行配置

  4. 复制原理

    如果一个Slave指定了一个master做老大,那么Master节点就会发送一个叫做sync的同步命令,在使用了SLAVEOF命令之后,Master接收到了,就会启动后台的存盘进程,开始收集修改数据集的命令,在存盘进程执行完毕之后,会将数据文件发送给Slave,实现一次完全同步

    说到同步,这个有两个概念需要多聊一嘴

    1、全量复制,Slave文件在接到Master发来的数据文件之后,会将其存盘并加载到内存

    2、增量复制,Master和Slave已经实现同步的情况下,如果Master继续修改数据集,会将命令再次收集起来,再发送给Slave
    只要是重新连接Master的服务器,在连接之后都会自动执行全量复制!

6、主从的两种模式以及如何手动更换主机

  1. 只有一个主机,从机只有一个等级,都直接连接主机,我们上面所说的主从就是这种,如图:

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

  2. 只有一个主机,但从机有多级,呈链状,如图:

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

    这种主从复制的特性,在实际应用场景中可以有多个节点,这样的节点具有Master和Slave的双重身份(但是实际上它还是一个Slave节点,只不过它是一个可以进行写操作的Slave节点),可以分担Redis的写压力,并且在真正的Master节点挂掉之后,这些具有双重身份的就可以顶上去工作,有效解决了一主二从的不可写问题

  3. 手动更换主机

    如果主机挂了,需要有一个从机来接替主机的作用,也就是变成一个新的主机,我们在从机上使用这个命令即可:

    slaveof no one

7、哨兵模式sentinel(自动更换主机)

原理:

由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

Redis提供了哨兵的命令,并且它是一个特殊的模式,它会创建出一个完全独立于Redis服务器的进程,一个哨兵可以对多台服务器进行监控,并且可以有多个哨兵,每个哨兵会定时发送PING命令给服务器,并且还要在一定时间内得到服务器的响应,得到响应之后哨兵模式才会判定你现在状态正常。如果在规定的时间内它发送的请求主机没有得到响应,那么哨兵便会初步判断,当前主机是主观下线,其余的哨兵发现这台主机没有在规定时间内响应数据,那么便会以每秒一次的频率对主机进行判断,它确实是主观下线了,那么主机就会被标记为客观下线,主机挂掉之后,哨兵便会通过投票的方式在挂掉的主机下的从机中选出一个作为新主机。

优点:

1、哨兵集群是基于主从复制来实现的,主从复制的优点全部具备

2、主从可以切换,故障可以转移,提升系统可用性

3、哨兵模式就是主从模式的升级,谋权篡位的手动到自动,更加健壮

缺点:

1、在线扩容比较麻烦,集群的数量达到上限,就会变得十分繁琐

2、实现哨兵模式的配置较为麻烦,如果出现故障,还会涉及到一些shell脚本的运行,这些都是非常麻烦的操作

哨兵配置文件sentinel.conf的内容说明和如何编写(根据自己的需求来定):

# 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 2  
  
# 当在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  

十二、Redis的缓存穿透、击穿和雪崩

Redis的缓存穿透、击穿和雪崩一直都是面试高频的问题,了解它非常有必要

1、缓存穿透(访问数据查不到且巨量)

在大多数场景中,数据库里的id字段一般来说都是自增。如果说,用户发送了一个请求,会首先进入缓存,查不到,就进入数据库,进了数据库,也查不到,查不到的话,如果说访问量较少,那还好,直接返回不存在嘛,因为这种少量的缓存穿透实际上是不可避免的,但是,一旦有一些不怀好意的坏蛋,在发送请求查询数据库的时候,主键的id字段故意给你来一个负数或者是一些远大于数据库最大id的数,而且进行巨大量的并发访问,这时候缓存中肯定是没有的,那这些请求就直接压给数据库了,数据库扛不住这么大的东西呀,那咋办,不解决数据库就只能挂掉呀。

三种解决方法:

1、在进行项目的整合时需要使用到API接口层,在接口层中定义自己的规则,对于不合法的参数可以直接返回,对于调用此接口的API的对象进行严查,任何可能发生的情况都要考虑到

2、在缓存中设置一个空对象,使用空对象完成后续请求,如图:

img

3、使用一个工具,叫做布隆过滤器(Bloom Filter),布隆过滤器使用的是一个bit数组和一个hash算法实现的数据结构,并且内存的使用率极低。使用布隆过滤器可以快速判断当前key是否存在,和Java的Optional类有点相似,布隆过滤器告诉你这个key不存在,那么它就一定不存在
img

2、缓存击穿(访问数据巨量)

缓存击穿,它是缓存穿透的一种特殊情况,一般情况下没有公司会去实现这样的业务,因为没有这样一条非常非常高频的热点数据能够搞垮一台服务器,可能性是非常小的

举个栗子,如果有一个非常高频的热点key,在某一个时刻过期,与此同时又有非常非常多的请求并发访问这个key,因为缓存时间已过,现在全部的请求又开始全部压在数据库上面了,很容易导致服务器挂掉

解决方法:

1、设置为当前key永不过期,但是不推荐这种做法,因为这样会长期占用Redis的内存空间

2、用Redis的分布式锁,如果说当前有非常非常多的请求传进来,这个时候有分布式锁的保护,可以允许这些请求中的其中一个放进来,缓存找不到就直接查数据库嘛,查完了再把数据放到缓存中让其他的请求直接查缓存即可,如图:

img

3、缓存雪崩(缓存失效)

缓存雪崩指的是,在同一个时间内,一大批的缓存,集体失效,就好像没有被缓存过一样,这个时候假设说有双十一双十二的这种秒杀活动,恰好在这个时刻缓存是成批成批的失效,那么缓存失效,这些请求也还是直接压上数据库的,如果服务器在没有加锁的情况下,这种流量几乎是瞬间达到一个峰值的,对于一个服务器来说,也是会出现宕机的情况

解决方法:

1、搭建Redis集群,在高可用的情况下,配置多台服务器,在服务器中保存同样的数据,这样即使是集群中的一台甚至是多台挂掉的情况下,也依旧能够正常工作

2、如果不搭建集群,也可以这么做:项目启动时,将数据库和Redis进行数据同步,将数据库中部分的数据信息首先加载进入Redis缓存,并且在加载进入缓存时,可以将这些缓存数据的存活时间设置为随机,并且在数据库和Redis缓存需要在一定的时间之内进行同步更新

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值