ITPub博客

首页 > > > NoSQL 介绍之Redis

NoSQL 介绍之Redis

作者:浪花小雨滴 时间:2020-06-23 16:15:34 0 删除 编辑
一切工业技术的革新都是为了解决当时遇到的瓶颈,那数据库也不例外。

在最早期Web1.0(是以编辑为特征,网站提供给用户的内容是网站编辑进行编辑处理后提供的,用户阅读网站提供的内容。这个过程是网站到用户的单向行为,web1.0时代的代表站点为新浪,搜狐,网易三大门户。),由于用户的访问量很少,设计和实现程序主要是为了解决功能问题,用一夫当关的高性能的单点服务器(小型机,典型的Oracle DB2)可以解决大部分问题,此阶段用到的主流的技术:Java、Jsp、RDBMS、Tomcat、HTML、Linux、Jdbc、SVN ...虽然效率不高,但是能完成功能没问题。

随着Web2.0(更注重用户的交互作用,用户既是网站内容的浏览者,也是网站内容的制造者,加强了网站与用户的互动,如微博、天涯社区、人人网、自媒体)时代到来,产生了大量的用户数据,加上后来智能移动设备的普及,所有的互联网平台都面临着性能挑战。要解决功能的扩展性的问题,此时更多的考虑是程序的设计原则,比如设计模式、框架等,常用程序的流行的框架有:Structs、Spring、SpringMVC、Hibernate、Mybatis ... (这些框架只是更好的解决了扩展性的问题,没有他们照样也是能完成功能的)。

但是随着用户量、访问量的海量增长,程序开始遇到了性能的问题,像计算密集型应用,如统计分析的应用,单点的CPU可能无法承受;像IO密集型应用,单点服务器的硬盘也无法存储,上面普通的技术无法解决性能的问题,此时出现了:NoSQL、Java多线程、Hadoop、Nginx、MQ、Kafka。

那么问题就围绕着如何解决CPU以及内存的压力、IO压力来开展。

  • 解决CPU、内存压力:负载均衡+缓存数据库
  • 解决IO压力:打破以业务逻辑为依据的存储,针对不同的数据结构类型改为以性能为最优先的存储方式。

NoSQL 数据库概述:

  • NoSQL(Not Only SQL),意思为"不仅仅是SQL",泛指非关系型数据库。
  • NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储,因此大大增加了数据库的扩展能力。
  • 不遵循SQL标准 // 增加了学习成本 不能通用
  • 不支持ACID
  • 远超于SQL的性能。

NoSQL适用场景:

  • 对数据高并发的读写
  • 海量数据的读写
  • 对数据高可扩展性的

NoSQL不适用场景:

  • 需要事务支持
  • 基于sql的结构化查询存储,处理复杂的关系,需要即席查询

什么是Redis?

redis 是一个基于内存的高性能key-value数据库。

Redis的特点?

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。

Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性能消息队列服务,用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。

Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

Redis的应用场景:

  • 配合关系型数据库做高速缓存
  1. 高频次、热门访问的数据(热点数据),降低数据库IO
  2. 分布式架构做session共享
  • 由于其具有持久化能力(可以将数据存入磁盘中),可以利用其多样性的数据结构存储特定的数据,如:
  1. 最新的N个数据 -- 通过List实现按自然时间排序的数据
  2. 排行榜,TOP N -- 利用zset(有序集合)
  3. 时效性的数据,比如手机验证码 -- Expire 过期时间设置
  4. 计数器,秒杀 -- 原子性,自增方法 INCR 、DECR
  5. 去除大量数据中的重复数据 -- 利用Set集合
  6. 构建队列 -- 利用 List 集合
  7. 发布订阅消息系统 -- pub/sub模式

