Java Web数据库篇之Redis概述

Java Web系列文章汇总贴: Java Web知识总结汇总


Redis简介

Redis是一个key-value存储系统,现在在各种系统中的使用越来越多,大部分情况下是因为其高性能的特性,被当做缓存使用。Redis由于其丰富的数据结构也可以被应用到其他场景。Redis是一个K-V的非关系型数据库(NoSQL),常见的NoSQL数据库有:K-V数据库如Redis、Memcached,列式数据库如大数据组件HBase,文档数据库如mogoDB。Redis应用广泛,尤其是被作为缓存使用。

Redis的具有很多优势:

  • 读写性能高–100000次/s以上的读速度,80000次/s以上的写速度;
  • K-V,value支持的数据类型很多:字符串(String),队列(List),哈希(Hash),集合(Sets),有序集合(Sorted Sets)5种不同的数据类型。
    -支持事务,操作都是原子性,Redis的所有操作都是单线程原子性的。
  • 特性丰富–支持订阅-发布模式,通知,也可用于缓存,消息,按key设置过期时间,过期后将会自动删除。
  • 在Redis3.0 版本引入了Redis集群,可用于分布式部署。

参考:
Redis 原理及应用(1)–数据类型及底层实现方式
关于redis,学会这8点就够了
关于Redis的问题看这篇就够了
Redis 3.0新特性


Redis与Memcached对比

  • redis与memcached相比,比仅支持简单的key-value数据类型,同时还提供list,set,zset,hash等数据结构的存储;
    redis支持数据的备份,即master-slave模式的数据备份;
  • redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用等等
  • 性能对比:由于Redis只使用单核(单线程),而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程。redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架。
  • 内存使用效率对比:使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。
  • 集群管理的区别。Redis支持集群模式,Memcached不支持,需要自己搭建集群。

参考:
Redis 原理及应用(2)–持久化方式、集群管理、事务及与Memcached的对比
分布式缓存Redis之与Memcached的比较
Redis与Memcached的比较 ,然后选择了Redis
redis和memcached的优缺点及区别


redis常见性能问题和解决方案:

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  • 尽量避免在压力很大的主库上增加从库
  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…
    这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

Redis线程模型

Java Web数据库篇之Redis概述_第1张图片
(图摘自石杉码农-视频课程)


Redis性能高的原因

  • 1)纯内存操作
  • 2)核心是基于非阻塞的IO多路复用机制
  • 3)单线程反而避免了多线程的频繁上下文切换问题

更多:
为什么说Redis是单线程的以及Redis为什么这么快!
Redis常见题目问答


Redis支持的数据类型及底层实现

Redis数据类型

字符串(String),散列(Hash),列表(List),集合(Set),有序集合(Sorted Set)

更多:
Redis各种数据类型应用场景

底层实现方式

Redis是由C语言编写的。Redis支持5种数据类型,以K-V形式进行存储,K是String类型的,V支持5种不同的数据类型,分别是:string,list,hash,set,sorted set,每一种数据结构都有其特定的应用场景。从内部实现的角度来看是如何更好的实现这些数据类型。Redis底层数据结构有以下数据类型:简单动态字符串(SDS),链表,字典,跳跃表,整数集合,压缩列表,对象。接下来,就探讨一下Redis是怎么通过这些数据结构来实现value的5种类型的。

1、简单动态字符串(simple dynamic string – SDS)

定义:

/* 
 * 保存字符串对象的结构 
 */ 
struct sdshdr { 
    // buf 中已占用空间的长度 
int len;

    // buf 中剩余可用空间的长度 
int free; 

    // 数据空间 
char buf[]; 
}

用途:

  • 实现字符串类型,还用作AOF持久化时的缓冲区。

好处:

  • 获取字符串长度(O(1))
  • 防止缓冲区溢出
  • 减少扩展或收缩字符串带来的内存重分配次数
  • 二进制安全

2、链表

定义:

typedef struct listNode{
 
      struct listNode *prev;
 
      struct listNode * next;
 
      void * value; 
 
}
 
 
 
typedef struct list{
 
    //表头节点
     listNode  * head;
 
    //表尾节点
     listNode  * tail;
 
    //链表长度
     unsigned long len;
 
    //节点值复制函数
     void *(*dup) (void *ptr);
 
    //节点值释放函数
     void (*free) (void *ptr);
 
    //节点值对比函数
    int (*match)(void *ptr, void *key);
 
}

