1.缓存

1.1 概念模型

看图,应用请求过来了要读取数据库给用户一个响应。DB是系统的核心,所有操作都是围绕着数据进行。缓存只是一种提高访问效率的优化手段。用户请求先从缓存里面取,取不到了再访问数据库。

1.2 常见缓存种类

  • EHcache(单机范围):轻量级缓存,它是运行在单机内存里的,方便简易。缺点是容量非常有限,应用场合不是很多,主技能在单机范围内
  • Memcache(第三方):存储内容很单一,只是字符串,有些场合无法满足需求
  • Redis(第三方):存储类型丰富,性能非常高、可靠性很高,满足多样化的需求(ps:面试可能会问到Redis和Memcache的区别)

2.Redis初步认识

  • 官方介绍 网址
  • Redis 由 C 系列语言开发 ,redis可理解是一种NoSql(not only sql),因为是运行在内存中的,速度比较快,所以叫做缓存,官方只是提供了标准的Linux版,微软开发维护了win版本。在实际开发中都是在Linux环境下的,但操作也没有太大的区别,为方便学习我选择win版本。win版传送门:下载地址

2.1 Redis启动相关

写在前面:win版redis需要在cmd命令行中启动在cmd界面CD到解压目录输入启动服务端命令(如下图红字部分 )注意:敲完命令别着急回车,先去conf查看一下服务器的ip和端口,看到下图就表示Redis已经运行起来了

2.2 Redis数据类型

redis是键值对的数据库,有5种主要数据类型

KEYS * 获得当前数据库的所有键
EXISTS key [key ...]    判断键是否存在,返回个数,如果key有一样的也是叠加数
DEL key [key ...]       删除键,返回删除的个数
TYPE key                获取减值的数据类型(string,hash,list,set,zset)
FLUSHALL                清空所有数据库
CONFIG [get、set]       redis配置

-inf 负无穷
+inf正无穷

  • 字符串类型(string)

    字符串类型是Redis的最基本类型,它可以存储任何形式的字符串。其它的四种类型都是字符串类型的不同形式。

  • 散列类型(hash)

    hash里面装的是键值对

  • 列表类型(list)

    特点:元素有序可重复,list在redis可以看作堆栈

  • 集合类型(set)

    特点:元素无序,不能重复 ;

    集合类型值具有唯一性,常用操作是向集合添加、删除、判断某个值是否存在,集合内部是使用值为空的散列表实现的。

  • 有序集合类型(SortedSet(zset))

    特点:元素有序,不能重复 ;

2.3 连接Redis

上面我们已经将redis的服务端开启了(注意启动后不能关闭窗口),接下来我们还需要客户端连接到服务端。操作跟之前的差不多,打开另一个cmd窗口输入相关关命令后回车如下图表示连接成功(-h:服务器ip,-p:端口号)

接下来我们敲一些命令玩玩 (不区分大小写)

  • GET、SET 语法:GET key,SET key value   value如果有空格需要双引号以示区分

同key不同value:会发生value被覆盖

setnx : (只可存不存在的字符串,已经存在的不会覆盖,根据这个特性可以将setnx作为分布式锁)

分布式锁:应用1先到redis建立一个key,然后应用2来了想再建立一个key(建立key是使用setnx)。谁建立key成功谁就可以去使用资源,前者用完了后者才可以使用,这时候就实现了分布式锁,类似上排队的情况。

  • Expire key second 设置key的过期时间

  • Ttl key:查看key的有效期

PS:Redis的五种类型语法命令就不继续演示,心血来潮的时候再前往这里参考其它语法:https://www.cnblogs.com/mingtianct/p/6291593.html

3.Redis发布订阅概念模型

信息源往某个或者多个频道发布消息,所有业务监听subscribe(订阅)都可以接收到该频道的消息。

3.1 命令行实战

环境准备

  • 服务器端保持运行状态
  • 客户端:3个redis-client窗口,1个发布消息(老师),2个订阅者一个相同的频道(学生)
  • 订阅信息命令:subscribe 频道名称
  • 发布消息命令:publish 频道名称 消息

接下来回车发布消息,很显然,两个订阅者监听的laoshi 频道在 发布消息后都接收到了消息。接到消息之后保持监听状态继续监听下一个消息。

