【JAVA程序员进阶之路】Redis基础知识两篇就知足(一)

导言

你们好,我是南橘,从接触java到如今也有差很少两年时间了,两年时间,从一名连java有几种数据结构都不懂超级小白,到如今懂了一点点的进阶小白,学到了很多的东西。知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了一些日常学习和面试中的重点(自我认为),但愿给你们带来一些帮助java

这篇文章的出现,首先要感谢一我的三太子敖丙 ,就是他的文章让我发现,原来Redis的知识如此的多姿多彩。恩恩,他的文章,我是期期都看mysql

这是这篇文章的思惟导图,由于用的是免费版的软件,因此有很多水印,须要原版的能够问我要程序员

一、Redis基础知识

要学习Redis基础知识,首先要知道Redis的五种基础数据结构,咱们先从这五种数据结构的使用场景和一些工做中(面试中)容易出现的点来介绍面试

一、1 String 字符串类型

是redis中最基本的数据类型,一个key对应一个valueredis

适用状况:算法

一、缓存: 经典使用场景,把经常使用信息,字符串,图片或者视频等信息放到redis中,redis做为缓存层,mysql作持久化层,下降mysql的读写压力。

2.计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据能够一步落地到其余的数据源。

3.session:经过redis实现session共享
复制代码

一、2 Hash (哈希)

对于Java中的HashMap,自己是一种KV对结构,如 value={{field1,value1},......fieldN,valueN}},很是好理解sql

适用状况:数据库

HashMap做为缓存,相比于string更节省空间的维护缓存信息,适合存储如用户信息,视频信息等
复制代码

底层用字典dict实现数组

一、3 List (链表)

Redis 的链表至关于 Java 语言里面的 LinkedList缓存

适用状况:

一、List在Redis中既能够作队列也能够作栈使用,它的插入和删除操做很是快,时间复杂度为 0(1),可是索引定位很慢,时间 复杂度为 O(n)。

二、能够做为微博的时间轴,有人发布微博,用lpush加入时间轴,展现新的列表信息。     

三、能够实现阻塞队列,左进右出的队列组完成设计
复制代码

list底层使用quickList(快速链表)实现

在早期的设计中, 当列表对象中元素的长度比较小或者数量比较少的时候,采用ziplist来存储,当列表对象中元素的长度比较大或者数量比较多的时候,则会转而使用双向列表linkedlist来存储。

这两种存储方式都各有优缺点

  • 双向链表linkedlist便于在表的两端进行push和pop操做,在插入节点上复杂度很低,可是它的内存开销比较大。首先,它在每一个节点上除了要保存数据以外,还要额外保存两个指针;其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
  • ziplist存储在一段连续的内存上,因此存储效率很高。可是,它不利于修改操做,插入和删除操做须要频繁的申请和释放内存。特别是当ziplist长度很长的时候,一次realloc可能会致使大批量的数据拷贝。

3.2版本更新以后,list的底层实现变成了quickList

quickList是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。

一、4 Set 集合

Redis 的集合至关于 Java 语言里面的 HashSet ,它内部的键值对是无序的、惟一 的。 它的内部实现至关于一个特殊的字典,字典中全部的 value 都是一个值 NULL 当集合中最后一个元素被移除以后,数据结构被自动删除,内存被回收。

适用状况:

一、标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者相似标签的能够给推荐关注的事或者关注的人。

二、点赞,或点踩,收藏等,能够放到set中实现

三、能够用来存储在某活动中中奖的用户 ID ,由于有去重功能,能够保证同 一个用户不会中奖两次。
复制代码

一、5 Zset 有序集合

它相似于 Java SortedSet HashMap 的结合体, 方面它是个 set ,保证 了内部 value 的惟性,另方面它可 以给每一个 value 赋予一个 score ,表明 这个 value 的排序权重。它的内部实现 用的是一种叫做“跳跃表”的数据 结构。

仍是和上期同样,从一位大佬那边借过来一张图片给你们展现,什么是跳跃表

从这张图片,咱们能够看出来:跳跃表的底层是一个顺序链表,每隔一个节点有一个上层的指针指向下一个节点,并层层向上递归。这样设计成相似树形的结构,可使得对于链表的查找能够到达二分查找的时间复杂度。

skiplist他不要求上下两层链表之间个数的严格对应关系,他为每一个节点随机出一个层数。好比上图第三个节点的随机出的层数是4,那么就把它插入到四层的空间上,而第四个节点随机出的层数是1,那它就只存在第一层空间上。

  • 当数据较少的时候,zset是由一个ziplist来实现的,就和list底层以前是同样的

