Redis 是一款使用 C 语言编写的高性能 key-value 数据库。
特点:
用途:
高速缓存。
分布式锁。
单点登录共享session。
计数器。
1、基本基于内存的,绝大部分请求都是在内存中操作。另外,Redis为了保证内存足够,实现冷热数据的分离,并直接自己构建了VM 机制,相比于一般的系统调用函数,由于通信的应用协议不一样,Redis更高效。
2、数据结构简单高效,如 Hash数据结构、跳表等。Hash数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);跳表基于有序的链表,包含多个指向后继结点的指针,实现快速查找。
3、采用单线程,使用多路I/O复用模型,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
Redis 支持五种数据类型
string:字符串
hash:哈希
list:列表
set:集合
sorted set:有序集合(也叫zset)
我们常说的String,List,Hash,Set,Sorted Set只是对外的编码,实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis可以在合适的场景选择更合适的内部编码:
redisobject最主要的信息:
//redisobject源码
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
//引用计数
int refcount;
//记录最后一次被程序访问的时间
unsigned lru:22;
}robj
因为c语言不具备自动内存回收功能,当将redisObject对象作为数据库的键或值而不是作为参数存储时其生命周期是非常长的,为了解决这个问题,Redis自己构建了一个内存回收机制,通过redisobject结构中的refcount实现.
现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!了解 Redis 和 Memcached 的区别和共同点,有助于我们在做相应的技术选型的时候,能够做到有理有据!
共同点 :
都是基于内存的缓存。
都有过期策略。
两者的性能都非常高。
区别 :
Redis 支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。
Redis 有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。
Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常。
Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.
Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。 (Redis 6.0 引入了多线程 IO )
Redis 支持发布订阅模型、Lua脚本、事务等功能,而Memcached不支持。并且,Redis支持更多的编程语言。
跳跃表基于有序单链表,在链表的基础上,每个结点不只包含一个指针,还可能包含多个指向后继结点的指针,这样就可以跳过一些不必要的结点,从而加快查找、删除等操作。
跳表有一个层级(level)的概念,层级越多访问越快。
比如上图有三个层级,从下到上依次是level1到level3。可以参考链接:跳表查看跳表是如何插入删除数据的。
跳跃表在 Redis 中使用不是特别广泛,只用在了两个地方。一是实现有序集合键,二是集群节点中用作内部数据结构。
Redis使用到了VM(virtual memory,虚拟内存), 通过VM功能可以实现冷热数据分离。使热数据仍在内存中,冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。
冷数据即是那些不经常访问、但又无法删除的信息。
惰性删除:不主动删除过期键,从键空间中获取键时,都检查取得的键是否过期,过期则删除;没过期则返回
定期删除:每隔一段时间对字典进行一次检查,删除里面的过期键。删除多少过期键、检查多少个数据库,由算法决定。
redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的 key。
参考:Java架构直通车——Redis缓存过期处理与内存淘汰机制
2.8 版以前:
Redis 通过同步(sync)和指令传播(command propagate)两个操作完成同步:
为了解决主从节点断线复制低效的问题(SYNC过程中生成、传输、载入 RDB 文件耗费大量 CPU、内存、磁盘 IO 资源),2.8 版开始新增 PSYNC 指令。
2.8版本后:
PSYNC 具有两种模式:
参考:Java架构直通车——Redis主从数据同步机制。
Pipeline指的是管道技术,指的是客户端允许将多个请求依次发给服务器,过程中而不需要等待请求的回复,在最后再一并读取结果即可。
多个指令之间没有依赖关系,可以使用 pipeline 一次性执行多个指令,减少 IO,缩减时间,提高吞吐量。
未使用pipeline执行N条命令:
使用了pipeline执行N条命令:
参考:Java架构直通车——基于Redis的Set NX实现分布式锁
为需要保证幂等性的每一次请求创建一个唯一标识token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:
如果存在, 正常处理业务逻辑, 并从redis中删除此token, 那么, 如果是重复请求, 由于token已被删除, 则不能通过校验, 返回请勿重复操作提示。
Redisson
优点:
缺点:
Jedis
优点:
缺点:
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念。哈希槽是 Redis 集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。通过对每个key做hash运算,判读其对应的哈希槽,从而找到在redis集群架构里对应存储该key的节点。
Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 算法计算的结果,对 16384 取模后放到对应的编号在 0-16383 之间的哈希槽,集群的每个节点负责一部分哈希槽。
参考:Java架构直通车——Redis 主从/哨兵/集群 架构详解
以下情况可能导致写操作丢失:
Redis存储在内存中的数据升到配置大小时,就进行数据淘汰。使用 allkeys-LRU (最近最少使用)策略,从数据集(server.db[i].dict)中挑选最近最少使用的数据优先淘汰,即可满足保存热点数据
参考:Java架构直通车——Redis缓存穿透/击穿/雪崩
缓存中的某些Key(可能对应用与某个促销商品)对应的value存储在集群中一台机器,使得所有流量涌向同一机器,成为系统的瓶颈,该问题的挑战在于它无法通过增加机器容量来解决。(当key失效的时候,大量的线程构建缓存,导致负载增加)
解决方案:
Redis使用过程中经常会有各种大key的情况, 比如单个简单的key存储的value很大。
由于redis是单线程运行的,如果一次操作的value很大会对整个redis的响应时间造成负面影响,导致IO网络拥塞。
解决:
将整存整取的大对象,分拆为多个小对象。可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响;
暂时不做了解。
参考:缓存架构
参考:分布式DB与Cache一致性
参考:Java架构直通车——Redis持久化和宕机恢复机制
Redis 提供 8 种数据淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
Redis的事务不像Mysql的事务功能强大。
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
实际正常开发中很少遇到使用Redis事务的场景,因为Lua脚本同样可以帮我们实现Redis事务相关功能,并且功能要强大很多。
Redis事务相关命令: