redis入门学习

Redis

1、Nosql概述

​ NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

​ 虽然NoSQL流行语火起来才短短一年的时间,但是不可否认,现在已经开始了第二代运动。尽管早期的堆栈代码只能算是一种实验,然而现在的系统已经更加的成熟、稳定。不过现在也面临着一个严酷的事实:技术越来越成熟--以至于原来很好的NoSQL数据存储不得不进行重写,也有少数人认为这就是所谓的2.0版本。这里列出一些比较知名的工具,可以为大数据建立快速、可扩展的存储库。

特点:

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

2、大数据量、高性能(redis 能一秒写八万次,读十一万次,Nosql的缓存记录级,是一种细粒度的缓存,性能会比较高)

3、数据类型是多样型的(不需要事先设计好数据库,随取随用)

2、Redis概述

​ Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

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

​ Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类keyvalue存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Python,Ruby,Erlang,PHP客户端,使用很方便。

3、Linux下安装Redis

1、下载redis安装包 redis-6.0.9.tar.gz

2、解压redis的安装包到 /opt文件下

3、 进入解压后的文件,可以看到redis的配置文件

4、基本的环境安装

yum install gcc-c++

#安装完成后,make一下
make
#之后make install
make install

5、redis的默认安装路径 usr/local/bin

6、将redis的配置文件复制到安装redis的目录下,usr/local/bin,这里注意,我们自己要创建一个目录

7、redis默认不是后台启动的、需要修改配置文件,改为后台启动

8、启动redis服务!

9、使用redis-cli进行连接测试

10、查看redis进程是否开启

11、关闭redis服务,shutdown

4、redis性能测试

redis-benchmark是一个压力测试工具,官方自带的性能测试工具。

执行命令为: redis-benchmark +命令参数

img

性能测试

#测试:100个并发,100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

5、redis基础的知识和命令

1、redis默认有16个数据库,默认使用的第0个

2、可以使用命令行进行数据库的切换,和查看数据库的大小

#切换数据库
select + 数据库号
#查看数据库大小
dbsize

3、还可以清空数据库中的值

#清空当前数据库
flushdb
#清空全部的数据库中的值
flushall

4、redis是单线程的

官方表示:redis是基于内存操作的,cpu不是redis的性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽。

redis为什么是单线程还这么快?

误区1:高性能的服务器一定是多线程的?

误区2:多线程(在一个cpu情况下,多线程是线程上下文切换的,由于速度快,就认为是多线程)一定比单线程高

核心:redis是将所有的数据全部放在内存中的,所以说使用单线程的效率是最高的,多线程的cpu上下文切换很耗时。

6、redis-key的一些基本命令

127.0.0.1:6379> set name xiaopang    #给key赋值
OK
127.0.0.1:6379> get name       #查看key的值
"xiaopang"
127.0.0.1:6379> exists name    #判断当前key是否存在
(integer) 1
127.0.0.1:6379> set age 11   
OK
127.0.0.1:6379> keys *   #查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> move name 2  #将该key移动到指定的数据库中
(integer) 1
127.0.0.1:6379> select 2   #切换数据库
OK
127.0.0.1:6379[2]> get name
"xiaopang"
127.0.0.1:6379[2]> select 0
OK
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name jinhao
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> del name    #删除指定的key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> expire age 5   #指定倒计时,删除该key,默认时间单位为秒
(integer) 1
127.0.0.1:6379> ttl age   #查看该key还剩多长时间被删除,如果已经被删除了,结果就是-2
(integer) -2
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set name jinhao
OK
127.0.0.1:6379> set age 11
OK
127.0.0.1:6379> type name  #查看该key的类型
string
127.0.0.1:6379> type age
string

7、5大数据类型

7.1、String(字符串)