ziplist是由一系列特殊编码的连续内存块组成的顺序存储结构,相似于数组,ziplist在内存中是连续存储的,可是不一样于数组,为了节省内存 ziplist的每一个元素所占的内存大小能够不一样

ziplist将一些必要的偏移量信息记录在了每个节点里,使之能跳到上一个节点或下一个节点。

  • 当数据较多的时候,zset是一个由dict 和一个 skiplist来实现的,dict用来查询数据到分数的对应关系,而skiplist用来根据分数查询数据

除了这五大基础数据结构,Redis还有更加专业的数据结构 HyperLogLog(基数统计的算法)、Geo(地理位置系列)、Pub\Sub(消息队列)、Pipeline(管道)、BloomFiler(布隆过滤器),都在不一样的地方有用到,有些我会在下文向你们介绍,还有一些深刻的我没有了解这么多,你们能够去敖丙的文章中进一步了解

Pipeline

能够将屡次IO往返时间缩减为一次,前提是pipleline执行的指令之间没有因果关系

管道(pipeline)能够一次性发送多条命令并在执行完后一次性将结果返回,pipeline 经过减小客户端与redis的通讯次数来实现下降往返延时时间,并且Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性。

注意:pipeline机制能够优化吞吐量,但没法提供原子性/事务保障

二、进阶知识

二、1 Redis实现分布式锁

随着互联网技术的飞速发展,愈来愈多的单体架构已经转型成了分布式架构,,分布式架构确实能带来性能和效率上的提高,可是也会带来数据一致性的问题。

分布式锁,就是解决分布式架构中数据一致性的专用武器,分布式锁须要知足一下三个方面方可放心使用:

  • 排他性:在同一时间只会有一个客户端能获取到锁,其它客户端没法同时获取

  • 避免死锁:这把锁在一段有限的时间以后,必定会被释放(正常释放或异常释放)

  • 高可用:获取或释放锁的机制必须高可用且性能佳

目前,我所知道的分布式锁大概有三种主流方式实现,分别是zookpeer,redis,还有本地数据库,今天我就介绍一下如何用redis实现分布式锁。

基于Redis实现的锁机制,主要是依赖redis自身的原子操做

setnx争抢锁,再用expire添加过时时间

你没有看错,就是这么简单,若是惧怕不妥,好比争抢锁的时候尚未设置过时时间就忽然宕机之类的问题,能够直接用jedis等封装好的RedisTemplate把setnx和expire合成一条指令使用。

可是,这样的分布式锁不是绝对安全的

首先,单点故障的问题不可避免 其次,由于使用锁的客户端,和redis服务器,不在一块儿啊!时间是有延迟的,咱们只能依靠redis的TTL命令来查询锁的剩余时间,而后根据这个剩余时间来判断锁是否超时。 然而在一般的计算机系统中,很难获取到一个可靠的时间。

  • 系统可能因于时间服务器同步调整时间,
  • 虚拟机可能调整时间,
  • JVM GC可能致使时间停顿

怎么办?

其实若是咱们使用的场景不须要那么强的安全性精确性,这样也足够用了,至少我如今的公司够用了(一个排名前列的第三方支付企业)。可是,精益求精是程序员们的本质,RedLock的出如今必定程度上解决了这个问题

我不是很了解RedLock,只能稍微给你们介绍一下,流程以下:

  1. 客户端获取当前时间,生成一个随机值做为锁的值(目的是更加精确的得到时间)
  2. 依次尝试在全部5个redis上取得同一个锁(使用相似单机redis锁的方法, 使用一样的key和同一个随机值) 获取锁的操做自己须要设定一个比较小的超时时间(如5-50ms), 防止在一个挂掉的redis上浪费太多时间 若是一个redis不可用,要尽快开始尝试下一个
  3. 客户端计算获取锁一共用了多长时间,经过用当前时间减去第1步获得的时间 若是客户端获取了多数redis上的这个锁(3到五个5),而且这时尚未超过锁的超时时间, 这个锁就算是获取成功了
  4. 若是锁获取成功了,有效时间就按锁超时时间-获取锁花费时间算
  5. 若是失败,尝试在全部redis上解除锁 (解除锁的操做是一段lua script,删除一个key若是key的value是第1步生成的随机值)

固然,它也不能解决问题,可是, redis锁只会在比较极端的状况下出错,若是不是须要特别精确,只须要保证绝大多数可靠的时候,能够放心使用redis集群或者redlock。

2.2Redis集群

解决了分布式锁的问题,可是仍是没有解决各类天灾人祸的问题,因此,这就须要Redis集群出马了

集群同步机制

