本文共 8226 字,大约阅读时间需要 27 分钟。
REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。
Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。
基本数据结构
Redis 与其他 key - value 缓存产品有以下三个特点:
1.Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。
2.Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
3.Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
1.在安装目录下输入命令redis-server.exe redis.windows.conf
2.另在redis目录下启动一个cmd窗口,原来的不要关闭,否则无法访问服务端
输入命令:redis-cli.exe -h 127.0.0.1 -p 6379
然后设置键值对:set mykey abc
取出键值对:get mykey
Redis的配置文件位于安装目录下,文件名为redis.windows.conf,可以通过config命令查看或者设置配置项。
eg:config get * :查看所有配置项
Redis支持五种数据类型:String、Hash(哈希)、List(列表)、Set(集合)、Sorted Set(有序集合)
String类型是二进制安全的,意思是Redis中String类型可以包含任何数据,比如jpg图像或者序列化的对象;
String类型是Redis最基本的数据类型,String类型最大能存储512MB;
set命令:设置一个key对应的String类型数据
语法:set key “value”
eg:
Redis hash是一个键值对(key-value)集合;
Redis hash是一个String类型的filed和value的映射表,hash特别适合用于存储对对象
eg:
del stringkey:删除前面测试的key
测试中:
HMSET 设置了两个filed=>value对;
HGET获得filed对应的value;
每个 hash 可以存储 232 -1 键值对(40多亿);
Redis List列表是简单的字符串列表,按照插入的顺序排序,可以添加一个元素到列表头部左边或者尾部右边;
LPUSH:将数据加进key对应的列表
LRANGE:从start到stop显示列表的数据(下标从0开始)
列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
Redis的Set是String类型的无序集合;
集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是O(1);
sadd命令:添加一个String元素到key对应的set集合中,成功返回1,如果元素已经在集合中返回0;
semembers key:查看key对应集合中的所有元素;
根据集合内元素的唯一性,第二次插入相同元素会被忽略;
集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。
Redis zset和set一样也是String类型元素的集合,不允许相同的元素;
不同的是zset中每个元素都会关联一个double类型的分数,redis是通过分数来为集合中的成员进行从小到大的排序;
zset中的成员是唯一的,但是score可以相同,可以多个成员关联一个score;
zadd命令:添加元素到集合中,成功返回1,元素在集合中存在则更新对应score(double类型分数),返回0;
语法:zadd key score member
eg:
RDB:Redis DataBase;
Redis默认通过快照(RDB)来将数据持久化到磁盘中,在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时是将快照文件直接读到内存;
步骤
1.设置持久化快照的条件;
2.指定持久化文件存储的目录;
快照保存过程
1.Redis单独创建一个子进程来进行持久化;
2.先将数据写入到一个临时文件中;
3持久化过程结束后,用临时文件替换上次持久化好的RDB文件,子进程退出;
在整个过程中,主进程是不进行任何IO操作的,这保证了极高的性能,如果需要进行大规模的数据的恢复,且对于数据恢复的完整性不敏感,那么RDB方式比AOF方式更高效;
触发机制
优点
大规模的数据恢复;对数据完整性要求不高;
缺点
最后一次持久化之后的数据可能丢失;子进程占用一定的内存空间;需要一定时间隔离进程操作;
AOF:Append Only File;
一旦Redis非法关闭,那么会丢失最后一次持久化之后的数据;如果数据不能允许丢失,那么要使用aof方式,Redis默认不使用AOF持久化;
实现过程
AOF以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件构建数据,即根据日志文件内容将写指令从前往后执行一次来完成数据的恢复工作;
优点
每一次修复都同步,文件完整性好;
缺点
数据文件大、修复速度慢;运行效率比RDB慢;
什么是主从复制
持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过从redis的主从复制机制就可以避免这种单点故障;
说明
主从复制作用
哨兵模式
后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库;
哨兵模式是一种特殊的模式,Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程它会独立运行;
哨兵原理
哨兵通过发送命令,等待Redis服务器的响应,从而监控运行的多个Redis实例;
哨兵作用
通过发送命令,让Redis服务器返回,监控其运行状态,包括主服务器和从服务器;
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,切换主机;
多哨兵模式
只有一个哨兵监控Redis服务器时可能会出问题,因此可以使用多个哨兵监控,各个哨兵之间也各自监控,形成多哨兵模式;
优点
基于主从复制模式,监测Redis服务器;
主从可以切换、故障实现转换、系统可用性更高;
手动部署,自动实现;
缺点
配置麻烦;
集群容量达到上限后,在线扩容困难;
缓存穿透:是指查询一个一定不存在的数据,由于缓存没有命中,于是向持久层查询,但是依旧无法获得数据——这就导致对这个不存在的数据每一次请求都要去持久层中查询,失去了缓存的意义,也给持久层造成了很大的压力;
解决方案
有很多方法可以有效解决缓存穿透的问题,最常见的是采用布隆过滤器;
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
有一个简单粗暴的方法:如果一个查询返回的数据为空(不管是数据不存在还是系统故障),我们仍然会把这个空结果进行缓存,但它的过期时间会很短,这样保护了后端数据源;
但是这种方法存在问题:
1.如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中有很多空值键;
2.即使对空值设置了短的过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致的一致性业务有影响;
缓存雪崩:是指我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,使DB瞬间压力过大雪崩;(或者缓存服务器某个节点宕机)
解决方案
缓存击穿:是指缓存在某一个时间点过期的时候,恰好在这个时间点对这个key有大量的并发请求过来,这类数据一般是热点数据,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发请求可能会瞬间把后端DB压垮;
解决方案
public String get(key) { //模拟请求数据 String value = redis.get(key); if (value == null) { //代表缓存值过期 //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功 value = db.get(key); redis.set(key, value, expire_secs); redis.del(key_mutex); } else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可 sleep(50); get(key); //重试 } } else { return value; } }
分布式锁:控制分布式系统不同进程共同访问共享资源的一种锁的实现;
存在问题
1.锁过期释放了,但是业务还没有执行完——假设线程A获得了锁,执行线程的任务,但是过了超时设置的时间,还没执行完,这时候线程B请求锁成功导致线程A中的任务并没有执行完成;
2.锁被别的线程误删——假设线程A执行完以后去删除key来释放锁,但是有可能持有锁的时间已经超时,锁已经被线程B持有,这时候线程A将线程B持有的锁释放了,导致线程B的任务没有执行完成;
Java的Redis客户端之一,RedissionLock这个类实现加锁/释放锁的操作;
Redission底层原理图
如图所示,只要线程一加锁成功,就会启动一个看门狗,它是一个后台线程,会每隔秒检查一下线程一是否还持有锁,如果还持有,就会不断地延长锁key地生存时间。因此,Redission解决了“锁过期释放,业务没有执行完”地问题;
红锁是Redis官方提出的一种分布式锁的算法,算法认为只要(N/2+1)个节点加锁成功,那么就认为获取了锁,解锁时将所有实例解锁;
思想
RedLock 的思想是使用多台 Redis Master ,节点完全独立,节点间不需要进行数据同步,因为 Master-Slave 架构一旦 Master 发生故障时数据没有复制到 Slave,被选为 Master 的 Slave 就丢掉了锁,另一个客户端就可以再次拿到锁。锁通过 setNX(原子操作) 命令设置,在有效时间内当获得锁的数量大于 (n/2+1) 代表成功,失败后需要向所有节点发送释放锁的消息。
流程
1.顺序向五个节点请求加锁;
2.根据一定的超时时间来推断是不是跳过该节点;
3.(N/2+1)个节点加锁成功并且花费小于锁的有效期;
4.认定加锁成功;
以业务名为key前缀,需要用冒号隔开,防止key冲突覆盖;
key长度尽量小于30字符;
key尽量设置过期时间,保证不使用的key能被及时清理;
给key设置过期时间时,注意不同业务的key尽量过期时间分散,放置缓存雪崩;
如果删除一个String类型的key,del时间复杂度是O(1),可以直接使用del;
如果删除一个List/Hash/Set/ZSet类型时,时间复杂度是O(n),元素越多越慢,会阻塞主线程;
删除方案:如果是List类型,执行lpop或者rpo,直到所有元素删除完成;如果是Hash/Set/ZSet类型,可以先执行hscan/sscan/scan查询,再执行hdel/srem/zrem依次删除每个元素;
使用短连接时每次都需要建立TCP三次握手、四次挥手,会增加耗时;而使用长连接可以建立一次连接后,一直使用redis命令,减少redis建立的时间;
合理设置连接池参数,长时间不操作redis时,也需要及时释放连接资源;
开启Redis中lazy-free机制后,如果删除一个big key时,释放内存的耗时操作会放到后台线程去执行,减少对主线程的阻塞影响;
转载地址:http://lrqzi.baihongyu.com/