####################################################################
127.0.0.1:6379> set name xiaopang   #set赋值
OK
127.0.0.1:6379> get  name         #get查看key的值
"xiaopang"
127.0.0.1:6379> APPEND name hello   #在指定key的值后面追加字符串
(integer) 13
127.0.0.1:6379> get name    
"xiaopanghello"
127.0.0.1:6379> keys *     #查看所有的key
1) "name"
2) "age"
127.0.0.1:6379> get age
"11"
127.0.0.1:6379> STRLEN name   #查看指定key的字符长度
(integer) 13 
127.0.0.1:6379> STRLEN age
(integer) 2
127.0.0.1:6379> EXISTS  name  #判断该key是否存在
(integer) 1
####################################################################
#自增
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> get age 
"1"
127.0.0.1:6379> INCR age  #让指定的key的值自增1
(integer) 2
127.0.0.1:6379> INCR age
(integer) 3
127.0.0.1:6379> get age
"3"
127.0.0.1:6379> decr age  #让指定的key的值自减1
(integer) 2
127.0.0.1:6379> get age 
"2"
127.0.0.1:6379> INCRBY age 10  #让指定的key的值指定自增多少,可以自己设置步长
(integer) 12
127.0.0.1:6379> get age
"12"
127.0.0.1:6379> DECRBY age 5  #让指定的key的值指定自减多少,可以自己设置步长
(integer) 7
####################################################################
#字符串范围 range
127.0.0.1:6379> set key1 hello,xiaopang
OK
127.0.0.1:6379> get key1
"hello,xiaopang"
127.0.0.1:6379> GETRANGE key1 0 4  #截取0~4之间的字符串,相当于Java中的subString方法
"hello"
127.0.0.1:6379> GETRANGE key1 0 -1 #获取全部的字符串内容,效果跟get key1 一样
"hello,xiaopang"
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 xxx  #替换从指定的key的指定位置开始替换,相当于Java中的replace方法
(integer) 7
127.0.0.1:6379> get key2
"axxxefg"
####################################################################
#setex 给key赋值,并设置失效时间,时间单位为秒
#setnx 创建当前key,如果当前key不存在,则创建成功,否则创建失败
#这里需要注意的是:使用普通的set给创建key,如果当前key存在,则新的key会覆盖原来的key
127.0.0.1:6379> setex key3 30 hello,redis #给key赋值,并设置失效时间,时间单位为秒
OK
127.0.0.1:6379> get key3
"hello,redis"
127.0.0.1:6379> ttl key3  #查看该key还剩多少时间
(integer) 14
127.0.0.1:6379> keys *
1) "key2"
2) "key1"
3) "key3"
127.0.0.1:6379> SETNX key4 xiaopang   #创建当前key,如果当前key不存在,则创建成功,否则创建失败
(integer) 1    #现在没有key4,创建成功
127.0.0.1:6379> get key4
"xiaopang"
127.0.0.1:6379> SETNX key4 jinhao
(integer) 0  #已经存在key4,创建失败
127.0.0.1:6379> get key4
"xiaopang"
127.0.0.1:6379> keys *
1) "key2"
2) "key1"
3) "key4"
####################################################################
#同时操作多个数据 mset mget 
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  #同时set多个key-value
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3     #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> MSETNX k1 v1 k4 v4  #这里msetnx有着类似原子性的操作,要么都成功,要么都失败
(integer) 0
####################################################################
#对象
set user:1  {name:xiaopang,age:3} #设置一个suer:1对象,对象值为json字符来保存
#这里key是一个巧妙的设计: user:{id}:{value},如此设计在redis中是可以的
127.0.0.1:6379> mset user:1:name xiaopang user:1:age 11
OK
127.0.0.1:6379> keys *
1) "user:1:age"
2) "user:1:name"
127.0.0.1:6379> get user:1:name
"xiaopang"
####################################################################
getset #先查看再赋值
127.0.0.1:6379> getset k1 v1  #先查看有没有k1,如果没有返回null,再给k1赋值
(nil)
127.0.0.1:6379> getset k1 v2
"v1"
127.0.0.1:6379> getset k1 v3
"v2"
127.0.0.1:6379> 

7.2、List(列表)

在redis中,我们可以把list玩成栈(Lpush,Lpop),队列(Lpush,Rpop),列表中的元素值是有序、有下标、可重复

所有list命令都是用l或r开头的

#########################################################################
#插入 lpush rpush
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 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> lrange list 0 -1   #从左边开始查看列表中全部的值
1) "one"
2) "two"
3) "three"
#########################################################################
#移除 lpop rpop
127.0.0.1:6379> LRANGE list 0 -1 #查看列表中全部的值
1) "one"
2) "two"
3) "three" 
127.0.0.1:6379> lpop list  #移除列表中最左边的元素
"one"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "three"
127.0.0.1:6379> rpop list  #移除列表中最右边的元素
"three"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
#########################################################################
#Lindex 获取列表中指定位置的值 
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LINDEX list 0  #获取列表中左边第0个元素的值
"three"
127.0.0.1:6379> LINDEX list 1  #获取列表中左边第1个元素的值
"two"
#########################################################################
#Llen 获取列表的长度
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> llen list  #返回列表的长度
(integer) 3
#########################################################################
#移除列表中指定的值 lrem
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lpush list two  #这里需要注意的是,列表中的值是可以重复的
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 three  #移除列表中指定的值,并声明移除值得数量
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "two"
3) "one"
127.0.0.1:6379> lrem list 2 two   #移除列表中指定的值,并声明移除值得数量
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
#########################################################################
#ltrim 修剪,截断,相当于string中得subString方法,截取指定范围的值
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> rpush list four
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> ltrim list 1 2 #通过下标截取指定长度的list列表,该list列表已经被修剪了
OK
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "three"
#########################################################################
#rpoplpush: 移除该列表中的最后一个元素,并将该元素存放到另外一个列表中去
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> RPOPLPUSH list mylist #移除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"
127.0.0.1:6379> 
#########################################################################
#lset 将指定下标的值替换成另外一个值,相当于一个更新操作
127.0.0.1:6379> EXISTS list  #判断list列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 redis #将指定下标的值替换,如果指定的列表不存在的话,就报错,替换失败
(error) ERR no such key
127.0.0.1:6379> lpush list mogondb
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "mogondb"
127.0.0.1:6379> lset list 0 redis  #将指定下标的值替换,如果指定的列表不存在的话,就报错,替换失败,如果有的话,就会替换成功
OK
127.0.0.1:6379> LRANGE list 0 0
1) "redis"
#########################################################################
#linsert 将某个具体的值插入到列表中指定值的前面或者后面
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> LRANGE list 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> LINSERT list before one zero #将zero插入到one值前面
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "zero"
2) "one"
3) "two"
4) "three"
127.0.0.1:6379> LINSERT list after three four #将four插入到three值得后面
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "zero"
2) "one"
3) "two"
4) "three"
5) "four"

