首页 > 数据库 >Redis 高性能

Redis 高性能

时间:2024-04-30 23:23:09浏览次数:22  
标签:hashmap 复杂度 Redis 链表 插入 高性能 哈希 entry

为什么Redis性能很高,遥遥领先于MySQL?

个人分析有如下原因:

  • IO多路复用
  • 物理结构上来说,它是内存数据库,内存的访问速度比硬盘快几个量级。

机械硬盘的随机访问速度一般为毫秒级,SSD硬盘的随机访问速度一般为微秒级,而内存的随机访问速度一般为纳秒级。

  • 逻辑结构上来说,归功于它的存储结构——哈希表,查找的时间复杂度为O(1)。
  • 还有一些系统设计方面的不同
    • 例如结构简单,通过单线程执行命令,避免了控制并发所需要的锁开销,例如,不用像MySQL一样,需要使用锁来控制写-写并发,使用MVCC来控制读-写并发。
    • 例如持久化,不保证数据一致性,默认通过RDB的方式保存快照数据,不像MySQL,每次更改都需要日志落盘,以保证一致性。

全局哈希表

我们知道Redis是NoSQL数据库,存储的全是键值对信息。如何管理这些键值对信息,从而进行快速的查找和增删呢?这就涉及到数据结构,应该使用二叉搜索树、B树、B+树、跳表还是哈希表?

Redis的选择是哈希表,为什么呢?

  • 哈希查找时拥有最快的速度,时间复杂度为O(1),时间复杂度不会随着数据量的增加而增加,而树的时间复杂度普遍为O(logN),时间复杂度随着数据量的增加而增加。
  • 哈希表的增删时也最为简单,只需要进行单节点的修改,而树形结构,还需要额外的动作来维护树的平衡。

Redis中的哈希表类似于java中的hashmap,哈希桶中每一个元素都是一个entry对象,而一个entry对象又包含key和value两个字段。

其中Key一般是一个String类型的字符串,而value则拥有多种数据类型,如String、List、Hash、Set、Sorted Set、Stream、JSON、GEO、BitMap、HyperLog等等。

哈希冲突

随着哈希表中的元素越来越多,哈希冲突的概率也就越高,哈希冲突即多个entry在哈希桶中占用同一个位置。在产生哈希冲突时,多个entry对象会组成为一个单向链表,在查找时,先通过哈希查找确定位置,如果该位置有多个entry对象,则遍历该单向链表。

哈希冲突较多时,会影响哈希查找的速度

多个entry对象怎么组成一个​​单向链表

entry对象源码如下,其中包含指向下一个entry对象的next指针,通过该指针可将多个entry串联起来。

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

新节点是怎么插入链表中的?

插入新节点时,采用的方式是头插入,原因是

  • 因为新插入的entry,可能会更加频繁的使用,所以将其插入在链表头,可以加快检索速度。
  • 头插入的时间复杂度为O(1),尾插入时还需要遍历链表,时间复杂度为O(N)。

那为什么JDK1.7中hashmap使用的头插入,而JDK1.8中使用的却是尾插入?

先说说基本实现上的区别:

  • 1.7版本hashmap的底层实现为数组+链表
  • 1.8版本hashmap的底层实现为数组+链表+红黑树,默认在链表长度为8时,将链表转换为红黑树,转换后访问的时间复杂度由O(N)变为O(logN),可以加快检索速度。

但为什么要改变冲突链表的插入方式?毕竟头插入的效率更高。

搜了一圈,普通说是因为头插入在并发时可能导致链表成环问题,但这十分不靠谱好吗?hashmap本来就不是线程安全的,谁会在多线程环境下用hashmap?

还有说法是代码是从新增的concurrentHashmap中拷贝过来的,因为concurrentHashmap中需要考虑多线程并发问题,使用的是尾插入方式。

哈希表扩容

在哈希冲突比较频繁时,为了使查找速度不受影响,我们不得不考虑哈希表扩容的问题了。

使用过java中hashmap应该都知道,哈希表有一个初始容量,当存储的对象数量接近设置的容量时,就会对哈希表进行扩容。所以为了避免扩容,一般会根据实际数据量设定合适的初始容量。

为什么要避免扩容?

因为哈希表的基础组成是数组和链表,而数组是无法动态调整长度的。在进行扩容时,需要创建新的数组,并为其申请分配内存空间,然后对已有数据进行rehash,再将其放到新的数组空间中。这是一个耗时操作,会影响用户请求。

redis中的情况

redis中的无法配置全局哈希表的容量,在存储了足够多的数据后,进行哈希扩容,岂不是会阻塞用户请求?

是的,如果一次性完成哈希扩容的话,是会影响用户请求的,并且数据量越多影响越大。所以redis中,进行哈希扩容的方式不是一次性扩容,而是使用一种渐进式哈希的方法。

  • redis中全局哈希表的初始长度为4