图示:

用途:

  • 作为List的底层实现。

好处:

  • 链表结构的特点是可以快速的在表头和表尾插入和删除元素,但查找复杂度高,是列表的底层实现之一,也因此列表没有提供判断某一元素是否在列表中的借口,因为在链表中查找复杂度高

3、字典

定义:
字典,又称为符号表(symbol table)、关联数组(associative array)或映射(map),是一种用于保存键值对的抽象数据结构。 
在字典中,一个键(key)可以和一个值(value)进行关联,字典中的每个键都是独一无二的。

typedef struct dict {
 
    // 类型特定函数
     dictType *type;
 
    // 私有数据
     void *privedata;
 
    // 哈希表
     dictht  ht[2];
 
    // rehash 索引
     in trehashidx;
}
 
typedef struct dictht {
 
   //哈希表数组
    dictEntry **table;
 
   //哈希表大小
    unsigned long size;
 
    //哈希表大小掩码,用于计算索引值
    unsigned long sizemask;
 
   //该哈希表已有节点的数量
    unsigned long used;
}
 
 
typeof struct dictEntry{

   //键
    void *key;
 
   //值
    union{
 
      void *val;
 
      uint64_tu64;
 
      int64_ts64;
 
   }
 
   struct dictEntry *next;
 
 }

我们存入里面的key 并不是直接的字符串,而是一个hash 值,通过hash 算法,将字符串转换成对应的hash 值,然后在dictEntry 中找到对应的位置。

这时候我们会发现一个问题,如果出现hash 值相同的情况怎么办?Redis 采用了链地址法来解决hash冲突。这与hashmap的实现类似

解决hash冲突:采用链地址法来实现。

扩充Rehash:随着对哈希表的不断操作,哈希表保存的键值对会逐渐的发生改变,为了让哈希表的负载因子维持在一个合理的范围之内,我们需要对哈希表的大小进行相应的扩展或者压缩,这时候,我们可以通过 rehash(重新散列)操作来完成。其实现方式和hashmap略有不同,因为dict有两个hash表dictht,所以它是通过这两个dictht互相进行转移的(dictht ht[2]的原因)。

Rehash操作渐进式,rehash 操作并不是一次性、集中式完成的,而是分多次、渐进式地完成的。采用渐进式rehash 的好处在于它采取分而治之的方式,避免了集中式rehash 带来的庞大计算量。

用途:

  • Redis本身的K-V存储就是利用字典这种数据结构的,另外value类型的哈希表也是通过这个实现的。

4、跳跃表

定义:
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速查找访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率和平衡树媲美 ——查找、删除、添加等操作都可以在O(logn)期望时间下完成。

Redis 的跳跃表 主要由两部分组成:zskiplist(链表)和zskiplistNode (节点)

typedef struct zskiplistNode{
 
   //层
      struct zskiplistLevel{
 
     //前进指针
         struct zskiplistNode *forward;
 
    //跨度
         unsigned int span;
 
    } level[];
 
  //后退指针
     struct zskiplistNode *backward;
 
  //分值
     double score;
 
  //成员对象
     robj *obj;
 
}

1、层:level 数组可以包含多个元素,每个元素都包含一个指向其他节点的指针。level数组的每个元素都包含:前进指针:用于指向表尾方向的前进指针,跨度:用于记录两个节点之间的距离

2、后退指针:用于从表尾向表头方向访问节点

3、分值和成员:跳跃表中的所有节点都按分值从小到大排序(按照这个进行排序的,也就是平衡二叉树(搜索树的)的节点大小)。成员对象指向一个字符串,这个字符串对象保存着一个SDS值(实际存储的值)

typedef struct zskiplist {
 
     //表头节点和表尾节点
      structz skiplistNode *header,*tail;
 
     //表中节点数量
      unsigned long length;
 
     //表中层数最大的节点的层数
      int level;
 
  
}zskiplist;

图示:

从结构图中我们可以清晰的看到,header,tail分别指向跳跃表的头结点和尾节点。level 用于记录最大的层数,length 用于记录我们的节点数量。

