超详细Redis入门教程

什么是NoSQL数据库

随着云计算、物联网等新一代技术的发展,在移动计算、社交网络的推动下,大数据技术产生并迅速地建立起生态体系。然而,大数据在推动技术变革的同时,企业对海量数据的存储、并发访问、扩展等要求越来越高。而基于ACID原则的传统关系型数据库开始出现了瓶颈。
在这里插入图片描述

这时出现了NoSQL数据库,(NoSQLNot Only SQL),意即“不仅仅是数据库”,是一项全新的数据库理念,其为解决海量数据的存储、并发访问以及拓展而产生,具有数据模型灵活、并发访问度高、易于扩展和伸缩、开发效率高以及开发成本低等优点,能够解决大规模数据集合多重数据种类挑战,尤其是大数据应用难题。
在这里插入图片描述

必须了解的基本理论

大数据对数据存储的挑战(3高):

  • 高并发读写需求: 对于实时性、动态性要求较高的社交网站,往往需要并发度达到每秒上万次的读写请求,需要数据库有着极高的并发负载。
  • 高性能存储和访问需求: 随着动态交互网站Web2.0的兴起,产生的数据爆发式增长,面对海量的数据传统的数据库的效率非常低,对于非关系型数据库是基于键值对的,数据处理过程不需要经过SQL层的解析,效率相对很高。
  • 高扩展性: 传统的关系型数据库很难实现水平扩展,当需要扩容时,关系型数据库往往需要进行停机维护与数据迁移,而这对于24小时不间断服务的网站显然是不可取的,而对于非关系型数据库是基于键值对,数据之间没有耦合性,能够非常容易水平扩展。

ACID理论:(事务的基本特性)

  • A(Atomicty)原子性: 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • C(Consistency)一致性: 事务前后数据的完整性必须保持一致。
  • I(Isolation)独立性: 多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
  • D(Durability)持久性: 一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

BASE理论:

  • BA(Basically Available)基本可用: 分布式系统在出现不可预知的故障的时候,允许损失部分可用性——但请注意,这绝不等价于系统不可用。
  • S(Soft state)软状态: 指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
  • E(Eventually Consistent)最终一致: 系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。

CAP原则:(分布式基本理论)

  • C(Consistency)一致性: 数据复制的时候,按照强一致性的方式进行数据复制。保证了在读操作总是能够读取到之前写入的数据,无论从那个主数据或者副本数据。
  • A(Availability)可用性: 数据写入成功后,正在进行数据复制时,任何一个副本节点发生异常也不会影响此次写入操作。可以理解为,此时数据的复制采用的是弱一致性,数据的读写操作在单台集器发生故障的情况下仍然可以正常执行。
  • P(Partition tolerance)分区容错性: 在服务实例发生异常时,分布式系统任然能够满足一致性和可用性。

在这里插入图片描述

NoSQL的特点:

  • 易扩展: NoSQL数据库中的数据之间是无关系的,这就使得数据库可以非常容易地扩展。
  • 高性能: NOSQL数据库具有高并发读写性能,适合处理海量的数据
  • 灵活的数据模型: NoSQL数据库不需要实现存储的数据建立相应的字段,用户可以随时存储自定义的各种数据格式。
  • 高可用: NoSQL在不太影响性能的情况下,可以方便地实现高可用的架构。
  • 低成本: nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜

NoSQL的缺点:

  • 维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。
  • 不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。
  • 不提供关系型数据库对事务的处理。

非关系型数据库和关系型数据库比较