Redis支持的数据类型?

  • Binary-safe strings
  • Lists: collections of string elements sorted according to the order of insertion. They are basically linked lists.
  • Sets: collections of unique, unsorted string elements.
  • Sorted sets: similar to Sets but where every string element is associated to a floating number value, called score. The elements are always taken sorted by their score, so unlike Sets it is possible to retrieve a range of elements (for example you may ask: give me the top 10, or the bottom 10).
  • Hashes: which are maps composed of fields associated with values. Both the field and the value are strings. This is very similar to Ruby or Python hashes.
  • Bit arrays (or simply bitmaps): it is possible, using special commands, to handle String values like an array of bits, you can set and clear individual bits, count all the bits set to 1, find the first set or unset bit, and so forth.
  • HyperLogLogs:this is a probabilistic data structure which is used in order to estimate the cardinality of a set. Don't be scared, it is simpler than it seems... See later in the HyperLogLog section of this tutorial.
  • Streams: append-only collections of map-like entries that provide an abstract log data type. They are covered in depth in the
Introduction to Redis Streams​redis.io

Redis为什么快?

  • 基于内存 // 基于内存的并不一定比基于文件的快,比如kafka的随机内存处理,但是大多数情况下是这样的。
  • 单线程:单线程的模型简单,不易出错,没有额外的性能处理;多线程会引入很多问题:共享资源、硬件受限(硬件切换等)。
  • 使用了操作系统中的多路IO复用:与底层硬件有关。
  1. BIO
  2. NIO:非阻塞式IO
  3. AIO:异步IO处理

// 厨师+灶台+老板

为什么redis需要把所有数据放到内存中?

Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。

如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

Redis是单进程单线程的

redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销

分布式

Redis支持主从的模式。原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。

这是一个典型的分布式读写分离模型。我们可以利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量。

读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。

因为数据库的“写”(写10000条数据到oracle可能要3分钟)操作是比较耗时的。但是数据库的“读”(从oracle读10000条数据可能只要5秒钟)。所以读写分离,解决的是,数据库的写入,影响了查询的效率。

通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。

读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。

数据分片模型

为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。可以将每个节点看成都是独立的master,然后通过业务实现数据分片。结合上面两种模型,可以将每个master设计成由一个master和多个slave组成的模型。

Redis的回收策略

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

使用Redis有哪些好处?

  • 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
  • 支持丰富数据类型,支持string,list,set,sorted set,hash
  • 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  • 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

Redis相比memcached有哪些优势?

  • memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
  • redis的速度比memcached快很多? // 为什么?
  • redis可以持久化其数据

Redis常见性能问题和解决方案?

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件。
  1. 写内存快照时,save命令调度rdbSave函数,会阻塞主线程的工作;
  2. AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以AOF文件的大小随着时间的流逝一定会越来越大;影响包括但不限于:对于Redis服务器,计算机的存储压力;AOF还原出数据库状态的时间增加;
  3. 为了解决AOF文件体积膨胀的问题,Redis提供了AOF重写功能:Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个文件所保存的数据库状态是相同的,但是新的AOF文件不会包含任何浪费空间的冗余命令,通常体积会较旧AOF文件小很多。
  4. AOF在重写的时候会占大量的CPU和内存资源。如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。
  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  • 尽量避免在压力很大的主库上增加从库
  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
  • 这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种,前文已经介绍。

由maxmemory-policy参数设置淘汰策略:

CONFIG SET maxmemory-policy volatile-lru      #淘汰有过时期的最近最好使用数据

Redis 适合的场景

Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别。

  • 会话缓存(Session Cache):用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。
  • 队列: Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。
  • 排行榜: 集合(Set)和有序集合(Sorted Set)也使得这些操作变的非常简单。可以从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,当然,这是假定你是根据你用户的分数做递增的排序。排行榜(leader board)按照得分进行排序。ZADD命令可以直接实现这个功能,而ZREVRANGE命令可以用来按照得分来获取前100名的用户,ZRANK可以用来获取用户排名,非常直接而且操作容易。
  • 计数器:Redis在内存中对数字进行递增或递减的操作实现的非常好。Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统。
  • 发布/订阅:发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!。
  • 需要精准设定过期时间:可以把有序集合(sorted set)的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。