小结:

  • 它实际上是一个链表,before node after ,left ,right 都可以插入
  • 如果key不存在,就会创建一个新的列表
  • 如果key存在,就在列表上新增内容
  • 如果移除了列表中所有的值,就成为了一个空链表,也就代表不存在

7.3set(集合)

无序,没有下标,存储的元素不能重复

#####################################################################
127.0.0.1:6379> sadd set one   #向set集合中添加集合元素
(integer) 1
127.0.0.1:6379> sadd set two 
(integer) 1
127.0.0.1:6379> sadd set three
(integer) 1
127.0.0.1:6379> SMEMBERS set   #查看set集合中所有的元素
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> SISMEMBER set two  #判断在指定的set集合中是否包含给出的值,如果集合中包含该元素的话,返回1,如果不包含的话,返回0
(integer) 1
127.0.0.1:6379> SISMEMBER set four
(integer) 0
#####################################################################
scard 查看集合中有多少个集合元素
127.0.0.1:6379> scard set   #查看set集合中有多少个集合元素
(integer) 3
127.0.0.1:6379> sadd set four
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "three"
2) "two"
3) "four"
4) "one"
127.0.0.1:6379> SCARD set   #查看set集合中有多少个集合元素
(integer) 4

#####################################################################
srem  移除集合中指定的元素
127.0.0.1:6379> srem set two  #移除set集合中指定的元素
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "three"
2) "four"
3) "one"
127.0.0.1:6379> srem set one  #移除set集合中指定的元素
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "three"
2) "four"

#####################################################################
srandmember 在set集合中随机挑选出指定个数的随机元素
127.0.0.1:6379> SMEMBERS set
1) "three"
2) "two"
3) "four"
4) "one"
127.0.0.1:6379> srandmember set  #在set集合中随机挑选出一个随机数
"one"
127.0.0.1:6379> srandmember set 
"two"
127.0.0.1:6379> srandmember set 
"one"
127.0.0.1:6379> srandmember set 
"three"
127.0.0.1:6379> srandmember set 2  #在set集合中随机挑选出两个随机数
1) "one"
2) "four"
127.0.0.1:6379> srandmember set 2
1) "two"
2) "four"

#####################################################################
spop 在set集合中随机移除一个集合元素,跟srem指定移除不一样
127.0.0.1:6379> smembers set  
1) "three"
2) "two"
3) "four"
4) "one"
127.0.0.1:6379> spop set    #在set集合中随机移除一个集合元素
"three"
127.0.0.1:6379> smembers set
1) "two"
2) "four"
3) "one"
127.0.0.1:6379> 
127.0.0.1:6379> spop set  #在set集合中随机移除一个集合元素
"two"
127.0.0.1:6379> smembers set
1) "four"
2) "one"
#####################################################################
smove 将set集合中的指定元素移动到另一个set集合中去
127.0.0.1:6379> smembers set1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> smembers set2
1) "four"
127.0.0.1:6379> smove set1 set2 two  #将set1集合中的指定元素移动到另一个set2集合中去
(integer) 1
127.0.0.1:6379> smembers set1
1) "three"
2) "one"
127.0.0.1:6379> smembers set2
1) "two"
2) "four"
#####################################################################
数字类的集合
 -差集 sdiff
 -交集 sinter
 -并集 sunion
 127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> sadd set1 b
