这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战
1.1 引入NoSQL
1.1.1 NoSQL的概念
NoSQL:即 Not-Only SQL( 泛指非关系型的数据库),作为关系型数据库的补充。 作用:应对基于海量用户和海量数据前提下的数据处理问题。
1.1.2 NoSQL的特征
- 可扩容,可伸缩
- 大数据量下高性能
- 灵活的数据模型、高可用
1.1.3 常见 Nosql 数据库
目前市面上常见的Nosql产品:Redis、memcache、HBase、MongoDB.
1.1.4 常见应用场景
以电商为例
- 第一类,在电商中我们的基础数据一定要存储起来,比如说商品名称,价格,生产厂商,这些都属于基础数据,这些数据放在MySQL数据库.
- 第二类,我们商品的附加信息,比如说,你买了一个商品评价了一下,这个评价它不属于商品本身,这一类数据呢,我们放在另外一个地方,我们放到MongoDB。它也可以用来加快我们的访问,他属于NoSQL的一种.
- 第三,图片内的信息。注意这种信息相对来说比较固定,他有专用的存储区,我们一般用文件系统来存储.
- 第四,搜索关键字。为了加快搜索,我们会用到一些技术,有些人可能了解过,像分ES、Lucene、solr都属于搜索技术.
- 第五,热点信息。访问频度比较高的信息,这种东西的第二特征就是它具有波段性,放到我们的redis这个解决方案中来进行存储.
1.2 Redis概念
1.2.1 redis的概念
概念:Redis (REmote DIctionary Server) 是用 C 语言开发的一个开源的高性能键值对(key-value)数据库.
1.2.2 redis的特征
- 数据间没有必然的关联关系;
- 内部采用单线程机制进行工作;
- 高性能。官方提供测试数据,50个并发执行100000 个请求,读的速度是110000 次/s,写的速度是81000次/s。
- 多数据类型支持
- 字符串类型,string list
- 列表类型,hash set
- 散列类型,zset/sorted_set
- 集合类型
- 有序集合类型
- 支持持久化,可以进行数据灾难恢复
1.2.3 redis的应用场景
- 为热点数据加速查询(主要场景)。如热点商品、热点新闻、热点资讯、推广类等高访问量信息等
- 即时信息查询。如各位排行榜、各类网站访问统计、公交到站信息、在线人数信息(聊天室、网站)、设备信号等
- 时效性信息控制。如验证码控制、投票控制等
- 分布式数据共享。如分布式集群架构中的 session 分离 消息队列
1.3 Redis的数据类型
string、hash、list、set、sorted_set/zset(应用性较低)
1.3.1 string数据类型
- 我们知道redis 自身是一个 Map,其中所有的数据都是采用 key : value 的形式存储.
- 数据类型指的是存储的数据的类型,也就是 value 部分的类型,key 部分永远都是字符串.
1.3.1.2 string类型
- 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型,string,他就是存一个字符串儿,注意是value那一部分是一个字符串,它是redis中最基本、最简单的存储数据的格式.
- 存储数据的格式:一个存储空间保存一个数据,每一个空间中只能保存一个字符串信息.
- 存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用.
1.3.1.3 string 类型数据操作的注意事项
-
数据操作不成功的反馈与数据正常操作之间的差异,表示运行结果是否成功 (integer) 0 → false 失败 (integer) 1 → true 成功
-
表示运行结果值 (integer) 3 → 3 3个 (integer) 1 → 1 1个
-
数据未获取到时,对应的数据为(nil),等同于null
-
数据最大存储量:512MB
-
string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算
-
按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围,将报错9223372036854775807(java中Long型数据最大值,Long.MAX_VALUE)
-
redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响
1.3.2 hash数据类型
- 需要的存储结构:一个存储空间保存多个键值对数据
- hash类型:底层使用哈希表结构实现数据存储
如上图所示,这种结构叫做hash,左边一个key,对右边一个存储空间。这里要明确一点,右边这块儿存储空间叫hash,也就是说hash是指的一个数据类型,他指的不是一个数据,是这里边的一堆数据,那么它底层呢,是用hash表的结构来实现的. 如果field数量较少,存储结构优化为类数组结构 , 如果field数量较多,存储结构使用HashMap结构.
1.3.2.1 hash类型数据操作的注意事项
- hash类型中value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到,对应的值为(nil)
- 每个 hash 可以存储 232 - 1 个键值对,hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计 的,切记不可滥用,更不可以将hash作为对象列表使用.
- hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈.
1.3.2.2 hash应用场景
应用场景: 双11活动日,销售手机充值卡的商家对移动、联通、电信的30元、50元、100元商品推出抢购活动,每种商品抢购上限1000 张.
==解决方法:==
- 以商家id作为key
- 将参与抢购的商品id作为field
- 将参与抢购的商品数量作为对应的value
- 抢购时使用降值的方式控制产品数量
1.3.3 list数据类型
- 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
- 需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
list类型:
保存多个数据,底层使用双向链表存储结构实现
因为它是双向的,所以他左边右边都能操作,它对应的操作结构两边都能进数据。这就是链表的一个存储结构。往外拿数据的时候怎么拿呢?通常是从一端拿,当然另一端也能拿。如果两端都能拿的话,这就是个双端队列,两边儿都能操作.
1.3.3.1 list 类型数据操作注意事项
- list中保存的数据都是string类型的,数据总容量是有限的,最多232 - 1 个元素(4294967295)
- list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作
- 获取全部数据操作结束索引设置为-1
- list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载
1.3.3.2 list 应用场景
- 企业运营过程中,系统将产生出大量的运营数据,如何保障多台服务器操作日志的统一顺序输出?
分析: 建立起redis服务器。当他们需要记日志的时候,记在哪儿,全部发给redis。等到你想看的时候,通过服务器访问redis获取日志。然后得到以后,就会得到一个完整的日志信息。那么这里面就可以获取到完整的日志了,依靠什么来实现呢?就依靠我们的list的模型的顺序来实现。进来一组数据就往里加,谁先进来谁先加进去,它是有一定的顺序的. ==解决:==
- 依赖list的数据具有顺序的特征对信息进行管理
- 使用队列模型解决多路信息汇总合并的问题
- 使用栈模型解决最新消息的问题
1.3.4 set 数据类型
set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的.
1.3.4.1 set 类型数据操作的注意事项
-
set 类型不允许数据重复,如果添加的数据在 set 中已经存在,将只保留一份。
-
set 虽然与hash的存储结构相同,但是无法启用hash中存储值的空间。
1.3.4.2 set 应用场景
应用场景:
- 黑名单
- 白名单
解决方案:
- 基于经营战略设定问题用户发现、鉴别规则
- 周期性更新满足规则的用户黑名单,加入set集合
- 用户行为信息达到后与黑名单进行比对,确认行为去向
- 黑名单过滤IP地址:应用于开放游客访问权限的信息源
- 黑名单过滤设备信息:应用于限定访问设备的信息源
- 黑名单过滤用户:应用于基于访问权限的信息源
1.4 Jedis
1.4.1 Jedis概述
Jedis就是提供了Java与redis的连接服务的,里边有各种各样的API接口,供我们调用.
(Java语言连接redis服务 Jedis(SpringData、Redis 、 Lettuce)
其它语言:C 、C++ 、C# 、Erlang、Lua 、Objective-C 、Perl 、PHP 、Python 、Ruby 、Scala)
1.4.2 Jedis入门案例
1 导入pom.xml坐标
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2 实现代码
public class JedisTest {
public static void main(String[] args) {
//1.获取连接对象 ip + port
Jedis jedis = new Jedis("192.168.40.130",6379);
//2.执行操作
//string类型
jedis.set("age","39");
String hello = jedis.get("hello");
System.out.println(hello);
//list类型
jedis.lpush("list1","a","b","c","d");
//遍历所有 0 ,-1指从第一个到最后一个
List<String> list1 = jedis.lrange("list1", 0, -1);
for (String s:list1 ) {
System.out.println(s);
}
//set类型
jedis.sadd("set1","abc","abc","def","poi","cba");
Long len = jedis.scard("set1");
System.out.println(len);
//3.关闭连接
jedis.close();
}
}
1.4.3 Jedis客户端Redis Desktop Manager
1.5 Redis持久化(RDB和AOF)
1.5.1 持久化概念
- 什么是持久化?
- 利用永久性存储介质将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化 。
- 持久化用于防止数据的意外丢失,确保数据安全性。
- 持久化过程保存什么?
- 计算机中的数据全部都是二进制,想要保存一组数据,有什么样的方式?
- 快照:现在内容是什么样,我就记下来就行了,那么这种是记录纯粹的数据,也叫做快照存储,也就是它保存的是某一时刻的数据状态.
- 日志:它记录你所有的操作过程.
- 计算机中的数据全部都是二进制,想要保存一组数据,有什么样的方式?
总结一下:
第一种:将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据。
第二种:将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程。
1.5.2 RDB
save指令: save指令:手动执行一次保存操作,默认值为 dump.rdb文件.
save指令工作原理
- redis是个单线程的工作模式,它会创建一个任务队列,所有的命令都会进到这个队列里边,在这儿排队执行,执行完一个消失一个,当所有的命令都执行完结束.
save指令保存的数据量很大会是什么现象呢? - 非常耗时,以至于影响到它在执行的时候,后面的指令都要等,这种模式是不友好的,这是save指令对应的一个问题,当cpu执行的时候会阻塞redis服务器,直到他执行完毕,所以不建议大家在线上环境用save指令.(解决方案bgsave)
bgsave指令 bgsave**指令,bg其实是background的意思,后台执行的意思,手动启动后台保存操作,但不是立即执行.
bgsave指令工作原理
当执行bgsave的时候,客户端发出bgsave指令给到redis服务器。注意,这个时候服务器马上回一个结果告诉客户端后台已经开始了,与此同时它会创建一个子进程,使用Linux的fork函数创建一个子进程,让这个子进程去执行save相关的操作.
Q:我们主进程一直在处理指令,而子进程在执行后台的保存,它会不会干扰到主进程的执行吗? A:不会,所以说这才是主流方案。子进程开始执行之后,它就会创建啊RDB文件把它存起来,操作完以后会把这个结果返回,也就是说bgsave的过程分成两个过程,第一个是服务端拿到指令直接告诉客户端开始执行了;另外一个过程是一个子进程在完成后台的保存操作,操作完以后回一个消息.
save配置自动执行 设置自动持久化的条件,满足限定时间范围内key的变化数量达到指定数量即进行持久化.
==e.g:满足以下条件自动保存==
- 在10秒内保存300key-value
save配置工作原理:
RDB三种启动方式对比:
RDB优点:
- RDB是一个紧凑压缩的二进制文件,存储效率较高
- RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
- RDB恢复数据的速度要比AOF快很多
- 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复
RDB缺点:
- RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据
- bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能
- Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象
1.5.3 AOF
RDB的存储的弊端:
- 存储数据量较大,效率较低,基于快照思想,每次读写都是全部数据,当数据量巨大时,效率非常低
- 大数据量下的IO性能较低
- 基于fork创建子进程,内存产生额外消耗
- 宕机带来的数据丢失风险
解决思路:
- 不写全数据,仅记录部分数据
- 降低区分数据是否改变的难度,改记录数据为记录操作过程
- 对所有操作均进行记录,排除丢失数据的风险
1.5.3.1 AOF概念
AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令 达到恢复数据的目的。与RDB相比可以简单理解为由记录数据改为记录数据产生的变化
ps:
AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式.
AOF写数据过程
1.5.3.2 AOF执行策略
- always(每次):每次写入操作均同步到AOF文件中数据零误差,性能较低,不建议使用.
- everysec(每秒):每秒将缓冲区中的指令同步到AOF文件中,在系统突然宕机的情况下丢失1秒内的数据 数据准确性较高,性能较高,建议使用,也是默认配置
- no(系统控制):由操作系统控制每次同步到AOF文件的周期,整体过程不可控
** AOF重写:** 随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重 写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单说就是将对同一个数据的若干个条命令执行结 果转化成最终结果数据对应的指令进行记录.
AOF重写作用:
- 降低磁盘占用量,提高磁盘利用率
- 提高持久化效率,降低持久化写时间,提高IO性能
- 降低数据恢复用时,提高数据恢复效率
AOF重写规则:
- 进程内具有时效性的数据,并且数据已超时将不再写入文件
- 非写入类的无效指令将被忽略,只保留最终数据的写入命令
- 对同一数据的多条写命令合并为一条命令
AOF工作流程及重写流程:
1.5.4 RDB与AOF区别
1.5.4.1 RDB与AOF区别对比
持久化方式RDBAOF占用存储空间小(数据级:压缩)大(指令级:重写)存储速度慢快恢复速度快慢数据安全性会丢失数据依据策略决定资源消耗高/重量级低/轻量级启动优先级低高
| 持久化方式 | RDB | AOF |
|---|---|---|
| 占用存储空间 | 小(数据级:压缩) | 大(指令级:重写) |
| 存储速度 | 慢 | 快 |
| 恢复速度 | 快 | 慢 |
| 数据安全性 | 会丢失数据 | 依据策略决定 |
| 资源消耗 | 高/重量级 | 低/轻量级 |
| 启动优先级 | 低 | 高 |
1.5.4.2 RDB与AOF应用场景
- 对数据非常敏感,建议使用默认的AOF持久化方案.
- AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出 现问题时,最多丢失0-1秒内的数据.(注意:由于AOF文件存储体积较大,且恢复速度较慢)
- 数据呈现阶段有效性,建议使用RDB持久化方案
- 数据可以良好的做到阶段内无丢失,且恢复速度较快,阶段点数据恢复通常采用RDB方案.(注意:利用RDB实现紧凑的数据持久化会使Redis性能降的很低)
综合比对
- DB与AOF的选择实际上是在做一种权衡,每种都有利有弊
- 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF
- 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB
- 灾难恢复选用RDB
- 双保险策略,同时开启 RDB和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据的量