Redis
基本数据类型
String(字符串)
String数据结构是简单的key-value类型 ,value其实不仅可以是String 还可以是数字
应用常见:常规的key-value缓存应用;常规计数:微博数 ,粉丝数等
hash(字典)
Hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,后续操作的时候,可以直接仅仅修改这个对象中的某个字段的值。
应用场景:存储用户信息,商品信息等等
hset <key><field> <value> 给key集合中的field键赋值value
hget<key1><field>从<key1>集合<field>中取出value
hmset <key1><field1><value1><field2<value2>...批量设置hash的值
hexists<key1><field>查看哈希表key中,给定域filed是否存在
hkeys<key>列出该hash集合的所有field
hvals<key>列出该hash集合的所有value
hincrby<key><field><increment>为哈希表 key 中的域field的值加上增值 1 -1
hsetnx<key><field><value>将哈希表key中的域field的值设置为value,当且仅当域field不存在
hash类型对应着两种数据结构
ziplist 压缩列表:当field-value长度短且个数少时使用
hashtable 哈希表:当field-value 多且长时使用
List (链表)
list就是链表,Redis list的实现是一个双向链表,支持反向查询和遍历,不过带来了额外的内存开销
是简单的字符串列表,按插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
应用场景:非常多,是Redis最重要的数据结构之一:
比如微博的关注列表,粉丝列表消息列表等功能都可以用Redis的list结构来实现。另外可以通过Lrang命令,就是从某个元素开始读取多少个,可以基于list实现分页查询,基于Redis实现简单的高性能分页
使用redis的list来实现缓存用户最近浏览商品
Set
与list类似 特点在于Set可以自动排重(自动去重)。当你需要存储一个列表数据,又不希望出现重复数据时,Set就是一个很好的选择
应用场景:在微博应用中,可以将一个用户所有的关注人存入一个集合,将其所有粉丝存入一个集合,Set可以非常方便的实现工同关注,共同粉丝,共同爱好等功能,求交集
Sorted Set
和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列。
应用场景:直播间各种礼物排行榜
有序集合zset
有序且没有重复元素字符串集合 zset和普通集合set很相似
但是每一个set都关联一个value值用于排序value可以重复
常用命令
zadd <key><score1><value1><score2><value2>...
将一个或者多个member元素及其score值加入到有序集合key当中
zrange<key>start><stop>[WITHSCORES]
返回有序集key中,下标在<start><stop>之间的元素
带WITHSCORES,可以让分数一起和值返回到结果集
zrangebyscore key minmax [withscores] [limit offset coount0]
返回有序集key中,所有的score值介于min和max之间(包括等于min和max)的成员
zrevrangebyscore key maxmin [withscores] [limit offset count]
同上,改为从大到小排序
zincrby <key><increment><value> 为元素的score加上增量
zrem <key><value> 删除该集合下,指定值的元素
zcount <key><min><max> 统计该集合,分数区间内的元素个数
zrank <key><value> 返回该值在集合中的排名,从0开始
zset底层使用了两个数据结构‘
1》hash, hash的作用就是关联元素value 和权重 score ,保障元素value的唯一性,可以通过元素value 找到对应的score的值
2》跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表
redis的发布和订阅
一个客户端 订阅 一个频道 SUBSCRIBE channel1
一个服务器在这个频道上发布的所有信息 都会被 订阅的客户端所接收
publish channel1 hello
Redis6的新数据类型
Bitmaps
1,本身不是一个数据类型,实际上是一个字符串(key-value) 但是它可以对字符串的位进行操作
2.Bitmaps 单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同,可以把Bitmaps想象成一个以位为单位的数组,数组的每个单位只能存储0和1,数组的下标在Bitmaps中叫做偏移量
1》格式
setbit<key><offset><value>设置Bitmaps中某个偏移量的值1/0
getbit<key><offset>获取Bitmaps中某个偏移量的值
bitcount 统计字符串被设置为1的数量
bitop 是一个复合结构,它可以做多个Bitmaps的 and交集 or 非 xor 异或 操作并将结果保存在destkey中
HyperLogLog
求集合中不重复的元素的个数的问题称为基数问题
解决方法:1》数据存储在MySQL表中 使用 distinct count 计算不重复的个数
2》使用Redis 提供的hash,set,bitmaps等数据结构进行处理
HyperLogLog是用来做基数统计的算法,HyperLogLog的优点是在输入元素的数量和体积非常大时,计算基数所需的空间总是固定的,并且是很小的
在Redis中每个HyperLogLog键只需要12KB内存就可以计算出2…^64 个不同元素的基数,
命令
pfadd key value 给集合key中加入
pfcount 可以统计出其中不重复的数量
pfmerge<destkey><sourcekey>[sourcekey...0]将一个或者多个HLL合并后的结果存在存在另一个HLL中,就是将两个key集合合并在一起
Geospatial
Redis 3.2 中增加了对CEO类型的支持,CEO,Geographic ,地理信息的缩小,
该类型,就是元素的2维坐标,在地图上就是经纬度,redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作
命令
groadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 bejing 加入某城市的经纬度
geopos <key><member>[member ...] 获取指定地区的 坐标值
geodist <key><member1><member2> [m|km|ft|mi] 获取两个位置之间的直线距离
georadius <key><longitude><latitude>radius m|km|ft|mi 以给定的经纬度为中心 找出某一半径内的元素
Jedis
它是Java语言操作Redis数据库的桥梁。Jedis客户端封装了Redis数据库的大量命令,因此具有许多Redis操作API
连接步骤
配置步骤
阿里云中需要添加安全组
2.启动redis /usr/local/bin/redis-server /etc/redis.conf 重启redis
防火墙未开启
Redis中的事务
事务的隔离等级:最高级 序列化
Redis中的事务是一个单独隔离操作:事务中的所有命令都会被序列化,按顺序执行。事务在执行过程中不会被其他客户端发送来的请求打断
Redis事务的主要作用就是串联多个命令防止别的命令插队
Mutil Exec discard
在输入Mutil命令开始,输入的命令都会被依次进入命令队列中,但是不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行
组队的过程中可以通过discard来放弃组队
3.事务的错误处理
1> 若是组队时某个命令出现了报告错误,执行时整个的所有队列都会被取消。
2> 若是在执行该队列时某个命令出现错误,则只有出现错误的命令执行失败
事务冲突问题 (redis中是悲观锁)
悲观锁
顾名思义就是很悲观 每次全数据的时候都认为别人会修改,所以在拿数据的时候都会上锁,这样别人想拿数据只能阻塞到锁开启,
传统的关系型数据库里面就用到了很多这种锁机制,比如说 行锁 表锁,读锁,写锁,
乐观锁
顾名思义就是很乐观,每次去拿数据的时候都认为别人不会修改,所有不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制
适用于多读的应用类型,这样可以提高吞吐量
Redis就是利用这种chek-and-set机制实现事务的
WATCH key [ket . . .] 乐观锁
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务被打断
事务的三个特性
单独的隔离操作
事务的所有命令都会被序列化,按顺序执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
没有隔离级别的概念
队列的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都没有被实际执行
不保证原子性
事务中如果有一天命令被执行失败,其他的命令依然会被执行,没有回滚
秒杀案例
乐观锁造成的库存遗留问题
问题:当一个用户修改本版号之后剩下的所有人的版本号都不匹配 导致 最后库存还有遗留
解决:
LUA脚本 嵌入式脚本语言
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提高性能
LUA脚本是类似redis事务,有一定的原子性不会被其他命令插队,可以完成一些redis事务性的操作
其只有在Redis 2.6以上的版本才支持
持久化操作RDB
是什么
RDB: 在指定的时间间隔内将内存中的数据集中快照写入磁盘,也就是行话讲的Snapshot 快照,
它恢复时是将快照文件直接读到内存里
备份是如何执行
Redis会单独创建 (fork) 一个子进程来进行持久化,会先将数据 写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式要更加的高效
缺点:最后一次持久化后的数据可能会消失
为什么:Redis在最后一次持久化时 若未达到持久化的限制 但主进程就挂掉了 rdb只会存储到上一次持久化结束时 内部的数据 上一次持久化之后的数据就会丢失
Fork
RDB持久化流程
配置位置
如何触发持久化
可以设置在M秒内N个key发生改变 就会自动进行持久化 并生出dump.rdb文件 其内保存N个可以 超出的重新计时
命令save VS bgsave
save:save时只管保存,其他不管,全部阻塞。手动保存。不建议
bgsave:Redis 会在后台异步进行快照操作,快照的同时还可以响应客户的请求
执行fulshall 命令,会产生一个空的dump.rdb文件
rdbcompression 释放压缩文件 默认压缩
rdbchecksum 检查完整性
优势
适合大规模的数据恢复
对数据完整性和一致性要求不高更适合使用
节省磁盘空间
恢复速度快
劣势
Fork的时候,内存中的数据被克隆了一份,大致两倍的膨胀性需要考虑
虽然Redis在fork时使用了写时拷贝技术,但是如果数据量大的时,还是比较耗性能
在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会失去最后一次快照后所有的修改
如何停止
持久化操作AOF
Append Only File
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不做记录),只许追加文件但不可以改写文件,redis启动之初读取该文件重新构建数据(重新执行一遍)
流程
AOF默认不开启
可以在reids.conf 中配置文件名称,默认为appendonly.aof
AOF文件的保存路径,同RDB的路径一致
AOF和RDB同时开启 redis听谁的
AOF和RDB同时开启 系统默认去AOF的数据(数据不会丢失)
AOF启动/修复/恢复
AOF同步频率设置
appendfsync always
始终同步,每次Redis的写入都会立刻记入日志,性能较差但数据完整性较好
appendfsync everysec
每秒同步 每秒记入一次日志,如果宕机,本秒的数据可能丢失
appendfsync no
redis 不主动进行同步,把同步时机交给操作系统。
Rewrite压缩
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增的重写机制 ,当AOF文件的大小超过所设定的阙值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof
重写原理:
和RDD流程一样
AOF持久化流程
优势
劣势
总结
AOF RDB 用哪种
主从复制
主机数据更新后根据配置和策略,自动同步到备机的master/slaver 机制,
Master以写为主,Slave以读为主
能干什么:读写分离,性能扩展(一个只负责读一个只负责写)
容灾快速恢复
怎么配置
-
创建/myredis文件家
-
复制redis.conf配置文件到/myreids文件夹中
-
将配置文件中的appendonly 改为no
-
新建redis6379.conf, redis6380.conf,redis6381.conf 一主两从(不能用一个端口号启动)所以设置不同的端口号,
-
在三个配置文件中分别写入内容 :
#redis6379.conf中: include /myredis/redis.conf pidfile /var/run/redis_6379.pid port 6379 #对于相应的端口号 dbfilename dump6379.rdb #对于相应的端口号 #redis6380.conf中: include /myredis/redis.conf pidfile /var/run/redis_6379.pid port 6380 dbfilename dump6380.rdb 下一个一样
-
启动三个redis服务 redis-service redis6379.conf ...
-
查看三台redis服务器 ps -ef |grep redis
-
查看三台主机运行情况 info replication
redis-cli -p 6379 #先连上一个服务器 info replication #查看
-
三台中配置主从关系(配主不配从)
-
在6380 6381两台服务器上执行命令 slaveof <ip><port> #为主机的ip和端口号
-
在主机上写,再从机上可以读取数据
-
再从机上写数据报错
-
主机挂掉,重启就行,一切如初
-
从机重启需重设:salveof <ip> <port> 可以将配置添加到文件中,永久生效
常用三招
一主二仆
当一个从服务器挂掉后:再次启动连接入主服务器中时 会将主服务器中的数据全部复制过来(从服务器中挂掉之后需要重新配置salveof <ip> <port> )
一个主服务器挂掉之后:重启之后不用配置引入为主
薪火相传
反客为主
(在薪火相传的基础之上) 当一个master宕机后,后面的salve中执行(slaveof no one)可以立刻升为mster,其后面的salver不用做任何修改(配合哨兵模式才可以使其自动升为master 否则 需要手动)
复制原理
-
当从服务器连接上主服务器之后,从服务器 向主服务器发送进行数据同步请求
-
主服务器接到从服务器发送过来同步信息,把主服务器数据进行持久化,rdb文件,把rdb文件发送从服务器,从服务器拿到rdb文件后进行读取
-
每次主服务器进行写操作之后,会和从服务器进行数据同步
哨兵原理
是反客为主的自动版,能够后台监控主机是否故障,如果故障了更具投票数自动将从库转换为主库
使用步骤
-
调整为一主二仆模式
-
自定义的。myredis目录下新建sentinel.conf文件,名字绝不能错
-
配置哨兵,填写内容
-
mymaster 是给监控对象起的名称
-
1表示至少有 1 个哨兵同意才能迁移
sentinel monitor mymaster 主机ip 主机端口号 1
-
-
启动哨兵 执行 redis-sentinel /myredis/sentinel.conf
-
查看哨兵
-
/usr/local/bin
-
redis 做压测可以用自带的redis-benchmark工具
-
-
当主机挂掉,从机选举中产生新的主机
-
那个从机会被选举为主机呢?根据优先级别:slave-priority
-
原主机重启后会变成从机
-
原主机的其他从机也会变成新主机的从机
-
-
复制延时问题
-
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延时,当系统很繁忙的时候,延迟问题会很严重,Slave机器数量的增加也会使这个问题更加严重。
-
从机的选择问题
优先级在redis.conf中默认:slave-priority 100 值越小优先级越高
偏移量是指获得原主机数据最全的
每个redis实例启动后都会随机生成一个40为的runid
Java中主从复制
连接池中设置
Redis集群
问题: 容量不够,redis如果进行扩容? 多台Redis进行扩容
并发写操作,redis如何分摊? Redis
另外,主从模式,薪火相传模式,主机宕机,都会导致ip地址发生变化,应用程序中配置需要修改对应的主机地址,端口等信息。
之前通过代理主机来解决,但是Redis3.0中提供了解决方案,就是无中心化集群配置
什么是集群
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储中数据的1/N
Redis集群通过分区来提供一定程度的可用性:既是集群中有一部分节点失效或者无法进行通信,集群也可以继续处理请求
搭建集群
-
删除持久化数据 先在Myredis目录中删除*.rdb文件
-
制作六个实例,6379,6380,6381,6389,6390,6391
-
配置基本信息 redis_6379.conf中
开启daemonize yes
Pid文件名字
指定端口号
Log文件名字
-
redis cluster 配置修改
cluter-enabled yes 打开集群模式
cluster-config-file nodes-6379.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联信息,超过该时间(毫秒),集群自动进行主从切换
3.将其复制五分作为其他redis的配置文件,使用查找替换修改这五个文件中 %s/6379/6380 //将其的配置中的端口号做修改
4.启动六个redis服务
(会自动生成节点配置文件 nodes-6379.conf)
redis-server redis6379.conf .....
-
-
-
将六个节点合成一个集群
-
组合之前,请确保所有的redis实例启动后,node-xxx.conf文件都生成正常
-
合体
cd /opt/redis-6.2.1/src //进入最开始的安装目录 需要在这里进行执行以下命令 低版本需要安装replicas 环境
redis-cli --clust create --cluster-replicas 1 ip:端口号 ip:端口号 ...
//replocas 1采用最简单的方式配置集群,一台主机,一台从机,正好三组
出现以下表示完成
-
-
不能使用普通方式连接因为无中心化连接每一个都可以成为主机,使用集群策略连接
-
-
-c 采用集群策略连接,设置
redis-cli -c -p 6379
-
-
通过cluster nodes 命令查看集群信息
-
redis cluster 如何分配这六个节点
-
一个集群至少要有三个节点
-
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点
-
分配原则:尽量保证每个数据库运行在不同的IP地址,每个从库和主库不在一个
-
-
什么是slots
-
在合体的最后完成时最后一行会出现 All 13684 slots covered.
-
-
-
表示一个集群包含13684个插槽(hash slot) ,数据库中的每个键都属于这13684个插槽的其中一个
-
集群使用公式CRC16(key)%16384 来计算键key属于哪一个插槽,其中CRC16(key) 用于计算键key和CRC16效验和
-
集群中每个节点复制处理一部分插槽 例如 一个节点有多个主节点,其中:节点A 复制 0 ~ 5660号插槽 节点B负责 ...
-
在集群中录入值
-
录入的值的插槽值不在当前节点管理范围内 就会自动切换到相应节点
-
不能直接同时加入多个key 及值 可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放入一个slot中去
-
-
查询集群中的值
-
CLUSTET CETKEYSINSLOT <slot><count> 返回count个slot槽中的键 //只能看当前所在节点范围内的插槽 其他节点的插槽均显示为0 需切换到相应的节点中
-
-
-
故障恢复
-
如果主节点下线?从节点能否自动升为主节点?注意15秒超时 可以
-
主节点恢复后,将会变成当前主机的从机
-
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes,那么,整个集群都挂掉了
-
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no,那么,该插槽数据全都不能使用,也无法存储
-
redis.conf中的参数
-
集群的Jedis开发
既是连接的不是主机,集群会自动切换主机存储。主机写从机读
无中心化主从集群,无论从哪台主机写的数据,其他主机上都可以读到数 从任何一个节点都能连上redis
Redis集群提供了以下好处
实现扩容
分摊压力
无中心配置相对简单
Redis集群的不足
多键操作不被支持
多键的Redis事务不被支持。lua脚本不被支持
由于集群方案出现较晚,很多公司采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster ,需要整体迁移而不是逐步过渡,复杂度较大
Redis6应用问题解决
缓存穿透
问题描述
应用服务器压力突然增大 导致Redis缓存命中率降低 使得应用服务器只能一直查寻数据库 导致数据库奔溃
问题原因:1.redis查询不到数据库
2.出现很多非正常url访问
解决方案
1.对空值缓存:如果一个查询返回的数据为空(不管数据存不存在),我们依然把这个空结果进行缓存,设置空结果的过期时间会很短,最长不超过5分钟
2.设置可访问名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较 ,如果访问id不在bitmaps里面,进行拦截,不允许访问。
3.采用布隆过滤器:(布隆过滤器(Bloom Filter) 是 1970 年由布隆提出。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
4.进行实时监控:当发现Redis的命中率开始急剧降低,需排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
缓存击穿
问题描述
解决方案
key可能会在某些时间点被超高并发地访问,是一种非常“热门”的数据,这个时候需要考虑一个问题:缓存被“击穿”的问题。
解决问题:
1》预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时常
2》实时调整:现场监控哪些数据热门,实时调整key的过期时长
3》使用锁:1.就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
2.先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
3.当操作返回成功时,在进行load db的操作,并回设缓存,最后删除mutex key
4.当操作返回失败,证明有线程在load db,当 前线程睡眠一段时间再重试整个get缓存的方法
缓存雪崩
问题描述
数据库压力变大服务器崩溃
在极少的时间段,查询大量key的集中过期情况
解决方案
缓存失效时的雪崩效应对底层系统的冲击非常可怕
方案:
1》构建多级缓存架构:nginx缓存 + redis 缓存 + 其他缓存(ehcache等)
2》使用锁或队列:使用加锁或者队列的方式保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落 到底层存储系统。不适用高并发情况
3》设置过期标志更新缓存
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存
4》将缓存失效实际分散开:
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件
分布式锁
描述
实现方案:1基于数据库实现分布式锁
2.基于缓存(Redis等)
3.基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
1.性能高:redis最高
2.可靠性:Zookeeper最高
实例基于redis实现分布式锁
redis:命令 setnx
# set sku:1:info"OK"NX PX 10000