【JAVA今法修真】 第三章 关系非关系 redis法器

2022-03-07 13:54:58 浏览数 (1)

您好,我是南橘,万法仙门的掌门,刚刚从九州世界穿越到地球,因为时空乱流的影响导致我的法力全失,现在不得不通过这个平台向广大修真天才们借去力量。 等我回去以后,大家都是万法仙门的长老,我会给大家数不尽的天材地宝,人人如龙,全民飞升。

作为一个仙侠世界的宗门,当然会存在宗门大比这种事情。

而作为我们的主角,李小庚自然也会在其中大放光彩。

万法仙门,宗门三号擂台,筑基组淘汰赛。

“承让!”

轻轻挥了挥衣袖,我们的主角李小庚朝已经飞出擂台的对手点了点头。

“三号擂台比试结束,胜者,云霄殿,李小庚。”裁判面无表情地给李小庚发了一个牌子。

“这人也太赖了吧!”

“草(我爱你)一上台就用法器,完全不给我们这些普通弟子活路啊!”

“云霄殿都是一群狗大户,结丹期的擂台上,云霄殿的二弟子带着炼器宗的oracle大杀四方,没想到这边又是一样的剧情。”

李小庚听到这些议论,嘴角露出一丝无奈的笑容,使用绝技发胶手轻轻整了整发型,望向议论纷纷的观众说道:“谢谢大家的支持,我会继续努力的!”

“不过他好帅啊,我原谅他了。”

“是啊,看到他的脸,这样一想不尊重公平竞技其实也没啥事吧?”

“草只是一种植物而已。”


回到一周前。

云霄殿中,李小庚一如既往的在给云小霄献殷勤。

“师父,下周就是宗门大比了。您天天在万法研究院里搞研究,肯定很累吧,小徒弟来给您松松肩膀。”

云小霄看着满脸谄媚的李小庚,轻哼了一下:“放心,人人都有份,给你Redis行不行?”

“Redis?炼器宗出品,号称能解决大部分分布式问题的神器?”

“没错,如果你想要,那我就要考验考验你。你知道Redis的五种基础数据结构是什么吗?”

“简单,师父,且听我娓娓道来。”

一、Redis基础知识

1、1 String 字符串类型

redis中最基本的数据类型,一个key对应一个value 适用情况: 1、缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。 2.计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。 3.session:通过redis实现session共享 复制代码

1、2 Hash (哈希)

对于Java中的HashMap,本身是一种KV对结构,如 value={{field1,value1},......fieldN,valueN}},非常好理解 适用情况: HashMap作为缓存,相比于string更节省空间的维护缓存信息,适合存储如用户信息,视频信息等 复制代码 底层用字典dict实现

1、3 List (链表)

Redis 的链表相当于 Java 语言里面的 LinkedList 适用情况: 1、List在Redis中既可以做队列也可以做栈使用,它的插入和删除操作非常快,时间复杂度为 0(1),但是索引定位很慢,时间 复杂度为 O(n)。 2、可以作为微博的时间轴,有人发布微博,用lpush加入时间轴,展示新的列表信息。 3、可以实现阻塞队列,左进右出的队列组完成设计 复制代码 list底层使用quickList(快速链表)实现 在早期的设计中, 当列表对象中元素的长度比较小或者数量比较少的时候,采用ziplist来存储,当列表对象中元素的长度比较大或者数量比较多的时候,则会转而使用双向列表linkedlist来存储。 这两种存储方式都各有优缺点

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

3.2版本更新之后,list的底层实现变成了quickList quickList是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。

1、4 Set 集合

Redis 的集合相当于 Java 语言里面的 HashSet ,它内部的键值对是无序的、唯一 的。 它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL 当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。 复制代码 适用情况: 1、标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。 2、点赞,或点踩,收藏等,可以放到set中实现 3、可以用来存储在某活动中中奖的用户 ID ,因为有去重功能,可以保证同 一个用户不会中奖两次。 复制代码

1、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(地理位置系列)、PubSub(消息队列)、Pipeline(管道)、BloomFiler(布隆过滤器),都在不同的地方有用到,有些我会在下文向大家介绍。

1、6 Pipeline

可以将多次IO往返时间缩减为一次,前提是pipleline执行的指令之间没有因果关系 管道(pipeline)可以一次性发送多条命令并在执行完后一次性将结果返回,pipeline 通过减少客户端与redis的通信次数来实现降低往返延时时间,而且Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性。 注意:pipeline机制可以优化吞吐量,但无法提供原子性/事务保障

“哎哟,不错哦,还能举一反三,是不是一早就打起Redis的主意了?”云小霄玩味的盯着李小庚的眼睛.

