Redis入门知识总结

如何学好Redis

建立知识框架,打造系统观。

Redis启动

Windows下:
在安装目录下执行:

 .\redis-server.exe .\redis.windows.conf
客户端连接Redis

Windows下:

 .\redis-cli.exe -h 127.0.0.1 -p 6379

Linux下安装Redis

参考官方说明即可

一.命令

String
set name xiaoming
get name
expire name 5 #5秒过期
setex name 5 xiaohong 
setnx name #不存在时设置

set age 10
incr age #对于数字可以增加1
incrby age 10 

#批量操作
mset name xiaoming age 10
mget name age
List
左右添加、左右删除
lpush books java python c++
lpop books
rpush c
rpop books
lindex books 1 #获取下标为1的元素(下标从0开始)
lindex books -1 #获取倒数第一个元素
注意下标从右往左计数。另外没有rindex命令。
lrange books 0 -1 #获取0到倒数第一个元素,即全部元素
ltrim books 1 -1 #保留下标1到最后一个元素,删除其它元素。即删除第一个元素
ltrim books -1 -1 #保留最后一个元素
ltrim books 2 3 #保留下标2到下标3的元素

底层结构:quicklist,用双向指针连接的zipList

Hash
hset nums one 1 #添加值
hget nums one    #获取值
hgetall nums       #获取所有键值对
hmset nums two 2 three 3 #批量添加
hmget nums one two #批量获取
hincrby nums one 1 #对value为int的键值对进行加法操作

底层结构:数组+链表

SET
sadd books python #添加元素
smembers books #列出所有元素
sismember books java #判断元素是否存在
scard books #列出set中元素个数
spop books #弹出一个元素。弹出规则?

底层结构:基于Hash,value设置为null

ZSET

是Redis独有的数据结构。一方面它是一个set,保证了内部value的唯一性。另一方面它可以给每一个value赋一个score,代表这个value的排序权重。按score从小到大排序。

#添加元素时要带着一个权重值
zadd books 9.0 "think in java"
zadd books 8.9 "java concurrency"
zadd books 8.6 "java cookbook"

zrange books 0 -1 #按score从小到大排序列出
zrevrange books 0 -1 #按score逆序排列
zcard books #获取元素个数
zscore books "think in java" #获取元素的score
zrank books "think in java"  #获取位次,从0开始,低到高排序
zrangebyscore books 0 8.91 #获取分值区间内的元素
zrangebyscore books -inf 8.91 withscore #-inf表示负无穷,withscore表示列出分数
zrem books "java concurrency" #删除value

底层结构:跳表

通用规则

对于list、hash、set、zset四种容器类型,有两个通用规则:
(1)create if not exists
(2)drop if no elements

过期时间

所有类型的元素都可以使用expire设置过期时间,只能对数据整体设置,例如不能对hash中的某个key单独设置过期时间。

设置过期时间

expire key

查看某个key的过期时间

ttl key
删除命令

所有类型都可以用del进行删除

二.使用Jedis操作Redis

引入依赖:

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.5.1</version>
        </dependency>

连接Redis

    /**
     * 连接Redis
     * */
    public static Jedis connectJedis() {
        Jedis jedis = new Jedis("localhost");
        return jedis;
    }

执行命令
通过Jedis对象执行命令

//Jedis中的方法和Redis命令基本是一一对应的
Jedis jedis = ConnectRedis.connectJedis();
jedis.set("name","xiaoming");
jedis.get("name");
jedis.hmset(key,hashmap);

思考:存储结构体信息改用hash还是string?
根据使用场景决定,原则是尽可能地减少查询次数。
每次使用是只使用其中的某个字段呢(hash)?还是需要全部字段呢(String)?
Hash不需要进行序列化、反序列化,是否可以承受这部分的性能损失?

三.分布式锁

版本1:

setnx lock true
//执行业务逻辑
del lock

使用setnx抢占锁,只有第一个执行成功的能抢到锁。
最后使用del删除锁。
问题:如果执行业务逻辑时出现异常,删除操作没有正常执行,则其它进程无法抢占到锁。

版本2:

setnx lock true
expire lock 5
//执行业务逻辑
del lock