(integer) 1
127.0.0.1:6379> sadd set1 c
(integer) 1
127.0.0.1:6379> sadd set2 c
(integer) 1
127.0.0.1:6379> sadd set2 d 
(integer) 1
127.0.0.1:6379> sadd set2 e
(integer) 1
127.0.0.1:6379> SDIFF set1 set2  #相对于set1集合来说,不同与set2集合中的元素有什么
1) "b"
2) "a"
127.0.0.1:6379> SINTER set1 set2  #交集
1) "c"
127.0.0.1:6379> SUNION set1 set2  #set1和set2的并集
1) "b"
2) "c"
3) "a"
4) "e"
5) "d"
微博,qq中的共同关注可以这样做出来

7.4、Hash(哈希类型)

其实,Hash就是将String类型中的value又当作了一个map键值对,其形式为 hset key key value

#####################################################################
127.0.0.1:6379> hset myhast key1 xiaopang  #设置一个具体key-value
(integer) 1
127.0.0.1:6379> hset myhaset key2 jinhao
(integer) 1
127.0.0.1:6379> hget myhast key1  #查看一个具体key-value
"xiaopang"
127.0.0.1:6379> HGETALL myhast  #查看该hash表中所有的key-value键值对
1) "key1"
2) "xiaopang"
#####################################################################
hmset,hmget同时给map赋值,查看
127.0.0.1:6379> hmset myhash key1 hello key2 world  #同时给多个key-value赋值
OK
127.0.0.1:6379> hmget myhash key1 key2 #同时查看多个key中vlaue值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash   #查看全部的key-value键值对
1) "key1"
2) "hello"
3) "key2"
4) "world"
#####################################################################
hdel 删除hash中指定的key字段,删掉后其相应的value也会被删除
127.0.0.1:6379> hdel myhasg key1  #删除hash中指定的key字段,如果没有则删除失败
(integer) 0
127.0.0.1:6379> hdel myhash key1  #删除hash中指定的key字段
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "key2"
2) "world"
127.0.0.1:6379> 
#####################################################################
hlen 查看hash的长度
127.0.0.1:6379> hlen myhash   #查看hash的长度
(integer) 1
127.0.0.1:6379> hset myhash key1 hello
(integer) 1
127.0.0.1:6379> hlen myhash
(integer) 2
#####################################################################
Hexists 判断hash中的key是否存在
127.0.0.1:6379> Hexists myhash key1 #判断hash中指定的key是否存在,如果存在返回一,如果不存在则返回0
(integer) 1
127.0.0.1:6379> HEXISTS myhash key4
(integer) 0
#####################################################################
hkeys 只显示hash中所有的key
hvals 只显示hash中所有的value
127.0.0.1:6379> hkeys myhash   #只显示hash中所有的key
1) "key2"
2) "key1"
127.0.0.1:6379> hvals myhash   #只显示hash中所有的value
1) "world"
2) "hello"
127.0.0.1:6379> 
#####################################################################
hincrBy  自增
127.0.0.1:6379> hgetall myhash
1) "key2"
2) "world"
3) "key1"
4) "hello"
5) "key3"
6) "2"
127.0.0.1:6379> HINCRby myhash key3 1  #指定key自增
(integer) 3
#####################################################################

小结:hash适合与对象的存储,而String类型更适合于字符串的存储。

7.5、Zset(有序集合)

Zset是在set的基础上,增加了一个进行排序的值score set k1 score1 v1

#####################################################################
127.0.0.1:6379> zadd myset 1 one  #在zset有序集合上赋值,并给出排序序号score
(integer) 1
127.0.0.1:6379> zadd myset 2 three 
(integer) 1
127.0.0.1:6379> zadd myset 3 two
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1  #查看zset上所有的值,并按照顺序来显示
1) "one"
2) "three"
3) "two"
127.0.0.1:6379> 
#####################################################################
zrangebyscore 根据scroe来排序显示从小到大 ,zrevrange 从大到小
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "xiaopang"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf  #根据scroe来排序从小到大来显示
1) "xiaoming"
2) "xiaopang"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores #根据scroe来排序从小到大来显示,顺便还带有scroe值
1) "xiaoming"
2) "500"
3) "xiaopang"
4) "1000"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores#根据scroe来排序从大到小来显示,顺便还带有scroe值
1) "zhangsan"
2) "5000"
3) "xiaoming"
4) "500"
#####################################################################
zrem 移除,zcard 获取集合中元素个数
127.0.0.1:6379> zrem salary xiaopang  #移除zset中指定的值
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "zhangsan"
127.0.0.1:6379> zcard salary #获取集合中元素个数
(integer) 2
127.0.0.1:6379> 
#####################################################################
zcount 获取给定区间的元素数量
127.0.0.1:6379> zadd zset 1 one
(integer) 1
127.0.0.1:6379> zadd zset 2 two 3 three 
(integer) 2
127.0.0.1:6379> ZCOUNT zset 1 3  #获取给定区间的元素数量
(integer) 3
127.0.0.1:6379> zcount zset 1 1
(integer) 1