3.2 Java实现Redis发布订阅

  • 新建RedisSubcribe类实现JedisPubSub抽象类作为订阅者,在void onMessage()中定义channel、message两个参数。

    public class RedisSubcribe extends JedisPubSub {
    @Override
    public void onMessage(String channel, String message) {
    System.out.println(channel + "--------" + message) ;
    }

  • 新建client项目(记得导入jedis的jar包)作为发布者,如下图所示

    运行订阅者,它处于堵塞状态(在等待即将发布的消息)

  • 接下来运行右边小窗口的代码,消息发布了,左边的订阅者也收到了发布消息,进程并没有结束,继续等待消息的发布。

    添加一条消息再测试一下

3.3 Redis发布订阅与ActiveMQ的比较

  1. ActiveMQ支持多种消息协议,包括AMQP,MQTT,Stomp等,并且支持JMS规范,但Redis没有提供对这些协议的支持;
  2. ActiveMQ提供持久化功能,但Redis无法对消息持久化存储,一旦消息被发送,如果没有订阅者接收,那么消息就会丢失;
  3. ActiveMQ提供了消息传输保障,当客户端连接超时或事务回滚等情况发生时,消息会被重新发送给客户端,Redis没有提供消息传输保障。

    总而言之,ActiveMQ所提供的功能远比Redis发布订阅要复杂,毕竟Redis不是专门做发布订阅的,但是如果系统中已经有了Redis,并且需要基本的发布订阅功能,就没有必要再安装ActiveMQ了,因为可能ActiveMQ提供的功能大部分都用不到,而Redis的发布订阅机制就能满足需求。

4.Redis事务

4.1 Redis事务机制

Redis的事务机制允许同时执行多条指令,它是原子性操作,事务中的命令要么全部执行,要么全部不执行,另外,事务中的所有指令都会被序列化,而且其开始执行过程中,不回被即时过来的指令所打断,其需要经历三个过程,分别为开始事务、命令入队以及执行事务。

4.2 Redis事务相关命令

MULTI 语义:开启事务,开启之后不会立即被执行,而是被放到了队列中,直到EXEC被调用之后,所有命令才会被序列化执行。所以开启一个事务之后还可以继续添加。如图1
EXEC 语义:触发并执行队列中所有的事务,而且是按照事务的次序执行的。如图2

图1

图2

4.3 关务事务报错的两种情况

  • 第一种:语法错误,事务将整体回滚
  • 第二种:语法不符合其相关的数据类型,首先是string数据类型下的正确命令,然后补充一个sadd,因为是s开头的,其实也是set,所以就强行将新的事务丢尽了string类型,redis检测sadd通过并将事务放到队列中,可是在exec的过程中出现了错误。sadd虽然是个错误的命令,但结果却能get 到了该错误之前的信息,所有事务没有回滚,把第一个数据存进去了。该错误很有可能在程序开发期间发现,一般很少在生产环境发现。 Redis事务是比较鸡肋!

4.4 WATCH命令详解

顾名思义,watch就是监控一个key,在监控期间执行事务,如果有key发生改变,那么所有事务都讲不会执行

取消watch看看结果又是如何

4.5 Redis持久化策略

重点关注两种方案

  • 快照模式

    查看conf文件,很显然:快照模式是redis的默认Save数据方式,其实也就是在多少时间要是发生一个key改变的话就进行快照save。快照模式优点是数据保存得比较全,如果数据量大了会导致快照数次比较多,这样会引起数据重复比较多就会给磁盘空间造成浪费,性能损耗。一般是不采用的。

  • aof (append only file)
  1. 强制每个操作持久化(严禁使用)
  2. 一秒钟一次(经常用)
  3. 系统决定(一般不用)

4.6 LRU缓存淘汰算法

LRU (Least Recently Used 即最近最少使用),LRU缓存就是使用这种原理实现,简单的说就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉,比如我们缓存10000条数据,当数据小于10000时可以随意添加,当超过10000时就需要把新的数据添加进来,同时要把过期数据删除,以确保我们最大缓存10000条,那怎么确定删除哪条过期数据呢,采用LRU算法实现的话就是将最老的数据删掉。

  • 最大内存配置

    通过redis的conf文件中搜索 maxmemory <bytes>来配置 Redis 的存储数据所能使用的最大内存限制。例如,要配置内存上限是100M的Redis缓存,在 redis.conf 配置如下: maxmemory 100mb

  • 回收策略

    当内存达到限制时,Redis 具体的回收策略是通过maxmemory-policy noeviction配置项配置的。

    以下的策略都是可用的:

    `

    • noenviction:不清除数据,只是返回错误,这样会导致浪费掉更多的内存,对大多数写命令(DEL 命令和其他的少数命令例外)
    • allkeys-lru:从所有的数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,以供新数据使用
    • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰,以供新数据使用
    • allkeys-random:从所有数据集(server.db[i].dict)中任意选择数据淘汰,以供新数据使用
    • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰,以供新数据使用
    • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,以供新数据使用`
![](https://cdn.jsdelivr.net/gh/chch455/tuchuang/2020/06/02/f7393248af8eaa9a51e080a69a6398d2.png)

当 cache 中没有符合清除条件的 key 时,回收策略 volatile-lru, volatile-random 和volatile-ttl 将会和 策略 noeviction 一样返回错误。选择正确的回收策略是很重要的,取决于你的应用程序的访问模式。但是,你可以在程序运行时重新配置策略,使用 INFO 输出来监控缓存命中和错过的次数,以调优你的设置。

5.Redis高可用方案

5.1 Redis线程模型(重灾区)

5.2 哨兵机制

有了主从复制的实现以后,如果想对主服务器进行监控,那么在redis2.6以后提供了一个"哨兵"的机制。顾名思义,哨兵的含义就是监控redis系统的运行状态。可以启动多个哨兵,去监控redis数据库的运行状态。其主要功能有两点:

  1. 监控所有节点数据库是否在正常运行。
  2. master数据库出现故障时,可以自动通过投票机制,从slave节点中选举新的master,实现将从数据库转换为主数据库的自动切换。

哨兵机制是有缺点的:

  1. 主从服务器的数据要经常进行主从复制,这样造成性能下降。
  2. 当主服务器宕机后,从服务器切换成主服务器的那段时间,服务是不能用的。

5.3 Redis Cluster分区实现原理

  • 槽(slot)概念

Redis Cluster中有一个16384长度的槽的概念,他们的编号为0、1、2、3……16382、16383。这个槽是一个虚拟的槽,并不是真正存在的。正常工作的时候,Redis Cluster中的每个Master节点都会负责一部分的槽,当有某个key被映射到某个Master负责的槽,那么这个Master负责为这个key提供服务,至于哪个Master节点负责哪个槽,这是可以由用户指定的,也可以在初始化的时候自动生成(redis-trib.rb脚本)。这里值得一提的是,在Redis Cluster中,只有Master才拥有槽的所有权,如果是某个Master的slave,这个slave只负责槽的使用,但是没有所有权。Redis Cluster怎么知道哪些槽是由哪些节点负责的呢?某个Master又怎么知道某个槽自己是不是拥有呢?

  • 位序列结构

Master节点维护着一个16384/8字节的位序列,Master节点用bit来标识对于某个槽自己是否拥有。比如对于编号为1的槽,Master只要判断序列的第二位(索引从0开始)是不是为1即可。

如上面的序列,表示当前Master拥有编号为1,134的槽。集群同时还维护着槽到集群节点的映射,是由长度为16384类型为节点的数组实现的,槽编号为数组的下标,数组内容为集群节点,这样就可以很快地通过槽编号找到负责这个槽的节点。位序列这个结构很精巧,即不浪费存储空间,操作起来又很便捷。

5.4 一致性哈希

    • 普通的HASH算法的缺点: 在使用Redis集群的时候,如果直接使用HASH算法 hash(key) % length,当缓存服务器变化时(宕机或新增节点),length字段变化,导致所有缓存的数据需要重新进行HASH运算,这样就导致原来的哪些数据访问不到了。而这段时间如果访问量上升了,容易引起服务器雪崩。因此,引入了一致性哈希
    • 一致性哈希:_通过对2^32取模的方式,保证了在增加/删除缓存服务器的情况下,其他缓存服务器的缓存仍然可用,从而不引起雪崩问题。_

2^32想象成一个圆,就像钟表一样。

圆环的正上方的点代表0,0点的右侧的第一个点代表1,以此类推,2,3,4,..........知道2^32-1,也就是说0的左侧第一个点代表_2^32-1,我们把这个由2^32个点组成的圆环成为Hash环。_

假设现在我们有4台服务器,通过Hash(服务器的IP地址)% 2^32得到服务器映射到Hash环上的位置。那么我要要缓存的数据对象怎么判断还存在哪些服务器上呢?

一致性哈希算法通过将缓存服务器和被缓存对象都映射到Hash环上以后,从被缓存对象的位置出发,沿顺时针方向遇到的第一个服务器,就是当前对象将要被缓存于的服务器,由于被缓存对象

与服务器hash后的值是固定的,所以在服务器不变的情况下,一个对象必定缓存在一个固定的服务器上,那么,当再次访问这对象时,只要再次使用相同的算法计算即可算出这对象被缓存到哪个服务器上。如下图:

实际应用中一致性哈希还可能出现极端状态如下图:

如果服务器和对象映射成上图这样子,那么被缓存的对象很有可能大部分集中缓存在某一台服务器上。这种现象称为Hash环的偏斜。

那如何解决该现象呢

我们可以通过虚拟节点的方式解决Hash环的偏移。

如果想要均衡的将缓存分布到这三台服务器上,最好能让这三台服务器的尽量多的,均匀的出现在Hash环上,但是,真实的服务器资源只有3台,那么如何凭空的让他们多起来呢?

做法就是既然没有多余的真正的物理服务器节点,我们就可能将现有的物理节点通过虚拟的方法复制出来,而被复制出来的节点被称为“虚拟节点”,加入虚拟节点后的Hash环如下图:

如果其中一个节点宕机了,一致性Hash能解决容灾问题吗?

明显可以的,我们假设有5台服务器,有a,f,c对象映射在A服务器上,r,t,v对象映射在E服务器上。如下图:

假如此时E服务器宕机了,其他服务器的对象仍然能被命中,因为对象的映射到服务器的位置已经固定了,不会出现因为宕机而让对象找不到。而宕机E上的对象会在下次容灾分配的时候,会把r,t,v这些对象重新分配到就近的服务器上,如下图:

OK,因学术未到位,先暂时结束本次Redis的相关学习!


ShikoWei
0 声望1 粉丝

Java 练习生


下一篇 »
【笔记】JMM