redis
官网 redis.io
redis:非关系型数据库,不仅仅是数据库
redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库。
一、nosql概述
1.1、为什么要用nosql
1、单机MysqL的年代
网站的瓶颈:
1、数据量太大。一个机器放不下
2、数据的索引,一个内存也放不下
3、访问量(读写混合),一个服务器承受不了
2、Memcached(缓存)+MysqL+垂直拆分
网站80%的情况都是在读。每次都要去查询数据库的话,就十分麻烦!所以希望减轻数据的压力,我们可以使用缓存来保障效率。
3、分库分表+水平拆分+MysqL集群
4、现代企业框架
1.2、什么是nosql
# 不仅仅是sql
#很多数据类型用户的个人信息,社交网络,地理位置等。这些数据类型的存储不需要一个固定的格式!不需要多月的操作就可以横向扩展的。
特点:
1、方便扩展(数据之间没有关系,很好扩展!)
2、大数据量高性能(redis一秒写8万次,读取11万,nosql的缓存记录级,性能高)
3、数据类型是多样性的!(不需要事先设计好数据库,随去随用)
4、
Nosql的四大分类
redis能干嘛?
1、内存存储,持久化,内存中是断电即失,持久化(rdb,aof)
2、效率高,可以用于高速缓存
二、redis下载安装
1、下载
[root@locahost ~]#wget http://download.redis.io/releases/redis-6.0.3.tar.gz
2、解压
[root@locahost ~]#tar -xf redis-6.0.3.tar.gz
3、环境安装
[root@locahost ~]#yum -y install centos-release-scl
[root@locahost ~]#yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
[root@locahost ~]#source /opt/rh/devtoolset-9/enable
[root@locahost ~]#echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
4、进入解压后的目录 , 指定安装目录 (redis默认的安装目录是 /usr/local/bin)
[root@locahost redis-6.0.3]# make PREFIX=/opt/redis install 指定安装目录
[root@locahost redis-6.0.3]# cd /opt/redis
[root@locahost redis]# ls
bin
[root@locahost redis]# cd -
/opt/redis-6.0.3
[root@locahost redis-6.0.3]# ls
00-RELEASENOTES deps README.md runtest-moduleapi tests
BUGS INSTALL redis.conf runtest-sentinel TLS.md
CONTRIBUTING Makefile runtest sentinel.conf utils
copYING MANIFESTO runtest-cluster src
5、将配置文件移动到安装目录下,最好保存一下原始得配置文件,复制一份作为启动的文件。
[root@locahost redis-6.0.3]# mv redis.conf /opt/redis
6、redis默认不是后台启动的,进入配置文件redis.conf修改:
找到daemonize no 改为daemonize yes
7、通过指定的配文件启动服务
[root@locahost redis]redis-server redis.conf
8、连接redis-server
[root@locahost redis]redis-cli -p 6379
1.1、启动redis-server
启动:
./redis-server
如果启动不了,出现
14755:M 10 Jan 2021 04:49:30.255 * RDB age 40 seconds
14755:M 10 Jan 2021 04:49:30.255 * RDB memory usage when created 0.77 Mb
14755:M 10 Jan 2021 04:49:30.255 * DB loaded from disk: 0.000 seconds
14755:M 10 Jan 2021 04:49:30.255 * Ready to accept connections
这个时候 你按什么键都没用。 后来查了很多资料 发现都没说到点子上,
然后 问了人 发现是因为 守护进程没开 也就是windows 中的服务没开一样
所以 运行 redis-server 命令改为 redis-server --daemonize yes 就能正常运行了
注意的是 当你 卡在 之前的命令 按了 ctrl+z的话 会发现 你 运行 redis-cli 后 也是一样的情况
这种情况是因为你在运行 redis-server的时候ctrl+z把这个service 挂起来了 也就是暂停的意思 我 没找到解决的办法 ,我直接重启linux。。希望有好心人 告诉我怎么解决
1.2、启动客户端
//这样来启动redis客户端了
$ ./redis-cli
//用set指令来设置key、value
127.0.0.1:6379> set name "roc"
OK
//来获取name的值
127.0.0.1:6379> get name
"roc"
//通过客户端来关闭redis服务端
127.0.0.1:6379> shutdown
127.0.0.1:6379>
[root@locahost bin]# ./redis-cli
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379> set name zzs
OK
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> get name
"zzs"
127.0.0.1:6379> set shsh "hello world"
OK
127.0.0.1:6379> get shsh
"hello world"
127.0.0.1:6379>
环境搭建(略)
性能测试
redis-benchmark:Redis官方提供的性能测试工具,参数选项如下:
简单测试:
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
结果:
基础知识
默认使用的第0个;
16个数据库为:DB 0~DB 15 默认使用DB 0 ,可以使用select n
切换到DB n,dbsize
可以查看当前数据库的大小,与key数量相关。
127.0.0.1:6379> config get databases # 命令行查看数据库数量databases
1) "databases"
2) "16"
127.0.0.1:6379> select 8 # 切换数据库 DB 8
OK
127.0.0.1:6379[8]> dbsize # 查看数据库大小
(integer) 0
不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> SELECT 8
OK
127.0.0.1:6379[8]> get name # db8中并不能获取db0中的键值对。
(nil)
127.0.0.1:6379[8]> DBSIZE
(integer) 0
127.0.0.1:6379[8]> SELECT 0
OK
127.0.0.1:6379> keys *
"counter:rand_int"
"mylist"
"name"
"key:rand_int"
"myset:rand_int"
127.0.0.1:6379> DBSIZE # size和key个数相关
(integer) 5
keys *
:查看当前数据库中所有的key。
flushdb
:清空当前数据库中的键值对。
flushall
:清空所有数据库的键值对。
所以Redis的性能瓶颈不是cpu,而是机器内存和网络带宽。
那么为什么Redis的速度如此快呢,性能这么高呢?QPS达到10W+
Redis为什么单线程还这么快?
核心:Redis是将所有的数据放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(cpu上下文会切换:耗时的操作!),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个cpu上的,在内存存储数据情况下,单线程就是最佳的方案。
三、五大数据类型
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
redis五种数据结构
redis数据结构 – 简介】
redis是一种高级的key:value存储系统,其中value支持五种数据类型:
1.字符串(strings)
2.字符串列表(lists)
3.字符串集合(sets)
4.有序字符串集合(sorted sets)
5.哈希(hashes)
而关于key,有几个点要提醒大家:
1.key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;
2.key也不要太短,太短的话,key的可读性会降低;
3.在一个项目中,key最好使用统一的命名模式,例如user:10000:passwd。
【redis数据结构 – strings】
有人说,如果只使用redis中的字符串类型,且不使用redis的持久化功能,那么,redis就和memcache非常非常的像了。这说明strings类型是一个很基础的数据类型,也是任何存储系统都必备的数据类型。
我们来看一个最简单的例子:
set mystr "Hello World!" //设置字符串类型
get mystr //@R_502_5254@类型
字符串类型的用法就是这么简单,因为是二进制安全的,所以你完全可以把一个图片文件的内容作为字符串来存储。
另外,我们还可以通过字符串类型进行数值操作:
127.0.0.1:6379> set mynum "2"
OK
127.0.0.1:6379> get mynum
"2"
127.0.0.1:6379> incr mynum
(integer) 3
127.0.0.1:6379> get mynum
"3"
看,在遇到数值操作时,redis会将字符串类型转换成数值。
由于INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果,假如,在某种场景下有3个客户端同时读取了mynum的值(值为2),然后对其同时进行了加1的操作,那么,最后mynum的值一定是5。不少网站都利用redis的这个特性来实现业务上的统计计数需求。
Redis多个数据库
Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。Redis是一个字典结构的存储服务器,而实际上一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。
每个数据库对外都是一个从0开始的递增数字命名,Redis默认支持16个数据库(可以通过配置文件支持更多,无上限),可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库,如要选择1号数据库:
然而这些以数字命名的数据库又与我们理解的数据库有所区别。首先Redis不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。另外Redis也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL命令可以清空一个Redis实例中所有数据库中的数据。
Redis-key
在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。
下面学习的命令:
-
exists key
:判断键是否存在 -
del key
:删除键值对 -
move key db
:将键值对移动到指定数据库 -
expire key second
:设置键值对的过期时间 -
type key
:查看value的数据类型
127.0.0.1:6379> keys * # 查看当前数据库所有key (empty list or set) 127.0.0.1:6379> set name qinjiang # set key OK 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> move age 1 # 将键值对移动到指定数据库 (integer) 1 127.0.0.1:6379> EXISTS age # 判断键是否存在 (integer) 0 # 不存在 127.0.0.1:6379> EXISTS name (integer) 1 # 存在 127.0.0.1:6379> SELECT 1 OK 127.0.0.1:6379[1]> keys * 1) "age" 127.0.0.1:6379[1]> del age # 删除键值对 (integer) 1 # 删除个数127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> EXPIRE age 15 # 设置键值对的过期时
(integer) 1 # 设置成功 开始计数
127.0.0.1:6379> ttl age # 查看key的过期剩余时间
(integer) 13
127.0.0.1:6379> ttl age
(integer) 11
127.0.0.1:6379> ttl age
(integer) 9
127.0.0.1:6379> ttl age
(integer) -2 # -2 表示key过期,-1表示key未设置过期时间
127.0.0.1:6379> get age # 过期的key 会被自动delete
(nil)
127.0.0.1:6379> keys *
"name"
127.0.0.1:6379> type name # 查看value的数据类型
string
关于TTL命令
Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:
-
当前key没有设置过期时间,所以会返回-1.
-
当前key有设置过期时间,而且key已经过期,所以会返回-2.
-
当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.
关于重命名RENAME
和RENAMENX
3.1、String(字符串)
普通的set、get直接略过。
常用命令及其示例:
APPEND key value
: 向指定的key的value后追加字符串
127.0.0.1:6379> set msg hello OK 127.0.0.1:6379> append msg " world" (integer) 11 127.0.0.1:6379> get msg “hello world”
DECR/INCR key
: 将指定key的value数值进行+1/-1(仅对于数字)
127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> incr age (integer) 21 127.0.0.1:6379> decr age (integer) 20
INCRBY 和 DECRBY
INCRBY/DECRBY key n
: 按指定的步长对数值进行加减
127.0.0.1:6379> INCRBY age 5 (integer) 25 127.0.0.1:6379> DECRBY age 10 (integer) 15
INCRBYFLOAT key n
: 为数值加上浮点型数值
127.0.0.1:6379> INCRBYFLOAT age 5.2 “20.2”
STRLEN key
: 获取key保存值的字符串长度
127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> STRLEN msg (integer) 11
GETRANGE 和 SETRANGE
GETRANGE key start end
: 按起止位置获取字符串(闭区间,起止位置都取)
127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> GETRANGE msg 3 9 “lo worl” 127.0.0.1:6379> GETRANGE msg 0 -1 #获取全部的字符串,和get key是一样的 “hello world”
SETRANGE key offset value
:用指定的value 替换key中 offset开始的值
127.0.0.1:6379> set msg hello OK 127.0.0.1:6379> setrange msg 2 hello (integer) 7 127.0.0.1:6379> get msg "hehello" 127.0.0.1:6379> set msg2 world OK 127.0.0.1:6379> setrange msg2 2 ww (integer) 5 127.0.0.1:6379> get msg2 "wowwd"
GETSET
GETSET key value
: 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
127.0.0.1:6379> GETSET msg test “hello world”
SETEX和 SETNX
SETNX key value
: 仅当key不存在时进行set
127.0.0.1:6379> SETNX msg test #如果msg 存在,则创建失败 (integer) 0 127.0.0.1:6379> SETNX name sakura (integer) 1
SETEX key seconds value
: set 键值对并设置过期时间
127.0.0.1:6379> setex name 10 root #设置过期时间为10s OK 127.0.0.1:6379> get name (nil)
MSET 和 MGET
MSET key1 value1 [key2 value2..]
: 批量set键值对
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 OK
MSETNX key1 value1 [key2 value2..]
: 批量设置键值对,仅当参数中所有的key都不存在时执行
# msetnx是一个原子性操作,要么一起成功要么一起失败
127.0.0.1:6379> MSETNX k1 v1 k4 v4 (integer) 0
MGET key1 [key2..]
: 批量获取多个key保存的值
127.0.0.1:6379> MGET k1 k2 k3 1) “v1” 2) “v2” 3) “v3”
PSETEX key milliseconds value
: 和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间
String类似的使用场景:value除了是字符串还可以是数字,用途举例:
3.2、List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
首先我们列表,可以经过规则定义将其变为队列、栈、双端队列等。
正如图Redis中List是可以进行双端操作的,所以命令也就分为了LXXX和RLLL两类,有时候L也表示List例如LLEN
-
LPUSH/RPUSH key value1[value2..]
从左边/右边向列表中PUSH值(一个或者多个)。 -
LRANGE key start end
获取list 起止元素==(索引从左往右 递增)== -
LPUSHX/RPUSHX key value
向已存在的列名中push值(一个或者多个) -
LINSERT key BEFORE|AFTER pivot value
在指定列表元素的前/后 插入value -
LLEN key
查看列表长度 -
LINDEX key index
通过索引获取列表元素 -
LSET key index value
通过索引为元素设值 -
LPOP/RPOP key
从最左边/最右边移除值 并返回 -
RPOPLPUSH source destination
将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部 -
LTRIM key start end
通过下标截取指定范围内的列表 -
LREM key count value
List中是允许value重复的 count > 0:从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索… count = 0:删除列表中所有的指定value。 -
BLPOP/BRPOP key1[key2] timout
移出并获取列表的第一个/最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 -
BRPOPLPUSH source destination timeout
和RPOPLPUSH功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
127.0.0.1:6380> FLUSHALL 清空所有数据库 OK 127.0.0.1:6380> clear 127.0.0.1:6380> LPUSH list 1 #将一个值或者多个值插入列表的头部(左) 1 127.0.0.1:6380> LPUSH list 2 2 127.0.0.1:6380> LPUSH list 3 3 127.0.0.1:6380> LRANGE list 0 -1 #获取list中的值 3 2 1 127.0.0.1:6380> LRANGE list 0 1 #通过区间获取具体的值 3 2 127.0.0.1:6380> RPUSH list a #将一个值或者多个值插入列表的头部(右) 4 127.0.0.1:6380> LPOP list #移除最左边(第一个)一个值 3 127.0.0.1:6380>RPOP list #移除最右边(最后一个)一个值 a 127.0.0.1:6380> LRANGE list 0 -1 2 1127.0.0.1:6380> LINDEX list 0 #通过下标获取某一个值
2
127.0.0.1:6380> LLEN list #返回列表的长度
2
127.0.0.1:6380> FLUSHALL
OK
127.0.0.1:6380> clear
127.0.0.1:6380> LPUSH list one
1
127.0.0.1:6380> LPUSH list two
2
127.0.0.1:6380> LPUSH list two
3
127.0.0.1:6380> LREM list 1 one #移除list中等于one的一个元素(最后加入的移除掉)
1
127.0.0.1:6380> LRANGE list 0 -1
two
two
127.0.0.1:6380> LREM list 2 one
0
127.0.0.1:6380> LREM list 2 two
2
127.0.0.1:6380> LRANGE list 0 -1127.0.0.1:6380> FLUSHALL
127.0.0.1:6380> LPUSH list one
1
127.0.0.1:6380> LPUSH list two
2
127.0.0.1:6380> LPUSH list three
3
127.0.0.1:6380> LPUSH list four
4
127.0.0.1:6380> LTRIM list 1 2 #通过下标截取指定的长度,这个list已经改变了,只剩下截取的长度
OK
127.0.0.1:6380> LRANGE list 0 -1
three
two
127.0.0.1:6380>
---------------------------LPOP--RPOP--------------------------127.0.0.1:6379> LPOP mylist # 左侧(头部)弹出
"k5"
127.0.0.1:6379> RPOP mylist # 右侧(尾部)弹出
"k3"---------------------------RPOPLPUSH--------------------------
127.0.0.1:6379> LRANGE mylist 0 -1
- "k4"
- "k2"
- "k6"
- "k1"
127.0.0.1:6379> RPOPLPUSH mylist newlist # 将mylist的最后一个值(k1)弹出,加入到newlist的头部
"k1"
127.0.0.1:6379> LRANGE newlist 0 -1- "k1"
127.0.0.1:6379> LRANGE mylist 0 -1- "k4"
- "k2"
- "k6"
---------------------------LTRIM--------------------------
127.0.0.1:6379> LTRIM mylist 0 1 # 截取mylist中的 0~1部分
OK
127.0.0.1:6379> LRANGE mylist 0 -1
- "k4"
- "k2"
初始 mylist: k2,k2,k2,k2,k2,k2,k4,k2,k2,k2,k2
---------------------------LREM--------------------------
127.0.0.1:6379> LREM mylist 3 k2 # 从头部开始搜索 至多删除3个 k2
(integer) 3删除后:mylist: k2,k2,k2,k4,k2,k2,k2,k2
127.0.0.1:6379> LREM mylist -2 k2 #从尾部开始搜索 至多删除2个 k2
(integer) 2删除后:mylist: k2,k2,k2,k4,k2,k2
---------------------------BLPOP--BRPOP--------------------------
mylist: k2,k2,k2,k4,k2,k2
newlist: k1127.0.0.1:6379> BLPOP newlist mylist 30 # 从newlist中弹出第一个值,mylist作为候选
- "newlist" # 弹出
- "k1"
127.0.0.1:6379> BLPOP newlist mylist 30- "mylist" # 由于newlist空了 从mylist中弹出
- "k2"
127.0.0.1:6379> BLPOP newlist 30
(30.10s) # 超时了127.0.0.1:6379> BLPOP newlist 30 # 我们连接另一个客户端向newlist中push了test, 阻塞被解决。
- "newlist"
- "test"
(12.54s)
lset
linsert
小结
-
list实际上是一个链表,before Node after , left, right 都可以插入值
-
如果key不存在,则创建新的链表
-
如果key存在,新增内容
-
如果移除了所有值,空链表,也代表不存在
-
在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
3.3、Set(集合)
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
-
SCARD key
获取集合的成员数 -
SMEMBERS key
返回集合中所有的成员 -
SISMEMBER key member
查询member元素是否是集合的成员,结果是无序的 -
SRANDMEMBER key [count]
随机返回集合中count个成员,count缺省值为1 -
SPOP key [count]
随机移除并返回集合中count个成员,count缺省值为1 -
SMOVE source destination member
将source集合的成员member移动到destination集合 -
SREM key member1[member2..]
移除集合中一个/多个成员 -
SDIFF key1[key2..]
返回所有集合的差集 key1- key2 - … -
SDIFFSTORE destination key1[key2..]
在SDIFF的基础上,将结果保存到集合中==(覆盖)==。不能保存到其他类型key噢! -
SINTER key1 [key2..]
返回所有集合的交集 -
SINTERSTORE destination key1[key2..]
在SINTER的基础上,存储结果到集合中。覆盖 -
SUNION key1 [key2..]
返回所有集合的并集 -
SUNIONSTORE destination key1 [key2..]
在SUNION的基础上,存储结果到集合中。覆盖 -
SSCAN KEY [MATCH pattern] [COUNT count]
在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分
--------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------127.0.0.1:6379> SADD myset m1 m2 m3 m4 # 向myset中增加成员 m1~m4
(integer) 4
127.0.0.1:6379> SCARD myset # 获取集合的成员数目
(integer) 4
127.0.0.1:6379> smembers myset # 获取集合中所有成员
"m4" "m3" "m2" "m1" 127.0.0.1:6379> SISMEMBER myset m5 # 查询m5是否是myset的成员 (integer) 0 # 不是,返回0 127.0.0.1:6379> SISMEMBER myset m2 (integer) 1 # 是,返回1 127.0.0.1:6379> SISMEMBER myset m3 (integer) 1
---------------------SRANDMEMBER--SPOP----------------------------------
127.0.0.1:6379> SRANDMEMBER myset 3 # 随机返回3个成员
"m2" "m3" "m4" 127.0.0.1:6379> SRANDMEMBER myset # 随机返回1个成员 "m3" 127.0.0.1:6379> SPOP myset 2 # 随机移除并返回2个成员 "m1" "m4"
将set还原到{m1,m2,m3,m4}
---------------------SMOVE--SREM----------------------------------------
127.0.0.1:6379> SMOVE myset newset m3 # 将myset中m3成员移动到newset集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
"m4" "m2" "m1" 127.0.0.1:6379> SMEMBERS newset "m3" 127.0.0.1:6379> SREM newset m3 # 从newset中移除m3元素 (integer) 1 127.0.0.1:6379> SMEMBERS newset (empty list or set)
下面开始是多集合操作,多集合操作中若只有一个参数默认和自身进行运算
setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}
-----------------------------SDIFF------------------------------------
127.0.0.1:6379> SDIFF setx sety setz # 等价于setx-sety-setz
"m4" 127.0.0.1:6379> SDIFF setx sety # setx - sety "m4" "m1" 127.0.0.1:6379> SDIFF sety setx # sety - setx "m5"
-------------------------SINTER---------------------------------------
共同关注(交集)
127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集
"m6" 127.0.0.1:6379> SINTER setx sety # 求setx sety的交集 "m2" "m6"
-------------------------SUNION---------------------------------------
127.0.0.1:6379> SUNION setx sety setz # setx sety setz的并集
"m4" "m6" "m3" "m2" "m1" "m5" 127.0.0.1:6379> SUNION setx sety # setx sety 并集 "m4" "m6" "m2" "m1" "m5"
3.4、Hash(哈希)
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。
-
HSET key field value
将哈希表 key 中的字段 field 的值设为 value 。重复设置同一个field会覆盖,返回0 -
HMSET key field1 value1 [field2 value2..]
同时将多个 field-value (域-值)对设置到哈希表 key 中。 -
HSETNX key field value
只有在字段 field 不存在时,设置哈希表字段的值。 -
HEXISTS key field
查看哈希表 key 中,指定的字段是否存在。 -
HGET key field value
获取存储在哈希表中指定字段的值 -
HMGET key field1 [field2..]
获取所有给定字段的值 -
HGETALL key
获取在哈希表key 的所有字段和值 -
HKEYS key
获取哈希表key中所有的字段 -
HVALS key
获取哈希表中所有值 -
HINCRBY key field n
为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段 -
HINCRBYFLOAT key field n
为哈希表 key 中的指定字段的浮点数值加上增量 n。 -
HSCAN key cursor [MATCH pattern] [COUNT count]
迭代哈希表中的键值对。
------------------------HSET--HMSET--HSETNX---------------- 127.0.0.1:6379> HSET studentx name sakura # 将studentx哈希表作为一个对象,设置name为sakura (integer) 1 127.0.0.1:6379> HSET studentx name gyc # 重复设置field进行覆盖,并返回0 (integer) 0 127.0.0.1:6379> HSET studentx age 20 # 设置studentx的age为20 (integer) 1 127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886 # 设置sex为1,tel为15623667886 OK 127.0.0.1:6379> HSETNX studentx name gyc # HSETNX 设置已存在的field (integer) 0 # 失败 127.0.0.1:6379> HSETNX studentx email [email protected] (integer) 1 # 成功----------------------HEXISTS--------------------------------
127.0.0.1:6379> HEXISTS studentx name # name字段在studentx中是否存在
(integer) 1 # 存在
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0 # 不存在
-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name # 获取studentx中name字段的value
"gyc"
127.0.0.1:6379> HMGET studentx name age tel # 获取studentx中name、age、tel字段的value
"gyc" "20" "15623667886" 127.0.0.1:6379> HGETALL studentx # 获取studentx中所有的field及其value "name" "gyc" "age" "20" "sex" "1" "tel" "15623667886" "email" "[email protected]"
--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx # 查看studentx中所有的field
"name" "age" "sex" "tel" "email" 127.0.0.1:6379> HLEN studentx # 查看studentx中的字段数量 (integer) 5 127.0.0.1:6379> HVALS studentx # 查看studentx中所有的value "gyc" "20" "1" "15623667886" "[email protected]"
-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel # 删除studentx 中的sex、tel字段
(integer) 2
127.0.0.1:6379> HKEYS studentx
"name" "age" "email"
-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age字段数值+1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1 # 非整数字型字段不可用
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6
"90.8"
Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于对象的存储,Sring更加适合字符串存储!
3.5、Zset(有序集合)
不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
score相同:按字典顺序排序
有序集合的成员是唯一的,但分数(score)却可以重复。
-
ZADD key score member1 [score2 member2]
向有序集合添加一个或多个成员,或者更新已存在成员的分数 -
ZCARD key
获取有序集合的成员数 -
ZCOUNT key min max
计算在有序集合中指定区间score的成员数 -
ZINCRBY key n member
有序集合中对指定成员的分数加上增量 n -
Zscore key member
返回有序集中,成员的分数值 -
ZRANK key member
返回有序集合中指定成员的索引 -
ZRANGE key start end
通过索引区间返回有序集合成指定区间内的成员 -
ZRANGEBYLEX key min max
通过字典区间返回有序集合的成员 -
ZRANGEBYscore key min max
通过分数返回有序集合指定区间内的成员==-inf 和 +inf分别表示最小最大值,只支持开区间()== -
ZLEXCOUNT key min max
在有序集合中计算指定字典区间内成员数量 -
ZREM key member1 [member2..]
移除有序集合中一个/多个成员 -
ZREMRANGEBYLEX key min max
移除有序集合中给定的字典区间的所有成员 -
ZREMRANGEBYRANK key start stop
移除有序集合中给定的排名区间的所有成员 -
ZREVRANGE key start end
返回有序集中指定区间内的成员,通过索引,分数从高到底 -
ZREVRANGEBYSCORRE key max min
返回有序集中指定分数区间内的成员,分数从高到低排序 -
ZREVRANGEBYLEX key max min
返回有序集中指定字典区间内的成员,按字典顺序倒序 -
ZREVRANK key member
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 -
ZINTERSTORE destination numkeys key1 [key2 ..]
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数,将score相加作为结果的score -
ZUNIONSTORE destination numkeys key1 [key2..]
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 -
ZSCAN key cursor [MATCH pattern\] [COUNT count]
迭代有序集合中的元素(包括元素成员和元素分值)
-------------------ZADD--ZCARD--ZCOUNT-------------- 127.0.0.1:6379> ZADD myzset 1 m1 2 m2 3 m3 # 向有序集合myzset中添加成员m1 score=1 以及成员m2 score=2.. (integer) 2 127.0.0.1:6379> ZCARD myzset # 获取有序集合的成员数 (integer) 2 127.0.0.1:6379> ZCOUNT myzset 0 1 # 获取score在 [0,1]区间的成员数量 (integer) 1 127.0.0.1:6379> ZCOUNT myzset 0 2 (integer) 2----------------ZINCRBY--Zscore--------------------------
127.0.0.1:6379> ZINCRBY myzset 5 m2 # 将成员m2的score +5
"7"
127.0.0.1:6379> Zscore myzset m1 # 获取成员m1的score
"1"
127.0.0.1:6379> Zscore myzset m2
"7"
--------------ZRANK--ZRANGE-----------------------------------
127.0.0.1:6379> ZRANK myzset m1 # 获取成员m1的索引,索引按照score排序,score相同索引值按字典顺序顺序增加
(integer) 0
127.0.0.1:6379> ZRANK myzset m2
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 1 # 获取索引在 0~1的成员
"m1" "m3" 127.0.0.1:6379> ZRANGE myzset 0 -1 # 获取全部成员 "m1" "m3" "m2"
testset=>{abc,add,amaze,apple,back,java,redis} score均为0
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> ZRANGEBYLEX testset - + # 返回所有成员
"abc" "add" "amaze" "apple" "back" "java" "redis" 127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分页 按索引显示查询结果的 0,1,2条记录 "abc" "add" "amaze" 127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 显示 3,4,5条记录 "apple" "back" "java" 127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员 "abc" "add" "amaze" "apple" 127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员 "apple" "back" "java"
-----------------------ZRANGEBYscore---------------------
127.0.0.1:6379> ZRANGEBYscore myzset 1 10 # 返回score在 [1,10]之间的的成员
"m1" "m3" "m2" 127.0.0.1:6379> ZRANGEBYscore myzset 1 5 "m1" "m3"
--------------------ZLEXCOUNT-----------------------------
127.0.0.1:6379> ZLEXCOUNT testset - +
(integer) 7
127.0.0.1:6379> ZLEXCOUNT testset [apple [java
(integer) 3
------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYscore--------------------------------
127.0.0.1:6379> ZREM testset abc # 移除成员abc
(integer) 1
127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典区间[apple,java]中的所有成员
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成员
(integer) 2
127.0.0.1:6379> ZREMRANGEBYscore myzset 0 3 # 移除score在 [0,3]的成员
(integer) 2
testset=> {abc,add,apple,amaze,back,java,redis} score均为0
myzset=> {(m1,1),(m2,2),(m3,3),(m4,4),(m7,7),(m9,9)}
----------------ZREVRANGE--ZREVRANGEBYscore--ZREVRANGEBYLEX-----------
127.0.0.1:6379> ZREVRANGE myzset 0 3 # 按score递减排序,然后按索引,返回结果的 0~3
"m9" "m7" "m4" "m3" 127.0.0.1:6379> ZREVRANGE myzset 2 4 # 返回排序结果的 索引的2~4 "m4" "m3" "m2" 127.0.0.1:6379> ZREVRANGEBYscore myzset 6 2 # 按score递减顺序 返回集合中分数在[2,6]之间的成员 "m4" "m3" "m2" 127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员 "java" "back" "apple" "amaze"
-------------------------ZREVRANK------------------------------
127.0.0.1:6379> ZREVRANK myzset m7 # 按score递减顺序,返回成员m7索引
(integer) 1
127.0.0.1:6379> ZREVRANK myzset m2
(integer) 4
mathscore=>{(xm,90),(xh,95),(xg,87)} 小明、小红、小刚的数学成绩
enscore=>{(xm,70),(xh,93),(xg,90)} 小明、小红、小刚的英语成绩
-------------------ZINTERSTORE--ZUNIONSTORE-----------------------------------
127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore # 将mathscore enscore进行合并 结果存放到sumscore
(integer) 3
127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores # 合并后的score是之前集合中所有score的和
"xm" "160" "xg" "177" "xh" "188"
127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN # 取两个集合的成员score最小值作为结果的
(integer) 3
127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores
"xm" "70" "xg" "87" "xh" "93"
-
set排序 存储班级成绩表 工资表排序!
-
普通消息,1.重要消息 2.带权重进行判断
-
排行榜应用实现,取Top N测试
四、三种特殊数据类型
4.1、Geospatial(地理位置)
使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用
-
geoadd key longitud(经度) latitude(纬度) member [..]
将具体经纬度的坐标存入一个有序集合 -
georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHdisT] [WITHHASH] [COUNT count]
以给定的经纬度为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。 -
GEORADIUSBYMEMBER key member radius...
功能与GEORADIUS相同,只是中心位置不是具体的经纬度,而是使用结合中已有的成员作为中心点。 -
geohash key member1 [member2..]
返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。
有效经纬度
-
有效的经度从-180度到180度。
-
有效的纬度从-85.05112878度到85.05112878度。
指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
关于GEORADIUS的参数
通过georadius就可以完成 附近的人功能
withcoord:带上坐标
withdist:带上距离,单位与半径单位相同
COUNT n : 只显示前n个(按距离递增排序)
# 添加位置经纬度 127.0.0.1:6379> GEOADD china:city 116.41667 39.91667 北京 (integer) 1 127.0.0.1:6379> GEOADD china:city 121.43333 31.23000 上海 (integer) 1 127.0.0.1:6379> GEOADD china:city 106.45000 29.56667 重庆 (integer) 1 127.0.0.1:6379> GEOADD china:city 114.06667 22.61667 深圳 (integer) 1 127.0.0.1:6379> GEOADD china:city 120.20000 30.26667 杭州 (integer) 1 127.0.0.1:6379> GEOADD china:city 108.95000 34.26667 西安 (integer) 1查看不同位置经纬度
127.0.0.1:6379> GEOPOS china:city 北京 西安
查看不同位置之间距离(默认是m)
127.0.0.1:6379> GEOdisT china:city 北京 上海
"1066981.1340"
127.0.0.1:6379> GEOdisT china:city 北京 上海 km
"1066.9811"
127.0.0.1:6379> GEOdisT china:city 北京 重庆 km
"1465.8918"
127.0.0.1:6379>查看指定经纬度周边范围内的地方(可以以此扩散到周围指定范围内的指定人数的好友)
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
重庆
西安
深圳
杭州
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
重庆
西安
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist #查看某经纬度周围500km的所有城市和距离/km
重庆
346.0548
西安
484.7511
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord #查看某经纬度周围500km的所有城市和经纬度
重庆
106.4500012993812561
29.56666939001875249
西安
108.95000249147415161
34.2666710302806834
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km count 1 #查看某经纬度周围500km的1个城市
重庆
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km count 3 #查看某经纬度周围500km的三个城市
重庆
西安
127.0.0.1:6379>查看地点周边的所有城市(类似于定位)
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 北京 1000 km
北京
西安
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 上海 1000 km
杭州
上海
127.0.0.1:6379>将城市的二维经纬度转成一位hash字符串
127.0.0.1:6379> GEOHASH china:city 北京 重庆
wx4g14s53n0
wm78nq6w2f0
127.0.0.1:6379>GEO底层是zset,所以可以用zset命令来操作
127.0.0.1:6379> zrange china:city 0 -1 #查看所有城市
重庆
西安
深圳
杭州
上海
北京
127.0.0.1:6379> zrem china:city 北京 #删除北京这个城市
4.2、Hyperloglog(基数统计)
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
其底层使用string数据类型。
什么是基数?
数据集中不重复的元素的个数。
应用场景:
传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。
-
PFADD key element1 [elememt2..]
添加指定元素到 HyperLogLog中 -
PFCOUNT key [key]
返回给定 HyperLogLog 的基数估算值。 -
PFMERGE destkey sourcekey [sourcekey..]
将多个 HyperLogLog 合并为一个 HyperLogLog
----------PFADD--PFCOUNT--------------------- 127.0.0.1:6379> PFADD myelemx a b c d e f g h i j k # 添加元素 (integer) 1 127.0.0.1:6379> type myelemx # hyperloglog底层使用String string 127.0.0.1:6379> PFCOUNT myelemx # 估算myelemx的基数 (integer) 11 127.0.0.1:6379> PFADD myelemy i j k z m c b v p q s (integer) 1 127.0.0.1:6379> PFCOUNT myelemy (integer) 11----------------PFMERGE-----------------------
127.0.0.1:6379> PFMERGE myelemz myelemx myelemy # 合并myelemx和myelemy 成为myelemz
OK
127.0.0.1:6379> PFCOUNT myelemz # 估算基数
(integer) 17
127.0.0.1:6379> clear 127.0.0.1:6379> PFADD mykey a b c d e f g h i j #设置mykey 集合 (integer) 1 127.0.0.1:6379> PFCOUNT mykey #统计mykey 集合基数数量 (integer) 10 127.0.0.1:6379> PFADD mykey2 i j z x c v b n m (integer) 1 127.0.0.1:6379> PFCOUNT mykey2 (integer) 9 127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 #2个集合取并集 OK 127.0.0.1:6379> PFCOUNT mykey3 (integer) 15 127.0.0.1:6379>
4.3、BitMaps(位图)
使用位存储,信息状态只有 0 和 1
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。
-
setbit key offset value
为指定key的offset位设置值 -
getbit key offset
获取offset位的值 -
bitop operration destkey key[key..]
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 -
BITPOS key bit [start] [end]
返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位
------------setbit--getbit-------------- 127.0.0.1:6379> setbit sign 0 1 # 设置sign的第0位为 1 (integer) 0 127.0.0.1:6379> setbit sign 2 1 # 设置sign的第2位为 1 不设置默认 是0 (integer) 0 127.0.0.1:6379> setbit sign 3 1 (integer) 0 127.0.0.1:6379> setbit sign 5 1 (integer) 0 127.0.0.1:6379> type sign string127.0.0.1:6379> getbit sign 2 # 获取第2位的数值
(integer) 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 4 # 未设置默认是0
(integer) 0
-----------bitcount----------------------------
127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数
(integer) 4
五、事务
Redis的单条命令是保证原子性的,但是redis事务不能保证原子性
Redis事务本质:一组命令的集合。----------------- 队列 set set set 执行 -------------------
事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。
一次性
顺序性
排他性
Redis事务没有隔离级别的概念
Redis单条命令是保证原子性的,但是事务不保证原子性!
Redis事务操作过程
-
开启事务(multi)
-
命令入队
-
执行事务(exec)
所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec)一次性完成。
127.0.0.1:6379> multi # 开启事务 OK 127.0.0.1:6379> set k1 v1 # 命令入队 QUEUED 127.0.0.1:6379> set k2 v2 # .. QUEUED 127.0.0.1:6379> get k1 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> keys * QUEUED 127.0.0.1:6379> exec # 事务执行 1) OK 2) OK 3) "v1" 4) OK 5) 1) "k3" 2) "k2" 3) "k1"
取消事务(discurd)
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> disCARD # 放弃事务 OK 127.0.0.1:6379> EXEC (error) ERR EXEC without MULTI # 当前未开启事务 127.0.0.1:6379> get k1 # 被放弃事务中命令并未执行 (nil)
事务错误
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> error k1 # 这是一条语法错误命令 (error) ERR unkNown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> EXEC (error) EXECABORT Transaction discarded because of prevIoUs errors. # 执行报错 127.0.0.1:6379> get k1 (nil) # 其他命令并没有被执行
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> INCR k1 # 这条命令逻辑错误(对字符串进行增量) QUEUED 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 3) (error) ERR value is not an integer or out of range # 运行时报错 4) "v2" # 其他命令正常执行 虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了。
监控
悲观锁:
-
很悲观,认为什么时候都会出现问题,无论做什么都会加锁
乐观锁:
使用watch key监控指定数据,相当于乐观锁加锁。
正常执行
127.0.0.1:6379> set money 100 # 设置余额:100 OK 127.0.0.1:6379> set use 0 # 支出使用:0 OK 127.0.0.1:6379> watch money # 监视money (上锁) OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> DECRBY money 20 QUEUED 127.0.0.1:6379> INCRBY use 20 QUEUED 127.0.0.1:6379> exec # 监视值没有被中途修改,事务正常执行 1) (integer) 80 2) (integer) 20
测试多线程修改值,使用watch可以当做redis的乐观锁操作(相当于getversion)
我们启动另外一个客户端模拟插队线程。
线程1:
127.0.0.1:6379> watch money # money上锁 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> DECRBY money 20 QUEUED 127.0.0.1:6379> INCRBY use 20 QUEUED 127.0.0.1:6379> # 此时事务并没有执行
模拟线程插队,线程2:
127.0.0.1:6379> INCRBY money 500 # 修改了线程一中监视的money (integer) 600
回到线程1,执行事务:
127.0.0.1:6379> EXEC # 执行之前,另一个线程修改了我们的值,这个时候就会导致事务执行失败 (nil) # 没有结果,说明事务执行失败 127.0.0.1:6379> get money # 线程2 修改生效 "600" 127.0.0.1:6379> get use # 线程1事务执行失败,数值没有被修改 "0"
解锁获取最新值,然后再加锁进行事务。
unwatch进行解锁。
注意:每次提交执行exec后都会自动释放锁,不管是否成功
六、redis 配置文件
单位 对大小不敏感
指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件 include /path/to/local.conf
网络
#绑定的主机地址 bind 127.0.0.1 #指定Redis监听端口,默认端口为6379, port 6379 #保护模式,默认开启 protect-mode yes
通用GENERAL
#Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程 daemonize no当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
loglevel notice
当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout
# 日志文件名,默认为空
logfile ""
设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
databases 16
快照
持久化,在规定时间内,执行了多少次操作,则会持久化到文件 .rdb .aof
#指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合 save <seconds> <changes> Redis默认配置文件中提供了三个条件: save 900 1 #表示900秒(15分钟)内至少有1个更改就进行持久化操作。 save 300 10 #300秒(5分钟)内至少有10个更改就进行持久化操作。 save 60 10000 #60秒内有至少10000个更改就进行持久化操作。保存rdb文件的时候,进行错误的检查校验
rdbchecksum yes
指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省cpu时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
指定本地数据库存放目录(rdb保存的目录)
dir ./
Security 安全
#设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭 requirepass foobared
限制客户端 CLIENTS
#设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息 maxclients 10000 最大客户端数量 maxmemory <bytes> 最大内存限制 maxmemory-policy noeviction # 内存达到限制值的处理策略redis 中的默认的过期策略是 volatile-lru 。
设置方式
config set maxmemory-policy volatile-lru
maxmemory-policy 六种方式
1、volatile-lru:#只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : #删除lru算法的key
3、volatile-random: #随机删除即将过期key
5、volatile-ttl :# 删除即将过期的
appendonly 模式 aof配置
appendonly no #默认是使用rdb方式持久化的,默认不开启aof模式的,大部分情况下rdb完全够用指定持久化文件的名字 默认为appendonly.aof
appendfilename appendonly.aof
指定更新日志条件,共有3个可选值:
appendfsync no:#不执行(同步)sync操作,表示等操作系统自己进行数据缓存同步到磁盘(快)
appendfsync always:#表示每次更新操都会sync,将数据写到磁盘(慢,安全)
appendfsync everysec:#表示每秒同步一次(折中,默认值)可能会丢失这一秒的数据
六、持久化—RDB
RDB:Redis Databases
什么是RDB
在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;
默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。
如何回复redis数据
1、只需要将rdb文件放在redis启动目录下,redis启动的时候会自动检查rdb文件,恢复其中的数据
查看rdb文件所在目录
127.0.0.1:6379> config get dir 1) "dir" 2) "/opt/redis/bin"
工作原理
在进行 RDB
的时候,redis
的主线程是不会做 io
操作的,主线程会 fork
一个子线程来完成该操作;
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)
触发机制
save
使用 save
命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;
由于
save
命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save
命令执行速度会非常慢,阻塞所有客户端的请求。
flushall命令
flushall
命令也会触发持久化 ;
触发持久化规则
满足配置条件中的触发条件 ;
可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作。
bgsave
bgsave
是异步进行,进行持久化的时候,redis
还可以将继续响应客户端请求 ;
bgsave和save对比
优缺点
优点:
-
适合大规模的数据恢复
-
对数据的完整性要求不高
缺点:
七、持久化AOF
Append Only File
将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
什么是AOF
快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
appendonly no yes
则表示启用AOF
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix
优点和缺点
appendonly yes # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用 appendfilename "appendonly.aof"appendfsync always # 每次修改都会sync 消耗性能
appendfsync everysec # 每秒执行一次 sync 可能会丢失这一秒的数据
appendfsync no # 不执行 sync ,这时候操作系统自己同步数据,速度最快
优点
缺点
十二、RDB和AOP选择
RDB 和 AOF 对比
如何选择使用哪种持久化方式?
一般来说, 如果想达到足以媲美 Postgresql 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
八、Redis发布与订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
命令
命令
描述
PSUBSCRIBE pattern [pattern..] #订阅一个或多个符合给定模式的频道。
PUNSUBSCRIBE pattern [pattern..] #退订一个或多个符合给定模式的频道。
PUBSUB subcommand [argument[argument]] #查看订阅与发布系统状态。
PUBLISH channel message #向指定频道发布消息
SUBSCRIBE channel [channel..] #订阅给定的一个或多个频道。
UNSUBSCRIBE channel [channel..] #退订一个或多个频道
------------订阅端---------------------- 127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道 Reading messages... (press Ctrl-C to quit) # 等待接收消息 1) "subscribe" # 订阅成功的消息 2) "sakura" 3) (integer) 1 1) "message" # 接收到来自sakura频道的消息 "hello world" 2) "sakura" 3) "hello world" 1) "message" # 接收到来自sakura频道的消息 "hello i am sakura" 2) "sakura" 3) "hello i am sakura"--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
- "sakura"
原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
缺点
-
如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
-
这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
应用
稍微复杂的场景,我们就会使用消息中间件MQ处理。
九、Redis主从复制
9.1、概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/@R_404_4997@),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
9.2、作用
-
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
-
故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
-
负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
-
高可用基石:主从复制还是哨兵和集群能够实施的基础。
为什么使用集群
1. 单台服务器难以负载大量的请求 2. 单台服务器故障率高,系统崩坏概率大 3. 单台服务器内存容量有限。
9.3、环境配置
我们在讲解配置文件的时候,注意到有一个replication
模块 (见Redis.conf中第8条)
查看当前库的信息:info replication
127.0.0.1:6379> info replication # Replication role:master # 角色 connected_slaves:0 # 从机数量 master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:
启动单机多服务集群:
9.4、一主二从配置
==默认情况下,每台Redis服务器都是主节点;==我们一般情况下只用配置从机就好了!
认老大!一主(79)二从(80,81)
使用SLAVEOF host port
就可以为从机配置主机了。
然后主机上也能看到从机的状态:
我们这里是使用命令搭建,是暂时的,==真实开发中应该在从机的配置文件中replication进行配置,==这样的话是永久的。
使用规则
-
从机只能读,不能写,主机可读可写但是多用于写。
127.0.0.1:6381> set name sakura # 从机6381写入失败 (error) READONLY You can't write against a read only replica.127.0.0.1:6380> set name sakura # 从机6380写入失败
(error) READONLY You can't write against a read only replica.127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> get name
"sakura"
2、当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
3、当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
这样也可以完成主从复制
第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
如果没有老大了,这个时候能不能选择出来一个老大呢?手动!
如果主机断开了连接,我们可以使用SLAVEOF no one
让自己变成主机!其他的节点就可以手动连接到最新的主节点(手动修改从机所认的老大,即相当于重新配置)!如果这个时候老大修复了,那么久重新连接!
十、Redis哨兵(Sentinel)模式
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
10.1、哨兵模式概述
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵的核心配置
sentinel monitor mymaster 127.0.0.1 6379 1
-
数字1表示 :当一个哨兵主观认为主机断开,就可以客观认为主机故障,然后开始选举新的主机。
这里的哨兵有两个作用
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
10.2、Redis配置哨兵模式
配置3个哨兵和1主2从的Redis服务器来演示这个过程。
服务类型 | 是否是主服务器 | IP地址 | 端口 |
---|---|---|---|
Redis | 是 | 192.168.11.128 | 6379 |
Redis | 否 | 192.168.11.129 | 6379 |
Redis | 否 | 192.168.11.130 | 6379 |
Sentinel | - | 192.168.11.128 | 26379 |
Sentinel | - | 192.168.11.129 | 26379 |
Sentinel | - | 192.168.11.130 | 26379 |
多哨兵监控Redis
首先配置Redis的主从服务器,修改redis.conf文件如下
# 使得Redis服务器可以跨网络访问 bind 0.0.0.0 # 设置密码 requirepass "123456" # 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置 slaveof 192.168.11.128 6379 # 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置 masterauth 123456
上述内容主要是配置Redis服务器,从服务器比主服务器多一个slaveof的配置和密码。
配置3个哨兵,每个哨兵的配置都是一样的。在Redis安装目录下有一个sentinel.conf文件,copy一份进行修改
# 禁止保护模式 protected-mode no # 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。 sentinel monitor mymaster 192.168.11.128 6379 2 # sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster 123456
上述关闭了保护模式,便于测试。
有了上述的修改,我们可以进入Redis的安装目录的src目录,通过下面的命令启动服务器和哨兵
# 启动Redis服务器进程 ./redis-server ../redis.conf # 启动哨兵进程 ./redis-sentinel ../sentinel.conf
注意启动的顺序。首先是主机(192.168.11.128)的Redis服务进程,然后启动从机的服务进程,最后启动3个哨兵的服务进程。
四、哨兵模式的其他配置项
配置项 | 参数类型 | 作用 |
---|---|---|
port | 整数 | 启动哨兵进程端口 |
dir | 文件夹目录 | 哨兵进程服务临时文件夹,默认为/tmp,要保证有可写入的权限 |
sentinel down-after-milliseconds | <服务名称><毫秒数(整数)> | 指定哨兵在监控Redis服务时,当Redis服务在一个默认毫秒数内都无法回答时,单个哨兵认为的主观下线时间,默认为30000(30秒) |
sentinel parallel-syncs | <服务名称><服务器数(整数)> | 指定可以有多少个Redis服务同步新的主机,一般而言,这个数字越小同步时间越长,而越大,则对网络资源要求越高 |
sentinel failover-timeout | <服务名称><毫秒数(整数)> | 指定故障切换允许的毫秒数,超过这个时间,就认为故障切换失败,默认为3分钟 |
sentinel notification-script | <服务名称><脚本路径> | 指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,比较常用 |
sentinel down-after-milliseconds配置项只是一个哨兵在超过规定时间依旧没有得到响应后,会自己认为主机不可用。对于其他哨兵而言,并不是这样认为。哨兵会记录这个消息,当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover,此时哨兵会重写Redis的哨兵配置文件,以适应新场景的需要。
测试
redis-sentinel xxx/sentinel.conf
成功启动哨兵模式
此时哨兵监视着我们的主机6379,当我们断开主机后:
哨兵模式优缺点
优点:
缺点:
-
Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
-
实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
哨兵模式的全部配置
完整的哨兵模式配置文件 sentinel.conf
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970# Example sentinel.conf哨兵sentinel实例运行的端口 默认26379
port 26379
哨兵sentinel的工作目录
dir /tmp
哨兵sentinel监控的redis主节点的 ip port
master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
故障转移的超时时间 failover-timeout 可以用在以下这些方面:
1. 同一个sentinel对同一个master两次failover之间的间隔时间。
2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
3.当想要取消一个正在进行的failover所需要的时间。
4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
默认三分钟
sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
SCRIPTS EXECUTION
配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
对于脚本的运行结果有以下规则:
若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
一个是事件的类型,
一个是事件的描述。
如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
通知脚本
sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
客户端重新配置主节点参数脚本
当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
以下参数将会在调用脚本时传给脚本:
<master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
目前<state>总是“failover”,
<role>是“@R_404_4997@”或者“observer”中的一个。
参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
这个脚本应该是通用的,能被多次调用,不是针对性的。
sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
十一、缓存穿透与雪崩
缓存穿透(查不到)
概念
在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
解决方案
布隆过滤器
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(量太大,缓存过期)
概念
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方案
-
设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
-
加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
缓存雪崩
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
-
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
-
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。