给锁加上一个过期时间,即使任务没有执行完也会在过期后释放锁。

问题:抢占锁和设置过期时间是两条语句,如果执行完第一条语句后,程序崩溃无法加入过期时间,就会有版本1一样的问题。

版本3:
使用一条指令,同时执行setnx和expire

set lock true ex 5 nx
//执行业务逻辑
del lock

将抢占锁和设置过期时间在一条语句中执行,避免了上述问题。锁一定会被释放。
问题:如果过期时间到了,但是业务代码没有执行完成,锁会释放掉,这时其它进程有可能抢占到锁导致并发执行问题。

Redission怎么实现分布式锁的?是否解决了上述问题?redLock是什么?

Redission在获取到锁后,会启动一个看门狗线程,不断判断任务是否还持有锁,持有的话进行续期。默认是每次续期30秒钟。避免出现任务还每执行完,但锁已经到期的情况。
那会不会出现机器宕机,没有释放锁的情况呢?不会因为机器宕机了,续期的线程也不会执行了,到时间自然就释放锁了。正常执行完时,unlock时也会停止看门狗线程。

问题:锁被其它进程释放掉。
情景:A任务执行超时,锁过期释放掉。B又申请了锁。A这时执行完成,把B的锁释放掉。
给锁加一个每个客户端唯一的值,只释放自己加的锁。需要进行判断后进行判断,为了保证原子性需要使用lua脚本。

仍然存在的问题:设置锁到master,这时进行主从复制,但master宕机了,从机变为主机但没有设置的锁信息。之后来加锁的进程可能成功,造成锁被获得了两遍。
解决:使用多台master,不使用master-salve结构。RedLock算法,向所有机器发送set lock true ex 5 nx命令只有半数以上机器设置成功时,才算加锁成功。执行完成后使用del删除。

四.消息队列

可以使用list做简单的消息队列。
使用rpush进行生产。
使用lbpop进行消费

#生产者
rpush msg-queue msg1
#消费者 0为等待时间,0表示一直等待。也可设置为大于0的值,表示等待若干秒
blpop msg-queue 0

五.Redis命令的原子性

在分布式锁应用中,申请锁、设置过期时间,需要保证原子性;判断再删除锁也需要原子性。
保证原子性有两种方法:一种是使用多合一的命令,如set的nx、ex参数。setnx命令等。或者使用lua脚本同时执行多个命令。

六.位图操作

#设置第一位位1
bitset xiaoming-login 1 1
bitset xiaoming-login 2 0
bitset xiaoming-login 3 1
bitset xiaoming-login 4 1
bitset xiaoming-login 5 0
bitset xiaoming-login 6 1
bitset xiaoming-login 7 1
#获取第1位的value(0 or 1)
bitget xiaoming-login 1
bitget xiaoming-login 5
#统计有多少个1
bitcount xiaoming-login
#可按字节指定范围统计1的个数
bitcount xiaoming-login 0 0 #第1个字节中1的个数
bitcount xiaoming-login 0 1 #前两个字节中1的个数

#查找第一个1、0的位置
bitpos bit 0 #查找第一个0的位置
bitpos bit 1 #查找第一个1的位置
bitpos bit 1 0 1 #查找前两个字节中第一个1的位置
bitpos bit 1 1    #查找第一到最后一个字节中第一个1的位置 

注意bitcount、bitpos中start、end的含义是以字节为单位的。字节从0开始计数。

位操作的典型应用:统计用户打卡天数。

七.HyperLogLog

以误差0.81%统计不重复的数据个数。

pfadd log user1 #添加元素
pfcount log     #返回元素个数
pfmerge log log2  #将log2合并到log

如果不需要要精确地统计个数,可以使用HyperLogLog,好处是相比于set占用空间少。只需要占用12k的空间,就可以存储大量的数据。(数据较少时不需要12K这么多)。

缺点:只能用来计数,不能判断某个元素是否在集合中。

典型应用:统计UV

八.布隆过滤器

上文说到HyperLogLog不能判断某个元素是否在集合中存在。而布隆过滤器就是为了解决这个问题的。
布隆过滤器的特点:如果判断某个元素不存在,则该元素一定不存在;如果判断某个元素存在,则有一定的误判概率,即该元素有可能不存在。