小结:该类型可用于做成绩表,排行耪等功能的实现

8、特殊类型

8.1、Geospatial地理位置

朋友的定位,附近的人,打车距离计算?

geoadd 添加地理位置

#添加城市数据
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqin 114.05 22.52 shengzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1

getpos 查看某个位置的经纬度

127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city hangzhou
1) 1) "120.1600000262260437"
   2) "30.2400003229490224"
127.0.0.1:6379> 

geodist 获取两个位置之间的距离

单位: m:表示米,km表示千米,mi表示英里 ,ft表示英尺,默认单位为米

127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai  km
"1067.3788"
127.0.0.1:6379> 

georadius 以给定的经纬度为中心,找出某一半径内的元素

附近的人(获得所有附近的人的地址,定位)通过半径来查询

127.0.0.1:6379> georadius china:city  130 30   1000 km  #查询指定位置,半径为1000km附近的城市
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> georadius china:city  130 30   1000 km withcoord   #查询附近的城市,并显示经纬度
1) 1) "hangzhou"
   2) 1) "120.1600000262260437"
      2) "30.2400003229490224"
2) 1) "shanghai"
   2) 1) "121.47000163793563843"
      2) "31.22999903975783553"
127.0.0.1:6379> georadius china:city  130 30   1000 km withcoord withdist  #查询附近的城市,并显示经纬度和据指定位置的距离
1) 1) "hangzhou"
   2) "946.7693"
   3) 1) "120.1600000262260437"
      2) "30.2400003229490224"
2) 1) "shanghai"
   2) "827.6683"
   3) 1) "121.47000163793563843"
      2) "31.22999903975783553"
127.0.0.1:6379> georadius china:city  130 30   1000 km withcoord withdist count 1 
#查询附近的城市,并显示经纬度和据指定位置的距离,并设置数量为一
1) 1) "shanghai"
   2) "827.6683"
   3) 1) "121.47000163793563843"
      2) "31.22999903975783553"
127.0.0.1:6379> 

georadiusbymember 给定城市为中心,查询周围的城市

127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 10000 km
1) "chongqin"
2) "shengzhen"
3) "hangzhou"
4) "shanghai"
5) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 2000 km
1) "chongqin"
2) "shengzhen"
3) "hangzhou"
4) "shanghai"
5) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1500 km
1) "chongqin"
2) "hangzhou"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> 

geohash 查询城市的经纬度hash值

其实,geo底层的实现原理就是zset,我们可以使用zset命令来操作geo!

8.2、Hyperloglog

存储不重复的元素

##############################################################################
pfadd #向集合中添加不同的元素,相同的元素只能存储一个
pfcount #计算集合中不同元素的个数
pfmerge #将两个集合合并,相同的元素则忽略
127.0.0.1:6379> PFADD myset a b c d e f g h #向集合中存储不同的数据,若是数据相同,只能存储一个
(integer) 1
127.0.0.1:6379> PFCOUNT myset     #统计集合中元素不同的个数
(integer) 8
127.0.0.1:6379> pfadd myset h i j k o m n
(integer) 1
127.0.0.1:6379> pfcount myset
(integer) 14
127.0.0.1:6379> pfadd myset2 a b c x y z
(integer) 1
127.0.0.1:6379> pfcount myset2
(integer) 6
127.0.0.1:6379> PFMERGE myset3 myset myset2  #合并两个集合,若是相同的,则忽略,不同的则合并
OK
127.0.0.1:6379> pfcount myset3   #统计集合中元素不同的个数
(integer) 17

8.3、bitmap 位图

一般可用于统计用户的活跃状态,登录状态,打卡等功能的实现

############################################################################
#应用场景:以下就是周一到周末的员工打卡情况
127.0.0.1:6379> setbit sign 1 0   #1为上班了,0为没有上班,请假了
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1 
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
127.0.0.1:6379> setbit sign 7 0
(integer) 0
############################################################################
127.0.0.1:6379> getbit sign 7   #查看员工某一天的打卡情况
(integer) 0
127.0.0.1:6379> getbit sign 4
(integer) 1
############################################################################
127.0.0.1:6379> bitcount sign   #统计该员工一共的打卡天数
(integer) 5

9、事务

Redis事务的本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行! 一次性、顺序性、排它性

  • redis事务是没有隔离级别的概念的!
  • redis单条命令保证原子性,但是事务不保证原子性!

redis事务的执行流程:

  • 开启事务(multi)
  • 命令入队(编写命令)
  • 执行事务(exec)

测试

1、正常执行事务

127.0.0.1:6379> multi   #开启一个事务
OK
127.0.0.1:6379> set k1 v1   #编写命令,命令入队,等待事务的执行
QUEUED
127.0.0.1:6379> set k2 v2 
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec   #执行事务 
1) OK
2) OK
3) "v2"
4) OK