跳跃表是有序集合的底层实现之一,主要有zskiplist 和zskiplistNode两个结构组成

  • 每个跳跃表节点的层高都是1至32之间的随机数
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的对象必须是唯一的
  • 节点按照分值的大小从大到小排序,如果分值相同,则按成员对象大小排序

用途:

  • 一个是实现有序集合键(sorted Sets),另外一个是在集群节点中用作内部数据结构。
    其实跳表主要是来替代平衡二叉树的,比起平衡树来说,跳表的实现要简单直观的多。
    跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速查找访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率和平衡树媲美 ——查找、删除、添加等操作都可以在O(logn)期望时间下完成。

更多:
Redis中的跳跃表
redis(五)跳跃表
跳跃表的原理及实现

5、整数集合

定义:
整数集合是集合建(sets)的底层实现之一,当一个集合中只包含整数,且这个集合中的元素数量不多时,redis就会使用整数集合intset作为集合的底层实现。他其实就是一个特殊的集合,里面存储的数据只能够是整数,并且数据量不能过大。

typedef struct intset{
 
    //编码方式
     uint32_t enconding;
 
   // 集合包含的元素数量
     uint32_t length;
 
    //保存元素的数组   
     int8_t contents[];
 
  
} 

整数集合是集合建的底层实现之一.

整数集合的底层实现为数组,这个数组以有序,无重复的范式保存集合元素,在有需要时,程序会根据新添加的元素类型改变这个数组的类型。

6、压缩列表

定义:
压缩列表是列表键(list)和哈希键(hash)的底层实现之一。当一个列表键只有少量列表项,并且每个列表项要么就是小整数,要么就是长度比较短的字符串,那么Redis 就会使用压缩列表来做列表键的底层实现。

  • zlbytes:用于记录整个压缩列表占用的内存字节数
  • zltail:记录要列表尾节点距离压缩列表的起始地址有多少字节
  • zllen:记录了压缩列表包含的节点数量。
  • entryX:要说列表包含的各个节点
  • zlend:用于标记压缩列表的末端

用途:

  • 压缩列表是一种为了节约内存而开发的顺序型数据结构
  • 压缩列表被用作列表键和哈希键的底层实现之一
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值
  • 添加新节点到压缩列表,可能会引发连锁更新操作。

更多:
Redis基础及底层实现


Redis过期策略(过期后Redis怎么处理)

概述

定期删除(定期删除一些过期的key)+惰性删除(查询到该key时删除该过期key)

详解

Redis所有的键都可以设置过期属性,内部保存在过期字典中。由于进程内保存了大量的键,维护每个键精准的过期删除机制会导致消耗大量的CPU,对于单线程的Redis来说成本过高,因此Redis采用惰性删除和定时任务删除机制实现过期键的内存回收。

  • 惰性删除:惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空,这种策略是出于节省CPU成本考虑,不需要单独维护TTL链表来处理过期键的删除。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。正因为如此,Redis还提供另一种定时任务删除机制作为惰性删除的补充。
  • 定时任务删除:Redis内部维护一个定时任务,默认每秒运行10次(通过配置hz控制)。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例,使用快慢两种速率模式回收键。
    比如:
  • 定时任务在每个数据库空间随机检查20个键,当发现过期时删除对应的键。
  • 如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或运行超时为止,慢模式下超时时间为25ms。
  • 如果之前回收键逻辑超时,则在Redis触发内部事件之前再次以快模式运行回收过期键任务,快模式下超时时间为1ms且2s内只能运行1次。
  • 快慢两种模式内部删除逻辑相同,只是执行的超时时间不同。

更多:
Redis内存回收策略


内存淘汰机制

当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。具体策略受maxmemory-policy参数控制,Redis支持6种策略(当数据过期后,并不会马上被删除依赖于上面提到的惰性删除和定时任务删除过期数据,删除后占用内存才会减少):

  • noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。推荐使用,这种机制最常用。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。应该也没人用吧,你不删最少使用 Key,去随机删。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。不推荐。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。依然不推荐。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。不推荐。如果没有对应的键,则回退到noeviction策略。

热点key处理

20万用户同时访问一个热点Key,如何优化缓存架构

你可能感兴趣的:(Java,Web知识总结)