狂神Redis笔记:
链接: https://pan.baidu.com/s/1-0nRe_3HzeuHumru_ReVPw 提取码: 7x9v 复制这段内容后打开百度网盘手机App,操作更方便哦
视频地址:https://www.bilibili.com/video/BV1S54y1R7SB?p=1
如今信息量井喷式增长,各种各样的数据出现(用户定位数据,图片数据等),大数据的背景下关系型数据库(RDBMS)无法满足大量数据要求。Nosql数据库就能轻松解决这些问题。
NoSQL = Not Only SQL(不仅仅是SQL)
关系型数据库:列+行,同一个表下数据的结构是一样的。
非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。
NoSQL泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的。
Nosql特点:
1.方便扩展(数据之间没有关系,很好扩展!)
2.大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
3.数据类型是多样型的!(不需要事先设计数据库,随取随用)
传统的 RDBMS 和 NoSQL对比:
传统的 RDBMS(关系型数据库)
- 结构化组织
- SQL
- 数据和关系都存在单独的表中 row col
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
- ...
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展
- ...
了解:3V + 3高
大数据时代的3V :主要是描述问题的
大数据时代的3高 : 主要是对程序的要求
真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的。
1.KV键值对
2.文档型数据库(bson数据格式)
MongoDB(掌握)
ConthDB
3.列存储数据库
4.图关系数据库
用于广告推荐,社交网络
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值对(key-value) | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 | Key 指向 Value 的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB, MongoDb | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法。 |
图形(Graph)数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群 |
# 商品信息
- 一般存放在关系型数据库:Mysql,阿里巴巴使用的Mysql都是经过内部改动的。
# 商品描述、评论(文字居多)
- 文档型数据库:MongoDB
# 图片
- 分布式文件系统 FastDFS
- 淘宝:TFS
- Google: GFS
- Hadoop: HDFS
- 阿里云: oss
# 商品关键字 用于搜索
- 搜索引擎:solr,elasticsearch
- 阿里:Isearch 多隆
# 商品热门的波段信息
- 内存数据库:Redis,Memcache
# 商品交易,外部支付接口
- 第三方应用
没有什么是加一层解决不了的,如果有就加两层!
Redis(Remote Dictionary Server ),即远程字典服务。
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
官网:https://redis.io/
官方推荐Linux搭建
https://github.com/dmajkic/redis
1.解压安装包
1.官网下载安装包!
2.解压,程序一般放在/opt
目录下
[root@iZ8vbiui8f5xtbf8jb9j1wZ opt]# tar -zxvf redis-6.2.3.tar.gz
3.基本环境安装
#安装gcc环境
yum install gcc-c++
#查看是否安装成功
gcc -v
# 然后进入redis目录下执行
make
# 然后执行
make install
4.redis默认安装路径 /usr/local/bin
5.将redis的配置文件复制到 程序安装目录 /usr/local/bin/kconfig
下
6.redis默认不是后台启动的,需要修改配置文件!
7.启动redis-server,使用kconfig目录下的redis.config作为配置文件
8.启动redis-cli客户端,使用端口号为6379
9.查看redis进程是否开启
10.关闭Redis服务 shutdown
11.再次查看进程是否存在
12.后面我们会使用单机多Redis启动集群测试
redis-benchmark
:Redis官方提供的性能测试工具,参数选项如下:
测试:
1.打开redis服务端和客户端
2.再开一个连接,切换到/usr/local/bin目录下
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
redis默认有16个数据库,默认使用第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 *
1) "counter:__rand_int__"
2) "mylist"
3) "name"
4) "key:__rand_int__"
5) "myset:__rand_int__"
127.0.0.1:6379> DBSIZE # size和key个数相关
(integer) 5
keys *
:查看当前数据库中所有的key。
flushdb
:清空当前数据库中的键值对。
flushall
:清空所有数据库的键值对。
Redis是单线程的,Redis是基于内存操作的。
所以Redis的性能瓶颈不是CPU,而是机器内存和网络带宽。
Redis为什么单线程还这么快?
核心:Redis是将所有的数据放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存存储数据情况下,单线程就是最佳的方案。
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
在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 *
1) "name"
127.0.0.1:6379> type name # 查看value的数据类型
string
关于TTL
命令
Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:
1.当前key没有设置过期时间,所以会返回-1.
2.当前key有设置过期时间,而且key已经过期,所以会返回-2.
3.当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.
关于重命名rename
和renamenx
rename key newkey
修改 key 的名称renamenx key newkey
仅当 newkey 不存在时,将 key 改名为 newkey 。普通的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 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 key start end |
按起止位置获取字符串(闭区间,起止位置都取) | 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> GETRANGE msg 6 10 “world” |
SETRANGE key offset value |
用指定的value 替换key中 offset开始的值 | 127.0.0.1:6379> SETRANGE msg 1 xxxx (integer) 11 127.0.0.1:6379> get msg “hxxxx world” |
GETSET key value |
如果不存在值,则返回nil,如果存在值,将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 | 127.0.0.1:6379> GETSET msg test “hello world” |
SETNX key value |
仅当key不存在时进行set | 127.0.0.1:6379> SETNX msg test (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 kuangshen OK 127.0.0.1:6379> get name (nil) |
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都不存在时执行 | 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除了是字符串还可以是数字,用途举例:
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
首先我们列表,可以经过规则定义将其变为队列、栈、双端队列等
List是可以进行双端操作的,所以命令也就分为了LXXX和RXXX两类,List的命令是以L开头的
命令 | 描述 |
---|---|
LPUSH/RPUSH key value1[value2..] |
从左边/右边向列表中PUSH值(一个或者多个)。 |
LPOP/RPOP key |
从最左边/最右边移除值 并返回 |
LRANGE key start end |
获取list 起止元素==(索引从左往右 递增)== |
LLEN key |
查看列表长度 |
LINDEX key index |
通过索引获取列表元素 |
LSET key index value |
通过索引为元素设值 |
RPOPLPUSH source destination |
将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部 |
LTRIM key start end |
通过下标截取指定范围内的列表 |
LREM key count value |
List中是允许value重复的 count > 0 :从头部开始搜索 然后删除指定的value 至多删除count个 count < 0 :从尾部开始搜索… count = 0 :删除列表中所有的指定value。 |
LINSERT key BEFORE|AFTER pivot value |
在指定列表元素的前/后 插入value |
---------------------------LPUSH---RPUSH---LRANGE--------------------------------
127.0.0.1:6379> lpush list one # 左插入
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 # 查看list中的所有元素
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> rpush list four # 右插入
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> rpush list five six seven
(integer) 7
---------------------------LPOP--RPOP--------------------------
127.0.0.1:6379> lpop list # 左侧弹出一个元素
"three"
127.0.0.1:6379> rpop list # 右侧弹出一个元素
"seven"
-----------------------------LLEN--LINDEX--LSET--TRIM--------------------------
127.0.0.1:6379> lindex list 1 # 查看下标为1的元素
"one"
127.0.0.1:6379> llen list # list的长度
(integer) 5
127.0.0.1:6379> lpush list six
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "six"
2) "two"
3) "one"
4) "four"
5) "five"
6) "six"
127.0.0.1:6379> lrem list 2 six # 删除list中两个six元素
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "four"
4) "five"
127.0.0.1:6379> ltrim list 1 -1 # 截取
OK
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "four"
3) "five"
---------------------------RPOPLPUSH--------------------------
127.0.0.1:6379> rpoplpush list newlist # 弹出list的一个元素,放入newlist里
"five"
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "four"
127.0.0.1:6379> lrange newlist 0 -1
1) "five"
---------------------------EXISTS--LSET--LINSTER----------------------
127.0.0.1:6379> rpoplpush list newlist # 弹出list的一个元素,放入newlist里
"five"
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "four"
127.0.0.1:6379> lrange newlist 0 -1
1) "five"
127.0.0.1:6379> exists list # 是否存在list
(integer) 1
127.0.0.1:6379> lset mylist 0 item # 将mylist的第0个元素设置为item
(error) ERR no such key
127.0.0.1:6379> lset list 1 two
OK
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
127.0.0.1:6379> linsert list before two nine # 在two元素前插入nine元素
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "nine"
3) "two"
小结
list实际上是一个链表,before Node after , left, right 都可以插入值
如果key不存在,则创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在
在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
应用场景:消息排队!消息队列(Lpush Rpop),栈(Lpush Lpop)
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Set命令以S开头!
命令 | 描述 |
---|---|
SADD key member1[member2..] |
向集合中无序增加一个/多个成员 |
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 - … |
SINTER key1 [key2..] |
返回所有集合的交集 |
SUNION key1 [key2..] |
返回所有集合的并集 |
---------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------
127.0.0.1:6379> sadd set hello # 向set中添加元素,不重复
(integer) 1
127.0.0.1:6379> sadd set hello
(integer) 0
127.0.0.1:6379> sadd set world
(integer) 1
127.0.0.1:6379> sadd set !
(integer) 1
127.0.0.1:6379> sadd set 99
(integer) 1
127.0.0.1:6379> smembers set # 查看set中所有元素,无顺序
1) "99"
2) "!"
3) "world"
4) "hello"
127.0.0.1:6379> sismember set hello # 是否存在某个元素
(integer) 1
127.0.0.1:6379> scard set # set共有几个元素
(integer) 4
---------------------SREM--SRANDMEMBER--------------------------------
127.0.0.1:6379> srem set 99 # 移除指定元素
(integer) 1
127.0.0.1:6379> smembers set
1) "!"
2) "world"
3) "hello"
127.0.0.1:6379> srandmember set #获取指定个数的随机元素
"hello"
127.0.0.1:6379> srandmember set
"hello"
127.0.0.1:6379> srandmember set
"world"
127.0.0.1:6379> srandmember set
"!"
127.0.0.1:6379> srandmember set
"world"
127.0.0.1:6379> srandmember set 2
1) "world"
2) "!"
---------------------SPOP--SMOVE--------------------------------
127.0.0.1:6379> spop set # 随机移除一个元素
"hello"
127.0.0.1:6379> smembers set
1) "!"
2) "world"
127.0.0.1:6379> smove set newset world # 将set中指定元素移到newset中
(integer) 1
127.0.0.1:6379> smembers set
1) "!"
127.0.0.1:6379> smembers newset
1) "world"
---------------------SDIFF--SINTER--SUNION------------------------------
127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> sadd set1 b
(integer) 1
127.0.0.1:6379> sadd set1 c
(integer) 1
127.0.0.1:6379> sadd set2 c
(integer) 1
127.0.0.1:6379> sadd set2 d
(integer) 1
127.0.0.1:6379> sadd set2 e
(integer) 1
127.0.0.1:6379> sadd set2 f
(integer) 1
# 微博,B站,共同关注!
# (并集) 数字集合类:
- 差集 SDIFF
- 交集
- 并集
127.0.0.1:6379> sdiff set1 set2 # 差集 set2-set1
1) "a"
2) "b"
127.0.0.1:6379> sdiff set2 set1 # 差集 set1-set2
1) "d"
2) "e"
3) "f"
127.0.0.1:6379> sinter set1 set2 # 交集
1) "c"
127.0.0.1:6379> sunion set1 set2 # 并集
1) "b"
2) "c"
3) "e"
4) "d"
5) "a"
6) "f"
应用场景:共同关注,共同爱好,二度好友
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。
Hash命令是以H开头!
命令 | 描述 |
---|---|
HSET key field value |
将哈希表 key 中的字段 field 的值设为 value 。重复设置同一个field会覆盖,返回0 |
HMSET key field1 value1 [field2 value2..] |
同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
HSETNX key field value |
只有在字段 field 不存在时,设置哈希表字段的值。 |
HGET key field value |
获取存储在哈希表中指定字段的值 |
HMGET key field1 [field2..] |
获取所有给定字段的值 |
HEXISTS key field |
查看哈希表 key 中,指定的字段是否存在。 |
HGETALL key |
获取在哈希表key 的所有字段和值 |
HKEYS key |
获取哈希表key中所有的字段 |
HLEN key |
获取哈希表中字段的数量 |
HVALS key |
获取哈希表中所有值 |
HDEL key field1 [field2..] |
删除哈希表key中一个/多个field字段 |
HINCRBY key field n |
为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段 |
HINCRBYFLOAT key field n |
为哈希表 key 中的指定字段的浮点数值加上增量 n。 |
HSCAN key cursor [MATCH pattern] [COUNT count] |
迭代哈希表中的键值对。 |
------------------------HSET--HMSET--HMGET----------------
127.0.0.1:6379> hset hash f1 v1 f2 v2 f3 v3
(integer) 3
127.0.0.1:6379> hget hash
(error) ERR wrong number of arguments for 'hget' command
127.0.0.1:6379> hget hash f2
"v2"
127.0.0.1:6379> hget hash f3
"v3"
127.0.0.1:6379> hmset hash f4 v4 f5 v5
OK
127.0.0.1:6379> hmget f1 f2 f3 f4 f5
1) (nil)
2) (nil)
3) (nil)
4) (nil)
127.0.0.1:6379> hmget hash f1 f2 f3 f4 f5
1) "v1"
2) "v2"
3) "v3"
4) "v4"
5) "v5"
------------------------HGETALL--HDEL--HLEN----------------
127.0.0.1:6379> hgetall hash
1) "f1"
2) "v1"
3) "f2"
4) "v2"
5) "f3"
6) "v3"
7) "f4"
8) "v4"
9) "f5"
10) "v5"
127.0.0.1:6379> hdel hash f5
(integer) 1
127.0.0.1:6379> hgetall hash
1) "f1"
2) "v1"
3) "f2"
4) "v2"
5) "f3"
6) "v3"
7) "f4"
8) "v4"
127.0.0.1:6379> hlen hash
(integer) 4
------------------------HEXISTS--HKEYS--HVALS----------------
127.0.0.1:6379> hexists hash f2
(integer) 1
127.0.0.1:6379> hkeys hash
1) "f1"
2) "f2"
3) "f3"
4) "f4"
127.0.0.1:6379> hvals hash
1) "v1"
2) "v2"
3) "v3"
4) "v4"
------------------------HINCRBY--HSETNX----------------
127.0.0.1:6379> hset hash f5 10
(integer) 1
127.0.0.1:6379> hincrby hash f5 10
(integer) 20
127.0.0.1:6379> hincrby hash f5 -10
(integer) 10
127.0.0.1:6379> hsetnx hash f6 v6
(integer) 1
127.0.0.1:6379> hsetnx hash f6 v6
(integer) 0
Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于对象的存储,Sring更加适合字符串存储!
不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
score相同:按字典顺序排序
有序集合的成员是唯一的,但分数(score)却可以重复。
Zset命令是以Z开头的!
命令 | 描述 |
---|---|
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 |
移除有序集合中给定的排名区间的所有成员 |
ZREMRANGEBYSCORE key min max |
移除有序集合中给定的分数区间的所有成员 |
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的成员
1) "m1"
2) "m3"
127.0.0.1:6379> ZRANGE myzset 0 -1 # 获取全部成员
1) "m1"
2) "m3"
3) "m2"
#testset=>{abc,add,amaze,apple,back,java,redis} score均为0
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> ZRANGEBYLEX testset - + # 返回所有成员
1) "abc"
2) "add"
3) "amaze"
4) "apple"
5) "back"
6) "java"
7) "redis"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分页 按索引显示查询结果的 0,1,2条记录
1) "abc"
2) "add"
3) "amaze"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 显示 3,4,5条记录
1) "apple"
2) "back"
3) "java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员
1) "abc"
2) "add"
3) "amaze"
4) "apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员
1) "apple"
2) "back"
3) "java"
-----------------------ZRANGEBYSCORE---------------------
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之间的的成员
1) "m1"
2) "m3"
3) "m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
1) "m1"
2) "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
1) "m9"
2) "m7"
3) "m4"
4) "m3"
127.0.0.1:6379> ZREVRANGE myzset 2 4 # 返回排序结果的 索引的2~4
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myzset 6 2 # 按score递减顺序 返回集合中分数在[2,6]之间的成员
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员
1) "java"
2) "back"
3) "apple"
4) "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的和
1) "xm"
2) "160"
3) "xg"
4) "177"
5) "xh"
6) "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
1) "xm"
2) "70"
3) "xg"
4) "87"
5) "xh"
6) "93"
应用案例:
String
List
Hash
Set
Zset
使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用
命令 | 描述 |
---|---|
geoadd key longitud(经度) latitude(纬度) member [..] |
将具体经纬度的坐标存入一个有序集合 |
geopos key member [member..] |
获取集合中的一个/多个成员坐标 |
geodist key member1 member2 [unit] |
返回两个给定位置之间的距离。默认以米作为单位。 |
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 表示单位为英尺。
-------------------------geoadd------------------------------
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing # 添加
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
-------------------------geopos ------------------------------
127.0.0.1:6379> geopos china:city beijing # 获取指定成员(一个或多个)的坐标
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city hangzhou shanghai xian
1) 1) "120.1600000262260437"
2) "30.2400003229490224"
2) 1) "121.47000163793563843"
2) "31.22999903975783553"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
-------------------------geodist ------------------------------
127.0.0.1:6379> geodist china:city shanghai hangzhou km # 计算两地的距离
"166.7613"
-------------------------georadius ------------------------------
127.0.0.1:6379> georadius china:city 110 30 500 km # 以(110,30)为中心,半径500km内的成员
1) "chongqing"
2) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist # 带上距离
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord # 带上坐标
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord withdist count 1 # 结果有两个,只要一个
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
-------------------------georadiusbymember ------------------------------
127.0.0.1:6379> georadiusbymember china:city hangzhou 500 km # 找出位于指定元素周围的其他元素!
1) "hangzhou"
2) "shanghai"
-------------------------geohash ------------------------------
127.0.0.1:6379> geohash china:city shanghai hangzhou 返回一个或多个位置元素的 Geohash 表示
1) "wtw3sj5zbj0"
2) "wtmkn31bfb0"
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city beijing # 底层zset,可以使用zrem移除元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
应用场景:朋友的定位,附近的人,打车距离等
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
其底层使用string数据类型
什么是基数?
数据集中不重复的元素的个数。
应用场景:
网页的访问量(UV):一个用户多次访问,也只能算作一个人。
传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。
命令 | 描述 |
---|---|
PFADD key element1 [elememt2..] |
添加指定元素到 HyperLogLog 中 |
PFCOUNT key [key] |
返回给定 HyperLogLog 的基数估算值。 |
PFMERGE destkey sourcekey [sourcekey..] |
将多个 HyperLogLog 合并为一个 HyperLogLog |
127.0.0.1:6379> pfadd mykey a b c d e f g h i j # 添加元素
(integer) 1
127.0.0.1:6379> pfcount 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 # 合并mykey和mykey2成为mykey3
OK
127.0.0.1:6379> pfcount mykey3 # 估算基数
(integer) 15
如果允许容错,那么一定可以使用Hyperloglog ,有0.81%的错误率!
如果不允许容错,就使用set或者自己的数据类型即可 !
使用位存储,信息状态只有 0 和 1
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。
命令 | 描述 |
---|---|
setbit key offset value |
为指定key的offset位设置值 |
getbit key offset |
获取offset位的值 |
bitcount key [start end] |
统计字符串被设置为1的bit数,也可以指定统计范围按字节 |
bitop operration destkey key[key..] |
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 |
BITPOS key bit [start] [end] |
返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位 |
使用bitmap 来记录 周一到周日的打卡!
周一:1 周二:1 周三:1 周四:0 …
127.0.0.1:6379> setbit sign 0 1 # 设置第0位为1,不设置默认是0
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
127.0.0.1:6379> getbit sign 1 # 获取第一位的值
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
127.0.0.1:6379> bitcount sign # 统计为1的个数
(integer) 4
应用场景:统计用户信息,活跃,不活跃! 登录 、 未登录! 打卡,365打卡! 两个状态的,都可以使用Bitmaps!
Redis的单条命令是保证原子性的,但是redis事务不能保证原子性
Redis事务本质:一组命令的集合。
----------------- 队列 set set set 执行 -------------------
事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。
- 一次性
- 顺序性
- 排他性
1.Redis事务没有隔离级别的概念
2.Redis单条命令是保证原子性的,但是事务不保证原子性!
Redis事务操作过程
所有事务中的命令在加入时都没有被执行,而是被放入一个队列中,直到提交时才会开始执行(Exec)一次性完成。
执行事务(exec)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> keys *
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
4) 1) "k3"
2) "k2"
3) "k1"
取消事务(discard)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> 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(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 # 错误,set命令没写全
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> 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(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> incr k1 # 字符串不能自增
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
监控
悲观锁:
乐观锁:
使用watch key
监控指定数据,相当于乐观锁加锁。
正常执行
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
测试多线程出错
所有命令都执行完,但是没有执行exec,
这时另一个客户端修改了money的值,
会造成事务执行出错
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
另一个客户端
127.0.0.1:6379> set money 0
(integer) 1
返回继续执行事务,发现事务执行失败
127.0.0.1:6379(TX)> exec
(nil)
执行失败后,先解锁,再重新加锁(锁money最新的值),再执行事务就好了
Jedis是Redis官方推荐使用的Java连接redis的客户端。
使用:
1.导入依赖
<dependencies>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
dependencies>
2.编码测试
测试前需要进行一些配置
firewall-cmd --list-ports # 只看端口信息
irewall-cmd --zone=public --add-port=6379/tcp --permanent #开启6379端口
systemctl restart firewalld.service #重启防火墙
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("阿里云公网IP",6379);
String result = jedis.ping();
System.out.println(result); //PONG
}
}
3.jedis操作事务
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("阿里云公网IP",6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("name","zhangsan");
jsonObject.put("age",18);
String result = jsonObject.toJSONString();
Transaction transaction = jedis.multi(); //开启事务
try {
transaction.set("user1",result);
transaction.set("user2",result);
transaction.exec(); //执行事务
} catch (Exception e) {
transaction.discard(); //放弃事务
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); //关闭连接
}
}
}
Jedis的API和Redis的命令一模一样,不介绍了!
springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换。
jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式
我们在学习SpringBoot自动配置的原理时,整合一个组件并进行配置一定会有一个自动配置类xxxAutoConfiguration,并且在spring.factories中也一定能找到这个类的完全限定名。Redis也不例外。
当看到xxTemplate时可以对比RestTemplat、SqlSessionTemplate,通过使用这些Template来间接操作组件。那么这俩也不会例外。分别用于操作Redis和Redis中的String数据类型。
在RedisTemplate上也有一个条件注解,说明我们是可以对其进行定制化的
1.导包
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2.编写配置文件
# 配置redis
spring.redis.host=阿里云公网IP
spring.redis.port=6379
3.使用RedisTemplate
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate 操作不同的数据类型,api和我们的指令是一样的
// opsForValue 操作字符串 类似String
// opsForList 操作List 类似List
// opsForHah
// 除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD
// 获取连接对象
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//connection.flushDb();
//connection.flushAll();
redisTemplate.opsForValue().set("mykey","kuangshen");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
4.定制RedisTemplate
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//使用StringRedisSerializer来序列化和反序列化redis的key值
RedisSerializer redisSerializer = new StringRedisSerializer();
//key
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
//value
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
测试正常!
坑: springboot通过get取得值,必须是springboot通过set设置的,不能是Redis客户端设置的值!!!springboot
get操作会反序列化!!!
单位对大小写不敏感
可以导入其他配置文件
就是好比我们学习Spring、Improt, include
网络
bind 127.0.0.1 -::1 # 绑定ip,默认只允许本机访问
protected-mode yes # 保护模式,默认开启
port 6379 # 端口
通用 GENERAL
daemonize yes # 以守护进程的方式运行,默认是 no
pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个 pid 文件!
#日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice # 日志等级
logfile "6379.log" # 日志文件名
databases 16 # 数据库的数量,默认是 16 个数据库
always-show-logo yes # 是否总是显示LOGO
快照 SNAPSHOTTING
# 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
save 60 10000
stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作!
rdbcompression yes # 是否压缩 rdb 文件,需要消耗一些cpu资源!
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验!
dbfilename dump.rdb # rdb持久化的文件名
dir ./ # rdb 文件保存的目录!
主从复制 REPLICATION
replicaof <masterip> <masterport> # 设置主机的ip和端口号
masterauth <master-password> # 校验主机密码
安全 SECURITY
客户端连接 CLIENTS
maxclients 10000 # 设置能连接上redis的最大客户端的数量
maxmemory <bytes> # redis 配置最大的内存容量
maxmemory-policy noeviction # 内存到达上限之后的处理策略
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
APPEND ONLY 模式 aof配置
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下, rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!
Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能!
什么是rdb
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!
触发机制
1.save的规则满足的情况下
2.执行 flushall 命令
3.退出redis,也会产生 rdb 文件!
备份就自动生成一个 dump.rdb
如何恢复rdb
1、只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb 恢复其中的数据!
2、查看需要存在的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"
# 如果在这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据
bgsave和save对比
命令 | save | bgsave |
---|---|---|
IO类型 | 同步 | 异步 |
阻塞? | 是 | 是(阻塞发生在fock(),通常非常快) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外的内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要fock子进程,消耗内存 |
优缺点
优点:
缺点:
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部在执行一遍!
什么是aof
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
使用AOF:
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错位(被修改了),这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix appendonly.aof
修复appendonly.aof后,会删除错误数据,正确数据不变
重写规则说明
aof 默认就是文件的无限追加,文件会越来越大!
如果 aof 文件大于 64m,太大了! fork一个新的进程来将我们的文件进行重写!
优缺点
appendonly yes # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会sync 消耗性能
appendfsync everysec # 每秒执行一次 sync 可能会丢失这一秒的数据
# appendfsync no # 不执行 sync ,这时候操作系统自己同步数据,速度最快
优点
缺点
若只打算用Redis 做缓存,可以关闭持久化
。若打算使用Redis 的持久化。建议RDB和AOF都开启
。其实RDB更适合做数据的备份,留一后手。AOF出问题了,还有RDB。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..] |
订阅给定的一个或多个频道。 |
SUBSCRIBE channel [channel..] |
退订一个或多个频道 |
测试:
#订阅端
127.0.0.1:6379> subscribe channel #订阅一个名为channel的频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel"
3) (integer) 1
#等待读取消息
1) "message" #消息
2) "channel" #哪个频道的消息
3) "nihao" #消息的内容
#发送端
127.0.0.1:6379> publish channel nihao #发布消息到频道
(integer) 1
127.0.0.1:6379> pubsub channels #查看活跃的频道
1) "channel"
2) "__sentinel__:hello" #哨兵模式
原理
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
应用
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
作用
1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
2.故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
3.负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
4.高可用基石:主从复制还是哨兵和集群能够实施的基础。
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机),原因如下:
1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较
大;
2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有
内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。
电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。
对于这种场景,我们可以使如下两种架构:
一主三从
层层链路(级联结果)
主从复制,读写分离! 80% 的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用! 一主二从!
只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis!
同步原理:
Slave 启动成功连接到 master 后会发送一个sync同步命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制
:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制
:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!
具体步骤如下:
Bgsave 命令用于在后台异步保存当前数据库的数据到磁盘。
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
模拟3个redis-server,所以需要3个redis.conf,每个配置文件需要修改以下信息:
默认都是主节点,所以只用配置从机就好了
认老大!一主(79)二从(80,81)
从机使用slaveof host prot
即可配置主机
主机信息
这是使用命令搭建的,服务端重启就失效,真实环境是修改配置文件的,配置文件是永久的
使用规则
1.从机只能读,不能写,主机可读可写但是多用于写。
127.0.0.1:6379> set k1 v1 # 主机写
OK
127.0.0.1:6380> get k1 # 从机读
"v1"
127.0.0.1:6381> get k1
"v1"
127.0.0.1:6381> set ks2 v2 # 从机不能写
(error) READONLY You can't write against a read only replica.
2.当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
3.当从机断电宕机后恢复,如果是配置文件配置的,就可以获取到主机的所有数据;命令配置的,需要重新配置成从机。
4.默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
slaveof no one
,选举出一个新的主机,其他从机再连接到这个主机,很麻烦主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例
。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
测试
使用的是一主二从
1.创建哨兵配置文件 sentinel.conf
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
后面的这个数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机!
2.启动哨兵
[root@iZ8vbiui8f5xtbf8jb9j1wZ bin]# redis-sentinel kconfig/sentinel.conf
3262:X 11 May 2021 21:31:06.608 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3262:X 11 May 2021 21:31:06.609 # Redis version=6.2.3, bits=64, commit=00000000, modified=0, pid=3262, just started
3262:X 11 May 2021 21:31:06.609 # Configuration loaded
3262:X 11 May 2021 21:31:06.609 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.3 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 3262
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
3262:X 11 May 2021 21:31:06.610 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
3262:X 11 May 2021 21:31:06.615 # Sentinel ID is 803214dc0ea094b1a5b544443d0efc6c78e7eb80
3262:X 11 May 2021 21:31:06.615 # +monitor master myredis 127.0.0.1 6379 quorum 1
3262:X 11 May 2021 21:31:06.616 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
3262:X 11 May 2021 21:31:46.763 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
此时断开主机
如果主机此时回来了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则!
哨兵模式
优点:
1.哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
2.主从可以切换,故障可以转移,系统的可用性更好
3.哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
1.Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
2.实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
哨兵模式的全部配置
# 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
sentinel monitor mymaster 127.0.0.1 6379 1
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs
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
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
#
# 目前总是“failover”,
# 是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
概念:
key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据库,造成数据库极大的压力,就有可能压垮数据库。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
解决方案:
1.布隆过滤器
将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
2.缓存空对象
如果一个查询请求返回的数据为空(缓存和数据库中都没找到),我们仍然把这个空结果进行缓存用于处理后续这个请求,但它的过期时间会很短,最长不超过五分钟。
这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
概念
key对应的数据存在,但在缓存过期的一刻,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。
比如热搜排行上,某某明星出轨了,缓存正好过期时,还有几百万人点击,直接冲垮数据库。
解决方案
1.设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
2.加互斥锁(分布式锁)
就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用setnx去set一个mutex key(互斥锁),当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}
概念
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建集群
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列
来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中
。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间
,让缓存失效的时间点尽量均匀。