Redis学习笔记(1)—— NoSQL&Redis简介

一、NoSQL概述

1.1 什么是NoSQL

  NoSQL(NoSQL = Not Only SQL),意指“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。

1.2 为什么需要NoSQL

  随着互联网web2.0网站的兴起,非关系型的数据库成了一个极其热门的新领域,非关系型数据库产品的发展非常迅速,而传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,例如:

  • High performance – 对数据库高并发读写的需求

   web2.0网站要根据用户个性化信息来实时生成动态页面和提供动态信息,所以基本上无法使用动态页面静态化技术,因此数据库并发负载非常高,往往要达到 每秒上万次读写请求。关系数据库应付上万次SQL查询还勉强顶得住,但是应付上万次SQL写数据请求,硬盘IO就已经无法承受了。

  其实对于普通的BBS网 站,往往也存在对高并发写请求的需求,例如像JavaEye网站的实时统计在线用户状态,记录热门帖子的点击次数,投票计数等,因此这是一个相当普遍的需 求。

  • Huge Storage – 对海量数据的高效率存储和访问的需求

  类似Facebook,twitter,Friendfeed这样的SNS网站,每天用户产生海量的用户动态,以Friendfeed为例,一个月就达到 了2.5亿条用户动态,对于关系数据库来说,在一张2.5亿条记录的表里面进行SQL查询,效率是极其低下乃至不可忍受的。再例如大型web网站的用户登 录系统,例如腾讯,盛大,动辄数以亿计的帐号,关系数据库也很难应付。

  • High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求

  在基于web的架构当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,你的数据库却没有办法像web server和app server那样简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展 是非常痛苦的事情,往往需要停机维护和数据迁移,为什么数据库不能通过不断的添加服务器节点来实现扩展呢?

  NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

1.3 主流NoSQL产品

  NoSQL数据库的四大分类:

  • 键值(Key-Value)存储数据库

    相关产品:Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB

    典型应用:内容缓存,主要用于处理大量数据的高访问负载

    数据模型:一系列键值对

    优势:快速查询

    劣势:存储的数据缺少结构化

  • 列存储数据库

    相关产品:Cassandra,HBase,Riak

    典型应用:分布式的文件系统

    数据模型:以列簇式存储,将同一列数据存在一起

    优势:查找速度快,可扩展性强,更容易进行分布式扩展

    劣势:功能相对局限

  • 文档型数据库

    相关产品:CouchDB、MongoDB

    典型应用:Web应用(与Key-Value类似,Value是结构化的)

    数据模型:一系列键值对

    优势:数据结构要求不严格

    劣势:查询性能不高,而且缺乏统一的查询语法

  • 图形(Graph)数据库

    相关产品:Neo4J、InfoGrid、Infinite Graph

    典型应用:社交网络

    数据模型:图结构

    优势:利用图结构相关算法

    劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案

1.4 NoSQL的特点

  在大数据存储上具备关系型数据库无法比拟的性能优势,例如:

  • 易扩展

   NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力

  • 大数据量,高性能

   NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单。

  • 灵活的数据模型

    NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。这点在大数据量的web2.0时代尤其明显。

  • 高可用

   NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。

1.5 关系型数据库和非关系型数据库的区别

  (1)关系型数据库(Oracle、MySQL)通过外键关联来建立表与表之间的关系。

      非关系型数据库(NoSQL)通常指数据以对象的形式存储在数据库中,而对象之间的关系通过每个对象自身的属性来决定。

      比如 有一个学生的数据:姓名:张三,性别:男,学号:12345,班级:二年级一班

      还有一个班级的数据:  班级:二年级一班,班主任:李四

     关系型数据库中,我们创建学生表和班级表来存这两条数据,并且学生表中的班级存储的是班级表中的主键。

     非关系型数据库中,我们创建两个对象,一个是学生(Student)对象,一个是班级(Grade)对象。通过设置Student类的classid等于Grade类的id来建立这种关系;

   (2)关系型数据库是基于关系表的数据库,最终会将数据持久化到磁盘上,而nosql数据库是基于特殊的结构,并将数据存储到内存的数据库。从性能上而言,nosql数据库   要优于关系型数据库,从安全性上而言关系型数据库要优于nosql数据库,所以在实 际开发中一个项目中nosql和关系型数据库会一起使用,达到性能和安全性的双保证。

 二、Redis概述

2.1 什么是Redis

  Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:

  1. 字符串类型(string)

  2. 散列类型(hash)

  3. 列表类型(list)

  4. 集合类型(set)

  5. 有序集合类型(sorted set)

   官方提供测试数据:50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s。数据仅供参考,根据服务器配置会有不同结果。

2.2 为什么使用redis

  分析:博主觉得在项目中使用redis,主要是从两个角度去考虑:性能并发。当然,redis还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件(如zookpeer等)代替,并不是非要使用redis。因此,这个问题主要从性能和并发两个角度去答。
回答:如下所示,分为两点
  (一)性能
  如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应