/* This is the initial size of every hash table */
#define DICT_HT_INITIAL_SIZE     4

渐进式哈希

那么渐进式哈希是怎么做的?在这之前,我们先看一下redis中相关的源码定义,

  • dictht:哈希表,用于管理键值对对象dictEntry
  • dict :字典,用于管理哈希表的初始化、扩容等

可以看到一个dict中存在两个哈希表,平时使用第一个,第二个处于备用状态。

在需要进行哈希扩容时,启用备用的哈希表。每当接受到客户端的请求后,先处理请求,然后顺便在旧哈希表中,从第一个位置开始每次移动一个位置上的entry对象到备用哈希表中。之后重复这个过程,直到完成所有数据的移动。

渐进式哈希巧妙将rehash的影响分摊在了每一次用户请求上,并且不会对其产生明显影响。

typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;

标签:hashmap,复杂度,Redis,链表,插入,高性能,哈希,entry
From: https://www.cnblogs.com/cd-along/p/18168870

相关文章

  • Redis 高可用之持久化
    Redis服务实例宕机后,其中的数据还能恢复吗?是的,与其他内存数据库不同(如memcache没有持久化),redis还提供了数据持久化功能,并提供两种持久化方式:AOF(appendonlyfile):逻辑文件,记录的是一条一条的更改命令。在进行数据恢复时,需要一条一条的重放日志,恢复速度较慢RDB(readdatabase)......
  • 【Redis】Redis的操作命令(五)——Redis 有序集合(sorted set)
    有序集合添加元素ZADDrunoobkey1redis有序集合移除元素ZRANGErunoobkey010WITHSCORES有序集合命令命令说明例子ZADDkeyscore1member1[score2member2]向有序集合添加一个或多个成员,或者更新已存在成员的分数 ZCARDkey获取有序集合的成员数 ......
  • 首届超算互联网峰会!天翼云弹性高性能计算E-HPC亮相!
    4月11日,首届超算互联网峰会暨国家超算互联网平台上线仪式在天津顺利举办,来自部委、省级科技厅、中国科学院、中国工程院、计算产业链相关企业等专家、代表数百人共聚一堂,见证了这一历史性时刻。天翼云作为副理事长单位受邀参会,围绕超算领域的前沿技术和应用,与业内专家共同探讨互联......
  • 【Redis】Redis的操作命令(四)——Redis 集合(SET)
    Redis的SET是String类型的无序列表。添加无序列表语句:SADDsetDemoredis获取无序列表语句SMEMBERSsetDemoRedis集合命令如下:命令描述例子SADDkeymember1[member2]向集合添加一个或多个成员 SCARDkey获取集合的成员数 SDIFFkey1[key2]返回......
  • Redis删除
    1.登录可以连接Redis的ECS实例,安装Redis客户端,详情请参见redis-cli连接。2.执行以下命令,删除模糊匹配到的Key。redis-cli-h[$Addr]-p[$port]-n[$db]-a[$Password]keys"[$Key]*"|xargs-r-t-n1redis-cli-h[$Addr]-p[$port]-n[$db]-a[$Password]delredis-c......
  • java代码运行出现DENIED Redis is running in protected mode because protected mode
    这个错误是因为开启了保护模式,导致出错。所以需要关闭redis的保护模式。编辑redis的redis.config  注释bind127.0.0.1 、修改protected-mode为no、修改 daemonize为no然后重启redis ......
  • 高性能摩托车灯降压恒流ic全亮/半亮/循环模式短路保护AP5126
    AP5126是一款PWM工作模式,高效率、外围简单、内置功率管,适用于12-80V输入的高精度降压LED恒流驱动芯片。输出最大功率可达15W,最大电流1.2A。AP5126可实现全亮/半亮功能切换,通过MODE切换:全亮/半亮/循环模式。AP5126工作频率固定在140KHZ,同时内置抖频电路,可以降低对......
  • cmd redis 设置密码
     cmdredis设置密码在Redis中设置密码,你需要修改Redis配置文件或者通过命令行设置。以下是通过命令行设置密码的方法:连接到Redis服务器。使用CONFIGSETrequirepassyourpassword命令来设置密码。例如,如果你想通过命令行设置密码为mysecretpassword,你可以这样做:1.re......
  • Redis中对数组的获取类型转换
    1#####Redis中对数组的获取类型转换23```java4//判断redis中键值key是否存在;5BooleancarWeizi_redis_service=redisService.hasKey("carWeizi_redis_service");6if(carWeizi_redis_service){7//获取对应的list数组传入时re......
  • 双token+redis(token无感刷新)
    为什么要使用双token+redis呢?单token+redis+自动续期不行吗?单token+redis的缺点:可能会出现用户正在操作的时候,token过期了,让用户重新登录的情况。单token+redis+自动续期的缺点:单token设置短期的话,虽然一直操作可以通过拦截器重置token过期时间让它续期,但是如果隔一会儿不操作......