Redis入门

Redis入门

狂神学Java
历史

  1. 优化数据结构和索引
  2. 文件缓存----通过IO流获取比每次访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了
  3. Memcached:通过再数据库和数据访问层之间再加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提高

早年MYISAM:表锁(查询一行数据将一张表锁起来),十分影响效率

早些年Innodb:行锁(每次查数据只锁一行)

慢慢的就开始使用分库表来解决写的压力

Nosql

Nosql=Not Only SQL

NOT Only Structured Query Language

关系型数据库:行+列,同一个表下数据的结构是一样的

非关系型数据库:数据储存没有固定的格式,并且可以横向扩展

Nosql泛指非关系型数据库,

NoSQL特点

  1. 方便扩展(数据之间没有关系,很好扩展)

  2. 大数据量高性能(Redis一秒可以写8万次,读写11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)

  3. 数据类型是多样性的(不需要先设计数据库,随去随用)

  4. 传统的RDBMS和NoSQL

    传统的RDBMS(关系型数据库)
    结构化组织
    SQL
    数据和关系都存再单独的表中  row  col
    操作,数据定义语言
    严格的一致性
    基础的食物
    
    NoSQL
    不仅仅是数据
    没有固定的查询语句
    键值对存储,列储存,文档储存,图形数据库
    最终一致性
    CAP定理和BASE
    高性能,高可用,高扩展
    

    大数据时代的3v:主要描述问题的

    1. 海量的Velume
    2. 多样Variety
    3. 实时Velocity

    大数据时代的3高:主要是对程序的要求

    1. 高并发
    2. 高可扩
    3. 高性能

    真正的公司中的实践:NoSQL+RDBMS 一起使用才是最强的

    商品信息
        一般存放在关系型数据库:Mysql
    
    商品描述,评论
    文档型数据库:MongoDB
    
    图片
    分布式文件系统:FastDFS
    淘宝:TFS
    Goolge:GFS
    Hadoop:HDFS
    阿里云:oss
    
    
    # 商品关键字
        搜索引擎:solr,elasticsearch
        阿里:Isearch,多隆
        
    #商品热门的波段信息
        内存数据库:Redis,Memcache
        
    #商品交易,外部支付接口
        第三方应用
    
    

NoSQL的四大分类

  1. KV键值对

    1. 新浪:Redis
    2. 美团:Redis+Tair
    3. 阿里,百度:Redis+memcache
  2. 文档型数据库(bson数据格式)

    1. MongoDB

      基于分布式文件存储的数据库,c++编写,用于处理大量的文档

      MongoDB是RDBMS和NoSQL的中间产品,MongoDB是非关系型数据库中功能最丰富的

    2. ConthDB

  3. 列存储数据库

    1. Hbase
    2. 分布式文件管理系统
  4. 图关系数据库

    用于广告推广,社交网络

    Neoconj,InfoGrid

Redis入门

概述

Redis(Remote Dictionary Server),远程字典服务

是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言的API

与memcached一样,为了保证效率,数据都是缓存在内存中,区别的是Redis会周期的把更新的数据写入硬盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步

能干什么

  1. 内存存储,持久化,内存是断电即逝的,所以需要持久化
  2. 高效率,用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器,计数器
  6. 。。。。。

特性

  1. 多样化的数据类型
  2. 持久化
  3. 集群
  4. 事务

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. 高性能的服务器都是多线程的

  1. 多线程一定比单线程效率高

核心:redis是将多有的数据放在内存中的,所以使用单线程操作效率是最高的

,不需要有多线程的上下文切换

对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都在一个cpu上

1. 操作

基本数据类型
基本数据类型方法说明
Redis-key
EXISTS age判断key是否存在
move age 1移除key到1号数据库
expire name 10设置key过期时间
ttl name查看key的过期剩余时间
type age查看key的类型
Stringget,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)
Listlpush,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后面
Setsadd,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并集
HashMap集合—key-
hset hash fields houset具体的key-value
hget hash fieldsget到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如果不存在则可以设置,存在则不能
Zsetscore
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种:

  1. 当前key没有设置过期时间,所以会返回-1.
  2. 当前key有设置过期时间,而且key已经过期,所以会返回-2.
  3. 当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.

关于重命名RENAMERENAMENX

  • 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除了是我们的字符串还可以是我们的数字

  1. 计数器
  2. 统计多单位的数量
  3. 粉丝数

3.List

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

在redis里面,我们可以把list玩成,栈,队列,阻塞队列

所有的list命令都是用l开头的[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPvbIltc-1597890996518)(狂神说 Redis.assets/image-20200813114255459.png)]

正如图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命令也可以使用

  1. GEOADD
  2. GEODIST
  3. GEOHASH
  4. GEOPOS
  5. GEORADIUS
  6. 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 执行 -------------------