命令1:创建布隆过滤器

bf.reserve key error_rate initial_size

通过error_rate可设置错误率。错误率越低,需要的存储空间就越大。initial_size表示预估添加的元素个数,当添加的元素个数超过它时误判率会提高。

如果不显示地创建布隆过滤器,直接使用bf.add进行元素添加,则创建一个默认的过滤器。批量添加bf.madd

判断元素是否存在,bf.exists。批量查询bf.mexists

应用:爬虫,判断url是否已经处理过。

九.简单限流器

需求:指定时间段Period内,用户的某个操作不超过max_count次。
使用zset。为每个用户的每个操作建立一个zset。value为时间戳,score为时间戳。
新的用户操作来到,先加到zset中,zadd key score value
再截取zset,删除now-period外的数据,使用命令:remrangebyscore key 0 now-period
然后使用scard key判断当前set中的个数,如果不超过max_count则可以进行操作,否则拒绝操作。

十.持久化

RDB

分为RDB和AOF。

RDB全量持久化,将内存中的数据存储到经过压缩的二进制文件RDB文件中。有两个命令save和bgsave。save在当前进程执行,执行期间Redis不再相应其它请求。bgsave通过fork一个子进程,进行持久化到RDB日志的工作。持久化的同时主进程继续提供服务,如果这时有写操作,则通过COW(Copy On Write 写时复制)功能对要修改的数据复制出一份进行修改,RDB持久化进程继续在原数据上进行读取操作。这也就达到了“快照”的能力。

这里也要注意如果写操作特别多,RDB时内存增长就会较多,需要注意内存使用风险。

而从rdb恢复数据的工作不需要手动执行,在redis启动时会自动执行。但如果开启了aof日志,会优先使用aof。aof更新频率高,更准确。
除了手动发送save、bgsave命令,可以通过配置,让redis自动执行。
默认情况下配置如下,标识900秒内至少有1个修改时或300秒内时至少有10次修改时或60秒内至少有1万次修改时执行bgsave。

save 900 1
save 300 10
save 60 10000

这个配置在配置文件中,可根据需要进行修改。

AOF

AOF通过记录执行的命令来持久化Redis。每执行一个命令就向aof文件中做一次记录。
刷新问题
问题是文件的写入操作会被OS先进行缓存,不会立马写入磁盘。这样就可能导致宕机时缓存中的数据无法被记录。
可以主动刷新缓冲区。Redis提供了一个配置来决定刷新缓冲区的频率。有三种频率:永不主动刷新、每个命令后主动刷新、每秒主动刷新一次。
再这三种方式中,刷新太快性能不好,永不刷新有数据损失风险,每秒刷新比较合适。即不影响性能,宕机时也只会损失1秒的数据。
数据还原
Redis重新执行一遍aof文件中的命令就恢复了数据。
AOF重写
对同一个key的多次操作都记录下来没有必要,只需要记录最终状态就好了。
使用bgrewriteaof可以重写AOF。重写过程:fork新进程,遍历所有key,将一个key记录为一条写入命令。重写过程中,主进程照常提供服务。只不过需要在进行AOF时,多写一份AOF到AOF重写缓存区。重写结束后,主进程将AOF重写缓冲区和重写后的AOF合并,得到最终的AOF文件。并替换原来的AOF文件。这样就保证了主进程尽可能的减少阻塞时间,并且完成了AOF文件的重写。AOF重写缓冲区是个关键。
AOF重写缓冲区是什么形式?一个文件吗?
AOF重写的触发

  1. auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
  2. auto-aof-rewrite-percentage: 这个值的计算方法是:当前AOF文件大小和上一次重写后AOF文件大小的差值,再除以上一次重写后AOF文件大小。也就是当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。
RDB+AOF

AOF是增量日志,磁盘刷新频率一般设置为每秒一次(可以平衡效率和数据丢失概率)。每次只需要记录一条命令,执行较快。但是使用AOF恢复数据时较慢(将所有命令重新执行一遍)。

