Redis基础篇

1.基本操作

flushdb -- 清空当前数据库
flushall -- 清空所有数据库
set key value
get key
keys q* --以q开头的数据
dbsize 存储多少key
exists xingxing,有就返回1,没有就是0
del xingxing **** .... 可以一次删除多个key,空格隔开


rename key
type xingxing 查看数据类型

2.数据类型

redis 数据类型 strings,hashes,lists,sets,sorted sets with range queries,bitmaps,
    hyperloglogs,geospatial indexes with radius queries and streams

2.1String-存储类型

1、INT 整型 2、Float 单精度浮点型 3、String 字符串

2.1.1String-操作命令

1、INT 整型
2、Float 单精度浮点型
3、String 字符串
String-操作命令
getrange qingshan 0 1
strlen qingshan 
append qingshan good ---追加
setnx qingshan pyy 
mset qingshan 2673 huihui 666 ---
mget qingshan huihui 
incr qingshan 一次增加1
incrby qingshan 100 一次增加100
decr qingshan 一次减1
decrby qingshan 100 一次减100
set mf 2.6 
incrbyfloat mf 7.3

2.1.2String-存储原理

redisObject 

String的三种编码

1、int,存储8个字节的长整型(long,2^63-1)

2、embstr,embstr格式的SDS,存储小于44个字节的字符串

3、raw,SDS,存储大于44个字节的字符串

SDS是什么?(Simple Dynamic String)

sdshdr5: 2^5=32byte(不用)

sdshdr8: 2^8=256byte

sdshdr16:2^16=65536byte=64KB

sdshdr32:2^32byte=4GB

为什么Redis要用SDS实现字符串?

1、内存空间预先分配 2、获取字符长度的时间复杂度O(n) 3、长度变更引起内存重新分配 4、用'\0'判断字符串结束

embstr和raw的区别?

embstr的只分配一次内存空间:RedisObject和SDS连续

raw 需要分配两次内存空间

int和embstr什么时候转化为raw?

1、int 数据不再是整数——raw

2、int大小超过了 long 的范围(2^63-1)——embstr

3、embstr长度超过了44个字节——raw

什么时候转化?会还原吗?

编码转换在 Redis 写入数据时完成,且转换过程不可逆, 只能从小内存编码向大内存编码转换(不包括重新set)

2.1.3String应用场景

1、缓存

Sting类型,缓存热点数据。例如 明星出滚,网站首页,报表数据等等,

可以显著提升热点数据访问的速度

2、分布式数据共享

String 类型, 因为Redis是分布式独立的服务,可以再多个应用之间共享

分布式Session

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

3、分布式锁 set NX EX

    String类型的setnx方法,只有不存在时才能添加成功,返回true

public Boolean getLock(Object lockObject){
    jedisUtil = getJedisConnetion();
    boolean flag = jedisUtil.setNX(lockObj,1);
    if(flag){
        exprire(lockObj,10);
    }
    return flag;
}

public void releaseLock(Object lockObject){
    del(lockObject);
}

4、分布式全局ID incr

INT类型,INCRBY,利用原子性

incrby userid 1000

(分库分表的场景,一次性拿一段)

5、计数器 incr

INT类型,INCR方法

例如:文章的阅读量,微博点赞书,允许一定的延迟,县写入Redis在定时同步到数据库

6、限流 incr

INT类型,INCR方法

以访问者的IP和其他信息作为key,访问一次增加一次计数,超过次数则返回false。

总结一下:利用redis本身的特性,和String内容的存储内容,以及提供的方法,我们可以用来达到很多的业务目的

3.HASH

一个KV怎么存储一张表的数据?

key分层

Hash 哈希

存储多个无序的键值对,最大存储数量2^32-1(40亿左右)。

String与Hash的区别

Hash特点: 1、节省内存空间 2、减少key冲突 3、取值减少性能消耗

Hash不适合的场景: 1、Field不能单独设置过期时间 2、需要考虑数据量分布的问题

3.1Hash基本操作

hset h1 f 6

hmset h1 a 1 b 2 c 3 d 4

hget h1 a

hmget h1 a b c d

hkeys h1

hvals h1

hgetall h1

hdel h1 a

hlen h1

del h1

3.2存储(实现)原理

Redis的Hash本身也是一个KV结构,是不是跟外层的哈希一样,用dictEntry实现呢

内层的哈希底层可以使用两种数据结构实现

ziplist:OBJ_ENCODING_ZIPLIST(压缩列表)

hashtable:OBJ_ENCODING_HT(哈希表)

ziplist结构:

ziplist是一个经过特殊编码的,由连续内存块组成的双向链表。 它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存 储上一个节点长度和当前节点长度。

Hash-什么时候用ziplist?

1、 一个hash对象保存的field数量<512个

2、 一个hash对象中所有的field和value的字符串长度都<64byte

HashTable(dict)

hashtable-dict存储结构

从最底层到最高层dictEntry->dictht->dict。它是一个数组+链表的结构。

注意:dictht后面是NULL说明第二个ht还没用到。dictEntry*后面是NULL说明没有hash到这个地址。dictEntry后面是NULL,说明没有发生哈希冲突

问题:为什么要定义两个哈希表,其中一个不用呢?

redis的hash默认使用的是ht[0],ht[1]不会初始化和分配空间。

哈希表dictht是用链地址法来解决碰撞问题的。在这种情况下,哈希表的性能取决于它的大小(size属性)和它所保存的节点的数量(used属性)之间的比率:

1)比率在1:1时(一个哈希表ht只能存储一个节点entry),哈希表的性能最好

2)如果节点数量比哈希表的大小要大很多的话(这个比例用ratio表示,5表示平均一个ht存储5个entry),那么哈希表就会退化成多个链表,哈希表本身的性能优势就不在存在。

如果单个哈希表的节点数量过多,哈希表的大小需要扩容。Redis里面的这种操作叫做rehash

rehash的步骤:

1. 为字符ht[1]哈希表分配空间。ht[1]的大小为第一个大于等于ht[0].used*2的2的N次方幂。比如已经使用了10000,那就是16384

2.将所有的ht[0]上的节点rehash到ht[1]上,重新计算hash值和索引,然后放入指定的位置。

3。当ht[0]全部迁移到了ht[1]之后,释放ht[0]的空间,将ht[1]设置为ht[0]表,并创建新的ht[1],为下次rehash做准备。

问题:什么时候出发扩容?

负载因子(源码dict.c):

static int dict_can_resize = 1;// 是否需要扩容

static unsigned int dict_force_resize_ratio = 5;// 扩容因子

扩容判断和扩容的操作大家可以自己看一下,跟HashMap一样,也有缩容。

总结一下,Redis的Hash类型,可以用ziplist和hashtable来实现。

3.3 应用场景

     跟String一样,String可以做的事情,Hash都可以做。

    存储对象类型的数据

比如对象或者一张表的数据,比String节省了更多key的空间,也更加便于集中管理。

补充案例:

4.List列表(有序)

4.1存储类型

存储有序的字符串(从左到右),元素可以重复。

最大存储数量2^32-1(40亿左右)。

4.2操作命令

4.3List存储原理(quicklist)

在早期版本中,数据量较小时用ziplist存储(特殊编码的双向链表),达到临界值时转换为linkedlist进行存储。

3.2版本之后,统一用quicklist来存储。quicklist存储了一个双向链表,每个节点都是一个ziplist,所以是zip与linkedlist的结合体

总体结构:

// quicklist.h 105行
typedef struct quicklist {
    quicklistNode *head; // 指向双向链表的表头
    quicklistNode *tail; // 表尾
    unsigned long count;        /* total count of all entries in all ziplists */
    unsigned long len;          /* number of quicklistNodes */
    int fill : QL_FILL_BITS;              /* fill factor for individual nodes ziplist最大大小,对应list-max-ziplist-size*/
    unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off 压缩深度,对应list-compress-depth*/
    unsigned int bookmark_count: QL_BM_BITS; // 4位,bookmarks数组的大小
    quicklistBookmark bookmarks[];  // bookmarks 是一个可选字段
    // quicklist 重新分配内存空间是使用,不使用时,不占空间
} quicklist;

redis.conf相关参数:

# Lists are also encoded in a special way to save a lot of space.
# The number of entries allowed per internal list node can be specified
# as a fixed maximum size or a maximum number of elements.
# For a fixed maximum size, use -5 through -1, meaning:
# -5: max size: 64 Kb  <-- not recommended for normal workloads
# -4: max size: 32 Kb  <-- not recommended
# -3: max size: 16 Kb  <-- probably not recommended
# -2: max size: 8 Kb   <-- good
# -1: max size: 4 Kb   <-- good
# Positive numbers mean store up to _exactly_ that number of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as necessary.
list-max-ziplist-size -2