2、取消事务 discard

127.0.0.1:6379> multi 
OK
127.0.0.1:6379> set k4 v4 
QUEUED
127.0.0.1:6379> DISCARD    #取消事务,
OK
127.0.0.1:6379> get k4
(nil)

3、编译型异常(代码有问题,命令错误),事务中所有的命令都不会被执行

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 k3  #编译型异常,其他命令也都执行不了
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> 

4、运行时异常(像1/0这种异常),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误的命令会抛出异常!

127.0.0.1:6379> set k1 v1 
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr 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) (error) ERR value is not an integer or out of range
2) OK
3) OK
127.0.0.1:6379> get k2 #虽然第一个命令报错了,但其他命令还是正常执行了
"v2"

10、Redis乐观锁

乐观锁:很乐观,认为什么时候都不会出问题,所以不会上锁,在更新的时候去判断以下,在此期间是否有人修改过这个数据。

平常的关系型数据库中使用的是一个version字段来设置乐观锁,跟新数据前先获取version,更新时在比较version且一同更新version的值,而redis的数据库中使用的是watch监控来设置乐观锁,使用watch来监视某个数据的值

悲观锁:很悲观,认为什么时候都会出问题 ,无论做什么都会加锁。这样大大的影响了性能。

redis监视测试

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set pass 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 pass 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

redis在监视过程中,所监视的数据发生了变动,导致事务执行失败

127.0.0.1:6379> set money 100
ok
127.0.0.1:6379> set pass 0
ok
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi 
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby pass 20
QUEUED
127.0.0.1:6379> exec    #在watch监视money时,其他线程修改money的值,使得该事务执行失败
(nil)
#在watch监视money时,其他线程修改money的值,使得该事务执行失败
127.0.0.1:6379> set money 200
OK

事务执行失败后,应该立即解锁,获得当前的最新值,再进行事务的操作

127.0.0.1:6379> UNWATCH  #先退出监视money ,让money获得最新值
OK
127.0.0.1:6379> watch money  #再重新监视,再进行事务的操作
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby pass 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 180
2) (integer) 20

11、Jedis

什么是Jedis?是redis官方推荐Java连接开发工具,使用Java操作redis中间件,如果你要使用Java操作redis,那么一定要对Jedis十分的熟悉。

测试连接:

1、导入相应的jar依赖

<dependencies>
     <dependency>
         <groupId>redis.clients</groupId>
         <artifactId>jedis</artifactId>
         <version>3.3.0</version>
     </dependency>
     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.62</version>
     </dependency>
 </dependencies>

2、测试连接

public class JoinTest {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("8.129.41.4",6379); 
        System.out.println(jedis.ping());
    }
}

注意我这里使用的是连接远程redis,需要对配置文件做一些操作,具体看https://www.cnblogs.com/coderhu1/p/13922634.html,还要记得把6379端口放行,以及防火墙的设置。

11.2jedis的事务实现

public class JoinTest {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("8.129.41.4",6379);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","redis");
        jsonObject.put("name","xiaopang");
        String result = jsonObject.toJSONString();

        Transaction multi = jedis.multi();
        try {
            multi.set("user",result);
            multi.exec();
        } catch (Exception e) {
            multi.discard(); //如果执行的命令行出现错误,则放弃事务的执行
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user"));
            jedis.close();
        }

    }
}

12、springboot集成redis

1、创建springboot项目

2、导入redis依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

3、配置redis的基本信息

#配置redis
spring.redis.host=8.129.41.4
spring.redis.port=6379

4、测试

  @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("myset","小胖学java");
        System.out.println(redisTemplate.opsForValue().get("myset"));

    }

这里,如果你要自定义redisTemple,也是可以的,像自定义一些关于序列化的配置,redis默认的是使用jdk序列化的。

13、redis.conf配置详解

单位

INCLUDES 包含

# include /path/to/local.conf
# include /path/to/other.conf

includes就是像spring中的improt一样,可以导入其他配置文件

NETWORK 网络

bind 127.0.0.1  #绑定的id,如果需要连接远程服务器的redis话,需要把这个改为*,或者直接把此行注释掉
protected-mode no  #保护模式,默认是开启的yes,一样的如果需要连接远程服务器的话,就需要把这个改为no
port 6379  #端口号

GENERAL 通用

daemonize yes  #以守护进程的方式运行,默认是no,我们需要自己开启,改为yes

pidfile /var/run/redis_6379.pid  #如果是以后代的方式运行,我们就需要指定一个pid文件

#日志,以下是各种日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)   默认为生产环境
# warning (only very important / critical messages are logged)
loglevel notice
logfile ""  #日志的文件位置名

databases 16  #redis默认是16个数据库