目前的数据库主要分为关系行数据库和非关系型数据库两类,二者在多方面均有区别,各有优势:
在这里插入图片描述

  • 存储方式: 传统数据库采用表的格式进行存储,数据以行和列的方式进行存储,读取和查询十分方便。而非关系型数据库不适合以表的格式进行存储,而是以数据集的方式进行存储,没有固定的格式,类似于键值对、图结构或者文档,可以可以很方便的进行扩展。

  • 存储结构: 关系型数据库按照结构化的方式存储数据,每张数据表都必须实现定义好数据表的表结构,然后再根据表结构对数据进行存储。而非关系型数据库采用的是动态结构,如果面对大量的非结构化数据,它可以非常轻松地适应数据类型和结构的改变,也可以根据需要改变数据表的结构。

  • 存储规范: 关系型数据库将数据按照最小关系表的形式进行存储,这样数据管理就变得清晰,但是随着数据表数量的增加,数据的管理会变得十分困难。非关系型数据库的数据存储方式是用平面数据集的方式存放,虽然会出现数据重复存放造成空间浪费的问题,但是通常单独的数据库都是采用单独存储的形式,将数据存为一个整体,对于大量数据的读写十分方便。

  • 扩展方式: 关系型数据库将数据存储到表中,随着表数量的增多,进行多表操作时会出现I/O瓶颈,想要解决这个问题,可以选择提高处理能力,选择更好的硬件设施,当时名不能彻底解决问题,而非关系型数据库使用的时数据集的方式,存储方式是分布式的,可以通过水平扩展的方式扩展数据库。

  • 查询方式: 关系型数据库采用结构化查询语言(SQL),可以执行更复杂的操作,而非关系型数据库使用的是非结构化语言(UnQL),并没有统一的标准,不同的数据库厂商提供的方式可能比不一样。

  • 规范化: 在关系型数据库中,一个数据实体会被分割为多个部分,然后对分割后的数据进行规范化处理,之后在将数据存储到多张关系数据表中,过程相对复杂。而非关系数据库没有这方面的问题,不需要规范化数据,通常会在一个单独的数据单元中存储一个复杂的数据实体。

  • 事务性: 关系型数据库遵循ACID规则,而非关系型数据库则强调BASE原则。

  • 读写性能: 关系型数据库强调数据的一致性为此降低了数据的读写性能,对于海量数据的处理效率很低。而非关系型数据库可以很好的应对海量数据。

  • 授权方式: 关系型数据库包括Oracle、SQL server 、DB2、MySQL等,除MySQL外其它大多数都是非开源的,需要支付高昂的使用费。非关系型数据库包括Redis、HBase、MongoDB、Memcache等都是开源的(企业版除外)

关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。

一般会将数据存储在关系型数据库中,在nosql数据

NoSQL数据库的分类

数据库分类数据类型常见数据库优势劣势应用场景
键值对存储数据库Key指向 Value的键值对Redis、Tokyo Cabinet/Tyrant、Voldemort、Oracle BDB快速查询存储的数据缺少结构化会话存储、网站购物车等
文档存储数据库BSON类型(二进制JSON)MongoDB、CouchDB、RavenDB数据结构要求不严格查询性能不高,而且缺乏统一的查询语法内容管理应用程序、电子商务应用程序等
列式存储数据库将同列数据存到一起HBase、Cassandra、Riak查找速度快,可扩展性强功能相对局限日志记录、博客网站等
图形存储数据库图结构Neo4J、FlockDB、AllegroGrap、GraphDB利用图结构相关算法需要对整个图做计算,不容易做分布式的集群方案欺诈检测、推荐应用等

认识Redis

在这里插入图片描述

Redis(Remote Dictionary Server)远程字典服务,是一种基于键值对(key-value)的NoSQL数据库,与很多键值对数据库不同,Redis中的值可以是由string、hash、list、set、zset等多种数据结构和算法组成,能够满足很多的应用场景,同时Redis会将所有数据都存放在内存中,读写速度非常快。

为什么说Redis的速度非常快呢?
据官方给出的数字,Redis的读写性可以达到10万/秒,在不考虑硬件性能的化,redis速度如此快的原因可以归纳为一下三点:
(1)Redis是基于C语言实现的,一般来说C语言距离操作系统更近,执行速度相对更快
(2)Redis中所有数据都是存储在内存中的,CPU对计算器的访问速度中内存远快于外存
(3)Redis采用了单线程架构,预防多线程可能产生的竞争问题

基本特性:
速度快、基于键值对、功能丰富、简单稳定、客户端语言多、持久化、主从复制、高可用与分布式。

Redis的应用场景:

  • 适用于大型网站的缓存机制,减少后端数据源的压力
  • 用于构建排行榜系统,进行各种复杂维度的计算
  • 网站中的计数器,比如视频网站的播放量、电商网站的浏览量
  • 社交网络,实现粉丝、点赞、好友推送等功能
  • 消息队列系统,Redis中提供了基本的消息队列机制,可以满足一般的消息队列的基本功能

安装教程

windows安装

下载地址:https://github.com/MicrosoftArchive/redis/releases
在这里插入图片描述