# Lists may also be compressed.
# Compress depth is the number of quicklist ziplist nodes from *each* side of
# the list to *exclude* from compression.  The head and tail of the list
# are always uncompressed for fast push/pop operations.  Settings are:
# 0: disable all list compression
# 1: depth 1 means "don't start compressing until after 1 node into the list,
#    going from either the head or tail"
#    So: [head]->node->node->...->node->[tail]
#    [head], [tail] will always be uncompressed; inner nodes will compress.
# 2: [head]->[next]->node->node->...->node->[prev]->[tail]
#    2 here means: don't compress head or head->next or tail->prev or tail,
#    but compress all nodes between them.
# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]
# etc.
list-compress-depth 0
typedef struct quicklistNode {
    struct quicklistNode *prev; // 前节点
    struct quicklistNode *next; // 后节点
    unsigned char *zl; // 指向实际的ziplist
    unsigned int sz;             /* ziplist size in bytes */
    unsigned int count : 16;     /* count of items in ziplist 占16bit,最大65536个*/
    unsigned int encoding : 2;   //是否采用了LZF压缩算法压缩节点/* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

4.4应用场景

List 主要用在存储有序内容的场景

例如用户的消息列表,网站的公告列表,活动列表,博客的文章列表,评论列表等等。思路:存储所有字段,LANGE取出一页,按顺序显示。

队列/栈

List还可以当做分布式环境的队列/栈使用

List提供了两个阻塞的弹出操作:BLPOP/BRPOP,可以设置超时时间。

BLPOP:BLPOP key timeout 移出并获取列表的第一个元素,如果列表没有元素,会阻塞别彪直到等待超时或发现可弹出元素为止

BLPOP:BLPOP key timeout 移出并获取列表的最后一个元素,如果列表没有元素,会阻塞别彪直到等待超时或发现可弹出元素为止

队列:先进先出:rpush blpop,左头右尾,左边出队列,后边进入队列

栈:先进后出,rpush,brpop

总结一下:

list存储有序的内容,用quicklist实现,本质上是数组+链表。

HashTabe也是数组加链表,只是内部编码结构不一样。

5.set集合

5.1存储类型

Set存储String类型的无序集合。 最大存储数量2^32-1(40亿左右)。

5.2操作命令

sadd myset a b c d e f g -- 添加一个或多个元素

smembers myset ---获取所有元素

scard myset --统计元素个数

srandmember myset --随机获取一个元素

spop myset --随机弹出一个元素

srem myset d e f --移除一个或多个元素

sismember myset a -- 查看元素是否存在

5.3存储原理

redis用intset或hashtable存储set。如果元素都是整数类型,就用inset存储。

typedef struct intset {
    uint32_t encoding;// 编码类型 int16_t,int32_t,int64_t
    uint32_t length;// 长度 最大长度:2^32
    int8_t contents[];// 用来存储成员的动态数组
} intset;

如果不是整数类型,就用hashtable(数组+链表)

如果元素的个数超过512个,也会用hashtable存储。跟一个配置有关:

5.4应用场景

抽奖:

随机获取元素 spop myset

点赞、签到、打卡

这条微博的ID是t1001,用户ID是u3001.

用like:t1001来维护t1001这条微博的所有点赞用户

用户u3001点赞了这条微博:sadd like:t1001 u3001

用户u3001取消点赞:srem like:t1001 u3001

用户u3001是否点赞:sismember like:t1001 u3001

点赞的所有用户:smembers like:t1001

点赞数:scard like:t1001

商品标签

用 tags:i5001来维护商品所有的标签。

sadd tags:i5001 画面清晰细腻

sadd tags:i5001 真彩清晰显示屏

sadd tags:i5001 流畅至极

set-集合命令

set1 {a,b,c } set2 { b,c,d}

获取差集  sdiff set1 set2 {a}

获取交集(intersection ) sinter set1 set2 {b,c}

获取并集  sunion set1 set2 {a,b,c,d}

商品筛选

sadd brand:huawei p40

sadd os:android p40

sad screensize:6.0-6.24 p40

筛选商品:品牌华为,操作系统安卓,屏幕在6.0-6.24之间的

sinter brand:huawei brand:android screensize:6.0-6.24

6.zset 有序集合

6.1存储类型

sorted set 存储有序的元素,每个元素有个score,按照score从小到大排名。score相同时,按照key的ASCII码排序。

数据结构对比:

 

6.2操作命令

// 添加元素

zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python

// 获取全部元素

zrange myzset 0 -1 withscores // 小到大

zrevrange myzset 0 -1 withscores // 大到小

zrangebyscore myzset 20 30 根据分值区间获取元素

zrem myzset php cpp 移除元素 也可以根据score rank删除

zcard myzset 元素个数

zincrby myzset 5 python 分值递增

zcount myzset 20 60 根据分值统计个数(包括20和60)

zrank myzset python 获取元素rank

zscore myzset python 获取元素score

// 也有倒序的rev操作

6.3底层编码

默认使用ziplist编码(第三次加到,hash的小编码,quicklist的node,都是ziplist)

在ziplist的内部,按照score排序递增来存储。插入的时候要移动之后的数据。

如果元素数量大于等于128,或者任一member长度大于等于64字节使用skiplist+dict存储

什么是skiplist(跳表)?

我们先来看一下有序链表

在这样的一个链表中,如果我们要查找某个数据,那么需要从头开始逐个比较,直到找到包含数据的那个节点,或者找到第一个比给定数据打的节点为止。时间复杂度为O(n)。同样,当我们要插入新数据的时候,也要经历相同的查找过程,从而确定插入的位置,二分查找只适用于有序数组,不适用于链表

       假如我们每相邻两个节点增加一个指针,让指针指向下下个节点(或者理解为有三个元素进入了第二层)

这样所有新增加的指针连成了一个新表,但包含的节点个数只有原来的一半

问题:是哪些元素运气这么好,进入到第二层?在插入一个数据的时候,决定要放到那一层,取决于一个算法,源码:t_zset.c 112行

/* Returns a random level for the new skiplist node we are going to create.
 * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL
 * (both inclusive), with a powerlaw-alike distribution where higher
 * levels are less likely to be returned. */
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

      现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据打的节点时,再到下一层进行查找。

比如,我们想查找23,超找的路径是沿着标红的指针所指向的方向进行的:

1,23首先和7进行比较,再和19比较,比他们都大,继续向后比较。

2.但23和26比较的时候,比26要小,因此回到下面的链表(原链表),与19在第一层的下一个节点22比较

3.23比22要大,眼下面的指针继续向后和26比较,23比26小,说明待查询数据23不在原链表中

在这个比较过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较,需要比较的节点数大概之后原来的一半,这就是跳跃表。

为什么不用AVL树或者红黑树?因为skiplist更加简洁。

因为level是随机的,得到的skiplist可能是这样的,有些在第四层,有些在第三层,有些在第二层,有些在第一层

我们看下Redis里面skiplist的实现

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

6.4应用场景

顺序会动态变化的列表

排行榜

例如百度热榜,微博热搜

id为6001的新闻点击数加1:zincrby hotNews:20251111 1 n6001

获取今天点击最多的15条:zrevrange hotNews:20251111 0 15 withscores

7 其他数据结构简介

7.1 BitMaps

BitMaps是在字符串类型上面定义的位操作。一个字节由8个二进制位组成。

set k1 a

获取value在offset处的值(a对应的ASCII码是97,转换为二进制数据是01100001)

getbit k1 0

修改二进制数据

setbit k1 6 1

setbit k1 7 0

get k1

统计二进制中1的个数

bitcount k1

获取第一个1或者0的位置

bitpos k1 1

bitpos k1 0

因为bit非常节省空间(1MB=8388608bit),可以用来做大数据量的统计

例如:在线人数统计,留存用户统计

setbit onlineusers 0 1

setbit onlineusers 1 1

setbit onlineusers 2 0

支持按位与,按位异或等等操作

BITOP AND destkey key [key ...] ,对一个或多个key 求逻辑与,并将结果保存到destkey中

BITOP OR destkey key [key ...].对一个或多个key 求逻辑或,并将结果保存到destkey中

BITOP XOR destkey key [key ...],对一个或多个key求逻辑异或,并将结果保存到destkey中

BITOP NOT destkey key 对给定key求逻辑非,并将结果保存到destkey中

计算出7天都在线的用户(假设用户编号有序,依次放在位图):

BITOP AND "7_days_both_online_users" "七天表"

应用场景:用户访问统计,在线用户统计

7.2Hyperloglogs

Hyperloglogs:提供了一种不太精确的基数统计方法,用来统计一个集合中不重复的元素个数,比如统计网站的UV,或者应用的日活,月活,存在一定的误差。

在Redis中实现的HyperLoglog,只需要12k内存就能统计2^64个数据。

7.3Geo

     业务需求:给客户使用的客户端要求获取半径1公里以内的门店,那么我们就要把门店的经纬度保存起来。如果是直接把经纬度保存在数据库中,一个字段存经度,一个字段存纬度。计算距离比较复杂。Redis的GEO直接提供了这个方法

操作:增加地址位置信息,获取地址位置信息,计算两个位置的距离,获取指定费恩威诶内的地理位置集合等等。GeoTest.java

7.4Streams

5.0推出的数据类型,支持多播的可持久化的消息队列,用户实现发布订阅功能,借鉴了kafka的设计

8 总结

数据结构总结:

编码转换总结:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值