always-show-logo yes  #在redis开启时,是否显示logo,就像springboot项目开启一样,有logo的出现

SNAPSHOTTING 快照

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件 。.rdb .aof文件。

redis是基于内存操作的数据库,如果没有持久化,那么数据断电即失!

save 900 1  #如果在900s内,如果至少有一个key进行了修改或赋值,我们即经行持久化操作

save 300 10   #如果在300s内,如果至少有 10 个key进行了修改或赋值,我们即经行持久化操作

save 60 10000  #如果在60s内,如果至少有10000个key进行了修改或赋值,我们即经行持久化操作

stop-writes-on-bgsave-error yes #如果持久化出错的话,是否还需要继续工作

rdbcompression yes  #是否压缩rdb文件,需要消耗一些cpu资源

rdbchecksum yes   #保存rdb文件的时候,进行错误的检查校验。

dir ./   #rdb文件保存的目录



SECURITY 安全

redis默认是没有密码的,可以通过vim 操作conf配置文件去设置密码,也可以在客户端里去设置密码

其中客户端的设置是

config set 	requirepass "你的密码"  #设置redis密码

config get requirepass  #查看redis的密码

CLIENTS 限制

maxclients 10000  #设置能连接redis的最大客户端数量

maxmemory <bytes>  #redis配置最大的内存容量

maxmemory-policy noeviction #内存达到上限之后的处理策略 ,像线程一样,4种拒绝策略

APPEND ONLY MODE aof配置

appendonly no  #默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!

appendfilename "appendonly.aof"  #持久化的文件的名字

# appendfsync always   #每次修改都会sync同步,消耗性能
appendfsync everysec   	#每秒执行一次 同步,可能会丢失这1s的数据
# appendfsync no   #不执行同步,这个时候操作系统自己同步数据,速度最快。


14、redis持久化

14.1、rdb持久化

​ 前面我们说过,Redis 相对于 Memcache 等其他的缓存产品,有一个比较明显的优势就是 Redis 不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。这几种丰富的数据类型我们花了两篇文章进行了详细的介绍,接下来我们要介绍 Redis 的另外一大优势——持久化。

  由于 Redis 是一个内存数据库,所谓内存数据库,就是将数据库中的内容保存在内存中,这与传统的MySQL,Oracle等关系型数据库直接将内容保存到硬盘中相比,内存数据库的读写效率比传统数据库要快的多(内存的读写效率远远大于硬盘的读写效率)。但是保存在内存中也随之带来了一个缺点,一旦断电或者宕机,那么内存数据库中的数据将会全部丢失。

  为了解决这个缺点,Redis提供了将内存数据持久化到硬盘,以及用持久化文件来恢复数据库数据的功能。Redis 支持两种形式的持久化,一种是RDB快照(snapshotting),另外一种是AOF(append-only-file)。本篇博客先对 RDB 快照进行介绍。

1、rdb简介

​ RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里。

2、测试

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
#在60s内执行这3个操作后,就会自动生成dump。rdb文件
[root@iZwz9cotjjqzivf3hw1al6Z bin]# rm -rf dump.rdb 
[root@iZwz9cotjjqzivf3hw1al6Z bin]# ls
chardetect        easy_install-3.8  mcrypt           redis-cli
cloud-id          jsondiff          mdecrypt         redis-sentinel
cloud-init        jsonpatch         myconfig         redis-server
cloud-init-per    jsonpointer       redis-benchmark
easy_install      jsonschema        redis-check-aof
easy_install-3.6  libmcrypt-config  redis-check-rdb

[root@iZwz9cotjjqzivf3hw1al6Z bin]# ls
chardetect      easy_install-3.6  libmcrypt-config  redis-check-rdb
cloud-id        easy_install-3.8  mcrypt            redis-cli
cloud-init      jsondiff          mdecrypt          redis-sentinel
cloud-init-per  jsonpatch         myconfig          redis-server
dump.rdb        jsonpointer       redis-benchmark
easy_install    jsonschema        redis-check-aof

3、rdb触发机制

  • save的规则满足的情况下,会自动触发rdb持久化
  • 也可以手动执行save命令,触发rdb持久化操作,该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。
  • 也可以手动执行bgsave命令,触发rdb持久化操作,bgsave则不会,它是异步操作
  • 在退出redis,执行shutdown关机时,也会触发我们的rdb持久化
  • 执行flushall命令,也会触发我们的rdb持久化,但里面是空的

4、恢复dump.rdb文件数据

​ 将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可,redis就会自动加载文件数据至内存了。Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。

 获取 redis 的安装目录可以使用 config get dir 命令

img

5、rdb持久化的优缺点:

优点:

1.RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。

2.生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。

3.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

缺点:

1、在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有丢失)

2、RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,如果不采用压缩算法(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)