RDB是快照,由主进程fork出子进程(fork过程阻塞主进程)将内存中的数据以二进制的形式存储到文件中。是全量日志。快照不能太频繁,因为会阻塞主进程。使用RDB恢复数据较快。但是RDB执行时间较长会丢失较多的数据。

如何同时利用RDB数据恢复快和AOF数据丢失少的优点呢?Redis4.0提出了一种RDB和AOF结合使用的方式。设定RDB以一定频率执行,AOF只需要记录两次RDB之间的日志。这样通过最后一次RDB日志,和这次RDB之后的AOF日志就可以恢复完整的数据。AOF也不会变的很大。
设置参数为:aof-use-rdb-preamble yes

十一.Redis为什么这么快?

(1)数据存储在内存中,内存中的操作本身就比较快。Redis在收到一个命令后可以以微妙级的速度查找到对应的value,并快速完成操作。
(2)Redis中的数据结构设计的非常高效。所有的Redis操作最终都落脚到对数据的查找、修改、删除等操作。高效合理的数据结构让这个过程非常快。
(3)Redis采用了单线程对外提供服务。通过使用IO多路复用可以同时处理大量请求。同时,由于是单线程不必进行线程切换进一步加快了Redis的速度。另外单线程下不需要考虑并发访问问题,不需要加锁等操作,这也提高了Redis的速度。

十二.Redis的键和值是如何组织的?

哈希表。
哈希表底层使用数组+链表。
也就是有一个全局的哈希表用于存储key和value的指针。

哈希冲突:使用链表。(这时可能会导致查询变慢)。

所以在元素个数较多时需要rehash。

rehash不是一次性完成的,否则会造成较长时间的服务不可用(服务线程用于rehash了)。

而是同时维护两个哈希表,在每次处理一个请求时,rehash数组的一个结点(包括整条链表的内容)。

rehash完成后再用新的哈希表替换原来的哈希表。原哈希表的空间可以回收。

rehash时新申请的数组长度是原数组的两倍。

十三.Redis面试问题

你的项目架构中,为什么会用redis?
用redis做了什么事?解决了什么问题?
用Redis的过程中,有没有发现一些问题?如何解决的?

如何用Redis解决海量数据的日活、月活问题?
如何用Redis解决高并发下黑名单、白名单问题?
在大数据环境中,Redis能解决什么问题?
在高并发下,基于并发安全,如何实现高质量的分布式锁?
Redis的String、Zset等数据结构的底层实现原理是什么?
在使用Redis的过程中,有没有发现它的短板问题?如何进行优化?
如何进行Redis的性能优化?
Redis的数据一致性如何处理?
如何进行Redis集群方案的选择?
阐述一下Rdis6.*的新特性。

十四.Redis客户端——Redission

官方文档

Redission将Redis中的String类型映射为通用对象桶bucket

十五.缓存雪崩、缓存击穿、缓存穿透

缓存穿透

十六.三大主线、两大纬度

三大主线:
高性能(线程模型、数据结构、持久化、网络框架)
高可靠(主从、哨兵)
高可扩展(分片集群、负载均衡)

两大纬度:
应用纬度:缓存、集群、数据结构应用
系统纬度:处理层、内存层、存储层、网络层

在这里插入图片描述

十七.Redis值类型的底层实现结构

数据类型底层实现
String简单动态字符串
List压缩列表、双向链表
Hash压缩列表、哈希表
Set哈希表、整形数组
Sorted Set跳表、压缩列表

可以看到在List、Hash和Sorted Set中都使用到了压缩列表。可见这是Redis中一个很重要的数据结构,在这篇文章中对这种结构做了介绍。

十八.Java键值对存储结构

Redis是一个key-value键值对存储系统,对于key和value的存储采用哈希表。这里的哈希表的实现和JDK中HashMap很像,使用了数组+链表的形式。其中链表是为了解决哈希冲突的。

在哈希冲突比较严重时Redis会进行rehash操作,比较独特的是Redis会采取渐进式rehash的方式,在多次的请求处理过程中,逐渐将元素拷贝到新的哈希表中。在这个过程中同时维护两个哈希表。rehash结束后,用新的哈希表替代老的哈希表。

十九.单线程工作模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值