将压缩包解压即可直接可以使用:
在这里插入图片描述
运行界面:
在这里插入图片描述

连接redis,双击客户端redis-cli:
在这里插入图片描述

Linux下安装

1.下载安装包:

官网:https://redis.io
中文网:http://www.redis.net.cn/

在这里插入图片描述

将安装包上传到linux服务器,这里上传到了opt目录:
在这里插入图片描述
远程登录,切换到opt目录,可以看到文件已经上传成功:
在这里插入图片描述
解压文件:tar -zxvf redis-5.0.7.tar.gz,并切换到redis文件目录:
在这里插入图片描述

确保虚拟机中有gcc环境:yum install gcc-c++,之后再当前目录输入make配置环境(时间较长):
在这里插入图片描述
一个问题:如果下载最新版Redis,由于版本太高会出现gcc版本与redis版本不匹配的问题,可以更新为更高版本的Jcc

执行make install确认是否安装成功:
在这里插入图片描述

切换到默认安装路径usr:cd /usr/local/bin
在这里插入图片描述
将redis的配置文件拷贝到该目录下:

新建一个文件夹:mkdir redis-config
拷贝:cp /opt/redis-5.0.7/redis.conf redis-config
在这里插入图片描述

编辑 redis.conf配置文件:vim redis.conf

设置以守护进程运行:
在这里插入图片描述
通过指定的配置文件启动redis服务:redis-server redis-config/redis.conf
在这里插入图片描述
使用redis客户端进行连接:redis-cli -p 6379
在这里插入图片描述
退出redis:

  • shutdown:退出服务器端
  • exit:退出服务器端

在这里插入图片描述

查看redis是否开启:ps -ef|grep redis
在这里插入图片描述

Redis常用指令

