目录
一.为什么要用NoSQL ?
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况!
二.什么是Nosql
NoSQL = Not Only SQL(不仅仅是SQL)
Not Only Structured Query Language
关系型数据库:列+行,同一个表下数据的结构是一样的。
非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。
NoSQL泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的。
Nosql特点
-
方便扩展(数据之间没有关系,很好扩展!)
-
大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
-
数据类型是多样型的!(不需要事先设计数据库,随取随用)
-
传统的 RDBMS 和 NoSQL
传统的 RDBMS(关系型数据库)
- 结构化组织
- SQL
- 数据和关系都存在单独的表中 row col
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
- ...
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展
- ...
三.Redis入门
Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务。
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis能该干什么?
- 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
- 高效率、用于高速缓冲
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(eg:浏览量)等
特性
-
多样的数据类型
-
持久化
-
集群
-
事务
环境搭建
官网:https://redis.io/
推荐使用Linux服务器学习。
windows版本的Redis已经停更很久了…
1.解压安装包
2.开启redis-server.exe
3.启动redis-cli.exe测试
Linux安装
-
下载安装包!
redis-5.0.8.tar.gz
-
解压Redis的安装包!程序一般放在
/opt
目录下 -
基本环境安装
yum install gcc-c++ # 然后进入redis目录下执行 make # 然后执行 make install
.
-
redis默认安装路径
/usr/local/bin
-
将redis的配置文件复制到 程序安装目录
/usr/local/bin/kconfig
下 -
redis默认不是后台启动的,需要修改配置文件!
-
通过制定的配置文件启动redis服务
-
使用redis-cli连接指定的端口号测试,Redis的默认端口6379
-
关闭Redis服务
shutdown
测试性能
**redis-benchmark:**Redis官方提供的性能测试工具,参数选项如下:
基础知识
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的速度如此快呢,性能这么高呢?QPS达到10W+
Redis为什么单线程还这么快?
- 误区1:高性能的服务器一定是多线程的?
- 误区2:多线程(CPU上下文会切换!)一定比单线程效率高!
核心:Redis是将所有的数据放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存存储数据情况下,单线程就是最佳的方案。
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 *
1) "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
RENAME key newkey
修改 key 的名称RENAMENX key newkey
仅当 newkey 不存在时,将 key 改名为 newkey 。
五大类型和方法:
String
APPEND key value | 向指定的key的value后追加字符 |
DECR/INCR key | 将指定key的value数值进行+1/-1(仅对于数字) |
INCRBYFLOAT key n | 为数值加上浮点型数值 |
STRLEN key | 获取key保存值的字符串长度 |
GETRANGE key start end | 按起止位置获取字符串(闭区间,起止位置都取) |
SETRANGE key offset value | 用指定的value 替换key中 offset开始的值 |
SETNX key value | 仅当key不存在时进行set |
SETEX key seconds value | set 键值对并设置过期时间 |
MSET key1 value1 [key2 value2..] | 批量set键值对 |
MSETNX key1 value1 [key2 value2..] | 批量设置键值对,仅当参数中所有的key都不存在时执行 |
MGET key1 [key2..] | 批量获取多个key保存的值 |
PSETEX key milliseconds value | SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间 |
getset key value | 如果不存在值,则返回nil,如果存在值,获取原来的值,并设置新的值 |
例子:
1.APPEND key value:
127.0.0.1:6379> set name hello
OK
127.0.0.1:6379> get name
"hello"
127.0.0.1:6379> append name world
(integer) 10
127.0.0.1:6379> get name
"helloworld"
2.DECR/INCR key
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> incr age
(integer) 19
127.0.0.1:6379> incr age
(integer) 20
127.0.0.1:6379> get age
"20"
3.INCRBY/DECRBY
127.0.0.1:6379> incrby age 2
(integer) 22
127.0.0.1:6379> get age
"22"
127.0.0.1:6379> decrby age 2
(integer) 20
127.0.0.1:6379> get age
"20"
4.INCRBYFLOAT key n
127.0.0.1:6379> incrbyfloat age 0.5
"20.5"
127.0.0.1:6379> get age
"20.5"
5.STRLEN key
127.0.0.1:6379> set name chenjunhua
OK
127.0.0.1:6379> get name
"chenjunhua"
127.0.0.1:6379> strlen name
(integer) 10
127.0.0.1:6379>
6.GETRANGE key start end
127.0.0.1:6379> get name
"chenjunhua"
127.0.0.1:6379> getrange name 1 3
"hen"
127.0.0.1:6379> getrange name 0 -1 //获取全部的字符
"chenjunhua"
7.SETRANGE key offset value
127.0.0.1:6379> getrange name 0 -1
"chenjunhua"
127.0.0.1:6379> setrange name 1 xxx
(integer) 10
127.0.0.1:6379> get name
"cxxxjunhua"
8.SETNX key value
127.0.0.1:6379> get name
"hua"
127.0.0.1:6379> setnx name hua
(integer) 0
127.0.0.1:6379> setnx name1 hua
(integer) 1
127.0.0.1:6379> get name1
"hua"
9.SETEX key seconds value
127.0.0.1:6379> setex name 10 aini
OK
127.0.0.1:6379> ttl name
(integer) 7
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 5
127.0.0.1:6379> get name
"aini"
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
10.MSET key1 value1 [key2 value2..]
11.MGET key1 [key2..]
127.0.0.1:6379> mset name1 hua name2 bo name3 gou
OK
127.0.0.1:6379> mget name1 name2 name3
1) "hua"
2) "bo"
3) "gou"
12.MSETNX key1 value1 [key2 value2..]
127.0.0.1:6379> msetnx name1 1 name2 2 name3 3
(integer) 0
127.0.0.1:6379> msetnx name4 1 name5 2 name6 3
(integer) 1
127.0.0.1:6379> mget name4 name5 name6
1) "1"
2) "2"
3) "3"
13.PSETEX key milliseconds(毫秒) value
127.0.0.1:6379> psetex name 10 hua
OK
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
14.getset key value
127.0.0.1:6379> set name hua
OK
127.0.0.1:6379> getset name shen
"hua"
127.0.0.1:6379> get name
"shen"
List:
- 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功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
- ltirm:修剪 list集合 但是会改变原有的list
- Rpoplpush source destination
- lset listname index exchange :改变listname这个集合的index元素为 exchange 前提是listname这个集合存在
- Linsert mylist before “world”“other”:在mylist这个集合中的 "world"元素之前插入 “other”元素
- Linsert mylist after“world”“new”:在mylist这个集合中的 "world"元素之后插入 “new”元素
- 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 - …
- 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]:在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分
- smove myset myset2:xxx 将myset中的xxx 移动到 myset2中
---------------------------LPUSH---RPUSH---LRANGE--------------------------------
127.0.0.1:6379> LPUSH mylist k1 # LPUSH mylist=>{1}
(integer) 1
127.0.0.1:6379> LPUSH mylist k2 # LPUSH mylist=>{2,1}
(integer) 2
127.0.0.1:6379> RPUSH mylist k3 # RPUSH mylist=>{2,1,3}
(integer) 3
127.0.0.1:6379> get mylist # 普通的get是无法获取list值的
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> LRANGE mylist 0 4 # LRANGE 获取起止位置范围内的元素
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> LRANGE mylist 0 2
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> LRANGE mylist 0 1
1) "k2"
2) "k1"
127.0.0.1:6379> LRANGE mylist 0 -1 # 获取全部元素
1) "k2"
2) "k1"
3) "k3"
---------------------------LPUSHX---RPUSHX-----------------------------------
127.0.0.1:6379> LPUSHX list v1 # list不存在 LPUSHX失败
(integer) 0
127.0.0.1:6379> LPUSHX list v1 v2
(integer) 0
127.0.0.1:6379> LPUSHX mylist k4 k5 # 向mylist中 左边 PUSH k4 k5
(integer) 5
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "k1"
5) "k3"
---------------------------LINSERT--LLEN--LINDEX--LSET----------------------------
127.0.0.1:6379> LINSERT mylist after k2 ins_key1 # 在k2元素后 插入ins_key1
(integer) 6
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "ins_key1"
5) "k1"
6) "k3"
127.0.0.1:6379> LLEN mylist # 查看mylist的长度
(integer) 6
127.0.0.1:6379> LINDEX mylist 3 # 获取下标为3的元素
"ins_key1"
127.0.0.1:6379> LINDEX mylist 0
"k5"
127.0.0.1:6379> LSET mylist 3 k6 # 将下标3的元素 set值为k6
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "k6"
5) "k1"
6) "k3"
---------------------------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
1) "k4"
2) "k2"
3) "k6"
4) "k1"
127.0.0.1:6379> RPOPLPUSH mylist newlist # 将mylist的最后一个值(k1)弹出,加入到newlist的头部
"k1"
127.0.0.1:6379> LRANGE newlist 0 -1
1) "k1"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"
3) "k6"
---------------------------LTRIM--------------------------
127.0.0.1:6379> LTRIM mylist 0 1 # 截取mylist中的 0~1部分
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "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: k1
127.0.0.1:6379> BLPOP newlist mylist 30 # 从newlist中弹出第一个值,mylist作为候选
1) "newlist" # 弹出
2) "k1"
127.0.0.1:6379> BLPOP newlist mylist 30
1) "mylist" # 由于newlist空了 从mylist中弹出
2) "k2"
127.0.0.1:6379> BLPOP newlist 30
(30.10s) # 超时了
127.0.0.1:6379> BLPOP newlist 30 # 我们连接另一个客户端向newlist中push了test, 阻塞被解决。
1) "newlist"
2) "test"
(12.54s)
-
list实际上是一个链表,before Node after , left, right 都可以插入值
-
如果key不存在,则创建新的链表
-
如果key存在,新增内容
-
如果移除了所有值,空链表,也代表不存在
-
在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
Set(集合)
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
eg:
---------------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 # 获取集合中所有成员
1) "m4"
2) "m3"
3) "m2"
4) "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个成员
1) "m2"
2) "m3"
3) "m4"
127.0.0.1:6379> SRANDMEMBER myset # 随机返回1个成员
"m3"
127.0.0.1:6379> SPOP myset 2 # 随机移除并返回2个成员
1) "m1"
2) "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
1) "m4"
2) "m2"
3) "m1"
127.0.0.1:6379> SMEMBERS newset
1) "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
1) "m4"
127.0.0.1:6379> SDIFF setx sety # setx - sety
1) "m4"
2) "m1"
127.0.0.1:6379> SDIFF sety setx # sety - setx
1) "m5"
-------------------------SINTER---------------------------------------
# 共同关注(交集)
127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集
1) "m6"
127.0.0.1:6379> SINTER setx sety # 求setx sety的交集
1) "m2"
2) "m6"
-------------------------SUNION---------------------------------------
127.0.0.1:6379> SUNION setx sety setz # setx sety setz的并集
1) "m4"
2) "m6"
3) "m3"
4) "m2"
5) "m1"
6) "m5"
127.0.0.1:6379> SUNION setx sety # setx sety 并集
1) "m4"
2) "m6"
3) "m2"
4) "m1"
5) "m5"
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中所有的字段 |
HLEN key | 获取哈希表中字段的数量 |
HVALS key | 获取哈希表中所有值 |
HDEL key field1 [field2..] | 删除哈希表key中一个/多个field字段 |
HINCRBY key field n | 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段 |
HINCRBYFLOAT key field n | 为哈希表 key 中的指定字段的浮点数值加上增量 n。 |
1.HSET key field value
2.HGET key field value
127.0.0.1:6379> hset map name hua
127.0.0.1:6379> hget map name
"hua"
3.HMSET key field1 value1 [field2 value2..]
127.0.0.1:6379> hset user name hua age 18
(integer) 2
127.0.0.1:6379> hget user name
"hua"
127.0.0.1:6379> hget user age
"18"
4.HSETNX key field value
127.0.0.1:6379> HSETNX map name shen //map中的name已经存在所以设置失败
(integer) 0
127.0.0.1:6379> hget map name
"hua"
5.HEXISTS key field
127.0.0.1:6379> HEXISTS map name
(integer) 1
6.HMGET key field1 [field2..]
127.0.0.1:6379> hmget user name age
1) "hua"
2) "18"
7.HGETALL key
127.0.0.1:6379> hgetall user
1) "name"
2) "hua"
3) "age"
4) "18"
8.HKEYS key
127.0.0.1:6379> hkeys user
1) "name"
2) "age"
9.HLEN key
127.0.0.1:6379> hlen user
(integer) 2
127.0.0.1:6379> hlen name
(integer) 0
127.0.0.1:6379> hlen user name //无法查看集合中的属性的len
(error) ERR wrong number of arguments for 'hlen' command
10.HVALS key
127.0.0.1:6379> hvals user
1) "hua"
2) "18"
11.HDEL key field1 [field2..]
127.0.0.1:6379> hdel user name age
(integer) 2
127.0.0.1:6379> hgetall user
(empty array)
12.HINCRBY key field n
127.0.0.1:6379> hset ages age 18
(integer) 1
127.0.0.1:6379> hincrby ages age 1
(integer) 19
127.0.0.1:6379> hincrby ages age 2
(integer) 21
127.0.0.1:6379> hget ages age
"21"
13.HINCRBYFLOAT key field n
127.0.0.1:6379> hincrbyfloat ages age 2.1
"23.1"
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 移除有序集合中给定的排名区间的所有成员
- 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 中
1.zadd
2.zrange
127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3.ZRANGEBYSCORE 排序从小到大
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 25000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 3000 huahua
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "xiaohong"
2) "huahua"
3) "zhangsan"
从高到低:zrevrange xxx 0 -1
127.0.0.1:6379> zrevrange salary 0 -1
1) "zhangsan"
2) "huahua"
4.ZRANGEBYSCORE xxx -inf +inf withscores
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "xiaohong"
2) "2500"
3) "huahua"
4) "3000"
5) "zhangsan"
6) "25000"
5.ZRANGEBYSCORE salary -inf(x) 2500(y) withscores (负无穷-2500) [X,Y]
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores
1) "xiaohong"
2) "2500"
6.zrem x y //在x中移除y 移除集合中指定的元素
127.0.0.1:6379> zrange salary 0 -1
1) "xiaohong"
2) "huahua"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "huahua"
2) "zhangsan"
7.ZCARD salary:获取有序集合中的个数
127.0.0.1:6379> ZCARD salary
(integer) 2
8.zcount XXX start end :获取指定区间的元素个数
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127.0.0.1:6379> zcount myset 1 3
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
127.0.0.1:6379> zcount myset 1 1
(integer) 1
127.0.0.1:6379> zcount myset 1 0
(integer) 0
其他的如果工作需要,可以去官方查api。
应用案例:
- set排序 存储班级成绩表 工资表排序!
- 普通消息,1.重要消息 2.带权重进行判断
- 排行榜应用实现,取Top N测试
三种特殊数据类型
Geospatial(地理位置)
使用经纬度定位地理坐标并用一个有序集合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点整数编码。 |
127.0.0.1:6379> GEOADD china:city 113.23 23.16 guangzhou
(integer) 1
127.0.0.1:6379> GEOADD china:city 114.07 22.62 shenzhen
(integer) 1
127.0.0.1:6379> GEOADD china:city 117.33 22.98 haifeng
(integer) 1
127.0.0.1:6379> GEOADD china:city 116.46 39.92 beijing
(integer) 1
127.0.0.1:6379> GEOADD china:city 116.46 39.92 beijing
(integer) 0
127.0.0.1:6379> GEOADD china:city 106.54 29.59 chongqing
(integer) 1
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.45999997854232788"
2) "39.9199990416181052"
127.0.0.1:6379> GEOpos china:city guangzhou
1) 1) "113.22999805212020874"
2) "23.1599994376353493"
127.0.0.1:6379> GEODIST china:city beijing guangzhou km
"1888.7510"
127.0.0.1:6379> GEODIST china:city guangzhou chongqing km
"977.2393"
127.0.0.1:6379> GEORADIUS china:city 113.23 23.16 5000 km withdist withcoord
1) 1) "chongqing"
2) "977.2394"
3) 1) "106.54000014066696167"
2) "29.58999896356930748"
2) 1) "guangzhou"
2) "0.0002"
3) 1) "113.22999805212020874"
2) "23.1599994376353493"
3) 1) "shenzhen"
2) "104.9567"
3) 1) "114.07000154256820679"
2) "22.61999990712862285"
4) 1) "haifeng"
2) "420.0214"
3) 1) "117.33000129461288452"
2) "22.97999874922604846"
5) 1) "beijing"
2) "1888.7509"
3) 1) "116.45999997854232788"
2) "39.9199990416181052"
127.0.0.1:6379> GEORADIUS china:city 113.23 23.16 500 km withdist withcoord
1) 1) "guangzhou"
2) "0.0002"
3) 1) "113.22999805212020874"
2) "23.1599994376353493"
2) 1) "shenzhen"
2) "104.9567"
3) 1) "114.07000154256820679"
2) "22.61999990712862285"
3) 1) "haifeng"
2) "420.0214"
3) 1) "117.33000129461288452"
2) "22.97999874922604846"
127.0.0.1:6379> GEOHASH china:city guangzhou beijing
1) "ws0eb85sf00"
2) "wx4g455wfe0"
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "guangzhou"
3) "shenzhen"
4) "haifeng"
5) "beijing"
127.0.0.1:6379> zrem china:city beijing
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "guangzhou"
3) "shenzhen"
4) "haifeng"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city guangzhou 200 km
1) "guangzhou"
2) "shenzhen"
有效经纬度
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
指定单位的参数 unit 必须是以下单位的其中一个:
-
m 表示单位为米。
-
km 表示单位为千米。
-
mi 表示单位为英里。
-
ft 表示单位为英尺。
关于GEORADIUS的参数
通过georadius就可以完成 附近的人功能
withcoord:带上坐标
withdist:带上距离,单位与半径单位相同
COUNT n : 只显示前n个(按距离递增排序)
Hyperloglog(基数统计)
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
其底层使用string数据类型
什么是基数?
基数是数据集中不重复的元素的个数。
应用场景:网页的访问量(UV):一个用户多次访问,也只能算作一个人
传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。
127.0.0.1:6379> pfadd name1 a b c d e f g h i j
(integer) 1
127.0.0.1:6379> pfadd name a b y j k l i o p n x c l i p
(integer) 1
127.0.0.1:6379> PFCOUNT name1
(integer) 10
127.0.0.1:6379> PFCOUNT name
(integer) 12
127.0.0.1:6379> PFMERGE name2 name1 name2
OK
127.0.0.1:6379> PFCOUNT name2
(integer) 10
如果允许容错,那么一定可以使用Hyperloglog !
如果不允许容错,就使用set或者自己的数据类型即可 !
BitMaps(位图)
使用位存储,信息状态只有 0 和 1
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。
应用场景
签到统计、状态统计,登录,未登录等状态为2个的场景,按位运算来存储 只有0 和1
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只能按字节,不能按位 |
------------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
string
127.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)一次性完成。
multi | 开启事务 |
discard | 放弃事务 |
exec | 执行任务 |
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)> get v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) (nil)
4) "v2"
##################################
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)> DISCARD
OK
编译性错误:
代码错误,一旦有错误,整个事务队列里等待被执行的任务均无效
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)> get k2
QUEUED
127.0.0.1:6379(TX)> getset k2
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
运行时错误:
**其他命令可以正常执行 ** >>> 所以不保证事务原子性
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)> get k2
QUEUED
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v2"
4) (error) ERR value is not an integer or out of range
监控
悲观锁:
很悲观,认为什么时候都会出现问题,无论做什么都会加锁
乐观锁:
- 很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
- 获取version
- 更新的时候比较version
使用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)> INCRby money -10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 90
2) (integer) 10
模拟错误:
127.0.0.1:6379> FLUSHdb
OK
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> mutil
OK
127.0.0.1:6379(TX)> INCRby money 10
QUEUED
127.0.0.1:6379(TX)> DECRBY out 10
QUEUED
##############
在这里执行之前加一个线程(开多一个客户端来修改值)
127.0.0.1:6379> set money 100
OK
##############
127.0.0.1:6379(TX)> exec
(nil)
这时就要解锁获取最新值,然后再加锁进行事务。
用unwatch
进行解锁。
127.0.0.1:6379> FLUSHdb
OK
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> mutil
OK
127.0.0.1:6379(TX)> INCRby money 10
QUEUED
127.0.0.1:6379(TX)> DECRBY out 10
QUEUED
##############
在这里执行之前加一个线程(开多一个客户端来修改值)
127.0.0.1:6379> set money 100
OK
##############
127.0.0.1:6379(TX)> exec
(nil)
#############
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> deCR money
QUEUED
127.0.0.1:6379(TX)> incr out
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 99
2) (integer) 1
##############
注意:
每次提交执行exec后都会自动释放锁,不管是否成功
六、Jedis
使用Java来操作Redis,Jedis是Redis官方推荐使用的Java连接redis的客户端。
1.导入依赖
<!--导入jredis的包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
指令与我们在linux系统下的完全一模一样
这里简单举几个例子
@Test
void contextLoads() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String set = jedis.set("name", "傻狗");
String user = jedis.mset("user:1:name", "chenjunhua","user:1:age","18");
System.out.println(jedis.mget("user:1:name","user:1:age"));
System.out.println(jedis.get("name"));
jedis.lpush("name1","1");
System.out.println(jedis.lpop("name1"));
}
3.事务
@Test
void test(){
Jedis jedis = new Jedis("127.0.0.1", 6379);
JSONObject j = new JSONObject();
j.put("hello", "world");
j.put("name", "hua");
Transaction multi = jedis.multi();
String s = j.toJSONString();
try {
//开启事务时不能用jedis.set;
multi.set("user1",s);
multi.set("user2",s);
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
注意:
//开启事务时不能用jedis.set;否则报错
模拟错误:
void test(){
Jedis jedis = new Jedis("127.0.0.1", 6379);
JSONObject j = new JSONObject();
j.put("hello", "world");
j.put("name", "hua");
Transaction multi = jedis.multi();
String s = j.toJSONString();
try {
//开启事务时不能用jedis.set;
multi.set("user1",s);
multi.set("user2",s);
int a = 1/0 ;
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
如果模拟之前不flushdb,结果不变 但是会报错
flushdb后:
void test(){
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject j = new JSONObject();
j.put("hello", "world");
j.put("name", "hua");
Transaction multi = jedis.multi();
String s = j.toJSONString();
try {
//开启事务时不能用jedis.set;
multi.set("user1",s);
multi.set("user2",s);
int a = 1/0 ;
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
监控的测试失败,有待研究
SpringBoot集成redis
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
根据springboot的源码分析中,这里回有一个redisAutoConfigration 和一个redisProperties配置类
我们可以看出我们要集成redis就要在application.properties中配置相关信息
配置:
spring.redis.port=6379
spring.redis.host=127.0.0.1
在这里如果你没有自己配置RedisTemplate的模板,那么你无法在redis-cli中获取在idea中设置的key的值
测试:注释掉自己配置的类
@Test
void contextLoads() {
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
connectionFactory.getConnection().flushDb();
redisTemplate.opsForValue().set("mykey", "chenjunhua");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
所以在我们没有自定义配置之前用这种方式是获取不到的,同样的在redis-cli中设置的idea中也获取不到,但是可以用keys * 获取到(idea中获取不到,下面举例),但是如果你没有配置序列化,那么默认使用jdk的序列化,这样就会乱码
比如:
@Test
void contextLoads() {
// RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
// connectionFactory.getConnection().flushDb();
redisTemplate.opsForValue().set("mykey", "chenjunhua");
System.out.println(redisTemplate.opsForValue().get("mykey"));
System.out.println(redisTemplate.opsForValue().get("name"));
System.out.println(redisTemplate.keys("*"));
}
所以当我们配置了自己定义的RedisTemplate的模板就正常使用了:
@Test
void contextLoads() {
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
connectionFactory.getConnection().flushDb();
redisTemplate.opsForValue().set("mykey", "chenjunhua");
System.out.println(redisTemplate.opsForValue().get("mykey"));
// System.out.println(redisTemplate.opsForValue().get("name"));
// System.out.println(redisTemplate.keys("*"));
}
redis-cli中:
再来:
idea中:
@Test
void contextLoads() {
// RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
// connectionFactory.getConnection().flushDb();
// redisTemplate.opsForValue().set("mykey", "chenjunhua");
System.out.println(redisTemplate.opsForValue().get("mykey"));
System.out.println(redisTemplate.opsForValue().get("name"));
System.out.println(redisTemplate.keys("*"));
}
完美成功!!
八、自定义Redis工具类
使用RedisTemplate需要频繁调用.opForxxx然后才能进行对应的操作,这样使用起来代码效率低下,工作中一般不会这样使用,而是将这些常用的公共API抽取出来封装成为一个工具类,然后直接使用工具类来间接操作Redis,不但效率高并且易用。可以去参考别的博客直接复制工具类,或者自己定义。
Redis.conf
容量单位不区分大小写,G和GB有区别
单位没有大小写之分
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
可以使用 include 组合多个配置问题
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf
绑定端口号,如果你想要你的redis都可以访问的话 可以 bind *
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
# JUST COMMENT OUT THE FOLLOWING LINE.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bind 127.0.0.1 -::1
保护模式和端口号配置
#
# By default protected mode is enabled. You should disable it only if
# you are sure you want clients from other hosts to connect to Redis
# even if no authentication is configured, nor a specific set of interfaces
# are explicitly listed using the "bind" directive.
protected-mode yes 保护模式启动
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379 配置端口号
持久化规则
由于Redis是基于内存的数据库,需要将数据由内存持久化到文件中
持久化方式:
- RDB
- AOF
快照
save 3600 1 : 当3600s内 操作了一个数据 进行快照持久化,生成.rdb文件
save 300 100 300 100
save 60 10000 60 10000
# save 60 5 自己配置的
日志输出级别
日志输出文件
RDB文件相关
Security模块中进行密码设置
客户端连接相关
maxclients 10000 最大客户端数量
maxmemory <bytes> 最大内存限制
maxmemory-policy noeviction # 内存达到限制值的处理策略
设置方式:
config set maxmemory-policy volatile-lru
maxmemory-policy 六种方式:
- volatile-lru:**只对设置了过期时间的key进行LRU(默认值)
- allkeys-lru : 删除lru算法的key
- volatile-random:**随机删除即将过期key
- allkeys-random:**随机删除
- volatile-ttl : 删除即将过期的
- noeviction : 永不过期,返回错误
AOF相关部分
持久化
持久化—RDB
什么是RDB :在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;
默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。
工作原理
在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,主线程会 fork 一个子线程来完成该操作;
- Redis 调用forks。同时拥有父进程和子进程。
- 子进程将数据集写入到一个临时 RDB 文件中。
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)
触发机制
- save的规则满足的情况下,会自动触发rdb原则
- 执行flushall命令,也会触发我们的rdb原则
- 退出redis,也会自动产生rdb文件
save
- 使用
save
命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了; - 由于
save
命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save
命令执行速度会非常慢,阻塞所有客户端的请求。
flushall命令
flushall
命令也会触发持久化
触发持久化规则
满足配置条件中的触发条件 ;
可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作。
bgsave
bgsave
是异步进行,进行持久化的时候,redis
还可以将继续响应客户端请求 ;
bgsave和save对比
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
- fork进程的时候,会占用一定的内容空间。
持久化AOF
什么是AOF
快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
如果要使用AOF,需要修改配置文件:
appendonly no yes则表示启用AOF
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix
优点
- 每一次修改都会同步,文件的完整性会更加好
- 没秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
RDB和AOP选择
RDB 和 AOF 对比
RDB | AOF | |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
如何选择使用哪种持久化方式?
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
十三、Redis发布与订阅
当有新消息通过 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 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
1) "sakura"
原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
缺点:
如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
应用
- 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
- 多人在线聊天室。
Redis主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础。
为什么使用集群
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
环境配置
我们在讲解配置文件的时候,注意到有一个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
既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:
- 端口号
- pid文件名
- 日志文件名
- rdb文件名
2.拷贝多个redis.conf 文件
然后修改配置文件:
指定端口 6379,依次类推
开启daemonize yes
Pid文件名字 pidfile /var/run/redis_6379.pid , 依次类推
Log文件名字 logfile "6379.log" , 依次类推
Dump.rdb 名字 dbfilename dump6379.rdb , 依次类推
上面都配置完毕后,3个服务通过3个不同的配置文件开启,我们的准备环境就OK 了!
启动单机多服务集群:
一主二从配置
==默认情况下,每台Redis服务器都是主节点;==我们一般情况下只用配置从机就好了!
认老大!一主(79)二从(80,81)
使用SLAVEOF host port
就可以为从机配置主机了
然后主机上也能看到从机的状态:
主机写入:
从机读取:
如果你想要在从机写入:
但是你在主机仍让是可以读的,只不过不会这么做:
我们这里是使用命令搭建,是暂时的,==真实开发中应该在从机的配置文件中进行配置,==这样的话是永久的。
使用规则
-
从机只能读,不能写,主机可读可写但是多用于写。
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"
-
当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
-
当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
-
第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
- 从机手动执行命令
slaveof no one
,这样执行以后从机会独立出来成为一个主机 - 使用哨兵模式(自动选举)
- 从机手动执行命令
如果没有老大了,这个时候能不能选择出来一个老大呢?手动!
如果主机断开了连接,我们可以使用SLAVEOF no one
让自己变成主机!其他的节点就可以手动连接到最新的主节点(手动)!如果这个时候老大修复了,那么久重新连接!
哨兵模式
**主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。*这不是一种推荐的方式,更多时候,我们优先考虑*哨兵模式。
单机单个哨兵:
主从切换技术方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这需要人工干预,还会造成一段时间内服务不可用,Redis从2.8开始正式提供了Sentinel(哨兵模式)来解决这个问题
主从切换自动版,Redis提供一个哨兵的命令,哨兵是一个独立的进程,独立运行的,哨兵会通过发送命令,等待Redis服务器响应,从而接口运行中的多个Redis实例,如果主机宕机了,会根据投票票数最多的从机,将其自动切换为主机
多哨兵模式:
假设主服务器宕机,哨兵1先检测到这个结果,系统并且不会马上进行failover过程,仅仅是哨兵1主观认为主服务器不可用,这现象称为主观下线,当其它哨兵也检测到主服务不可用,并且数量达到一定值,那么这些哨兵之间就会进行一次投票,投票的由一个哨兵发起,进行failover(故障转移)操作,切换成功后,就会通过发布订阅模式,让各哨兵把自己监控的从服务器实现切换主机客观下线
哨兵的作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
哨兵的核心配置:sentinel monitor mymaster 127.0.0.1 6379 1
测试
redis-sentinel xxx/sentinel.conf:xxx为你放置sentinel.conf的目录
成功启动哨兵模式
此时哨兵监视着我们的主机6379,当我们把主机断开:
当我们又连上6379后:
6379变成了 客机,6381谋权篡位成功。
优点:
- 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
- 主从可以切换,故障可以转移,系统的可用性更好
- 哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
完整的哨兵模式配置文件 sentinel.conf
# 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>是“leader”或者“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)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
缓存击穿(量太大,缓存过期)
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方案:
- 设置热点数据永不过期
- 这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
- 加互斥锁(分布式锁)
- 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
缓存雪崩
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
redis高可用
- 这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
限流降级
- 这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
- 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。