Redis中有主从机制,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。先讲一下Redis主从同步的流程:

  • 1.第一次同步时,从服务器向主服务器发送一次SYNC命令,主服务器收到以后作一次bgsave、并同时将后续修改操做记录到内存buffer,待完成后将RDB文件全量同步到复制节点
  • 2.复制节点接收完成后将RDB镜像加载到内存中,加载完成后,再通知主节点
  • 3.后续的增量数据经过AOF日志同步便可,有点相似数据库的binlog

同时,在2.8版本以后,Redis能够自动判断是须要全量同步仍是增量同步,效率比较高,增量同步其实就是在完成全量同步后,开始新复制时向主服务器发送PSYNC( )命令(runid是上次复制的主服务器id,offset是从服务器的复制偏移量),主服务器会根据这个两个参数来决定作哪一种同步,判断服务器id是否和本机相同,复制偏移量是否在缓冲区中。

集群的高可用性

  • Redis Sentinal(哨兵模式)集群着眼于高可用,在master宕机时自动将slave提高为master,继续提供服务
  • Redis Cluster集群着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储

关于这些集群的东西一章内容确定写不完,我会在之后的文章里向你们介绍

二、3异步队列

Redis的本职工做是缓存,可是因为它多才多艺,成为队列也不错,有一些阻塞式的API让其有能力作消息队列;另外,作消息队列的其余特性例如FIFO(先入先出)也很容易实现,只须要一个List对象从头取数据,从尾部塞数据便可

  • 在Redis中,若是让List结构做为队列、rpush生产消息、lpop消费消息、当lpop没有消息的时候,能够当sleep一会再重试,这就至关于生产者消费模式模式了。同时List有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
  • pub\sub主题订阅者模式、能够实现1对N的消息队列,实现生产一次,消费屡次。可是,它也有不足之处,若是让pub\sub主题订阅者模式、消费者下线的状况下,消息会丢失、不如直接用MQ

二、4延时队列

在Redis中,能够利用 sorted-set 来作延时队列

zadd key score1 value1 score2 value2

  • socre为执行时间,key为队列名,value为数据
  • 消费队列循环从sorted-set根据score获取(zrangebyscore)小于等于当前时间的且score最小的一条数据轮询处理
  • 若是没有取到数据,睡一会再去获取

可是,Redis的延时队列没法返回ACK,因此须要本身实现

二、5 持久化

Redis有两种持久化的方式,分别是RDB和AOF

RDB作镜像全量持久化、AOF作增量持久化,由于RDB会耗费较长时间,不够实时,在停机的时候会致使大量有效数据丢失,因此须要AOF来配合使用,在redis实例重启时,会使用RDB持久化文件从新构建内存,再使用AOF重放近期的操做指令来实现完整恢复重启以前的状态。

RDB机制

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。RDB提供了三种机制来触发持久化

  • 一、save触发方式---客户端发起save请求

    执行save命令期间,Redis不能处理其余命令,直到RDB过程完成为止

  • 二、bgsave触发方式--客户端发起bgsave请求

    执行该命令时,Redis会在后台异步进行快照操做,快照同时还能够响应客户端请求

  • 三、自动触发

    自动触发是由咱们的配置文件来完成的,在redis.conf文件中配置,你们能够去了解一下,这里就不写那么多东西了

AOF机制

全量备份老是耗时的(随机的传说老是好的???),有时候咱们提供一种更加高效的方式AOF,Redis会将每个收到的写命令都经过write函数追加到文件中,就是日志记录。

和RDB同样,AOF也有三种同步机制:

  • 一、always:同步持久化 每次发生数据变动会被当即记录到磁盘 性能较差但数据完整性比较好

  • 二、everysec:每秒同步,异步操做,每秒记录 若是一秒内宕机,有数据丢失

  • 三、no:从不一样步

Redis自己的机制是AOF持久化开启存在AOF文件时,优先加载AOF文件。AOF文件不存在时候,加载RDB文件。加载AOF\RDB文件后,Redis启动成功。AOF\RDB文件存在错误时,Redis启动失败并打印错误信息。

不要问AOF和RDB用哪一个,个人经验就是,全都用。 RDB同步快,可是要损失最多五分钟的内容,AOF同步慢,可是每秒同步的状况下最多损失1s的内容,损失的内容也能够经过日志去找回。

机器断电对数据丢失的影响

AOF日志中能够进行sync属性的配置,若是不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据,但在高性能要求下每次都sync是不现实的,通常都使用定时sync,好比1s一次,这个时候就最多丢失1s的数据。

结语

emmmm,第二篇文章也慢慢的写完了,在写文章的过程当中,真的是发现本身懂得越多,不懂越多,并且由于公司周末要加班,因此这将Redis分红两篇来完成,但愿你们能够喜欢~~ 同时须要思惟导图的话,能够联系我,毕竟知识越分享越香!

在这里插入图片描述
相关文章
相关标签/搜索