安装
wget http://download.redis.io/releases/redis-5.0.7.tar.gz
tar -zxvf redis-5.0.7.tar.gz
mv redis-5.0.7 /usr/local/redis 不需要先创建/usr/local.redis文件夹
cd /usr/local/redis
make
make install
vi redis.conf
* bind 0.0.0.0 开发访问
* daemonize yes 设置后台运行
redis-server ./redis.conf 启动
redis-cli 进入命令行,进行简单的命令操作
vi redis.conf
> requirepass password 修改密码
redis-cli 再次进入cmd
> shutdown save 关闭redis,同时持久化当前数据
redis-server ./redis.conf 再次启动redis
redis-cli 进入命令行
> auth password
将redis配置成系统服务,redis/utils中自带命令,我们只需修改参数
/usr/local/redis/utils/./install_server.sh
[root~ utils]# ./install_server.sh
Welcome to the redis service installer
Please select the redis port for this instance: [6379] 默认端口不管
Selecting default: 6379
Please select the redis config file name [/etc/redis/6379.conf] /usr/local/redis/redis.conf 修改配置文件路径
Please select the redis log file name [/var/log/redis_6379.log] /usr/local/redis/redis.log 修改日志文件路径
Please select the data directory for this instance [/var/lib/redis/6379] /usr/local/redis/data 修改数据存储路径
Please select the redis executable path [/usr/local/bin/redis-server]
Selected config:
Port : 6379
Config file : /usr/local/redis/redis.conf
Log file : /usr/local/redis/redis.log
Data dir : /usr/local/redis/data
Executable : /usr/local/bin/redis-server
Cli Executable : /usr/local/bin/redis-cli
chkconfig --list | grep redis 查看redis服务配置项
redis_6379 0:off 1:off 2:on 3:on 4:on 5:on 6:off
服务名是redis_6379
redis-cli -h x.x.x.x -p x 连接
auth "password" 验证密码
redis-cli --raw可以避免中文乱码
exit 退出
select index 切换到指定的数据库
keys * 显示所有key,如果键值对多不建议使用,keys会遍历所有key,可以在从节点使用;时间复杂度O(N)
dbsize 算出所有的key的数量,只是数量;时间复杂度O(1)
exists key key是否存在,存在返回1,不存在返回0;时间复杂度O(1)
incr key 将key的值加一,是原子操作
decr key 将key的值加一,会出现复数,是原子操作
del key 删除key,删除成功返回1,失败返回0;时间复杂度O(1)
expire key seconds 设置过期时间,过期之后就不存在了;时间复杂度O(1)
ttl key 查看key剩余的过期时间,key不存在返回-2;key存在没设置过期时间返回-1;(TTL Time To Live)
persist key 去掉key的过期时间,再查看ttl key,返回值是-1,表示key存在并且没有设置过期时间
type key 查看类型;时间复杂度O(1)
config get * 获取配置信息
set key value插入值
sadd myset 1 2 3 4 插入set
get key获取值
del key删除key
cat redis.conf | grep -v "#" | grep -v "^$" 查看配置文件,去除所有的#,去除所有的空格
setnx key value #key不存在,才设置
set key value xx #可以存在,才设置
set key value [exporation EX seconds | PX milliseconds] [NX|EX]
mget key1 key2 key3 批量获取 1次mget=1次网络时间+n次命令时间;时间复杂度O(n)
mset key1 value1 key2 value2 批量插入;时间复杂度O(n)
n次get = n次网络时间 + n次命令时间,mget一次就能完成,省去大量的网络时间
getset key newvalue # set key newvalue并返回旧的value
append key value #将value追加到旧的value
strlen key #获取value的长度,中文占2个字节
incrbyfloat key 3.5 #增加key对应的值
set/get/del, incr(自增1)/decr(自减1)/incrby(incrby key n自增n)/decrby
getrange key start end #获取value从start到end的值
setrange key index value #设置指定下标为一个新的值
hset key field value #给key的field设置值
hget key field #获取key的field的值
hdel key field #删除key的field的值
hgetall key #获取key的所有值
hexists key field # 判断key的field是否存在
hlen key #获取key field的数量
hmset key field1 value1 field2 value2
hmget key field1 field2
hsetnx/hincrby/hdecry/hincrbyfloat
lpush key value1 value2...valueN #从左边插入
rpush key value1 value2...valueN #从右边插入
linsert key before|after value newValue
rinsert key before|after value newValue
lpop key #从左边弹出一个item
rpop key #从右边弹出一个item
lrem key count value #若count等于0或者不填,表示删除所有的value值相等的item;若count>0,表示从左到右删除最多count个value相等的item;若count<0,表示从右到左,删除最多Math.abs(count)个value相等的项
ltrim key start end #按照索引范围修剪列表,可以用来慢删除,因为全删除可能会阻塞redis
lrang key start end #获取key中从start到end的值
lindex key index #取第index的值
llen key #算出列表的长度
lset key index newValue #修改index的值为newValue
blpop key timeout #lpop阻塞版本,timeout是阻塞时间,timeout=0表示死等,lpop会立马返回,有时候数据更新不那么及时,或者消息队列中消息未及时处理,我们可以使用这个
brpop key timeout
lpush + LPOP = STACK
lpush + RPOP = QUEUE
lpush + ltrim = 有序的集合
lpush + rpop = 消息队列
sadd key value #不支持插入重复元素,失败返回0
srem key element #删除集合中的element元素
smembers key #查看集合元素
sinter key1 key2 #取出相同:交集
sdiff key1 key2 #取出key1中key2没有的元素:差集
sunion key1 key2 #取出二者所有的元素:并集
sdiff|sinter|sunion store key #将结果存到key中,有时候计算一次耗时
scard key #计算集合大小
sismember key element #判断element是否在集合中
srandmember #返回所有元素,结果是无序的,小心使用,可能结果很大
smembers key #获取集合中的所有元素
spop key #从集合中随机弹出一个元素
scan
SADD = Tagging
SPOP/SRANDMEMBER = Random item
SADD + SINTER = Social Graph
zadd key score element #添加score和element O(logN): 使用xx和跳表的数据结构
zrem key element #删除元素
zscore key element #返回元素的分数
zincrby key increScore element #增加或减少元素分数
zcard key #返回元素的总个数
zrank key element #获取element的排名
zrange key start end [withscores] #返回指定索引范围内的升序元素
zrangebyscore key minScore maxScore [withscore] #返回分数在minScore和maxScore之间的元素
zcount key minScore maxScore #返回有序集合内在指定分数范围内的个数
zremrangebyrank key start end #删除指定排名内的元素
zremrangebyscore key minScore maxScore #删除指定分数内的元素
zrevrang/zrevrange/集合间的操作zsetunion
info replication 查看分片,能够获取到主从的数量和状态
config get databases 获取所有数据库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-86qRnZ3y-1613619964700)(png/redis-data-type-structure.PNG)]
string
hash
list
set
zset
Redis客户端: Java的Jedis(Socket通信),Python的redis-py
慢查询
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lYKcmDsI-1613619964703)(png/redis-cmd-lifecycle.png)]
两点说明:
pipeline流水线(批量操作)
当遇到批量网络命令的时候,n次时间=n次网络时间+n次命令时间。举个例子,北京到上海的距离是1300公里,光速是3万公里/秒,假设光纤传输速度是光速的2/3,也就是万公里/秒,那么一次命令的传输时间是 1300/20000*2(来回)=13毫秒,
什么是pipeline流水线,1次pipeline(n条命令)=1次网络时间+n次命令时间;pipeline命令在redis服务端会被拆分,因此pipeline命令不是一个原子的命令。注意每次pipeline携带数据量;pipeline每次只能作用在一个Redis节点上;M操作和pipeline的区别,M(mset)操作是redis的原生命令,是原子操作,pipeline不是原子操作。
for(int i = 0; i < 10000; i++>) {
jedis.hset(key, field, value); //1万次hset差不多要50秒
for(0->100) {
Pipeline pipeline = jedis.pipelined();
for(0->100) {
pipeline.hset(key,field,value);
}
pipeline.syncAndReturnAll(); //拆分100次,每次100个命令,大概需要0.7秒
}
发布订阅:类似生产者消费者模型
bitmap:位图:数据量很大的时候节省存储内存,数据量小了,不节省
hyperloglog(算法,数据结构):
GEO: 3.2提供的用于计算地理位置信息;数据类型是zset,可以使用zset的删除命令
使用场景:微信摇一摇看附近好友
api:
总结下Redis数据结构和类型的常见用法
类型 | 简介 | 特性 | 使用场景 |
---|---|---|---|
String | 二进制安全 | 可以包含任何数据,比如JPG图片或者序列化的对象,一个键最大能存储512M | |
Hash | 键值对集合,即编程中的Map | 适合存储对象,并且可以向数据库中那样update一个属性(Memcache中需要将字符串反序列化成对象之后再修改属性,然后序列化回去) | 存取/读取/修改 用户信息 |
List | 双向链表 | 增删快,提供了操作某一元段元素的API | 1. 最新消息,按照时间线显示 2. 消息队列 |
Set | 哈希表实现,元素不重复 | 添加/删除/修改的复杂度都是O(1),为集合提供求交集/并集/差集的操作 | 1. 打label/tag,如文章 2. 查找共同好友 3. 抽奖系统 |
Zset | 将Set中的元素增加一个double类型的权重score,按照score排序 | 数据插入集合就好序了 | 排行榜 |
Hyperloglog | 本质是string | 极小空间完成独立数据量统计 | 统计基数,不完全正确 |
GEO | 数据类型是zset | 存储地理位置信息,并提供计算距离等操作 | 微信摇一摇查看附近好友 |
Bitmap | 位图 | 数据量很大的时候节省存储内存,数据量小了不节省 | 1. 设置用户的状态 2. BitMap解决海量数据寻找重复、判断个别元素是否在 |
Redis没有直接使用C语言的传统字符串表示,而是自己构建了一种名为简单动态字符串(Simple Dynamic String, SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。
每个sds.h/sdshdr结构表示一个SDS值:
struct sdshdr {
int len; // 记录buf数组中已经使用的字节数量
int free; // 记录buf数组中未使用字节的数量
char buf[]; // 字节数组,用于保存字符串。SDS遵循C字符串以空字符结尾的惯例
}
持久化的作用:redis所有数据保存在内存中,对数据的更新将异步地保存到磁盘上。
主流数据库持久化实现方式:快照(MySQL Dump/Redis RDB),写日志(MySQL Binlog/Redis AOF)
RDB:
save(同步) - 会产生阻塞
bgsave(异步) - 不会阻塞
命令 | save | bgsave |
---|---|---|
IO类型 | 同步 | 异步 |
阻塞 | 是 | 是(阻塞发生在fork子进程 |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要fork,消耗内存 |
自动触发:多少秒内有多少changes会异步(bgsave)生成一个RDB文件,如60秒内有1W条changes,默认的规则,可以改;不是很好吧,无法控制频率;另外两条是900秒内有1条changes, 300秒内有10条changes;
配置
其他不能忽视的点:
AOF:
RDB & AOF最佳策略:RDB优先于AOF先启用
常见问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpmcQL4p-1613619964705)(png/redis-aof-append-block.png)]
主从复制:单机故障/容量瓶颈/QPS瓶颈;一个master可以有多个slave,一个slave只能有一个master,数据必须是单流向,从master流向slave
复制的配置:
全量复制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Beksma8n-1613619964707)(png/redis-full-reclipate.png)]
部分复制:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pJOZhYyd-1613619964708)(png/redis-part-replication.png)]
故障处理:
sentinel monitor mymaster 127.0.0.1 7000 2 监控的主节点名字是mymaster,2表示2个sentinel觉得当前master有问题提才发生故障转移
sentinel down-after-milliseconds mymaster 30000 表示30秒不通之后就停掉master
sentinel parallel-syncs mymaster 1 表示每次并发的复制是1个在复制,这样可以减少master的压力
sentinel failover-timeout mymaster 180000 故障转移时间
如何选择合适的slave的节点
运维和开发
JedisSentinelPool的实现
看源代码
两种方式
port ${port}
daemonize yes
dir "/path"
dbfilename "dump-${port}.rdb"
logfile "${port}.log"
cluster-enabled yes
cluster-config-file nodes-${port}.conf #redis启动后这个nodes-port.conf会自动生成
开启命令 redis-server redis-7000.conf / redis-server redis-7001.conf /…,
此时开启的节点都是相互孤立的没有任何通信,
查看当前状态
127.0.0.1:7000> cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:0
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0
显示当前是cluster状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r9ybM5NT-1613619964710)(png/redis-cluster.png)]
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001
[root@xx cluster]# redis-cli -p 7000 cluster meet 47.x.x.16 7001
OK
[root@xx cluster]# redis-cli -p 7000 cluster nodes
862e370d2342cf6bf883421003846e171770234e :7000@17000 myself,master - 0 0 0 connected
886b6e6c29f985f7f85acb1bf548d0937918eca3 4.x.x.6:7001@17001 handshake - 0 0 0 connected
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7002
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7003
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7004
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7005
这地方注意啊,手动配置的时候有个巨坑(坑了我1天):在redis cluster架构中,每个redis要开发两个端口,比如一个是6379,那么另一个就是加10000之后的端口号,比如16379。16379端口是用来进行节点间通信的,也就是cluster bus集群总线,cluster bus的通信用来进行故障检测、配置更新、故障转移授权等操作。
redis-cli -h 127.0.0.1 -p 7000 cluster addslots {0...5461}
redis-cli -h 127.0.0.1 -p 7001 cluster addslots {5462...10922}
redis-cli -h 127.0.0.1 -p 7002 cluster addslots {10923...16383}
redis-cli -h 127.0.0.1 -p 7003 cluster replicate ${node-id-7000}
redis-cli -h 127.0.0.1 -p 7004 cluster replicate ${node-id-7001}
redis-cli -h 127.0.0.1 -p 7005 cluster replicate ${node-id-7002}
常用命令
cluster notes 查看集群的node几点情况
cluster slots 查看slots的分布
cluster info 查看当前集群的状态,这个命令可以看到集群是否属于可用状态
redis-cli -c -p x 注意加-c,表示集群
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQGdazdu-1613619964711)(png/redis-cluster-nodes.png)]
开始的一串字符是nodeid
, 整行表示的意识是我的nodeId是多少,哪个ip的那个端口,我是主还是从,是从的话从的谁(谁的nodeId),是主的话还能看到slots的分布情况
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
Redis事务从开始到执行会经历以下三个阶段:开始事务 -> 命令入队 -> 执行事务。单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。这是官网上的说明 From redis docs on transactions: It’s important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.
Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:
Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。管道技术最显著的优势是提高了 redis 服务的性能。
Redis 分区
关于I/O多路复用(又被称为“事件驱动”),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。写操作类似。操作系统的这个功能通过select/poll/epoll/kqueue之类的系统调用函数来使用,这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的I/O操作都能在一个线程内并发交替地顺序完成,这就叫I/O多路复用,这里的“复用”指的是复用同一个线程。
下面举一个例子,模拟一个tcp服务器处理30个客户socket。假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:1. 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡住,全班都会被耽误。这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。2. 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。3. 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。 这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。
缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。
想象一下这个情况,如果传入的参数为-1,会是怎么样?这个-1,就是一定不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。即便是采用UUID,也是很容易找到一个不存在的KEY,进行攻击。
小编在工作中,会采用缓存空值的方式,也就是【代码流程】中第5步,如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒。
redisTemplate.opsForValue().set(String.valueOf(goodsId), null, 60, TimeUnit.SECONDS); // 设置过期时间
解决方案
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效。
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。
小编在做电商项目的时候,一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源。
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
解决方案:
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
设置热点数据永远不过期。
redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
缓存击穿
缓存击穿,是指存在hot key,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
小编在做电商项目的时候,把这货就成为“爆款”。
其实,大多数情况下这种爆款很难对数据库服务器造成压垮性的压力。达到这个级别的公司没有几家的。所以,务实主义的小编,对主打商品都是早早的做好了准备,让缓存永不过期。即便某些商品自己发酵成了爆款,也是直接设为永不过期就好了。
解决方案
Redis番外篇
Redis 最开始的设计可能就是想做一个缓存来用。但是分布式环境复杂,暴露的问题可能比较多,所以 Redis 就要做集群。做集群后,可能和 Memcahed 效果类似了,我们要超越它,所以可能就有了多数据类型的存储结构。光做缓存,如何已宕机数据就丢失了。我们的口号是超越 Memcahed,所以我们要支持数据持久化。于是可能就有了 AOF 和 RDB,就可以当数据库来用来。这样 Redis 的高效可靠的设计,所以它又可以用来做消息中间件。这就是 Redis 的三大特点,可以用来做:缓存、数据库和消息中间件。
再来说说,Redis 如何设计成但进程单线程的?
根据官方的测试结果《How fast is Redis?》来看,在操作内存的情况下,CPU 并不能起到决定性的作用,反而可能带来一些其他问题。比如锁,CPU 切换带来的性能开销等。这一点我们可以根据官方的测试报告,提供的数据来证明。而且官方提供的数据是可以达到100000+的QPS(每秒内查询次数),这个数据并不比采用单进程多线程 Memcached 差!所以在基于内存的操作,CPU不是 Redis 瓶颈的情况下,官方采用来单进程单线程的设计。
Redis单线程为什么这么快?
快的原因有主要三点:
Redis如何处理过期数据?Slave不能处理数据,那数据过期了怎么办?
1主2从的模式中,当master挂掉之后怎么办?
这种典型的模式就不上图了,master读写,slave1/2只读,当master挂掉之后,redis服务不可用,需要立马手动处理。两种处理方式,第一种是把master重新启动起来,不用改变现有的主从结构,缺点是什么呢,master重新启动并完成RDB/AOF的恢复是个耗时的过程,另外会造成slave1/2发生全量复制;第二种就是重新选举新的master,具体怎么做呢?选折其中一个slave,执行命令 slave no one来解除自己是从服务器的身份,使其称为一个master,注意的点是这个slave要改成读写模式;连到另一个slave,执行slave new master,让它去找master。整个过程是一个手动的过程,Redis Sentinel就是这样一个功能,自动完成切换,帅的一比。
Redis分布式锁你真的会用吗?
https://www.xttblog.com/?p=4598
Redis使用注意的点
Redis Sentinel和Redis Cluster的区别
redis3.0以后推出了cluster,具有Sentinel的监控和自动Failover能力,同时提供一种官方的分区解决方案
Redis分布式锁/Redis的setnx命令如何设置key的失效时间(同时操作setnx和expire
Redis的setnx命令是当key不存在时设置key,但setnx不能同时完成expire设置失效时长,不能保证setnx和expire的原子性。我们可以使用set命令完成setnx和expire的操作,并且这种操作是原子操作。
下面是set命令的可选项:
set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)
案例:设置name=p7+,失效时长100s,不存在时设置
1.1.1.1:6379> set name p7+ ex 100 nx
假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
可以使用keys [pattern]来列举出来,由于Redis是单线程的,在使用keys命令的时候会导致线程阻塞一段时间,我们也可以使用scan指令以无阻塞的方式取出来,但会有一定重复的概率,客户端去重就可以了。如果是主从模式的话,可以在从服务器执行keys命令,尽量不影响现有业务。
使用过Redis做异步队列么,你是怎么用的?
Redis如何实现延时队列?
RDB的原理是什么?
你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
Pipeline有什么好处,为什么要用pipeline?
可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。
是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?
Redis集群是如何通信的?
Redis集群采用gossip(流言)协议来通信,Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的Gossip消息,常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息。集群中的每个节点都会单独开辟一个TCP通道,用于节点之间的彼此通信,通信端口是在基础端口的基础上加上1万,每个节点在固定周期内通过特定规则选择几个节点发送ping消息,接收到ping消息的节点用pong消息作为响应。ping消息发送封装了自身节点和部分其他节点的状态数据,pong消息:当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新,一段时间后整个集群达到了状态的一致性。
PS:定时任务默认每秒执行10次,每秒会随机选取5个节点找出最久没有通信的节点发送ping消息,用于保证Gossip信息交换的随机性
Reids Key是如何寻址的?
分布式寻址算法
hash 算法(大量缓存重建)
一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
redis cluster 的 hash slot 算法
hash 算法
来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致大部分的请求过来,全部无法拿到有效的缓存,导致大量的流量涌入数据库。
一致性 hash 算法
一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。
来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,遇到的第一个 master 节点就是 key 所在位置。
在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。
燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。
redis cluster 的 hash slot 算法
redis cluster 有固定的 16384 个 hash slot,对每个 key 计算 CRC16 值,然后对 16384 取模,可以获取 key 对应的 hash slot。
redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 hash tag 来实现。
如何解决DB和缓存一致性问题?
经典的使用场景是对于热点的数据读操作是从Redis中读取的,那么当数据发生写的操作该怎么办?因为写的操作要同时在数据库和缓存中进行,涉及到双写,那么就必然存在双写数据不一致的问题,如果业务的场景是要求强一致性,那就不要用缓存。那么我接下来就结合我的理解谈谈一些经典的使用场景下,如何尽可能的保证双写的一致性。
读的时候先从Redis读取,如果Redis中没有,那么再去数据库中读,读完之后放回Redis,然后返回响应。写的时候先删除缓存,然后再去写入数据库,然后等待100毫秒再次删除缓存。这里有几点点需要说明:
为什么是先删除缓存而不是先更新数据?
假设我们先更新数据,再删除缓存,那么存在数据更新成功,但是删除缓存失败的情况。先删除缓存,如果数据库写操作成功,下次读缓存时会从数据库获取新的数据并放入缓存;如果数据库写操作失败,那么再次读到的数据也是旧的数据,也保证了缓存的一致性。另外,有的缓存数据是经过计算的数据,不是单纯的某个字段的值,如果去更新的话会是一个复杂的过程,倒不如下次获取的时候去计算并放入缓存,这也是一个“懒”的思想。
为什么写入数据库后还要再次删除缓存?采用延时双删策略
我们在数据库写操作的时候是不能保证没有读的操作,特别是在高并发场景下,往往数据库写操作还没完成,就已经有读的操作完成并将修改前的数据放入缓存,这也就造成了缓存和数据库中的数据不一致的问题。那么解决这个问题有一下方法
第二种方案:异步更新缓存(基于订阅binlog的同步机制)
技术整体思路:
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis
读Redis:热数据基本都在Redis
写MySQL:增删改都是操作MySQL
更新Redis数据:MySQ的数据操作binlog,来更新到Redis
Redis更新
1)数据操作主要分为两大块:
一个是全量(将全部数据一次写入到redis)
一个是增量(实时更新)
这里说的是增量,指的是mysql的update、insert、delate变更数据。
2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。
当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。
以上就是Redis和MySQL数据一致性详解。
redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?
参考Redis过期策略
redis在什么情况下会变慢?
单线程的redis,如何知道要运行定时任务?
redis是单线程的,线程不但要处理定时任务,还要处理客户端请求,线程不能阻塞在定时任务或处理客户端请求上,那么,redis是如何知道何时该运行定时任务的呢?
Redis 的定时任务会记录在一个称为最小堆的数据结构中。这个堆中,最快要执行的任务排在堆的最上方。在每个循环周期,Redis 都会将最小堆里面已经到点的任务立即进行处理。处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是接下来处理客户端请求的最大时长,若达到了该时长,则暂时不处理客户端请求而去运行定时任务。
Redis的五种数据类型分别是什么数据结构实现的?
Redis的字符串数据类型既可以存储字符串,又可以存储整数和浮点数,Redis在内部是怎么存储这些值的?
Redis的一部分命令只能对特定的数据类型执行操作,比如append只能对字符串执行,hset只能对hash执行,而另一部分命令却可以对所有数据类型执行,比如del, type, expire,不同的命令在执行时如何进行类型检查的?Redis在内部是否实现了一个类型系统?
Redis没有直接使用C语言的传统字符串表示,而是自己构建了一个名为简单动态字符串的抽象类型,并将SDS用作Redis的默认字符串表示。
struct sdshdr {
// buf中已经占用空间的长度
int len;
// buf中剩余可用空间的长度
int free;
// 数据空间,值存在这里。
char bu[];
}