Redis常见性能问题和解决方案:

  1. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件。
  2. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次。
  3. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内。
  4. 尽量避免在压力很大的主库上增加从库。
  5. 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

Redis的并发竞争问题如何解决:

Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:

  1. 客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized
  2. 服务器角度,利用setnx实现锁
  3. 对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题

redis事务的了解CAS // 放弃

Redis持久化的几种方式:

  • 快照(snapshots):

缺省情况情况下,Redis把数据快照存放在磁盘上的二进制文件中,文件名为dump.rdb。你可以配置Redis的持久化策略,例如数据集中每N秒钟有超过M次更新,就将数据写入磁盘;或者你可以手工调用命令SAVE或BGSAVE

  • AOF:快照模式并不十分健壮,当系统停止,或者无意中Redis被kill掉,最后写入Redis的数据就会丢失。这对某些应用也许不是大问题,但对于要求高可靠性的应用来说,Redis就不是一个合适的选择。Append-only文件模式是另一种选择,你可以在配置文件中打开AOF模式
  • 虚拟内存方式
  • diskstore方式

Redis的缓存失效策略和主键失效机制?

作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略。在Redis当中,有生存期的key被称为volatile。在创建缓存时,要为给定的key设置生存期,当key过期的时候(生存期为0),它可能会被删除。

  • 影响KEY的生存时间:生存时间可以通过使用DEL命令来删除整个key来移除,或者被SET和GETSET命令覆盖原来的数据;对一个key执行INCR命令,对一个List进行LPUSH命令,或者对一个哈希表执行HSET命令,都不会修改key本身的生存时间;如果使用RENAME对一个key进行改名,那么改名后的key的生存时间和改名前一样。
  • 如何更新生存时间:可以对一个已经带有生存时间的key执行EXPIRE命令,新指定的生存时间会取代旧的生存时间。过期时间的精度已经被控制在1ms之内,主键失效的时间复杂度是O(1);EXPIRE和TTL命令搭配使用,TTL可以查看key的当前生存时间。设置成功返回1;当key不存在或者不能为key设置生存时间时,返回0。
  1. 最大缓存配置:在redis中,允许用户设置最大使用内存大小。即设置server.maxmemory参数值:默认为0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。redis内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。(有6种数据淘汰策略)
  2. 使用策略规则:如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru;如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random。
  3. 三种数据淘汰策略:ttl和random比较容易理解,实现也会比较简单。主要是lru最近最少使用淘汰策略,设计上会对key按失效时间排序,然后取最先失效的key进行淘汰。

缓存穿透、缓存雪崩的解决方案

我们在用缓存的时候,不管是Redis或者Memcached,基本上会通用遇到以下三个问题:

  • 缓存穿透

我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了。解决方案:

方案1:封装的缓存SET和GET部分增加个步骤,如果查询一个KEY不存在,就已这个KEY为前缀设定一个标识KEY;以后再查询该KEY的时候,先查询标识KEY,如果标识KEY存在,就返回一个协定好的非false或者NULL值,然后APP做相应的处理,这样缓存层就不会被穿透。当然这个验证KEY的失效时间不能太长。

方案2:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,一般只有几分钟。

方案3:采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉(未必,但是可以挡掉一些),从而避免了对底层存储系统的查询压力。

  • 缓存雪崩

缓存雪崩是指当缓存服务器重启或者大量缓存集中在某一个时间段失效,瞬间所有的请求都被打到了数据库,数据库就崩了。解决方案:

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

方案2:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

方案3:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31537575/viewspace-3158918/ ,如需转载,请注明出处,否则将追究法律责任。

下一篇: HBase 开始运行
请登录后发表评论 登录
全部评论

注册时间:2018-05-16

  • 博文量
    4259
  • 访问量
    39968