服务器相关命令

  • ping : 检测连接是否存活
  • echo: 在命令行打印一些内容
  • quit、exit: 退出客户端
  • shutdown: 退出服务器端
  • info: 返回redis相关信息
  • config get dir/* 实时传递接收的请求
  • showlog: 显示慢查询
  • select n: 切换到数据库n,redis默认有16个数据库(DB 0~DB 15),默认使用的第0个
  • dbsize: 查看当前数据库大小
  • move key n: 不同数据库之间数据是不能互通的,move移动键到指定数据库
  • flushdb: 清空当前数据库中的键值对。
  • flushall: 清空所有数据库的键值对。

key相关命令

在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。

常用命令:

  • keys * :查看当前数据库中所有的key
  • dbsize: 键总数
  • exists key: 检查键是否存在
  • del key [key …]: 删除键
  • expire key seconds: 键过期
  • ttl key: 获取键的有效时长
  • persist key: 移除键的过期时间
  • type key: 键的数据结构类型
  • randomkey: 随机返回数据库中一个键
  • rename key1 key2 : 重命名
  • renamex key1 key2 : 当key2不存在时,key1重命名

代码示例:
在这里插入图片描述

五大数据类型

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。其通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:

  • 字符串类型: string
  • 哈希类型: hash
  • 列表类型: list
  • 集合类型: set
  • 有序集合类型: sortedset(zset)

String(字符串)

字符串类型是Redis最基础的数据结构,其它的几种数据结构都是在字符串类型基础上构建的,字符串的值可以是:字符串、数字、二进制,但其值最大不能超过512M。

使用场景: 缓存、计数器、对象存储缓存(共享session)、限速

常用命令:

  • set key value: 设置一个key的value值
  • setnx key value: 仅当key不存在时进行set
  • setex key seconds value: set 键值对并设置过期时间
  • mset key value [key value …]: 设置多个key value
  • msetnx key1 value1 [key2 value2…]: 批量设置键值对,仅当参数中所有的key都不存在时执行,原子性操作,一起成功,一起失败
  • get key: 返回key的value
  • mget key [key …] : 批量获取多个key保存的值
  • exists key [key …]: 查询一个key是否存在
  • decr/incr key: 将指定key的value数值进行+1/-1(仅对于数字)
  • incrby/decrbyB key n: 按指定的步长对数值进行加减
  • incrbyfloat key n: 为数值加上浮点型数值
  • append key value: 向指定的key的value后追加字符串
  • strlen key: 返回key的string类型value的长度。
  • getset key value: 设置一个key的value,并获取设置前的值,如果不存在则返回null
  • setrange key offset value: 设置指定位置的字符
  • getrange key start end: 获取存储在key上的值的一个子字符串

代码示例:
在这里插入图片描述

List(列表)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边),也可以获取指定范围指定下标的元素等。一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

两个特点:
1.列表中的元素是有序的,可以通过索引下标获取某个元素霍某个某个范围内的元素列表
2.列表中的元素可以是重复的

使用场景: 消息队列、栈、文章列表等。

常用指令:

  • 添加操作
  • lpush/rpush key value1[value2…]: 从左边/右边向列表中PUSH值(一个或者多个)
  • lpushx/rpushx key value: 向已存在的列名中push值(一个或者多个),list不存在 lpushx失败
  • linsert key before|after pivot value: 在指定列表元素的前/后 插入value
  • 查找操作
  • lindex key index: 通过索引获取列表元素
  • lrange key start end: 获取list 起止元素 (索引从左往右 递增)
  • llen key: 查看列表长度
  • 删除操作
  • lpop/rpop key: 从最左边/最右边移除值 并返回
  • lrem key count value: count >0:从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索… count = 0:删除列表中所有的指定value。
  • ltrim key start end: 通过下标截取指定范围内的列表
  • rpoplpush source destination: 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部
  • 修改操作
  • lset key index value: 通过索引为元素设值
  • 阻塞操作
  • blpop/brpop key1[key2] timout: 移出并获取列表的第一个/最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
  • brpoplpush source destination timeout: 和rpoplpush功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

代码示例:
在这里插入图片描述

Set(集合)

Redis的Set是string类型的无序集合,我们不能通过索引获取元素。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

应用场景: 标签(tag)

常用命令:

  • 集合内操作
  • sadd key member1[member2…]: 向集合中无序增加一个/多个成员
  • srem key member1[member2…]: 移除集合中一个/多个成员
  • scard key: 获取集合的成员数
  • smembers key: 返回集合中所有的成员
  • sismember key member: 查询member元素是否是集合的成员,若存在返回1,不存在返回0
  • srandmember key [count]: 随机返回集合中count个成员,count缺省值为1
  • spop key [count]: 随机移除并返回集合中count个成员,count缺省值为1
  • 集合间操作
  • sinter key1 [key2…]: 返回所有集合的交集
  • sinterstore destination key1[key2…]: 在SINTER的基础上,存储结果到集合中。覆盖
  • sunion key1 [key2…]: 返回所有集合的并集
  • sunionstore destination key1 [key2…]: 在SUNION的基础上,存储结果到及和张。覆盖
  • sdiff key1[key2…]: 返回所有集合的差集 key1- key2 - …
  • sdiffstore destination key1[key2…]: 在SDIFF的基础上,将结果保存到集合中。覆盖
  • smove source destination member: 将source集合的成员member移动到destination集合
  • sscan key [MATCH pattern] [COUNT count]: 在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分

代码示例:

在这里插入图片描述

Hash(哈希)

几乎所有的编程语言都提供了哈希(hash)结构,Redis中 hash 是一个string类型的field和value的映射表value={{field1,value1},{field2,value2}…},可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。

应用场景: 用户信息缓存

常用命令:

  • hset key field value: 将哈希表 key 中的字段 field 的值设为 value。重复设置同一个field会覆盖,返回0
  • hmset key field1 value1 [field2 value2…]: 同时将多个 field-value (域-值)对设置到哈希表 key 中。
  • hsetnx key field value: 只有在字段 field不存在时,设置哈希表字段的值。
  • hget key field value: 获取存储在哈希表中指定字段的值
  • hmget key field1 [field2…]: 获取所有给定字段的值
  • hexists key field: 查看哈希表 key 中,指定的字段是否存在。
  • hdel key field1 [field2…]: 删除哈希表key中一个/多个field字段
  • hlen key: 获取哈希表中字段的数量
  • hkeys key: 获取所有字段field
  • hvals key: 获取哈希表中所有值value
  • hgetall key: 获取在哈希表key 的所有字段和值
  • hincrby key field n: 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段
  • hincrbyfloat key field n: 为哈希表 key 中的指定字段的浮点数值加上增量 n。
  • hscan key cursor [MATCH pattern] [COUNT count]: 迭代哈希表中的键值对。

代码示例:

在这里插入图片描述

Zset(有序集合)

在有序集合中保留了不能有重复成员的特性,但其中的成员是可以排序的,每一个元素都会关联一个double类型的分数(score)作为排序依据,score相同时按字典顺序排序。redis正是通过分数来为集合中的成员进行从小到大的排序。

应用场景: 排行榜系统,成绩单,工资表

常用命令:

  • 集合内
  • zadd key score member1 [score2 member2]: 向有序集合添加一个或多个成员,或者更新已存在成员的分数
  • zcard key: 获取有序集合的成员数
  • zscore key member: 返回有序集中,成员的分数值
  • zcount key min max: 计算在有序集合中指定区间score的成员数
  • zlexcount key min max: 在有序集合中计算指定字典区间内成员数量
  • zincrby key n member: 有序集合中对指定成员的分数加上增量 n
  • zscan key cursor [MATCH pattern] [COUNT count]: 迭代有序集合中的元素(包括元素成员和元素分值)
  • 范围查询
  • zrank key member: 返回有序集合中指定成员的索引
  • zrevrank key member: 返回有序集合中指定成员的索引,从大到小排序
  • zrange key start end: 通过索引区间返回有序集合成指定区间内的成员
  • zrevrange key start end: 通过索引区间返回有序集合成指定区间内的成员,分数从高到底
  • zrangebylex key min max: 通过字典区间返回有序集合的成员
  • zrevrangebylex key max min: 按字典顺序倒序返回有序集合的成员
  • zrangebyscore key min max: 返回有序集中指定分数区间内的成员 -inf 和 +inf分别表示最小最大值,只支持开区间
  • zrevrangebyscore key max min: 返回有序集中指定分数区间内的成员,分数从高到低排序
  • 删除操作
  • zrem key member1 [member2…]: 移除有序集合中一个/多个成员
  • zremrangebylex key min max: 移除有序集合中给定的字典区间的所有成员
  • zremrangebyrank key start stop: 移除有序集合中给定的排名区间的所有成员
  • zremrangebyscore key min max: 移除有序集合中给定的分数区间的所有成员
  • 集合间操作
  • zinterstore destination numkeyskey1 [key2 …]: 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数,将score相加作为结果的score
  • zunionstore destination numkeys key1 [key2…]: 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中

代码示例:
在这里插入图片描述

Redis发布订阅模式

Redis提供了基于“发布/订阅”模式的消息机制,在这种模式下,消息发布者和订阅者不进行直接通信,发布者向指定频道(channel)中发布消息,订阅该频道的客户端都可以接收该消息。

在这里插入图片描述

Redis中提供若干命令支持发布订阅功能:

  • 发布消息给指定频道: publish channel message

  • 从一个或多个频道中订阅消息:subscribe channel [channel …]

  • 取消订阅指定的频道:unsubscribe [channel [channel …]]

  • 按照模式订阅频道:psubscribe pattern [pattern …]

  • 按照模式取消订阅频道:punsubscribe [pattern [pattern …]]

  • 查阅订阅的频道:

    查看活跃频道:pubsub channels [pattern]
    查看频道订阅数:pubsub numsub [channel …]
    查看模式订阅数:pubsub numpat

测试发布订阅:
客户端在执行订阅命令后进入订阅状态,只能接受subscribe、psubscribe、unsubscribe、punsubscribe四个命令。新开启的订阅客户端无法收到频道之前的消息。
在这里插入图片描述
测试查询订阅:
在这里插入图片描述

使用场景:聊天室、公告牌、服务之间利用信息解耦等

Redis事务控制

Redis事务的本质是一组命令的集合,一次执行多个指令,事务中所有命令都被序列化,其他客户端提交的命令请求不会插入到事务执行命令序列中,简单来说:要不全执行,要不全不执行。Redis中提供了简单的事务功能,需要multiexec两个命令实现。

事务执行流程:

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

在Redis的事务中的命令出现不同错误时,处理机制也会有所差异:

编译型异常(命令错误),事务中所有命令都不会执行,因为Redis没有隔离级别的概念,队列中的命令没有提交之前都不会实际的被执行:
在这里插入图片描述

运行时异常(1/0),如果事务队列中存在语法性错误,那么执行命令的时候,其它命令是可以正常执行的,错误命令抛出异常,由此看出Redis的单条命令保证原子性,但是事务并不保证原子性,不支持回滚功能。

在这里插入图片描述

通过事务实现上锁:
在有些应用场景需要在事务之前,确保事务中的key没有被其它客户端修改过,才执行事务,否则不执行,着类似于乐观锁的概念,在Redis中也提供了相关实现方法:

  • 监视、加锁(watch)
  • 取消监视、解锁(unwatch)

乐观锁:

假设数据一般情况不会造成冲突(很乐观,认为什么时候都不会出问题),在数据提交更新时正式对数据的冲突与否进行检测,发现了冲突,发出错误信息,让用户决定如何处理,适用于读操作多的场景,可以提高程序的吞吐量。
为了避免数据库的幻读,业务处理时间过长,乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性

实现方法:

当有多个线程在操控redis的时候,被watch监视的key值如果发生改变,正在进行的事务将会失败,每次加锁后都要进行解锁,再加锁去重新获取最新的值

正常流程:
在这里插入图片描述
出现线程问题:
在这里插入图片描述

Redis持久化

Redis是一个内存数据库,当redis服务器重启,获取电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。目前Redis支持的存储机制有RDB和AOF。

RDB机制

RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB的过程分为手动触发和自动触发。

触发机制:

1.通过save与bgsave命令手动触发

  • save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间的阻塞,线上环境不建议使用。
  • bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短(微秒级别)。

2.通过配置文件自动触发
redis的配置文件redis.windwos.conf中有一下默认内容:

#900秒内,至少有一个key进行了修改,就进行持久化操作
save 900 1
#300秒内,至少有10个key进行了修改,就进行持久化操作
save 300 10
#60秒内,至少有1000个key进行了修改,就进行持久化操作
save 60 10000

流程说明:
1.父进程执行fork操作创建子进程
2.子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原来文件进行原子替换。
3.进程发送信号给父进程表示完成,父进程更新统计信息。

在这里插入图片描述

整个过程主进程不进行任何io操作,保证了性能,如果进行大规模数据恢复,RDB和AOP都可以进行数据恢复,RDB数据恢复完整性不敏感,RDB更加高效,缺点时最后一次持久化后的数据可能丢失,默认使用的就是RDB,一般情况不需要修改这个配置。

RDB文件处理:

RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置指定,默认为一个dump.rdb文件(在生产环境中最好对dump.rdb文件进行备份)。可以通过执行config set dir {newDir}config set dbfilename {newFileName}运行期动态执行,当下次运行时会保存到新目录。

如果redis加载的RDB文件损坏时拒绝启动,可以使用Redis提供的redis-check-dump工具检测RDB文件并获得对应的错误报告。

RDB优点:

  • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适合备份:父进程正常处理用户请求,fork分支一个子进程进行备份。
  • 适合大规模的数据恢复,如果服务器宕机了,不要删除rdb文件,重启自然在目录下,自动会读取,并且数据恢复速度远大于AOF方式。

RDB缺点:

  • RDB并不能做到实时持久化,存储需要一定的时间间隔,可以自行修改设置。如果redis意外宕机,最后一次的修改数据会丢失。
  • fork进程的时候,会占用一定的内存空间。
  • RDB使用特定的二进制格式保存,可能存在新老版本不兼容问题。

AOF机制

AOF(append only file)持久化: 以独立日志的方式记录每次执行的命令(除读操作外),重启时重新执行AOF文件中的命令来恢复文件。(aof默认是文件无限追加,大小会不断扩张,如果是文件过大就需要写很久)

开启AOF需要我们设置配置:appendonly yes,默认不打开。文件名通过appendfilename配置设置,默认文件名为appendonly.aof

执行流程:

  • 命令写入(append):所有命令会追加到aof_buf(缓冲区)中。
  • 文件同步(sync):AOF缓冲区根据对应的策略向硬盘做同步操作。
  • 文件重写(rewrite):随着文件越来越大,需要定期对AOF文件进行重写,达到压缩目的。
  • 重启加载(load):重启时,可以加载AOF文件进行数据恢复

在这里插入图片描述
AOF文件损坏:

加载损坏的AOF文件时会拒绝启动,并打印日志。我们可以先备份文件,后采用redis-check-aof --fix命令修复,修复后使用diff -u对比数据的差异,找出丢失的数据,有些可以进行人工修复。

AOF可能会出现结尾保存不完整的情况,比如机器断电导致结尾命令没有写全。Redis提供了aof-load-truncated配置来兼容这种情况,默认开启。这样在加载出问题时会继续启动,并打印警告日志。

Redis客户端操作

Jedis

Java有很多优秀的Redis客户端,使用较为广泛的是客户端Jedis,官方推荐的操作Redis的中间件。

使用步骤:

  • 导入jedis的jar包(依赖:redis.clients.jedis.3.3.0)
  • 建立连接:Jedis jedis = new Jedis("localhost",6379);
  • 操作:jedis.set("key","value");
  • 关闭连接,或者直接就使用jedis连接池:jedis.close();

直接连接与连接池方式的比较:

优点缺点
直连简单方便,适用与少量长期连接的场景存在每次新建/关闭TCP连接的开销,资源无法控制,可能出现连接泄露。并且Jedis对象处于线程不安全的状态
连接池无需每次都生成Jdeis对象,降低开销,并且连接池的形式可以有效的保护和控制系统资源相对于直连,使用相对麻烦,在资源管理上需要很多参数来保证,一旦规划不合理会出现问题

搭建工程:
在这里插入图片描述

引入依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.58</version>
</dependency>

测试连接:

public class Ping {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println("连接成功");
        //查看服务是否运行
        System.out.println("服务正在运行: "+jedis.ping());
    }
}

运行结果:连接成功
在这里插入图片描述
Jedis中的API接口名和操作名一样,根据API文档使用就行:

基本测试:

public class TestJedis {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        System.out.println("清空数据:"+jedis.flushDB());
        System.out.println("判断某个键是否存在:"+jedis.exists("username"));
        System.out.println("新增<'username','ly'>的键值对:"+jedis.set("username", "ly"));
        System.out.println("新增<'password','123456'>的键值对:"+jedis.set("password", "123456"));
        System.out.print("系统中所有的键如下:");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);

        System.out.println("删除键password:"+jedis.del("password"));
        System.out.println("判断键password是否存在:"+jedis.exists("password"));
        System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));
        System.out.println("随机返回key空间的一个:"+jedis.randomKey());
        System.out.println("重命名key:"+jedis.rename("username","name"));
        System.out.println("取出改后的name:"+jedis.get("name"));
        System.out.println("按索引查询:"+jedis.select(0));
        System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
        System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
        System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
    }
}

运行结果:
在这里插入图片描述

Jedis实现事务控制:

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestMulti {
    public static void main(String[] args) {
        //创建客户端连接服务端,redis服务端需要被开启
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("user1", "xiaoming");
        jsonObject.put("user2", "xiaohong");
        //开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        try{
            //向redis存入一条数据
            multi.set("json", result);
            //再存入一条数据
            multi.set("json2", result);
            //这里引发了异常,用0作为被除数
            //int i = 10/0;
            //如果没有引发异常,执行进入队列的命令
            multi.exec();
        }catch(Exception e){
            e.printStackTrace();
            //如果出现异常,回滚
            multi.discard();
        }finally{
            System.out.println(jedis.get("json"));
            System.out.println(jedis.get("json2"));
            //最终关闭客户端
            jedis.close();
        }
    }
}

运行结果:
在这里插入图片描述
在开启事务期间添加异常,用0作为被除数int i = 10/0;,再次测试结果:
在这里插入图片描述

Jedis连接池: JedisPool

之前通过直连的方式使用Jedis,由于Redis客户端使用的是TCP协议,所有每次都需要我们新建TCP连接,使用后在断开连接,对于频繁访问Redis的场景显然并不高效,在实际生产环境中一般使用连接池的方式对Jedis连接进行管理。

使用步骤:

Jedis提供了JedisPool这个类作为对Jedis的连接池,使用时首先需要我们创建一个JedisPool连接池对象,之后通过调用方法 getResource()获取Jedis连接。

1.Jedis连接池,通常JedisPool是单例的。

//0.创建一个配置对象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);
config.setMaxIdle(10);

//1.创建Jedis连接池对象
JedisPool jedisPool = new JedisPool(config,"localhost",6379);

相关参数:

参数名含义默认值
MaxTotal最大连接数8
MaxIdle最大空闲连接数8
MinIdle最小空闲连接数0
MinEvictableIdleTimeMillis逐出连接的最小空闲时间1800000毫秒(30分钟)
MaxWaitMillis获取连接时的最大等待毫秒数如果超时就抛异常, 小于零:阻塞不确定的时间-1
TimeBetweenEvictionRunsMillis逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程-1
NumTestsPerEvictionRun每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n)3
Lifo是否启用后进先出true
TestOnBorrow在获取连接的时候检查有效性false
TestWhileIdle在空闲时检查有效性false
JmxEnabled是否启用pool的jmx管理功能true
EvictionPolicyClassName设置的逐出策略类名,默认当连接超过最大空闲时间,或连接数超过最大空闲连接数逐出DefaultEvictionPolicy
SoftMinEvictableIdleTimeMillis对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断逐出策略
BlockWhenExhausted连接耗尽时是否阻塞, false报异常,ture阻塞直到超时true

2.从连接池获取对象,并使用:

//2.获取连接
Jedis jedis = jedisPool.getResource();

//3. 使用
jedis.set("key1","value1");

//4. 关闭 归还到连接池中
jedis.close();

自定义工具类:

在resources目录下新建jedis.properties配置文件:

host = 127.0.0.1
port = 6379
maxTotal= 50
maxIdle = 10

编写工具类:

public class JedisPoolUtils {

    private static JedisPool jedisPool;

    static{
        //读取配置文件
        InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
        //创建Properties对象
        Properties pro = new Properties();
        //关联文件
        try {
            pro.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //获取数据,设置到JedisPoolConfig中
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
        config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));

        //初始化JedisPool
        jedisPool = new JedisPool(config,pro.getProperty("host"),Integer.parseInt(pro.getProperty("port")));

    }

    /**
     * 获取连接方法
     */
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