不过李小庚是何许人物?举世誉之而不加劝,举世非之而不加沮,脸色毫无任何变化:“哪能啊师父,你也知道我除了修炼就是去图书馆了,关于redis的书籍这段时间也是小有涉猎,不信您再考考我?”

“长本事了嘛小庚同学!”云小霄来了兴趣。

当年收李小庚入门的时候,百分之五十(其实是百分之百)是看脸,剩下百分之五十的考量也是他的基础非常扎实,完全不像普通的炼气期。李小庚本人也十分争气,入门半年便突破了筑基期,并且不急不躁,一步一个脚印的践行着万法仙门三年筑基的规划。学习今法,虽然快速的掌握各种先进技能能很快的突破,但是若是没有坚实的基础,那么未来的道路肯定会遇到各种各样的问题。

“那么我来考考你,Redis如何实现分布式锁?”

“我有一言,请诸位静听。”

二、集群与分布式锁

2、1 Redis实现分布式锁

随着互联网技术的飞速发展,越来越多的单体架构已经转型成了分布式架构,,分布式架构确实能带来性能和效率上的提升,但是也会带来数据一致性的问题。 分布式锁,就是解决分布式架构中数据一致性的专用武器,分布式锁需要满足一下三个方面方可放心使用:

  • 排他性:在同一时间只会有一个客户端能获取到锁,其它客户端无法同时获取
  • 避免死锁:这把锁在一段有限的时间之后,一定会被释放(正常释放或异常释放)
  • 高可用:获取或释放锁的机制必须高可用且性能佳

目前,我所知道的分布式锁大概有三种主流方式实现,分别是zookpeer,redis,还有本地数据库,今天我就介绍一下如何用redis实现分布式锁。 基于Redis实现的锁机制,主要是依赖redis自身的原子操作 setnx争抢锁,再用expire添加过期时间 没有看错,就是这么简单,如果害怕不妥,比如争抢锁的时候还没有设置过期时间就突然宕机之类的问题,可以直接用jedis等封装好的RedisTemplate把setnx和expire合成一条指令使用。

“但是这样的分布式锁真的安全吗?如果法器服务器宕机怎么办?”

“当然,这样的单体redis所使用的分布式锁的安全性并不高。”李小庚仿佛知道云小霄会这样问,嘴角不经意泛起一抹微笑。

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

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

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。

“你说你使用RedLock工具,那么我直接击毁你的redis单体怎么办?你还如何保持分布式锁的健壮性?而且我们的万法玄天阵也是用到了分布式锁来确保出入弟子的身份ID不被盗用,按你的方式来使用,如果结果不够精确,那对于其他门派来说咱的阵法就和筛子没有区别了。”云小霄挥手从殿内的盆栽上弄来了几个小橘子,望向了第三面墙外的各位,好像在提示大家可以喝口水动一动了。

“那么,就要提到Redis集群了。”李小庚推了推眼镜。

2、2Redis集群

集群同步机制

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进行分片存储

“原来是在这里等着我呢。李小庚,如果我给你一套redis集群,你如何去实现你所设想的方案呢?”云小霄笑出了声,本来只想给一个单体redis法器,再凭借李小庚自己的扎实的基础,就有希望在筑基期大比上夺魁的,没想到他的目标一直都是redis集群。

“不急不急,我从书中看到了三套实现redis集群的方案,还请师父帮忙演示一二。”李小庚也知道云小霄知道了自己的目的,纵使脸皮再厚也开始微微泛红,配上一双剑眉下一对细长的桃花眼,云小霄的口水不争气的流了下来。

云小霄伸了伸懒腰,然后单手指天:“剑来!”。

无数红白相间细小飞剑从房间的各个角落出现并环绕在她的身边,这边是李小庚一直心心念念的redis集群了。

“接下来集群的指挥权交给你,看你表演咯~”

李小庚学有样学样:“今日我李小庚便要重回陆地神仙之境。”

三、Redis集群的实现

3、1传统的主从模式

大概是所有的集群都有主从模式 主从模式的一个作用是备份数据,这样当一个节点损坏(指不可恢复的硬件损坏)时,数据因为有备份,可以方便恢复。 另一个作用是负载均衡,所有客户端都访问一个节点肯定会影响Redis工作效率,有了主从以后,查询操作就可以通过查询从节点来完成。 在主从模式中,一个Master可以有多个Slaves,默认配置下,master节点可以进行读和写,slave节点只能进行读操作,无法进行写操作。 如果修改默认配置,可以让slave进行写,但是这毫无意义,因为写入的数据不会同步给其他slave,同时,master节点如果修改了,slave上的数据会马上被覆盖slave节点挂了不影响其他slave节点的读和master节点的读和写,重新启动后会将数据从master节点同步过来。master节点挂了以后,不影响slave节点的读,Redis将不再提供写服务,master节点启动后Redis将重新对外提供写服务。 所以,我们可以发现Redis的主从和Zookeeper的主从完全不一样!它竟然不会选举! 这个缺点影响是很大的,尤其是对生产环境来说,是一刻都不能停止服务的,所以一般的生产坏境是不会单单只有主从模式的。所以有了下面的sentinel模式。