事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。

  • 一次性
  • 顺序性
  • 排他性

  1. Redis事务没有隔离级别的概念
  2. 所有命令都在事务中,并没有被直接执行,只有发起执行命令的时候才会执行!EXec
  3. 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的客户端。

  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>
  1. 编码测试

    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配置

  1. 配置文件 unit单位对大小写不敏感

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASHodF5A-1610855287568)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115113728926.png)]

  1. 包含(好比import)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LXae7xNO-1610855287570)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115113818243.png)]

  2. 网络配置

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]

  3. 日志输出级别

    # 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 ""  #日志文件名
    
    
  4. 持久化:在规定的时间内,执行了多少次操作,则会持久化到文件 .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 : 永不过期,返回错误

  5. 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 一个子线程来完成该操作;

  1. Redis 调用forks。同时拥有父进程和子进程。
  2. 子进程将数据集写入到一个临时 RDB 文件中。
  3. 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)

bgsave

bgsave 是异步进行,进行持久化的时候,redis 还可以将继续响应客户端请求

在这里插入图片描述

触发机制


  1. save的规则满足的情况下,会自动触发rdb原则 (上边的持久化配置文件中)
  2. 执行flushall命令,也会触发我们的rdb原则
  3. 退出redis,也会自动产生rdb文件
save

使用 save 命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;

由于 save 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save命令执行速度会非常慢,阻塞所有客户端的请求。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

bgsave和save对比

命令savebgsave
IO类型同步异步
阻塞?是(阻塞发生在fock(),通常非常快)
复杂度O(n)O(n)
优点不会消耗额外的内存不阻塞客户端命令
缺点阻塞客户端命令需要fock子进程,消耗内存

优缺点

优点:

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
  2. 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

优点

  1. 每一次修改都会同步,文件的完整性会更加好
  2. 每秒同步一次,可能会丢失一秒的数据
  3. 从不同步,效率最高

缺点

  1. 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
  2. aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化

9.RDB和AOP选择

RDB 和 AOF 对比

RDBAOF
启动优先级
体积
恢复速度
数据安全性丢数据根据策略决定

如何选择使用哪种持久化方式?

一般来说, 如果想达到足以媲美 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 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

在这里插入图片描述

客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。

缺点

  1. 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
  2. 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。

应用

  1. 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
  2. 多人在线聊天室。

稍微复杂的场景,我们就会使用消息中间件MQ处理

11.Redis的主从复制

1. 概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

2. 作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  2. 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  3. 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
  4. 高可用基石:主从复制还是哨兵和集群能够实施的基础。

3. 为什么使用集群

  1. 单台服务器难以负载大量的请求
  2. 单台服务器故障率高,系统崩坏概率大
  3. 单台服务器内存容量有限。

4. 环境搭建(linux 过了再搞)

5.复制原理

Slave启动成功连接到master后会发送一个sync命令

Maser接受命令,启动后台的存盘进程,同时收集所有接受到的用于修改数据集命令,在后台进程执行完毕之后, master将传送整个数据文件到slave,并完成一次完全同步

全量复制:而slave服务在接受到数据库文件数据后,将其存盘并加载到内存中

增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

但是只要是重新连接master,一次完全同步(全量复制)将自动执行,我们的数据一定可以在从机中看到

6. 使用规则

  1. 从机只能读,不能写,主机可读可写但是多用于写。

     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
    
  2. 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。

  3. 当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。

  4. 第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:

    • 从机手动执行命令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

哨兵模式优缺点

优点:

  1. 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
  2. 主从可以切换,故障可以转移,系统的可用性更好
  3. 哨兵模式是主从模式的升级,手动到自动,更加健壮

缺点:

  1. Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
  2. 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项

13.Redis缓存穿透和雪崩

缓存穿透(查不到)

概念

在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F4EzwqDT-1610855287581)(C:\Users\18335\AppData\Roaming\Typora\typora-user-images\image-20201115155508550.png)]

1.布隆过滤器

是一种数据结构,对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力

img

2.缓存空对象

当存储层不命中后(没找到),即使返回的是空对象也将其缓存起来。同时设置一个过期时间,之后再访问这个数据将会从缓存中获取这个空的对象,保护后端数据源。

一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。

在这里插入图片描述

这样做有一个缺陷

  1. 存储 空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间

  2. 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

缓存击穿(访问数量太大,缓存过期)

概述

相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方法

  1. 设置热点数据永不过期

    这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

  2. 加互斥锁(分布式锁)

    在访问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的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力

img

2.缓存空对象

当存储层不命中后(没找到),即使返回的是空对象也将其缓存起来。同时设置一个过期时间,之后再访问这个数据将会从缓存中获取这个空的对象,保护后端数据源。

一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。

在这里插入图片描述

这样做有一个缺陷

  1. 存储 空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间

  2. 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

缓存击穿(访问数量太大,缓存过期)

概述

相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方法

  1. 设置热点数据永不过期

    这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

  2. 加互斥锁(分布式锁)

    在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

[外链图片转存中…(img-57zGGuAN-1610855287582)]

缓存雪崩

大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

在这里插入图片描述

解决方案

  • redis高可用

    这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群

  • 限流降级

    这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

  • 数据预热

    数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值