SpringBoot整合Redis

导入依赖pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

编写配置文件applacation.yml:

spring:
    redis:
      host: 127.0.0.1 # Redis服务器地址
      port: 6379      # Redis服务器连接端口
      password:    # Redis服务器连接密码(默认为空)
      database: 0  # Redis数据库索引(默认为0)
      jedis:
        pool:
          max-active: 8   # 连接池最大连接数(使用负值表示没有限制)
          max-wait: -1ms  # 连接池最大阻塞等待时间(使用负值表示没有限制)
          max-idle: 500   # 连接池中的最大空闲连接
          min-idle: 0      # 连接池中的最小空闲连接
      lettuce:
        shutdown-timeout: 0ms
      timeout: 1000 # 连接超时时间(毫秒)

测试:

@SpringBootTest
public class SpringbootRedisTest {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("key1","value1");
        redisTemplate.opsForValue().set("key2","张三");
        System.out.println("key1="+redisTemplate.opsForValue().get("key1"));
        System.out.println("key2="+redisTemplate.opsForValue().get("key2"));
    }

}

运行结果:
在这里插入图片描述

查看RedisTemplate类:
在这里插入图片描述

测试代码:

@Test
public void test() throws JsonProcessingException {
    //真实开发一般有使用json传递对象
    User user =new User();
    user.setName("龙源");
    user.setAge(10);
    String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user",jsonUser);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

//新建一个User类
@Component
public class User implements Serializable {//需要序列化Serializable
    private String name;
    private int age;
    
    /**
     * Get,Set,toString
     **/
}

运行结果:
在这里插入图片描述
在客户端进行查看:发现user对象前面有转义字符
在这里插入图片描述

通过序列化解决问题:

编写配置类,自定义RedisTemplate,修改默认的序列化方式:

@Configuration
public class RedisConfig {

  @Bean
  @SuppressWarnings("all")
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
      //为了方便开发,使用<String,Object>
      RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
      template.setConnectionFactory(factory);
      
      //Json序列化配置
      Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
      ObjectMapper om = new ObjectMapper();
      om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
      om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
      jackson2JsonRedisSerializer.setObjectMapper(om);
      
      //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;
  }

}

再次测试:

//为了方便识别可以修改:RedisTemplate
@Autowired
//private RedisTemplate redisTemplate;
private RedisTemplate<String,Object> redisTemplate;

@Test
public void test() throws JsonProcessingException {
    //真实开发一般有使用json传递对象
    User user =new User();
    user.setName("龙源");
    user.setAge(10);
    String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user",jsonUser);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

运行结果:
在这里插入图片描述

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 27
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙源lll

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值