Redis入门
狂神学Java
历史
- 优化数据结构和索引
- 文件缓存----通过IO流获取比每次访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了
- Memcached:通过再数据库和数据访问层之间再加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提高
早年MYISAM:表锁(查询一行数据将一张表锁起来),十分影响效率
早些年Innodb:行锁(每次查数据只锁一行)
慢慢的就开始使用分库表来解决写的压力
Nosql
Nosql=Not Only SQL
NOT Only Structured Query Language
关系型数据库:行+列,同一个表下数据的结构是一样的
非关系型数据库:数据储存没有固定的格式,并且可以横向扩展
Nosql泛指非关系型数据库,
NoSQL特点
-
方便扩展(数据之间没有关系,很好扩展)
-
大数据量高性能(Redis一秒可以写8万次,读写11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)
-
数据类型是多样性的(不需要先设计数据库,随去随用)
-
传统的RDBMS和NoSQL
传统的RDBMS(关系型数据库) 结构化组织 SQL 数据和关系都存再单独的表中 row col 操作,数据定义语言 严格的一致性 基础的食物
NoSQL 不仅仅是数据 没有固定的查询语句 键值对存储,列储存,文档储存,图形数据库 最终一致性 CAP定理和BASE 高性能,高可用,高扩展
大数据时代的3v:主要描述问题的
- 海量的Velume
- 多样Variety
- 实时Velocity
大数据时代的3高:主要是对程序的要求
- 高并发
- 高可扩
- 高性能
真正的公司中的实践:NoSQL+RDBMS 一起使用才是最强的
商品信息 一般存放在关系型数据库:Mysql 商品描述,评论 文档型数据库:MongoDB 图片 分布式文件系统:FastDFS 淘宝:TFS Goolge:GFS Hadoop:HDFS 阿里云:oss # 商品关键字 搜索引擎:solr,elasticsearch 阿里:Isearch,多隆 #商品热门的波段信息 内存数据库:Redis,Memcache #商品交易,外部支付接口 第三方应用
NoSQL的四大分类
-
KV键值对
- 新浪:Redis
- 美团:Redis+Tair
- 阿里,百度:Redis+memcache
-
文档型数据库(bson数据格式)
-
MongoDB
基于分布式文件存储的数据库,c++编写,用于处理大量的文档
MongoDB是RDBMS和NoSQL的中间产品,MongoDB是非关系型数据库中功能最丰富的
-
ConthDB
-
-
列存储数据库
- Hbase
- 分布式文件管理系统
-
图关系数据库
用于广告推广,社交网络
Neoconj,InfoGrid
Redis入门
概述
Redis(Remote Dictionary Server),远程字典服务
是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言的API
与memcached一样,为了保证效率,数据都是缓存在内存中,区别的是Redis会周期的把更新的数据写入硬盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步
能干什么
- 内存存储,持久化,内存是断电即逝的,所以需要持久化
- 高效率,用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器,计数器
- 。。。。。
特性
- 多样化的数据类型
- 持久化
- 集群
- 事务
Redis具体
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16
16个数据库
默认使用第0个
使用 select n 切换数据库
127.0.0.1:6379> config get database #命令行查看数据库数量
(empty list or set)
127.0.0.1:6379> select 8 #切换数据库
OK
127.0.0.1:6379[8]> dbsize #查看数据库大小
(integer) 0
127.0.0.1:6379[8]> set name hou #输入数据
OK
127.0.0.1:6379[8]> select 8
OK
127.0.0.1:6379[8]> get name #不能获取其他数据库数据
"hou"
127.0.0.1:6379[8]> dbsize #size与key相关
(integer) 1
127.0.0.1:6379[8]> select 0
OK
127.0.0.1:6379> flushall #清空所有数据库键值对
OK
Redis是单线程,基于内存操作
所以Redis的性能瓶颈不是cpu,而是机器内存和网络带宽
Redis是基于C语言写的
为什么Redis这么快
误区:1. 高性能的服务器都是多线程的
- 多线程一定比单线程效率高
核心:redis是将多有的数据放在内存中的,所以使用单线程操作效率是最高的
,不需要有多线程的上下文切换
对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都在一个cpu上
1. 操作
基本数据类型
基本数据类型 | 方法 | 说明 |
---|---|---|
Redis-key | ||
EXISTS age | 判断key是否存在 | |
move age 1 | 移除key到1号数据库 | |
expire name 10 | 设置key过期时间 | |
ttl name | 查看key的过期剩余时间 | |
type age | 查看key的类型 | |
String | get,set | |
append age age | 追加字符串,如果key不存在就相当于set key | |
strlen age | 获取字符串的长度 | |
incr views | 自增1 | |
decr views | 自减1 | |
incrby views 10 | 设置步长,指定增量 | |
decrby views 5 | ||
getrange key1 0 5 | 截取字符串[0 ,5] | |
getrange key1 0 -1 | 获取所有字符串 | |
setrange key2 1 xs | 替换指定位置字符串 | |
setex key3 30 hello | 设置key3过期时间,30秒后失效 | |
setnx mykey rredis | 不存在的话设置成功,存在设置失败,分布锁中常用 | |
mset k1 v1 k2 v2 k3 v3 | 同时设置多个值 | |
mget k1 k2 k3 | 同时获取多个值 | |
msetnx k1 v1 k4 v4 | 不存在设置失败,证明msetnx为原子性操作 | |
set user:1{name:houquanwei,age:3} | #设置一个user:1 对象值为json字符来保存一个对象 #这里的key:user:{id}:{filed} | |
getset | 先get然后set | |
getset db mongodb | 如果存在,获取原来的值(db),并设置新的值(mongodb) | |
List | lpush,rpush,lpop,rpop | |
lpush list one | 将一个值或者多个值放到头部,左边进到最右边 | |
lrange list 0 1 | 左边开始第0个第1个 | |
rpush list right | 右边进入 | |
lpop list | 移除左边的第一个 | |
rpop list | 移除右边的第一个 | |
lindex list 1 | 根据下标拿值 只能左边 | |
llen list | 列表长度 | |
lrem list 1 222 | 移除一个222值,精确匹配 | |
lrem list 2 two | 移除2个two,批量操作 | |
ltrim mylist 1 2 | 截取指定的长度,通过下标,截断了就修改了列表 | |
rpoplpush list mylist | 移除列表的最后一个元素,将他移动到新的列表中 | |
lset | 将列表中指定下标的值替换成另一个 | |
exists list | 判断列表是否存在 | |
lset list 0 item | 不存在就报错,存在就替换 | |
linsert list before world other | 把other插入world前面 | |
linsert list after world object | 插入world后面 | |
Set | sadd,smembers,sismember | |
sadd myset hello | 向myset集合中添加hello | |
smembers myset | 查看set值 | |
sismember myset hello | 判断myset中有没有hello | |
scard myset | 获取set值的个数 | |
srem myset hou | 移除set集合中的指定元素 | |
srandmember myset | 随机抽取一个元素 | |
srandmember myset 2 | 随机抽出指定个数的元素 | |
spop myset | 随机删除 | |
smove set2 set1 python | 将set2指定元素移到另一个set1,没有就创建一个set1 | |
sdiff set1 set2 | 差集查看不同的,左边set | |
sinter set1 set2 | 交集 | |
sunion set1 set2 | 并集 | |
Hash | Map集合—key- | |
hset hash fields hou | set具体的key-value | |
hget hash fields | get到hou | |
hmset hash field1 quan field1 qei f2 java | 设置多个key-value,qei顶掉quan,key不可重复 | |
hmget hash field1 f2 | 获取多个 | |
hmgetall hash | 获取全部 | |
hdel hash field1 | 删除对应的key-value通过key | |
hlen hash | 获得hash的字段数量(key-value) | |
hexists hash f2 | 判断指定字段是否存在 | |
hkeys hash | 只获取keys | |
hvals hash | 只获取values | |
hincrby hash f3 1 | 指定增量 | |
hsetnx hash f4 hello | 如果不存在则可以设置,存在则不能 | |
Zset | score | |
zadd set1 1 on | 添加 | |
zadd set1 2 to 3 ok | 添加多个 | |
zrange set1 0 -1 | 输出 | |
zrangebyscore set1 -inf +inf | 排序从小到大,从负无穷到正无穷 | |
zrangebyscore set1 -inf +inf withscores | 排序带上score | |
zrangebyscore salary -inf 3800 withscores | 排序配合score | |
zrevrange set1 0 -1 | 从大到小 | |
zrem set1 ok | 移除有序集合中的指定元素 | |
zcard set1 | 获取有序集合中的个数 | |
zcount salary 100 5000 | 判断100 5000之间的值有多少 | |
特殊数据类型
特殊数据类型 | 方法 | 说明 |
---|---|---|
Geospatial | 地理位置,有序集合zset保存 | |
geoadd china:city 121.47 31.23 shanghai | 添加城市信息,超过经纬度就会报错 | |
geopos china:city beijing | 获得指定城市的经纬度 | |
geodist china:city shanghai shenz km | 返回两个地区的距离(千米单位),默认米单位 | |
georadius china:city 120 25 1000 km withcoord withdist | 以(120 25)为范围方圆1000km的城市的经度和维度 | |
GEORADIUSBYMEMBER china:city shanghai 1000 km | 以上海为中心 | |
geohash china:city shanghai | 返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码 | |
zrange china:city 0 -1 | 查看 | |
zrem china:city shanghai | 删除 | |
Hyperloglog(基数统级) | pfadd | 底层String |
pfadd myp a b c d e f g | 添加 | |
pfcount myp | 统计myp基数数量 | |
pfmerge myp mypp | 将多个合并 | |
Bitmaps | 信息状态只有 0 和 1 两个状态的结果就可以用他 | |
setbit sign 0 1 | 在sign上的第0为设置为1 | |
setbit sign 1 1 | 在sign上的第1为设置为1 | |
setbit sign 2 0 | 在sign上的第2为设置为0 | |
getbit sign 2 | 获得sign第二个的值 | |
bitcount sign | 获得 sign为1的个数 | |
高级
Redis 发布订阅(pub/sub) | 消息通信模式 | |
---|---|---|
subscribe hou | 订阅一个频道 | |
publish hou hello | 发布者发布信息到指定频道 | |
PUBSUB subcommand [argument[argument]] | 查看订阅与发布系统状况 | |
PSUBSCRIBE pattern [pattern..] | 订阅一个或多个符合给定模式的频道 | |
PUNSUBSCRIBE pattern [pattern..] | 退订一个或多个符合给定模式的频道。 | |
2.Redis五大数据类型
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
1.Redis-key
在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> EXISTS age #判断key是否存在
(integer) 1
127.0.0.1:6379> move age 1 #移除key到1号数据库
(integer) 1
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "age"
127.0.0.1:6379[1]> move age 0
(integer) 1
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) -1
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> keys name
1) "name"
127.0.0.1:6379> expire name 10 #设置key过期时间
(integer) 1
127.0.0.1:6379> ttl name #查看key的过期剩余时间
(integer) 6
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2 #表示过期
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type age #查看key的类型
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 。
2. String
get ,set 略过
127.0.0.1:6379> type age #查看类型
string
127.0.0.1:6379> append age age #追加字符串,如果key不存在就相当于set key
(integer) 4
127.0.0.1:6379> get age
"1age"
127.0.0.1:6379> strlen age #获取字符串的长度
(integer) 4
127.0.0.1:6379> append age name
(integer) 8
127.0.0.1:6379> get age
"1agename"
127.0.0.1:6379>
#############################################
步长
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views #自减1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10 #设置步长,指定增量
(integer) 11
127.0.0.1:6379> decrby views 5 #与上边相反
(integer) 6
#################################
#range 字符串范围
127.0.0.1:6379> set key1 hello,houquanwei
OK
127.0.0.1:6379> get key1
"hello,houquanwei"
127.0.0.1:6379> getrange key1 0 5 #截取字符串【】
"hello,"
127.0.0.1:6379> getrange key1 0 -1 #获取所有的字符串 和get key一样
"hello,houquanwei"
127.0.0.1:6379>
###############################
#替换
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xs #替换指定位置的字符串
(integer) 7
127.0.0.1:6379> get key2
"axsdefg"
127.0.0.1:6379>
#########################
#setex(set with expire) #设置过期时间
#setnx(set if not exist) #不存在设置 (分布式锁中常常使用)
127.0.0.1:6379> setex key3 30 hello #设置key3过期时间,30秒后失效
OK
127.0.0.1:6379> ttl key3
(integer) 26
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey rredis #不存在的话设置成功
(integer) 1
127.0.0.1:6379> ttl key3
(integer) 3
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> keys *
1) "key2"
2) "mykey"
3) "key1"
127.0.0.1:6379> setnx mykey ok #存在了创建失败
(integer) 0
127.0.0.1:6379>
################################
#mset
#mget
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> mget k1 k2 k3 #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #不存在设置失败,证明msetnx为原子性操作,
(integer) 0
127.0.0.1:6379> get k4
(nil)
#对象
set user:1{name:houquanwei,age:3} #设置一个user:1 对象值为json字符来保存一个对象
#这里的key:user:{id}:{filed}
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
###########################
#getset 先get然后set
127.0.0.1:6379> getset db redis #如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb #如果存在,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
数据结构是相同的!
String 类似的使用场景:value除了是我们的字符串还可以是我们的数字
- 计数器
- 统计多单位的数量
- 粉丝数
3.List
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
在redis里面,我们可以把list玩成,栈,队列,阻塞队列
所有的list命令都是用l开头的
正如图Redis中List是可以进行双端操作的,所以命令也就分为了LXXX和RLLL两类,有时候L也表示List例如LLEN
127.0.0.1:6379> keys *
(empty list or set)
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
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1 #左边开始第0个第1个
1) "three"
2) "two"
127.0.0.1:6379> rpush list right #右边进入
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
#########################33
#lpop
#rpop
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list #移除左边的第一个
"one"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> rpop list #移除右边的第一个
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
#################################
#lindex
127.0.0.1:6379> lindex list 1 #根据下标拿值 只能左边
"one"
#######################
#llen 列表长度
127.0.0.1:6379> lpush list two
(integer) 1
127.0.0.1:6379> lpush list three
(integer) 2
127.0.0.1:6379> llen list
(integer) 2
###############################33
#lrem 移除指定的值
127.0.0.1:6379> lrange list 0 -1
1) "222"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 1 222 #移除一个222值,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "two"
3) "three"
4) "two"
127.0.0.1:6379> lrem list 2 two #移除2个two
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
#####################################
trim 修剪 : list 截断
127.0.0.1:6379> rpush mylist hello0
(integer) 1
127.0.0.1:6379> rpush mylist hello1
(integer) 2
127.0.0.1:6379> rpush mylist hello2
(integer) 3
127.0.0.1:6379> rpush mylist hello3
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello0"
2) "hello1"
3) "hello2"
4) "hello3"
127.0.0.1:6379> ltrim mylist 1 2 #截取指定的长度,通过下标,截断了就修改了列表
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
########################################
#rpoplpush 移除列表的最后一个元素,将他移动到新的列表中
127.0.0.1:6379> rpush list one
(integer) 1
127.0.0.1:6379> rpush list two
(integer) 2
127.0.0.1:6379> rpush list three
(integer) 3
127.0.0.1:6379> rpoplpush list mylist
"three"
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
#############################################
#lset 将列表中指定下标的值替换成另一个
127.0.0.1:6379> exists list #判断列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item #不存在就报错
(error) ERR no such key
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "one"
127.0.0.1:6379> lset list 0 item #存在就替换
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other #不存在这个值就报错
(error) ERR index out of range
############################################
#linsert 将某个具体的value插入到列表中某个元素的前面或后面
127.0.0.1:6379> lpush list hello
(integer) 1
127.0.0.1:6379> lpush list world
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "world"
2) "hello"
127.0.0.1:6379> linsert list before world other #插入world前面
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "other"
2) "world"
3) "hello"
127.0.0.1:6379> linsert list after world object #插入world后面
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "other"
2) "world"
3) "object"
4) "hello"
- list实际上是一个链表,before Node after , left, right 都可以插入值
- 如果key不存在,则创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
应用:
消息排队!消息队列(Lpush Rpop),栈(Lpush Lpop)
4.Set
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息
##############################3
#sadd 像集合中添加元素
#smembers 查看指定set 的值
#sismember 判断一个值在不在set中
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> sadd myset hou
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "world"
3) "hou"
127.0.0.1:6379> sismember myset hello #判断
(integer) 1
127.0.0.1:6379> sismember myset ll
(integer) 0
####################################
127.0.0.1:6379> scard myset #获取set中的个数
(integer) 3
################################
127.0.0.1:6379> srem myset hou #移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> smembers myset
1) "hello"
2) "world"
################################
127.0.0.1:6379> smembers myset #查询所有的元素
1) "hello"
2) "springboot"
3) "world"
127.0.0.1:6379> srandmember myset #随机抽取一个元素
"hello"
127.0.0.1:6379> srandmember myset
"hello"
127.0.0.1:6379> srandmember myset 2 #随机抽出指定个数的元素
1) "hello"
2) "world"
127.0.0.1:6379> srandmember myset
"world"
##########################################
# 随机删除key
127.0.0.1:6379> smembers myset
1) "hello"
2) "springboot"
3) "world"
127.0.0.1:6379> spop myset
"springboot"
127.0.0.1:6379> smembers myset
1) "hello"
2) "world"
127.0.0.1:6379> spop myset #随机删除
"world"
127.0.0.1:6379> smembers myset
1) "hello"
##################################
#将指定的key移动到另一个set
127.0.0.1:6379> sadd set1 hello
(integer) 1
127.0.0.1:6379> sadd set1 world
(integer) 1
127.0.0.1:6379> sadd set1 one
(integer) 1
127.0.0.1:6379> sadd set2 java
(integer) 1
127.0.0.1:6379> sadd set2 python
(integer) 1
127.0.0.1:6379> smove set2 set java #如果没有set就创建一个
(integer) 1
127.0.0.1:6379> smembers set1
1) "hello"
2) "one"
3) "world"
127.0.0.1:6379> smembers set
1) "java"
127.0.0.1:6379> smove set2 set1 python #将指定元素移到另一个set
(integer) 1
127.0.0.1:6379> smembers set1
1) "python"
2) "hello"
3) "one"
4) "world"
127.0.0.1:6379> smembers set2
(empty list or set)
#########################################3
#共同关注
#数字集合类:差集,交集,并集
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2 #差集查看不同的,左边set
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2 #交集
1) "c"
127.0.0.1:6379> sunion key1 key2 #并集
1) "c"
2) "b"
3) "a"
4) "e"
5) "d"
5.Hash
Map集合—key-
127.0.0.1:6379> hset hash field1 hou #set具体的key-value
(integer) 1
127.0.0.1:6379> hget hash field1
"hou"
127.0.0.1:6379> hset hash f2 quan
(integer) 1
127.0.0.1:6379> hmset hash field1 quan field1 qei f2 #设置多个key-value
java
OK
127.0.0.1:6379> hget hsah f2
(nil)
127.0.0.1:6379> hget hash f2
"java"
127.0.0.1:6379> hmget hash f2
1) "java"
127.0.0.1:6379> hmget hash field1 f2 #获取多个
1) "qei"
2) "java"
127.0.0.1:6379> hgetall hash #获取全部
1) "field1"
2) "qei"
3) "f2"
4) "java"
#############################
127.0.0.1:6379> hgetall hash
1) "field1"
2) "qei"
3) "f2"
4) "java"
127.0.0.1:6379> hdel hash field1 #删除对应的key-value通过key
(integer) 1
127.0.0.1:6379> hgetall hash
1) "f2"
2) "java"
###############################
127.0.0.1:6379> hgetall hash
1) "f2"
2) "java"
127.0.0.1:6379> hlen hash #获得hash的字段数量
(integer) 1
127.0.0.1:6379> hset hash f1 hou
(integer) 1
127.0.0.1:6379> hlen hash
(integer) 2
127.0.0.1:6379> hgetall hash
1) "f2"
2) "java"
3) "f1"
4) "hou"
##########################
127.0.0.1:6379> hexists hash f1 #判断指定字段是否存在
(integer) 1
127.0.0.1:6379> hexists hash f3
(integer) 0
#####################
127.0.0.1:6379> hkeys hash #只获取fields
1) "f2"
2) "f1"
127.0.0.1:6379> hvals hash #只获取values
1) "java"
2) "hou"
##############################
127.0.0.1:6379> hset hash f3 5
(integer) 1
127.0.0.1:6379> hincrby hash f3 1 #指定增量
(integer) 6
127.0.0.1:6379> hget hash f3
"6"
127.0.0.1:6379> hsetnx hash f4 hello #如果不存在则可以设置,存在则不能
(integer) 1
Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于对象的存储,Sring更加适合字符串存储
6.Zset(有序集合)
不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
score相同:按字典顺序排序
有序集合的成员是唯一的,但分数(score)却可以重复。
127.0.0.1:6379> zadd set1 1 one
(integer) 1
127.0.0.1:6379> zadd set1 2 two 3 three # 添加多个
(integer) 2
127.0.0.1:6379> zrange set1 0 -1
1) "one"
2) "two"
3) "three"
###############################3
排序
127.0.0.1:6379> zadd salary 5000 zhangsan 2500 xiahong 3600 ming
(integer) 3
127.0.0.1:6379> zrangebyscore salary -inf +inf #排序从小到大,从负无穷到正无穷
1) "xiahong"
2) "ming"
3) "zhangsan"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #排序带上score
1) "xiahong"
2) "2500"
3) "ming"
4) "3600"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 3800 withscores #排序配合score范围
1) "xiahong"
2) "2500"
3) "ming"
4) "3600"
127.0.0.1:6379> zrevrange salary 0 -1 #从大到小
1) "zhangsan"
2) "xiahong"
################################
移除 rem
127.0.0.1:6379> zrange salary 0 -1
1) "xiahong"
2) "ming"
3) "zhangsan"
127.0.0.1:6379> zrem salary ming #移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiahong"
2) "zhangsan"
127.0.0.1:6379> zcard salary #获取有序集合中的个数
(integer) 2
127.0.0.1:6379> zcount salary 100 5000 #判断100 5000之间的值有多少
(integer) 2
###########################
应用案例:
- set排序 存储班级成绩表 工资表排序!
- 普通消息,1.重要消息 2.带权重进行判断
- 排行榜应用实现,取Top N测试
3.三种特殊数据类型
1.geospatial地理位置
附件的人,打车距离
Redis在geo的Redis3.2就推出了!这个功能可以推算地理位置的信息,两个人之间的距离
使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用
- GEOADD
- GEODIST
- GEOHASH
- GEOPOS
- GEORADIUS
- GEOPADIUSBYMEMBER
有效经纬度
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度
指定单位的参数 unit 必须是以下单位的其中一个:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
关于GEORADIUS的参数
通过
georadius
就可以完成 附近的人功能withcoord:带上坐标
withdist:带上距离,单位与半径单位相同
COUNT n : 只显示前n个(按距离递增排序)
#########################3
#两级无法添加,一般直接通过Java程序直接导入
# 参数 key 值(longitud(经度) latitude(纬度) member)
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 160.50 29.53 choongqin
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.5 shenz
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzou 108.96 34.26 xian
(integer) 2
#超过有效的精度维度就会报错
127.0.0.1:6379> geoadd china:city 39.90 116.4 hangzou
(error) ERR invalid longitude,latitude pair 39.900000,116.400000
##########################
#geopos 获取集合中的一个/多个成员坐标
127.0.0.1:6379> geopos china:city beijing #获得指定城市的经度纬度
1) 1) "116.39999896287918"
2) "39.900000091670925"
127.0.0.1:6379> geopos china:city shanghai shenz
1) 1) "121.47000163793564"
2) "31.229999039757836"
2) 1) "114.04999762773514"
2) "22.500001138003192"
##################################3
#geodist 返回两个给定位置之间的距离。默认以米作为单位
127.0.0.1:6379> geodist china:city shanghai shenz
"1217731.4459"
127.0.0.1:6379> geodist china:city shanghai shenz km #千米为单位
"1217.7314"
###########################
#georadius 以给定的经纬度为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素
#GEORADIUSBYMEMBER 与上边一样只不过是城市
127.0.0.1:6379> georadius china:city 120 25 1000 km
1) "shenz"
2) "hangzou"
3) "shanghai"
127.0.0.1:6379> georadius china:city 120 25 1000 km withcoord withdist #以(120 25)为范围方圆1000km的城市的经度和维度
1) 1) "shenz"
2) "666.4144"
3) 1) "114.04999762773514"
2) "22.500001138003192"
2) 1) "hangzou"
2) "583.0388"
3) 1) "120.16000002622604"
2) "30.240000322949022"
3) 1) "shanghai"
2) "707.7597"
3) 1) "121.47000163793564"
2) "31.229999039757836"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 1000 km #以上海为中心
1) "hangzou"
2) "shanghai"
#############################
#geohash 返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。
127.0.0.1:6379> geohash china:city shanghai
1) "wtw3sj5zbj0" #获取成员经纬坐标的geohash表示
################################
127.0.0.1:6379> zrange china:city 0 -1 #查看
1) "xian"
2) "shenz"
3) "hangzou"
4) "shanghai"
5) "beijing"
6) "choongqin"
127.0.0.1:6379> zrem china:city shanghai #删除
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "xian"
2) "shenz"
3) "hangzou"
4) "beijing"
5) "choongqin"
2. Hyperloglog(基数统计)
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
其底层使用string数据类型
基数:数据集中不重复的元素的个数
127.0.0.1:6379> pfadd myp a b c d e f g # 添加
(integer) 1
127.0.0.1:6379> type myp #查看类型
string
127.0.0.1:6379> pfcount myp #统计myp基数数量
(integer) 7
127.0.0.1:6379> pfadd myp e f g k z m f g
(integer) 1
127.0.0.1:6379> pfcount myp
(integer) 10
127.0.0.1:6379> pfadd mypp q e r y u i o
(integer) 1
127.0.0.1:6379> pfmerge myp mypp #将多个合并
OK
127.0.0.1:6379> pfcount myp
(integer) 16
3. Bitmaps
使用位存储,信息状态只有 0 和 1 两个状态的结果就可以用他
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。
127.0.0.1:6379> setbit sign 0 1 #在sign上的第0为设置为1
(integer) 0
127.0.0.1:6379> setbit sign 1 1 #第1个设置为1
(integer) 0
127.0.0.1:6379> setbit sign 2 0 #第2个为0
(integer) 0
127.0.0.1:6379> type sign #sign类型
string
127.0.0.1:6379> getbit sign 2 #获得sign第二个的值
(integer) 0
127.0.0.1:6379> bitcount sign #获得 sign为1的个数
(integer) 2
事务
Redis的单条命令是保证原子性的,但是redis事务不能保证原子性
Redis事务本质:一组命令的集合。
----------------- 队列 set set set 执行 -------------------
事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。
- 一次性
- 顺序性
- 排他性
- Redis事务没有隔离级别的概念
- 所有命令都在事务中,并没有被直接执行,只有发起执行命令的时候才会执行!EXec
- Redis单条命令是保证原子性的,但是事务不保证原子性!
Redis事务操作过程
- 开启事务(
multi
) - 命令入队
- 执行事务(
exec
)
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #命令
QUEUED
127.0.0.1:6379> get k1 #命令
QUEUED
127.0.0.1:6379> set k2 v2 #命令
QUEUED
127.0.0.1:6379> set k3 v3 #命令
QUEUED
127.0.0.1:6379> exec #执行命令
1) OK
2) "v1"
3) OK
4) OK
###########################3
#事务取消:discard
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> set k4 v4
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k4 #事务取消了所以k4没有执行
(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" # 其他命令正常执行
# 虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了。
# 所以说Redis单条指令保证原子性,但是Redis事务不能保证原子性。
监控
悲观锁:
- 很悲观,认为什么时候都会出现问题,无论做什么都会加锁
乐观锁:
- 很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
- 获取version
- 更新的时候比较version
使用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
123456789101112131415
测试多线程修改值,使用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> # 此时事务并没有执行
123456789
模拟线程插队,线程2:
127.0.0.1:6379> INCRBY money 500 # 修改了线程一中监视的money
(integer) 600
12
回到线程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"
1234567
解锁获取最新值,然后再加锁进行事务。
unwatch
进行解锁。
注意:每次提交执行exec后都会自动释放锁,不管是否成功
4.Jedis
使用Java来操作Redis,Jedis是Redis官方推荐使用的Java连接redis的客户端。
- 导入依赖
<!--导入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>
-
编码测试
public class testjedis { public static void main(String[] args) { //new一个jedis对象 Jedis jedis = new Jedis("127.0.0.1",6379); //jedis命令就是之前学的 System.out.println(jedis.ping()); } }
事务
public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); JSONObject jsonObject = new JSONObject(); jsonObject.put("hello","world"); jsonObject.put("name","hou"); jedis.flushDB(); //开启事务 Transaction multi = jedis.multi(); String s = jsonObject.toJSONString(); try{ multi.set("user1",s); multi.set("user2",s); int i=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(); } }
5.SpringBoot
没学
6.Redis配置
- 配置文件 unit单位对大小写不敏感
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASHodF5A-1610855287568)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115113728926.png)]
-
包含(好比import)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LXae7xNO-1610855287570)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115113818243.png)]
-
网络配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pK4mTf3i-1610855287572)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115113958197.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2QuyYLNt-1610855287575)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115114244136.png)]
-
日志输出级别
# 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 "" #日志文件名
-
持久化:在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb .aof,redis是内存数据库如果没有持久化断电即失怎么办
#900秒内,至少有一个key进行了修改,进行持久化 save 900 1 #300秒 ,10个key save 300 10 #60s,至少10000修改就持久化 save 60 10000 stop-writes-on-bgsave-error yes #持久化出错是否继续工作,默认开启继续工作 rdbcompression yes #是否压缩rdb文件,需要消耗cpu的资源 rdbchecksum yes#保存rdb文件的时候进行错误的校验 dir ./ #rdb保存的路径
REPLICATION #主从复制
密码
################ SECURITY ##################### requirepass 123456 #设置密码123456,默认没有密码
客户端设置
maxclients 10000 # 最大客户端数量 maxmemory <bytes> #最大内存限制 maxmemory-policy noeviction # 内存达到限制值的处理策略
redis默认的过期策略volatile-lru
maxmemory-policy 六种方式
**1、volatile-lru:**只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
**3、volatile-random:**随机删除即将过期key
**4、allkeys-random:**随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
-
aof配置
appendonly no #默认不开启AOF
appendfilename "appendonly.aof" #持久化的文件名字
# appendfsync always #每次修改都会同步
appendfsync everysec #每秒执行一个sync,可能会丢失
# appendfsync no #不执行sync,这个时候操作系统自己同步数据
7.持久化RDB
什么是RDB
在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;
默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。
工作原理
在进行 RDB
的时候,redis
的主线程是不会做 io
操作的,主线程会 fork
一个子线程来完成该操作;
- Redis 调用forks。同时拥有父进程和子进程。
- 子进程将数据集写入到一个临时 RDB 文件中。
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)
bgsave
bgsave
是异步进行,进行持久化的时候,redis
还可以将继续响应客户端请求
触发机制
- save的规则满足的情况下,会自动触发rdb原则 (上边的持久化配置文件中)
- 执行flushall命令,也会触发我们的rdb原则
- 退出redis,也会自动产生rdb文件
save
使用 save
命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;
由于
save
命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save
命令执行速度会非常慢,阻塞所有客户端的请求。
bgsave和save对比
命令 | save | bgsave |
---|---|---|
IO类型 | 同步 | 异步 |
阻塞? | 是 | 是(阻塞发生在fock(),通常非常快) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外的内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要fock子进程,消耗内存 |
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
- fork进程的时候,会占用一定的内容空间。
8.持久化AOF
Append Only File
将我们所有的命令都记录(只记读)下来,history,恢复的时候就把这个文件全部再执行一遍
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
什么是AOF
快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
如果要使用AOF,需要修改配置文件: 上边的aof配置文件
appendonly no yes
则表示启用AOF
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错误,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix
优点
- 每一次修改都会同步,文件的完整性会更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
9.RDB和AOP选择
RDB 和 AOF 对比
RDB | AOF | |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
如何选择使用哪种持久化方式?
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
10.Redis发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MunfX2Uq-1610855287577)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115131533116.png)]
下图展示了频道 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 hou #订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "hou"
3) (integer) 1
# 等待信息推送
1) "message" #消息
2) "hou" #消息的频道
3) "hello" #消息的内容
1) "message"
2) "hou"
3) "nihao"
#发送端
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> publish hou hello #发布者发布信息到指定频道
(integer) 1
127.0.0.1:6379> publish hou nihao
(integer) 1
原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
缺点
- 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
- 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
应用
- 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
- 多人在线聊天室。
稍微复杂的场景,我们就会使用消息中间件MQ处理
11.Redis的主从复制
1. 概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
2. 作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础。
3. 为什么使用集群
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
4. 环境搭建(linux 过了再搞)
5.复制原理
Slave启动成功连接到master后会发送一个sync命令
Maser接受命令,启动后台的存盘进程,同时收集所有接受到的用于修改数据集命令,在后台进程执行完毕之后, master将传送整个数据文件到slave,并完成一次完全同步
全量复制:而slave服务在接受到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将自动执行,我们的数据一定可以在从机中看到
6. 使用规则
-
从机只能读,不能写,主机可读可写但是多用于写。
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" 12345678910
-
当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
-
当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
-
第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
- 从机手动执行命令
slaveof no one
,这样执行以后从机会独立出来成为一个主机 - 使用哨兵模式(自动选举)
- 从机手动执行命令
如果没有老大了,这个时候能不能选择出来一个老大呢?手动!
如果主机断开了连接,我们可以使用SLAVEOF no one
让自己变成主机!其他的节点就可以手动连接到最新的主节点(手动)!如果这个时候老大修复了,那么久重新连接!
12.哨兵模式(谋权篡位自动版)
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立进行,哨兵通过发送命令,等待Redis服务器响应,从而监控运行多个Redis实例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-20YxWaFQ-1610855287579)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115151147262.png)]
哨兵的作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控可能出现问题(也死了怎么办),为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0hemke1b-1610855287580)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115151516839.png)]
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
更多信息参考博客:https://www.jianshu.com/p/06ab9daf921d
哨兵模式优缺点
优点:
- 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
- 主从可以切换,故障可以转移,系统的可用性更好
- 哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
13.Redis缓存穿透和雪崩
缓存穿透(查不到)
概念
在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F4EzwqDT-1610855287581)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115155508550.png)]
1.布隆过滤器
是一种数据结构,对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力
2.缓存空对象
当存储层不命中后(没找到),即使返回的是空对象也将其缓存起来。同时设置一个过期时间,之后再访问这个数据将会从缓存中获取这个空的对象,保护后端数据源。
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
这样做有一个缺陷
-
存储 空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
-
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(访问数量太大,缓存过期)
概述
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方法
-
设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
-
加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-57zGGuAN-1610855287582)(C:\Users\18335\Desktop\整理\java\ssm 重启\Redis\image-20201115161318252.png)]
缓存雪崩
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
-
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
-
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
DT-1610855287581)]
1.布隆过滤器
是一种数据结构,对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力
2.缓存空对象
当存储层不命中后(没找到),即使返回的是空对象也将其缓存起来。同时设置一个过期时间,之后再访问这个数据将会从缓存中获取这个空的对象,保护后端数据源。
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
这样做有一个缺陷
-
存储 空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
-
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(访问数量太大,缓存过期)
概述
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方法
-
设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
-
加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
[外链图片转存中…(img-57zGGuAN-1610855287582)]
缓存雪崩
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
-
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
-
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。