image

  (二)并发
  如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。
image

2.3 使用redis有什么缺点

  分析:大家用redis这么久,这个问题是必须要了解的,基本上使用redis都会碰到一些问题,常见的也就几个。

  (一) 缓存和数据库双写一致性问题

  分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。
首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
  (二) 缓存雪崩问题

  一般中小型传统软件企业,很难碰到这个问题。如果有大并发的项目,流量有几百万左右。这两个问题一定要深刻考虑。
  缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
  解决方案:
  ① 给缓存的失效时间,加上一个随机值,避免集体失效。
  ② 使用互斥锁,但是该方案吞吐量明显下降了。
  ③ 双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。然后细分以下几个小点

  • I 从缓存A读数据库,有则直接返回
  • II A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
  • III 更新线程同时更新缓存A和缓存B。

  (三) 缓存击穿问题

  一般中小型传统软件企业,很难碰到这个问题。如果有大并发的项目,流量有几百万左右。这两个问题一定要深刻考虑。

  缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
  解决方案:
  ① 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
  ② 采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
  ③ 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
  (四) 缓存的并发竞争问题

  分析:这个问题大致就是,多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了。或者是多客户端同时获取一个key,修改值之后再写回去,只要顺序错了,数据就错了。

  这个时候要注意什么呢?大家思考过么。需要说明一下,博主提前百度了一下,发现答案基本都是推荐用redis事务机制。博主不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。
  ① 如果对这个key操作,不要求顺序
  这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
  ② 如果对这个key操作,要求顺序
  假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
  期望按照key1的value值按照 valueA-->valueB-->valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下

  1. 系统A key 1 {valueA 3:00}
  2. 系统B key 1 {valueB 3:05}
  3. 系统C key 1 {valueC 3:10}

  那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。

  其他方法,比如利用队列,将set方法变成串行访问也可以。总之,灵活变通。

2.4 单线程的redis为什么这么快

  分析:这个问题其实是对redis内部机制的一个考察。其实根据博主的面试经验,很多人其实都不知道redis是单线程工作模型。所以,这个问题还是应该要复习一下的。
  回答:主要是以下三点
  (一)纯内存操作
  (二)单线程操作,避免了频繁的上下文切换
  (三)采用了非阻塞I/O多路复用机制

  题外话我们现在要仔细的说一说I/O多路复用机制,因为这个说法实在是太通俗了,通俗到一般人都不懂是什么意思。博主打一个比方:小曲在S城开了一家快递店,负责同城快送服务。小曲因为资金限制,雇佣了一批快递员,然后小曲发现资金不够了,只够买一辆车送快递。
  经营方式一
  客户每送来一份快递,小曲就让一个快递员盯着,然后快递员开车去送快递。慢慢的小曲就发现了这种经营方式存在下述问题

  • 几十个快递员基本上时间都花在了抢车上了,大部分快递员都处在闲置状态,谁抢到了车,谁就能去送快递
  • 随着快递的增多,快递员也越来越多,小曲发现快递店里越来越挤,没办法雇佣新的快递员了
  • 快递员之间的协调很花时间

  综合上述缺点,小曲痛定思痛,提出了下面的经营方式
  经营方式二
  小曲只雇佣一个快递员。然后呢,客户送来的快递,小曲按送达地点标注好,然后依次放在一个地方。最后,那个快递员依次的去取快递,一次拿一个,然后开着车去送快递,送好了就回来拿下一个快递。

  对比
  上述两种经营方式对比,是不是明显觉得第二种,效率更高,更好呢。在上述比喻中:

  • 每个快递员------------------>每个线程
  • 每个快递-------------------->每个socket(I/O流)
  • 快递的送达地点-------------->socket的不同状态
  • 客户送快递请求-------------->来自客户端的请求
  • 小曲的经营方式-------------->服务端运行的代码
  • 一辆车---------------------->CPU的核数

  于是我们有如下结论
  1、经营方式一就是传统的并发模型,每个I/O流(快递)都有一个新的线程(快递员)管理。
  2、经营方式二就是I/O多路复用。只有单个线程(一个快递员),通过跟踪每个I/O流的状态(每个快递的送达地点),来管理多个I/O流。

  下面类比到真实的redis线程模型,如图所示
image
  参照上图,简单来说,就是。我们的redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
需要说明的是,这个I/O多路复用机制,redis还提供了select、epoll、evport、kqueue等多路复用函数库,大家可以自行去了解。

2.5 Redis的应用场景

  • 缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使用)
  • 聊天室的在线好友列表。
  • 任务队列。(秒杀、抢购、12306等等)
  • 应用排行榜
  • 网站访问统计
  • 数据过期处理(可以精确到毫秒)
  • 分布式集群架构中的session分离

参考资料:http://www.cnblogs.com/wuyepeng/p/9744393.html

     https://www.cnblogs.com/rjzheng/p/9096228.html

原文地址:https://www.cnblogs.com/yft-javaNotes/p/10077864.html