14.2 aof

 Redis的持久化方式之一RDB是通过保存数据库中的键值对来记录数据库的状态。而另一种持久化方式 AOF 则是通过保存Redis服务器所执行的写命令来记录数据库状态。

比如对于如下命令:

img

RDB 持久化方式就是将 str1,str2,str3 这三个键值对保存到 RDB文件中,而 AOF 持久化则是将执行的set,sadd,lpush 三个命令保存到 AOF 文件中。

1、开启aof

​ redis默认是使用rdb持久化的,所以使用aof需要修改conf配置文件,将 redis.conf 的 appendonly 配置改为 yes 即可。

  AOF 保存文件的位置和 RDB 保存文件的位置一样,都是通过 redis.conf 配置文件的 dir 配置:

2、aof数据文件恢复

​ 重启 Redis 之后就会进行 AOF 文件的载入。

  异常修复命令:redis-check-aof --fix 进行修复

3、aof的优缺点

优点:

1 、AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。

2、AOF 文件使用 Redis 命令追加的形式来构造,因此,即使 Redis 只能向 AOF 文件写入命令的片断,使用 redis-check-aof 工具也很容易修正 AOF 文件。

3、AOF 文件的格式可读性较强,这也为使用者提供了更灵活的处理方式。例如,如果我们不小心错用了 FLUSHALL 命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢复数据。

缺点:

1、对于具有相同数据的的 Redis,AOF 文件通常会比 RDF 文件体积更大。

2、AOF的修复速度也比RDB慢,以及运行效率也比RDB低。

15、发布订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

img

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

img

image-20201219195015677

测试:

查看订阅的:

127.0.0.1:6379> SUBSCRIBE xiaopang
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "xiaopang"
3) (integer) 1
1) "message"
2) "xiaopang"
3) "hello,redis"
1) "message"
2) "xiaopang"
3) "woshizhangjinhao"

发布订阅的:

127.0.0.1:6379> PUBLISH xiaopang "hello,redis"
(integer) 1
127.0.0.1:6379> PUBLISH xiaopang "woshizhangjinhao"
(integer) 1

16、主从复制

主从复制

​ 和Mysql主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。下图为级联结构。

img

主从复制的配置:

1、配从(库)不配主(库);

2、从库配置:slaveof [主库IP] [主库端口];

​ 补充:每次slave与master断开后,都需要重新连接,除非你配置进redis.conf文件;

键入info replication 可以查看redis主从信息。

全量复制
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
- 从服务器连接主服务器,发送SYNC命令;
- 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
- 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
- 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

img

增量复制
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

注意点
如果多个Slave断线了,需要重启的时候,因为只要Slave启动,就会发送sync请求和主机全量同步,当多个同时出现的时候,可能会导致Master IO剧增宕机。

- 一主二仆

一个Master,两个Slave,Slave只能读不能写;当Slave与Master断开后需要重新slaveof连接才可建立之

​ 前的主从关系;Master挂掉后,Master关系依然存在,Master重启即可恢复。

- 反客为主

​ 当Master挂掉后,Slave可键入命令 slaveof no one使当前redis停止与其他Master redis数据同步,转成

​ Master redis。

想要深入理解请看这里

16.2、哨兵模式

​ 反客为主的自动版,能够后台监控Master库是否故障,如果故障了根据投票数自动将slave库转换为主库。一组sentinel能同时监控多个Master。

概述:

​ 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

​ 这里的哨兵有两个作用

​ 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。

​ 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

​ 然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,所以哨兵也是支持集群的,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

​ 用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

使用步骤:

​ 1、在Master对应redis.conf同目录下新建sentinel.conf文件,名字绝对不能错;

​ 2、配置哨兵,在sentinel.conf文件中填入内容:

​ sentinel monitor 被监控数据库名字(自己起名字) ip port 1

​ 说明:上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机。

3、启动哨兵模式:

​ 命令键入:redis-sentinel /myconfig/sentinel.conf

注意:当master主机宕机,redis哨兵重新选择主机后,这时,master主机重新连接上时,会自动成为新的主节点的从节点,也就是变成了从机了。

17、redis缓存穿透、击穿、以及雪崩

缓存穿透

描述:

​ 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案:

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
1、缓存空对象,从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

2、布隆过滤器。**bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,

缓存击穿

描述:

*缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期)*,这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

解决方案:

1、设置热点数据永远不过期。

2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。

3、布隆过滤器****。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,

4、加互斥锁,互斥锁参考代码如下:

img

说明:

​ 1)缓存中有数据,直接走上述代码13行后就返回结果了

​ 2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。

​ 3)当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据,上面代码明显做不到这点。

缓存雪崩

描述:

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。
原文地址:https://www.cnblogs.com/xiaopanjava/p/14165990.html