3、2sentinel模式(哨兵模式)

哨兵模式要搭配主从模式来使用,主从不能自己选举,那我们就加一个哨兵,当sentinel发现master节点挂了以后,sentinel就会从slave中重新选举一个master。 哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下两个。 (1)监控主服务器和从服务器是否正常运行。 (2)主服务器出现故障时自动将从服务器转换为主服务器。 这不就皆大欢喜了吗? 哨兵的工作方式:

  • 1、每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
  • 2、如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)
  • 3、如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态 当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)
  • 4、在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。 当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
  • 5、若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中,这就是cluster模式。

3、3cluster模式

cluster的出现是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。 Redis-Cluster采用无中心结构,它的特点如下:

  • 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
  • 节点的失效是通过集群中超过半数的节点检测失效时才生效。
  • 客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

cluster可以说是sentinel和主从模式的结合体,通过cluster可以实现主从和master重选功能,所以如果配置三个副本三个分片的话,就需九六个Redis实例。 因为Redis的数据是根据一定规则分配到cluster的不同机器的,当数据量过大时,可以新增机器进行扩容,这种模式适合数据量巨大的缓存要求,当数据量不是很大使用sentinel即可。 引用一张大佬的图片来直观展现一下什么是 Redis-Cluster

每个请求访问Redis-Cluster集群的时候,都会进行一个路由,路由可以通过Hash(也可以用别的)来进行随机分片,但是如果完全hash的话很可能导致分片们旱的旱死,涝的涝死。,所以,提出了**一致性哈希(自动缓存迁移) 虚拟节点(自动负载均衡)**的方法来解决问题 一致性哈希的原理 :将所有master node落在一个圆环上面,然后,有一个key过来之后。同样就是hash值,然后会用hash值在圆环对应的各个点上(每个点都有一个hash值)去对比,看hash值落在那个位置,落在圆环上面以后,就会顺时针旋转去寻找距离自己最近的一个节点,数据的存储于读取都在该节点进行。 一致性哈希的优势 :保证了任何一个master宕机,只会影响之前在那个master上面的数据,因为照着顺时针走,全部在之前的master上面找不到了,master也宕机了,就会继续顺着顺时针走到下一个master节点去。这样就只会有一部分数据丢失。

在李小庚的指挥下,reids集群也慢慢的变成了他的模样,一会儿在空中变成,一会儿变成,好不快乐。

“不要急,小庚,除此之外,关于redis还有一些其他的东西你需要掌握。”

四、Redis进阶知识

4、1异步队列

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

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

4、2延时队列

在Redis中,可以利用 sorted-set 来做延时队列 zadd key score1 value1 score2 value2

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

但是,Redis的延时队列无法返回ACK,所以需要自己实现

4、3 持久化

Redis有两种持久化的方式,分别是RDB和AOF RDB做镜像全量持久化、AOF做增量持久化,因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量有效数据丢失,所以需要AOF来配合使用,在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。 RDB机制 RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。RDB提供了三种机制来触发持久化

  • 1、save触发方式---客户端发起save请求 执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止
  • 2、bgsave触发方式--客户端发起bgsave请求 执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求
  • 3、自动触发 自动触发是由我们的配置文件来完成的,在redis.conf文件中配置,大家可以去了解一下,这里就不写那么多东西了

AOF机制 全量备份总是耗时的(随机的传说总是好的???),有时候我们提供一种更加高效的方式AOF,Redis会将每一个收到的写命令都通过write函数追加到文件中,就是日志记录。 和RDB一样,AOF也有三种同步机制:

  • 1、always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
  • 2、everysec:每秒同步,异步操作,每秒记录 如果一秒内宕机,有数据丢失
  • 3、no:从不同步

Redis本身的机制是AOF持久化开启存在AOF文件时,优先加载AOF文件。AOF文件不存在时候,加载RDB文件。加载AOFRDB文件后,Redis启动成功。AOFRDB文件存在错误时,Redis启动失败并打印错误信息。 不要问AOF和RDB用哪个,我的经验就是,全都用。 RDB同步快,但是要损失最多五分钟的内容,AOF同步慢,但是每秒同步的情况下最多损失1s的内容,损失的内容也可以通过日志去找回。 解决机器断电对数据丢失的影响 AOF日志中可以进行sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据,但在高性能要求下每次都sync是不现实的,一般都使用定时sync,比如1s一次,这个时候就最多丢失1s的数据。

0 人点赞