Redis超详细学习

Redis超详细学习

一、Redis入门

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2010年5月开始,Redis的开发由Pivotal赞助。免费和开源!是当下最热门的NoSQL技术之一!也被人们称为结构化数据库!–摘自百度百科

Redis能干嘛?

  1. 内存存储、持久化,内存中是断电即失,所以说持久化很重要(rdb、aof),RDB、AOF的区别

  2. 效率高,可以用于高速缓存

  3. 发布订阅系统

  4. 地图信息分析

  5. 计时器、计数器(浏览量)

特性

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

学习资料

Redis中文网

Redis官方网站

B站狂神–Redis篇

Redis推荐在Linux系统上安装使用

二、在Windows系统下安装

  1. 下载安装包:http://github.com/dmajkic/redis/releases
  2. 下载完毕得到安装包

在这里插入图片描述

  1. 解压到自己电脑的环境目录下就可以了

  2. 开启Redis,双击运行即可

  3. 使用redis客户端连接redis

在这里插入图片描述

Windows系统下安装使用十分方便,但是推荐Redis推荐我们使用Linux去开发并使用

三、在Linux系统下安装

  1. 官网下载安装包

在这里插入图片描述

  1. 将这个压缩包用ftp传输到linux服务器的opt目录下

在这里插入图片描述

  1. 解压Redis安装包!

在这里插入图片描述

​ 解压完成:

在这里插入图片描述

  1. 进入redis-6.0.6文件夹

在这里插入图片描述

  1. 装载环境
yum install gcc-c++

make

make install

如果安装不成功就是redis版本和gcc版本问题了

  1. redis的默认安装路径是:/usr/local/bin

在这里插入图片描述

  1. 创建一个文件夹,将redis的配置文件移动到该文件夹
mkdir lconfig
                cp /opt/redis-6.0.6/redis.conf lconfig
redis.conf(我们之后使用这个配置文件进行启动)

在这里插入图片描述

  1. redis默认不是后台启动,先修改配置文件

    vim redis.conf
    

在这里插入图片描述

  1. 启动redis服务

在这里插入图片描述

  1. 客户端redis-cli连接测试

在这里插入图片描述

11.查看redis的进程是否开启(开一个新会话查看)

在这里插入图片描述

12.关闭redis服务

在这里插入图片描述

四、测试性能工具

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

Redis 性能测试

redis-benchmark命令参数:

在这里插入图片描述

五、redis的基础知识

5.1、redis默认有16个数据库:

在这里插入图片描述

在这里插入图片描述

默认使用的是第0个数据库

可以使用select进行切换数据库:

在这里插入图片描述

#查看数据库所有的key
127.0.0.1:6379[3]> keys *
1) "name"

#清空当前数据库
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty array)

#清空所有的数据库
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name lxf
OK
127.0.0.1:6379> set sex 男
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> FLUSHALL
OK
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> keys *
(empty array)

基本使用:

127.0.0.1:6379> set name lys
OK
127.0.0.1:6379> get name   
"lys"
127.0.0.1:6379> EXISTS name #判断name是否存在
(integer) 1
127.0.0.1:6379> exists name1 
(integer) 0
127.0.0.1:6379> move name 1 #移除name
(integer) 1
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name lxf
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> EXPIRE name 10 #设置name过期时间(秒)
(integer) 1
127.0.0.1:6379> ttl name #查看name还有多久过期
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name #name已经过期
(nil)


127.0.0.1:6379> set age 13
OK
127.0.0.1:6379> set name lxf
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> type name #查看name是什么类型的
string
127.0.0.1:6379> type age
string

所有命令:

Redis命令中心

5.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)

六、五大数据类型

6.1、String(字符串类型)

常规key-value缓存应用:微博数,粉丝数等。

127.0.0.1:6379> set key1 v1 #设置值
OK
127.0.0.1:6379> get key1 #获取值
"v1"
127.0.0.1:6379> keys *
1) "key1"

127.0.0.1:6379> exists key1 #判断key1是否存在
(integer) 1

127.0.0.1:6379> append key1 hello #给key1增加一段字符串
(integer) 7
127.0.0.1:6379> get key1
"v1hello"

127.0.0.1:6379> strlen key1 #获取字符串的长度
(integer) 7

127.0.0.1:6379> append key1 ,redis 
(integer) 13
127.0.0.1:6379> get key1
"v1hello,redis"

127.0.0.1:6379> append name "zhangsan "#如果name不存在等于新建了一个key叫zhangsan
(integer) 9
127.0.0.1:6379> get name
"zhangsan "

127.0.0.1:6379> set age 3
OK
127.0.0.1:6379> del age #删除一个key
(integer) 1
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> set views 0 #浏览量
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #浏览量加一
(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 #浏览量减一
(integer) 1
127.0.0.1:6379> get views
"1"

127.0.0.1:6379> incrby views 10 #浏览量增10
(integer) 11
127.0.0.1:6379> get views
"11"

127.0.0.1:6379> decrby views 5 #浏览量减5
(integer) 6
127.0.0.1:6379> get views 
"6"

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set key1 "hello redis"
OK
127.0.0.1:6379> get key1
"hello redis"
127.0.0.1:6379> getrange key1 0  5 #获取字符串的前6个字符
"hello "
127.0.0.1:6379> getrange key1 0 -1 #获取字符串的所有字符
"hello redis"

127.0.0.1:6379> set key2 a1cdefg
127.0.0.1:6379> setrange key2 1 xx #字符串区间替换
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"

127.0.0.1:6379> setex key4 20 wan #创建一个值并设置它的过期时间
OK
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> ttl key4
(integer) 9
127.0.0.1:6379> get key4
"wan"


127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> setnx k2 hhh  #如果这个值不存在则创建,如果存在则不做操作
(integer) 0
127.0.0.1:6379> setnx k4 hhh
(integer) 1
127.0.0.1:6379> get k4
"hhh"


127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #设置多个键值对
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k1"
3) "k2"

127.0.0.1:6379> mget k1 k2 k3 #获取多个值
1) "v1"
2) "v2"
3) "v3"

127.0.0.1:6379> msetnx k3 v33 k4 v4 #设置多个键值对,并且key不存在(原子性操作)
(integer) 0
127.0.0.1:6379> keys *
1) "k3"
2) "k1"
3) "k2"

#存储对象
127.0.0.1:6379> mset user:1:name lxf user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "lxf"
2) "2"
127.0.0.1:6379> keys *
1) "k1"
2) "user:1:age"
3) "k2"
4) "user:1:name"
5) "k3"

#先获取再设置
127.0.0.1:6379> getset key5 whh
(nil)
127.0.0.1:6379> get key5
"whh"
127.0.0.1:6379> getset key5 hww
"whh"
127.0.0.1:6379> get key5
"hww"
6.2、List(列表)

Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部

添加或者删除元素,这样List即可以作为栈,也可以作为队列

127.0.0.1:6379> lpush list one two 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 0 #展示第一个数据
1) "three"
127.0.0.1:6379> lset list 0 zero #设置第一个数据为0(有值则更新,无值则报错)
OK
127.0.0.1:6379> lrange list 0 -1
1) "zero"
2) "two"
3) "one"

127.0.0.1:6379> rpush list four #从右插入一个数
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "zero"
2) "two"
3) "one"
4) "four"

127.0.0.1:6379> lpop list #从左边移除一个数
"zero"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "four"

127.0.0.1:6379> rpop list #从有右移除一个数
"four"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"

127.0.0.1:6379> lindex list 0 #通过下标获取一个值
"two

127.0.0.1:6379> llen list #显示list的长度
(integer) 2

127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "six"
3) "five"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 2 five #移除重复的两个five
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "six"
2) "two"
3) "one"

127.0.0.1:6379> lrange list 0 -1
1) "six"
2) "two"
3) "one"
127.0.0.1:6379> ltrim list 1 1 #截取区间的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"

127.0.0.1:6379> lpush list three four five
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
127.0.0.1:6379> rpoplpush list ortherlist #将一个list的最后一个值另一个list中
"two"
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
127.0.0.1:6379> lrange ortherlist 0 -1
1) "two"

127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist redis
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "redis"
127.0.0.1:6379> linsert mylist after "hello" "my" #在指定值后面插入一个值(与之对应:before)
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "my"
3) "redis"
6.3、Set(集合)

在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为

集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功

127.0.0.1:6379> sadd myset "hello" #myset集合中添加一个值
(integer) 1
127.0.0.1:6379> sadd myset "redis"
(integer) 1
127.0.0.1:6379> sadd myset "!"
(integer) 1

127.0.0.1:6379> smembers myset #查看myset集合中所有的值
1) "!"
2) "redis"
3) "hello"

127.0.0.1:6379> SISMember myset hello #判断myset集合中是否包含一个值
(integer) 1
127.0.0.1:6379> SISMember myset hel
(integer) 0

127.0.0.1:6379> scard myset #获取myset集合中的元素个数
(integer) 3

127.0.0.1:6379> srem myset "hello" #删除myset集合中的一个元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "!"
2) "redis"

127.0.0.1:6379> srandmember myset  #随机一个元素
"!"
127.0.0.1:6379> srandmember myset 2 #随机指定个数元素
1) "redis"
2) "!"

127.0.0.1:6379> sadd myset my
(integer) 1
127.0.0.1:6379> spop myset #随机移除一个元素
"my"
127.0.0.1:6379> sadd myset a b c d e f
(integer) 6
127.0.0.1:6379> spop myset 2#随机移除指定个数元素
1) "b"
2) "c"

127.0.0.1:6379> sadd myset "hello" "my" "redis!"
(integer) 3
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "redis!" #将一个set集合中的元素移到另一个集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "my"
127.0.0.1:6379> smembers myset2
1) "set2"
2) "redis!

127.0.0.1:6379> sadd set1 a b c
(integer) 3
127.0.0.1:6379> sadd set2 c d e
(integer) 3
127.0.0.1:6379> sdiff set1 set2 #查看差集
1) "a"
2) "b"
127.0.0.1:6379> sinter set1 set2 #查看交集
1) "c"
127.0.0.1:6379> sunion set1 set2 #查看并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"
6.4、Hash(哈希)

Map集合、key–>value

hash特别适合用于存储对象。存储部分变更的数据,如用户信息等。

127.0.0.1:6379> hset myhash field1 lxf field2 lys field lss #给hash赋多个key-value
(integer) 3

127.0.0.1:6379> hget myhash field2 #获取指定值
"lys"
127.0.0.1:6379> hmset myhash field3 lss field null #设置多个值
OK

127.0.0.1:6379> hget myhash field 
"null"
127.0.0.1:6379> hmget myhash field3 field2 #获取多个指定值
1) "lss"
2) "lys"

127.0.0.1:6379> hgetall myhash #获取多个值
1) "field1"
2) "lxf"
3) "field2"
4) "lys"
5) "field"
6) "null"
7) "field3"
8) "lss"

127.0.0.1:6379> hdel myhash field #根据key删除hash指定的key-value
(integer) 1

127.0.0.1:6379> hlen myhash #获取myhash中的元素个数
(integer) 3

127.0.0.1:6379> hexists myhash field1 #判断myhash中是否含有field1的key
(integer) 1
127.0.0.1:6379> hexists myhash field
(integer) 0

127.0.0.1:6379> hkeys myhash #查看所有的key
1) "field1"
2) "field2"
3) "field3"
127.0.0.1:6379> hvals myhash #查看所有的value
1) "lxf"
2) "lys"
3) "lss"

127.0.0.1:6379> hgetall myhash
 1) "field1"
 2) "lxf"
 3) "field2"
 4) "lys"
 5) "field3"
 6) "lss"
 7) "field5"
 8) "5"
 9) "field"
10) "0"

127.0.0.1:6379> hincrby myhash field5 5 #给field5的value加上5
(integer) 10
127.0.0.1:6379> hincrby myhash field5 -5#给field5的value减去上5
(integer) 5
127.0.0.1:6379> hsetnx myhash field 0#如果field不存在则创建,存在则不做操作
(integer) 0
127.0.0.1:6379> hsetnx myhash field4 4
(integer) 1
127.0.0.1:6379> hgetall myhash
 1) "field1"
 2) "lxf"
 3) "field2"
 4) "lys"
 5) "field3"
 6) "lss"
 7) "field5"
 8) "5"
 9) "field"
10) "0"
11) "field4"
12) "4"

#更多操作:
127.0.0.1:6379> hset user:1 name lxf 
(integer) 1
127.0.0.1:6379> hget user:1 name
"lxf"
6.5、Zset(有序集合)

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如

一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分,

这样在数据插入集合的时候,就已经进行了天然的排序。可以用sorted set来做带权重的队列,比如普

通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让

重要的任务优先执行。排行榜应用。。。。

127.0.0.1:6379> zadd myset 1 one #增加单个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 
(integer) 1

127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
127.0.0.1:6379> zadd myset 3 three 4 four
(integer) 2

127.0.0.1:6379> zrange myset 0 -1 #增加多个值
1) "one"
2) "two"
3) "three"
4) "four"

127.0.0.1:6379> zadd salarys   5000 lys   1000 lrs  3000 lss  10000 lxf
(integer) 4

127.0.0.1:6379> zrange salarys 0 -1#递增排序
1) "lrs"
2) "lss"
3) "lys"
4) "lxf"

127.0.0.1:6379> zrevrange salarys 0 -1#递减排序
1) "lxf"
2) "lys"
3) "lss"
4) "lrs"

127.0.0.1:6379> zrangebyscore salarys -inf +inf #从负无穷到正无穷,递增排序
1) "lrs"
2) "lss"
3) "lys"
4) "lxf"

127.0.0.1:6379> zrevrangebyscore salarys +inf -inf #从正无穷到负无穷,递减排序
1) "lxf"
2) "lys"
3) "lss"
4) "lrs"

127.0.0.1:6379> zrange salarys 0 -1
1) "lrs"
2) "lss"
3) "lys"
4) "lxf"
127.0.0.1:6379> zrem salarys lys #移除一个元素
(integer) 1
127.0.0.1:6379> zrange salarys 0 -1
1) "lrs"
2) "lss"
3) "lxf"

127.0.0.1:6379> zcard salarys #获取有序集合的元素个数
(integer) 3

127.0.0.1:6379> zcount salarys 100 5000 #查看区间的元素数
(integer) 2
127.0.0.1:6379> zcount salarys 6000 10000
(integer) 1

#更多:http://redis.cn/commands.html#sorted_set

七、三种特殊数据类型

7.1、geospatial地理位置

作用:朋友的定位,附近的人,打车距离,方圆几里的人

查询地区经纬度在线工具

#geoadd 添加地理位置[经度 纬度 名称]
#规则:两极无法直接添加,我们一般会下载城市数据,直接通过java程序直接一次性导入
#有效的经度从-180度到180度。
#有效的纬度从-85.05112878度到85.05112878度。
127.0.0.1:6379> geoadd china:city 116.40 39.90  beijing #添加单个城市的纬经度 
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai #上海
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing #重庆
(integer) 1
127.0.0.1:6379> geoadd china:city 114.08 22.54 shenzhen 120.15 30.28 hangzhou 125.14  42.92 xian 119.30 26.07 fuzhou #添加多个城市的纬经度 深圳 杭州 西安 福州
(integer) 4

127.0.0.1:6379> geopos china:city beijing #获取单个指定城市的经纬度
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city chongqing shanghai #获取多个指定城市的经纬度
1) 1) "106.49999767541885376"
   2) "29.52999957900659211"
2) 1) "121.47000163793563843"
   2) "31.22999903975783553"

获取两地的距离

如果两个位置之间的其中一个不存在, 那么命令返回空值。

指定单位的参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。

如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。

GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差

127.0.0.1:6379> geodist china:city chongqing shanghai m #查看重庆到上海的直线距离 单位: m
"1447673.6920"
127.0.0.1:6379> geodist china:city chongqing shanghai km
"1447.6737"
127.0.0.1:6379> geodist china:city chongqing shanghai mi
"899.5450"
127.0.0.1:6379> geodist china:city chongqing shanghai ft
"4749585.6037"

以一个经纬度为中心,找出半径内的所有元素

127.0.0.1:6379> georadius china:city 115 30 1000 km #找出china:city中在(115,30)1000km范围内的元素
1) "shenzhen"
2) "fuzhou"
3) "hangzhou"
4) "shanghai"
5) "chongqing"
127.0.0.1:6379> georadius china:city 115 30 500 km#找出china:city中在(115,30)500km范围内的元素
1) "hangzhou"


127.0.0.1:6379> georadius china:city 115 30 500 km withdist #找出china:city中在(115,30)500km范围内的元素,并返回它们之间的距离
1) 1) "hangzhou"
   2) "496.3065"
127.0.0.1:6379> georadius china:city 115 30 500 km withdist withcoord #找出china:city中在(115,30)500km范围内的元素,并返回它们之间的距离,并且返回它的经纬度
1) 1) "hangzhou"
   2) "496.3065"
   3) 1) "120.15000075101852417"
      2) "30.2800007575645509"
127.0.0.1:6379> georadius china:city 115 30 1000 km count 2#找出china:city中在(115,30)1000km范围内的元素中的两个
1) "hangzhou"
2) "fuzhou"
127.0.0.1:6379> georadius china:city 115 30 1000 km asc#找出china:city中在(115,30)1000km范围内的元素并升序排序
1) "hangzhou"
2) "fuzhou"
3) "shanghai"
4) "chongqing"

127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 500 km #以一个城市去找这个城市范围内的元素,也可以排序、输出两地距离、选择输出多少个、输出经纬度
1) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"

127.0.0.1:6379> geohash china:city beijing chongqing #将二维的经纬度转换成一维的hash字符串
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

Zset操作Geo实现元素的移除

127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "shenzhen"
3) "fuzhou"
4) "hangzhou"
5) "shanghai"
6) "beijing"
7) "xian"
127.0.0.1:6379> zrem china:city xian #移除西安
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "shenzhen"
3) "fuzhou"
4) "hangzhou"
5) "shanghai"
6) "beijing"
7.2、HyperLogLog(不精确的去重计数)

网页的计算访问量(同一个人访问多次还是算一次)

传统方式:set保存用户的id,然后就可以统计set中的元素数作为标准判断!(如果保存了大量的用户id,就会比较麻烦,我们的目的是计数,而保存用户id就显得赘余了)

HyperLogLog统计优点:占用内存固定,2^64种不同的元素的技术,只需要12KB内存,只需要从内存角度来比较的话Hyperloglog是首选(0.81%的错误率,是可以被接受的)

测试:

127.0.0.1:6379> pfadd mykey a b c d e f g h i j #创建第一组元素 mykey
(integer) 1
127.0.0.1:6379> pfcount mykey #打印mykey的元素个数
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j k m n o p #创建第二组元素 mykey2
(integer) 1
127.0.0.1:6379> pfcount mykey2 #创建第二组元素 mykey2
(integer) 7
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 #合并第二组元素和第一组元素给mykey3(并集)
OK
127.0.0.1:6379> pfcount mykey3 #看并集的数量
(integer) 15

如果允许容错,那么可以使用hyperloglog

如果不允许容错,就使用set或者自己的数据类型即可

7.3、Bitmaps

位存储

统计用户信息,活跃,不活跃,登录,未登录,打卡。两个状态的都可以使用BitMaps

Bitmaps位图,数据结构,都是操作二进制来进行记录,就只有0和1两种状态

测试:

#0-6:周日到周六 sign:是否打卡 0:未打卡 1:已打卡
127.0.0.1:6379> setbit sign 0 1 #设置第一位为1 已打卡
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(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 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0

127.0.0.1:6379> getbit sign 0 #获取第一位为1 已打卡
(integer) 1
127.0.0.1:6379> getbit sign 4 
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0

127.0.0.1:6379> bitcount sign #统计sign中的1的数量
(integer) 4

八、Redis事务

Redis事务本质:一组命令的集合。一个事务中的所有的命令串行化,在事务的执行过程中会按顺序执行!

一次性,顺序性,排他性

Redis事务没有隔离级别的概念

Redis单条命令保存原子性的,但是事务不保存原子性!

redis的事务:

  • 开启事务()
  • 命令入列()
  • 执行事务()

正常执行事务:

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

放弃事务

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 k4
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
127.0.0.1:6379> get key4 #事务队列命令都不会被执行
(nil)

编译型异常,事务中所有的命令都不会被执行

127.0.0.1:6379> flushdb
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 v3
QUEUED
127.0.0.1:6379> getset k3  #语句执行出错
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.#事务也报错
127.0.0.1:6379> get v1 #所有的命令都不会被执行
(nil)

运行时异常,如果事务队列中存在语法性问题,那么执行命令的时候,其它命令是可以正常执行的,错误命令抛出异常!

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> get v3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #这个命令执行报错了,但是下面的命令还是可以执行的
2) OK
3) OK
4) (nil)

监控​watch:

悲观锁:

  • 很悲观,认为大概率会出现问题,无论做什么都会加锁

乐观锁:

  • 很乐观,认为很小概率会出现问题,所以操作的时候直接不加锁,更新数据的时候去做冲突检测
  • 获取version–>更新时比较

正常执行完毕:

127.0.0.1:6379> set money 100 #设置money(存入的钱)为100
OK
127.0.0.1:6379> set out 0 	 #设置out(取出的钱)为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 out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

重开一个会话叫会话2(连接一个端口):

127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10 #执行到这不继续执行先去原会话操作
QUEUED


127.0.0.1:6379> exec #执行完原会话,值已被修改,而且我们的money是被监控的,所以再去执行这里的事务就会失败
(nil)

原会话:

127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK

测试多会话修改值,使用watch可以当作redis的乐观锁操作。

解锁再加锁:

#事务执行失败之后,先解锁再加锁
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 100
QUEUED
127.0.0.1:6379> incrby out 100
QUEUED
127.0.0.1:6379> exec
1) (integer) 900
2) (integer) 120

九、Jedis(java操作Redis)

Jedis:是Redis官方推荐的java连接开发工具,使用java操作Redis中间件,如果你要使用java操作redis,那么一定要对jedis十分熟悉!

9.1、导入相关的依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.58</version>
</dependency>
9.2、编码测试
  • 连接数据库
  • 操作命令
  • 断开连接
package com.lxf;

import redis.clients.jedis.Jedis;

public class TestPing {
    public static void main(String[] args) {
        //1.new一个jedis对象即可
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //jedis所有的命令就是我们之前学习到的所有命令
        //测试连接:
        System.out.println(jedis.ping());
    }
}
9.3、常用的API
  • key的基本操作:
 		//1.new一个jedis对象即可
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //jedis所有的命令就是我们之前学习到的所有命令
        System.out.println(jedis.ping());

        System.out.println("清空数据:"+jedis.flushDB());
        System.out.println("判断username键是否存在:"+jedis.exists("username"));
        System.out.println("新增<'username','lxf'>的键值对:"+jedis.set("username","lxf"));
        System.out.println("新增<'password','123'>的键值对:"+jedis.set("password","123"));
        System.out.println("查看系统中所有的键:");
        System.out.println(jedis.keys("*"));
        System.out.println("删除password:"+jedis.del("password"));
        System.out.println("判断password是否存在:"+jedis.exists("password"));
        System.out.println("查看username键位存储的类型:"+jedis.type("username"));
        System.out.println("新增<'age','18'>的键值对:"+jedis.set("age","18"));
        System.out.println("新增<'birthday','2020-02-02'>的键值对:"+jedis.set("birthday","2020-02-02"));
        System.out.println("随机返回key空间的一个:"+jedis.randomKey());
        System.out.println("重命名key:"+jedis.rename("username", "name"));
        System.out.println("取出修改后的name:"+jedis.get( "name"));
        System.out.println("按索引查询:"+jedis.select(1));
        System.out.println("返回当前数据库中所有的key的数目:"+jedis.dbSize());
        System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
		jedis.close();//关闭连接

结果:

PONG
清空数据:OK
判断某个键是否存在:false
新增<'username','lxf'>的键值对:OK
新增<'password','123'>的键值对:OK
查看系统中所有的键:
[password, username]
删除password:1
判断password是否存在:false
查看username键位存储的类型:string
新增<'age','18'>的键值对:OK
新增<'birthday','2020-02-02'>的键值对:OK
随机返回key空间的一个:birthday
重命名key:OK
取出修改后的name:lxf
按索引查询:OK
返回当前数据库中所有的key的数目:0
删除所有数据库中的所有key:OK
  • String:
Jedis jedis = new Jedis("127.0.0.1", 6379);

        jedis.flushDB();
        System.out.println("=============增加数据===================");
        System.out.println(jedis.set("k1", "v1"));
        System.out.println(jedis.set("k2", "v2"));
        System.out.println(jedis.set("k3", "v3"));
        System.out.println(jedis.set("k4", "v4"));
        System.out.println("=============增加数据===================");
        System.out.println("删除k2:"+jedis.del("k2"));
        System.out.println("获取k2:"+jedis.get("k2"));
        System.out.println("修改k1:"+jedis.set("k1","val1"));
        System.out.println("获取k1的值:"+jedis.get("k1"));
        System.out.println("在k1的上再加上值:"+jedis.append("k1",",100"));
        System.out.println("获取k1的值:"+jedis.get("k1"));
        System.out.println("增加多个键值对:"+jedis.mset("k5","v5","k6","v6","k7","v7"));
        System.out.println("获取多个值:"+jedis.mget("k5","k6","k7"));
        System.out.println("删除多个值:"+jedis.del("k3","k6"));
        System.out.println("打印所有的key:");
        System.out.println(jedis.keys("*"));
        System.out.println("=========================================================");
        jedis.flushDB();//将数据库清空
        System.out.println("如果key不存在再添加(存在则不添加):"+jedis.setnx("k1", "v1"));
        System.out.println("如果key不存在再添加(存在则不添加):"+jedis.setnx("k2", "v2"));
        System.out.println("如果key不存在再添加(存在则不添加):"+jedis.setnx("k3", "v3"));
        System.out.println("如果key不存在再添加(存在则不添加):"+jedis.setnx("k3", "vv3"));
        System.out.println("打印所有的键值对"+jedis.mget("k1","k2","k3"));
        System.out.println("设置键值对并设置3秒有效时间:"+jedis.setex("k4", 3, "vvvv4"));
        System.out.println("获取k4的值:"+jedis.get("k4"));
        try {
            //线程休眠4秒
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("获取k5的值:"+jedis.get("k4"));
        System.out.println("设置k5:"+jedis.set("k5", "vvvv5"));
        System.out.println("获取k5的前4个字符"+jedis.getrange("k5", 0, 3));
        System.out.println("获取k5的值:"+jedis.get("k5"));
        System.out.println("替换k4的前2个字符"+jedis.setrange("k5", 0, "xx"));
        System.out.println("获取k5的值:"+jedis.get("k5"));
		jedis.close();//关闭连接

结果:

===============添加一个list==============
collections的内容:[TreeSet, HashMap, HashSet, LinkedHashMap, WeakHashMap, HashMap, Stack, Vector, ArrayList, TreeMap]
collections区间0-3的元素:[TreeSet, HashMap, HashSet, LinkedHashMap]
========================================
删除指定元素的个数:2
collections的内容:[TreeSet, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList, TreeMap]
collections列表出栈(左端):TreeSet
collections列表出栈(右端):TreeMap
collections的内容:[HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList]
修改collections指定下标1的内容:OK
获取collections的长度:6
获取collections下标为2的元素:WeakHashMap
================================================
sortedList排序前:[10, 2, 5, 9, 6, 3]
[2, 3, 5, 6, 9, 10]
sortedList排序后:[10, 2, 5, 9, 6, 3]
  • List:
Jedis jedis = new Jedis("127.0.0.1", 6379);

        jedis.flushDB();//清空数据库
        System.out.println("===============添加一个list==============");
        //从左插入
        jedis.lpush("collections", "ArrayList","Vector","Stack","HashMap","WeakHashMap","LinkedHashMap");
        jedis.lpush("collections","HashSet");
        jedis.lpush("collections","HashMap");
        jedis.lpush("collections","TreeSet");
        //从右插入
        jedis.rpush("collections","TreeMap");
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("collections区间0-3的元素:"+jedis.lrange("collections", 0, 3));
        System.out.println("========================================");
        System.out.println("删除指定元素的个数:"+jedis.lrem("collections", 2, "HashMap"));
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("collections列表出栈(左端):"+jedis.lpop("collections"));
        System.out.println("collections列表出栈(右端):"+jedis.rpop("collections"));
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("修改collections指定下标1的内容:"+jedis.lset("collections", 1, "HashSet2"));
        System.out.println("获取collections的长度:"+jedis.llen("collections"));
        System.out.println("获取collections下标为2的元素:"+jedis.lindex("collections",2));
        System.out.println("================================================");
        jedis.lpush("sortedList","3","6","9","5","2","10");
        System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));
        System.out.println(jedis.sort("sortedList"));
		jedis.close();//关闭连接

结果:

===============添加一个list==============
collections的内容:[TreeSet, HashMap, HashSet, LinkedHashMap, WeakHashMap, HashMap, Stack, Vector, ArrayList, TreeMap]
collections区间0-3的元素:[TreeSet, HashMap, HashSet, LinkedHashMap]
========================================
删除指定元素的个数:2
collections的内容:[TreeSet, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList, TreeMap]
collections列表出栈(左端):TreeSet
collections列表出栈(右端):TreeMap
collections的内容:[HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList]
修改collections指定下标1的内容:OK
获取collections的长度:6
获取collections下标为2的元素:WeakHashMap
================================================
sortedList排序前:[10, 2, 5, 9, 6, 3]
[2, 3, 5, 6, 9, 10]
sortedList排序后:[10, 2, 5, 9, 6, 3]
  • Set
Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();//清空数据库
        System.out.println("===============向集合中添加元素(不重复)==============");
        System.out.println(jedis.sadd("myset", "e1","e2","e3","e4","e5","e6"));
        System.out.println(jedis.sadd("myset","e6"));
        System.out.println("myset的所有元素为:"+jedis.smembers("myset"));
        System.out.println("删除一个元素e0:"+jedis.srem("myset","e1"));
        System.out.println("myset的所有元素为:"+jedis.smembers("myset"));
        System.out.println("删除两个元素e5,e6:"+jedis.srem("myset", "e5","e6"));
        System.out.println("myset的所有元素为:"+jedis.smembers("myset"));
        System.out.println("随机删除集合中的一个元素:"+jedis.spop("myset"));
        System.out.println("myset的所有元素为:"+jedis.smembers("myset"));
        System.out.println("myset包含元素的个数:"+jedis.scard("myset"));
        System.out.println("判断myset里是否包含某一元素:"+jedis.sismember("myset","e2"));
        System.out.println("==========================================");
        System.out.println(jedis.sadd("eleSet1", "e2","e4","e3","e0","e7","e5"));
        System.out.println(jedis.sadd("eleSet2", "e1","e5","e10","e3","e0","e8"));
        System.out.println("eleSet1和eleSet2交集"+jedis.sinter("eleSet1","eleSet2"));
        System.out.println("eleSet1和eleSet2差集"+jedis.sdiff("eleSet1","eleSet2"));
        System.out.println("eleSet1和eleSet2并集"+jedis.sunion("eleSet1","eleSet2"));
        System.out.println("将eleSet1中的e7删除并移入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e7"));
        System.out.println("将eleSet2中的e8删除并移入eleSet1中:"+jedis.smove("eleSet2", "eleSet1", "e8"));
        System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
        System.out.println("eleSet2中的元素:"+jedis.smembers("eleSet2"));
        System.out.println("eleSet3中的元素:"+jedis.smembers("eleSet3"));
		jedis.close();//关闭连接

结果:

===============向集合中添加元素(不重复)==============
6
0
myset的所有元素为:[e1, e2, e3, e4, e6, e5]
删除一个元素e0:1
myset的所有元素为:[e2, e3, e4, e6, e5]
删除两个元素e5,e6:2
myset的所有元素为:[e2, e3, e4]
随机删除集合中的一个元素:e4
myset的所有元素为:[e2, e3]
myset包含元素的个数:2
判断myset里是否包含某一元素:true
==========================================
7
6
eleSet1和eleSet2交集[e5, e8, e3, e0]
eleSet1和eleSet2差集[e2, e7, e4]
eleSet1和eleSet2并集[e7, e5, e0, e2, e4, e10, e8, e1, e3]
将eleSet1中的e7删除并移入eleSet3中:1
将eleSet2中的e8删除并移入eleSet1中:1
eleSet1中的元素:[e8, e2, e3, e0, e4, e5]
eleSet2中的元素:[e10, e1, e3, e0, e5]
eleSet3中的元素:[e7]
  • hash
Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("k1", "v1");
        map.put("k2", "v2");
        map.put("k3", "v3");
        map.put("k4", "v4");
        map.put("k5", "v5");
        map.put("k6", "v6");
        //添加名称为hash(key)的hash元素
        jedis.hmset("hash",map);
        //向名称为hash的hash中添加key为key6,value为value6元素
        jedis.hset("hash", "k6","6");
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("散列hash的所有key值为:"+jedis.hkeys("hash"));
        System.out.println("散列hash的所有value值为:"+jedis.hvals("hash"));
        System.out.println("将k6的值加上4:"+jedis.hincrBy("hash", "k6", 4));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "k3","k2"));
        System.out.println("散列hash中的键值对个数:"+jedis.hlen("hash"));
        System.out.println("判断hash中是否存在k2:"+jedis.hexists("hash", "k4"));
        System.out.println("获取hash中的单个值:"+jedis.hmget("hash", "k1"));
        System.out.println("获取hash中的多个值"+jedis.hmget("hash", "k5","k1"));
		jedis.close();//关闭连接

结果:

散列hash的所有键值对为:{k3=v3, k4=v4, k5=v5, k6=6, k1=v1, k2=v2}
散列hash的所有key值为:[k3, k4, k5, k6, k1, k2]
散列hash的所有value值为:[v1, v2, v3, v4, v5, 6]
将k6的值加上4:10
散列hash的所有键值对为:{k3=v3, k4=v4, k5=v5, k6=10, k1=v1, k2=v2}
删除一个或者多个键值对:2
散列hash中的键值对个数:4
判断hash中是否存在k2:true
获取hash中的单个值:[v1]
获取hash中的多个值[v5, v1]
9.4、事务
  • 正常执行:
Jedis jedis = new Jedis("127.0.0.1", 6379);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "redis");
        jsonObject.put("name", "lxf");
        //开启事务
        Transaction multi = jedis.multi();
        try {
            multi.set("user1",jsonObject.toJSONString());
            multi.set("user2",jsonObject.toJSONString());

            //执行事务
            multi.exec();
        } catch (Exception e) {
            //放弃事务
            multi.discard();
            e.printStackTrace();
        }finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();//关闭连接
        }

结果:

{"name":"lxf","hello":"redis"}
{"name":"lxf","hello":"redis"}
  • 执行失败:
Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();//清空数据库
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "redis");
        jsonObject.put("name", "lxf");
        //开启事务
        Transaction multi = jedis.multi();
        try {
            multi.set("user1",jsonObject.toJSONString());
            multi.set("user2",jsonObject.toJSONString());
            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();//关闭连接
        }

结果:

java.lang.ArithmeticException: / by zero
	at com.lxf.TestTx.main(TestTx.java:22)
null
null

十、SpringBoot整合

SpringBoot操作数据:spring-data jpa jdbc mongdb redis

SpringData也是和SpringBoot齐名的项目

**说明:**在SpringBoot2.x之后,原来的jedis被替换为了lettuce

**jedis:**采用的直连,多个线程操作的话是不安全的,如果想要避免不安全–>使用jedis pool连接池(BIO模式)

**lettuce:**采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据了(更像NIO模式)

10.1、新建一个SpringBoot项目

添加模块:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

源码分析:

@Bean
@ConditionalOnMissingBean(
    name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    //默认的RedisTemplate没有过多的设置,redis对象都是需要序列化
    //两个泛型都是Object,Object的类型,我们后使用需要强制转换<String,Object>
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

//由于String是redis中最常使用的类型,所以说单独提出来了一个bean!
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}
10.2、配置文件
#SpringBoot所有的配置类都有对应的自动配置类,RedisAutoConfiguration
#自动配置类都会绑定一个properties 配置文件 RedisProperties
spring.redis.host=127.0.0.1
spring.redis.port=6379
10.3、测试
package com.lxf;

import ...

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        //redisTemplate的方法(操作不同的数据类型,api和我们的指令是一样的):
        //opsForValue  操作字符串
        //opsForHash   操作hash
        //opsForList   操作list
        //...

        //我们常用的方法可以直接通过redisTemplate操作,比如事务,基本的crud,连接数据库...

        //1.获取redis连接
        // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        // connection.flushDb();
        // connection.flushAll();

        //
        ValueOperations opsForValue = redisTemplate.opsForValue();
        opsForValue.set("mykey", "刘一手");
        System.out.println(opsForValue.get("mykey"));
    }

}

测试存储对象(用jackson转换):

在这里插入图片描述

测试存储对象(不用jackson转换):

在这里插入图片描述

将User继承Serializable接口就可以了

10.4、自己编写redisTemplate
package com.config;


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author lxf
 */
@Configuration
public class RedisConfig {

    /**
     * 自己定义一个redisTemplate
     * @param factory 
     * @return
     */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        //为了自己开发方法,一般直接使用<String,Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        
        //序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(om.getPolymorphicTypeValidator());
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 
        
        // key采用String的序列化方式 
        template.setKeySerializer(stringRedisSerializer); 
        // hash的key也采用String的序列化方式 
        template.setHashKeySerializer(stringRedisSerializer); 
        // value序列化方式采用jackson 
        template.setValueSerializer(jackson2JsonRedisSerializer); 
        // hash的value序列化方式采用jackson 
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet(); 
        return template; 
    }
}

十一、熟悉Redis.conf 的基本配置

11.1、位置

在这里插入图片描述

11.2、Units 单位

在这里插入图片描述

配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit,不区分大小写

11.3、INCLUDES 包含

在这里插入图片描述

可以通过includes包含,redis.conf 可以作为总文件,可以包含其他文件!

11.4、NETWORK 网络配置
bind 127.0.0.1 # 绑定的ip
protected-mode yes # 保护模式
port 6379  默认端口
11.5、GENERAL 通用
daemonize yes # 默认情况下,Redis不作为守护进程(后台进程)运行。需要开启的话,改为 yes 
supervised no # 可通过upstart和systemd管理Redis守护进程 
pidfile /var/run/redis_6379.pid # 以后台进程方式运行redis,则需要指定pid 文件 
loglevel notice 
		# 日志级别。可选项有: 
		# debug(记录大量日志信息,适用于开发、测试阶段);   
		# verbose(较多日志信息); 
		# notice(适量日志信息,使用于生产环境); 
		# warning(仅有部分重要、关键信息才会被记录)。 
logfile "" # 日志文件的位置,当指定为空字符串时,为标准输出 
databases 16 # 设置数据库的数目。默认的数据库是0 
always-show-logo yes # 是否总是显示logo
11.6、SNAPSHOPTING 快照
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) 
save 900 1 
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) 
save 300 10 
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化) 
save 60 10000 

stop-writes-on-bgsave-error yes # 持久化出现错误后,是否依然进行继续进行工作,默认不继续

rdbcompression yes # 使用压缩rdb文件 yes:压缩,但是需要一些cpu的消耗。no:不压 缩,需要更多的磁盘空间 

rdbchecksum yes # 是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时 候,会有大概10%的性能损耗 

dbfilename dump.rdb # dbfilenamerdb文件名称 

dir ./ # dir 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
11.7、SECURITY安全
# 启动redis,连接客户端 
# 获得和设置密码 
config get requirepass config set requirepass "123456" 
#测试ping,发现需要验证 
127.0.0.1:6379> ping NOAUTH Authentication required. 
# 验证 127.0.0.1:6379> auth 123456 OK
127.0.0.1:6379> ping 
PONG
11.8、限制
maxclients 10000 # 设置能连上redis的最大客户端连接数量 

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

maxmemory-policy noeviction # maxmemory-policy 内存达到上限的处理策略 
        #volatile-lru:利用LRU算法移除设置过过期时间的key。 
        #volatile-random:随机移除设置过过期时间的key。 
        #volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL) 
        #allkeys-lru:利用LRU算法移除任何key。 
        #allkeys-random:随机移除任何key。 
        #noeviction:不移除任何key,只是返回一个写错误。
11.9、append only模式
appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种 方式在许多应用中已经足够用了 
appendfilename "appendonly.aof" # appendfilename AOF 文件名称 

appendfsync everysec # appendfsync aof持久化策略的配置 
        # no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。 
        # always表示每次写入都执行fsync,以保证数据同步到磁盘。 
        # everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。

十二、Redis持久化

(面试和工作,持久化都是重点!)

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘中,那么一旦服务器进程退出,服务器的数据库状态也会消失。所以Redis提供了持久化功能!

一篇文章彻底理解Redis持久化:RDB和AOF

redis持久存储RDB和AOF的区别及优缺点

12.1、RDB(Redis DataBase)
  • RDB是什么

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。

  • 执行流程:

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。

这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那

RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

  • Fork:

Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量,环境变量,程序计数器等)

数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。

Rdb 保存的是 dump.rdb 文件:

在这里插入图片描述

配置位置及SNAPSHOTTING解析:

在这里插入图片描述

这里配置可以修改,比如:save 120 1。这个配置为120秒至少有更改就生成快照文件

  • 注意:

如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以。

若要修改完毕需要立马生效,可以手动使用 save 命令!立马生效 !

  • 其余命令解析:

**Stop-writes-on-bgsave-error:**如果配置为no,表示你不在乎数据不一致或者有其他的手段发现和控

制,默认为yes。

**rbdcompression:**对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用

LZF算法进行压缩,如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。

**rdbchecksum:**在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约

10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。默认为yes。

  • 如何触发RDB快照

1、配置文件中默认的快照配置,建议多用一台机子作为备份,复制一份 dump.rdb

2、命令save或者是bgsave

save 时只管保存,其他不管,全部阻塞

bgsave,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave

命令获取最后一次成功执行快照的时间。

3、执行flushall命令,也会产生 dump.rdb 文件,但里面是空的,无意义 !

4、退出的时候也会产生 dump.rdb 文件!

  • 如何恢复

1、将备份文件(dump.rdb)移动到redis安装目录并启动服务即可(会自动识别)

2、CONFIG GET dir 获取目录

127.0.0.1:6379> config get dir 

dir 

/usr/local/bin 
  • 优点:

1、适合大规模的数据恢复

2、对数据完整性和一致性要求不高

  • 缺点:

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

2、Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。

12.2、AOF(Append Only File)
  • aof是什么

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件

但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件

的内容将写指令从前到后执行一次以完成数据的恢复工作

Aof保存的是 appendonly.aof 文件

在这里插入图片描述

appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这 种方式在许多应用中已经足够用了 

appendfilename "appendonly.aof" # appendfilename AOF 文件名称 

appendfsync everysec # appendfsync aof持久化策略的配置 
		# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。 
		# always表示每次写入都执行fsync,以保证数据同步到磁盘。 
		# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。 
		
No-appendfsync-on-rewrite #重写时是否可以运用Appendfsync,用默认no即可,保证数据安 全性

Auto-aof-rewrite-min-size # 设置重写的基准值 

Auto-aof-rewrite-percentage #设置重写的基准值
  • AOF 启动/修复/恢复

    • 正常恢复:

      • 启动:设置Yes,修改默认的appendonly no,改为yes
      • 将有数据的aof文件复制一份保存到对应目录(confifig get dir)
      • 恢复:重启redis然后重新加载
    • 异常恢复:

      • 启动:设置Yes
      • 故意破坏 appendonly.aof 文件!
      • 修复: redis-check-aof --fix appendonly.aof 进行修复
      • 恢复:重启 redis 然后重新加载
  • Rewrite是什么:

AOF 采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小

超过所设定的阈值时,Redis 就会启动AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以

使用命令 bgrewriteaof !

重写原理:

AOF 文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再

rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧

的aof文件,这点和快照有点类似!

触发机制:

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的已被且文件大

于64M的触发。

  • 优点和缺点:

优点:

1、每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差

但数据完整性比较好

2、每秒同步: appendfsync everysec 异步操作,每秒记录 ,如果一秒内宕机,有数据丢失

3、不同步: appendfsync no 从不同步

缺点:

1、相同数据集的数据而言,aof 文件要远大于 rdb文件,恢复速度慢于 rdb。

2、Aof 运行效率要慢于 rdb,每秒同步策略效率较好,不同步效率和rdb相同。

十三、Redis发布订阅

  • 是什么:

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道。

  • 订阅/发布消息图:

在这里插入图片描述

#会话一:开启服务端,再开启一个客户端连接到6379端口,再订阅一个频道
127.0.0.1:6379> subcribe lxf
(error) ERR unknown command `subcribe`, with args beginning with: `lxf`, 
127.0.0.1:6379> subscribe lxf
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "lxf"
3) (integer) 1
1) "message"	 #消息
2) "lxf" 		 #频道名
3) "hello,world" #监听到会话二发送的信息
#会话二:开启一个客户端连接到6379端口,直接给这个客户端发送信息
[root@lxf bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> publish lxf hello,world
(integer) 2
  • 命令

这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。

在这里插入图片描述

  • 原理

Redis是使用C实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍

此加深对 Redis 的理解。

Redis 通过 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。

通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel

,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关

键,就是将客户端添加到给定 channel 的订阅链表中。

通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel

字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。

Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个

key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应

的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

  • 使用场景:
    • Pub/Sub构建实时消息系统

    • Redis的Pub/Sub系统可以构建实时的消息系统

    • 比如很多用Pub/Sub构建的实时聊天系统的例子。

聊天室思路和私聊思路:

开启一个频道(相当于群),所有人在不输入的时候都是订阅这个频道,单独一个人输入时他从订阅状态切换至发送信息状态。私聊的话直接将客户数限制为两个就可以了。

十四、Redis主从复制

概念

主从复制,是将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);==数据的复制是单向的,只能由主节点到从节点。==Master以写为主,Slave以读为主。==默认情况下,每台Redis服务器都是主节点;==且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点;

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 数据故障:当主节点出现了问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从的复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
  4. 高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不行的()

1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较

大;

2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有

内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。

14.1、主机对多分机(第一种):
1.原理图:

在这里插入图片描述

2.环境配置:

只需要配从库不需要配主库,从库配置:

slaveof 主库ip 主库端口 # 配置主从 
Info replication # 查看信息

注意:每次与 master 断开之后,都需要重新连接,除非你配置进 redis.conf 文件!

3.准备工作:
  • 我们配置主从复制,至少需要三个,一主二从!配置三个客户端

在这里插入图片描述

  • 拷贝多个redis.conf 文件

在这里插入图片描述

  • 修改配置:
#三个文件的配置都改成自己对应的,比如6379的配置如下:
1、指定端口 6379
2、开启daemonize yes
3、Pid文件名字 pidfile /var/run/redis_6379.pid 
4、Log文件名字 logfile "6379.log" 
5、Dump.rdb 名字 dbfilename dump6379.rdb 

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.测试(还可以配置redis.conf文件,实现主分机固定)
  • 启动三个服务

在这里插入图片描述

  • 三个会话分别连不同的端口

会话一连接6379端口:

[root@lxf bin]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master #所有redis服务器都是主机
connected_slaves:0
master_replid:72a09c52a2fb74e88586a16ae8be29ec639cf9d7
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

会话二连接6380端口:

[root@lxf bin]# redis-cli -p 6380
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_replid:607baf4e60948facc6af5ae8dd83f24a4f01e38d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

会话三连接6381端口:

[root@lxf bin]# redis-cli -p 6381
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_replid:75e249b2e9cb2e0643e7c7ab1e09cb8c0b6f753a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
  • 配置为一个Master 两个Slave,只需要将6380和6381作为6379的从机就好

会话三:

127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave #从机
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:84
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:9ac44bbf9063a80c8985f1fe3a84567bc5ccaf28
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84

会话二:

127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave #从机
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:70
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:9ac44bbf9063a80c8985f1fe3a84567bc5ccaf28
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:57
repl_backlog_histlen:14

会话一:

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 #连接的分机数
slave0:ip=127.0.0.1,port=6381,state=online,offset=210,lag=0
slave1:ip=127.0.0.1,port=6380,state=online,offset=210,lag=0
master_replid:9ac44bbf9063a80c8985f1fe3a84567bc5ccaf28
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210
  • 在主机设置值,在从机都可以取到!从机不能写值!

会话一:

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v1
OK

会话二:

127.0.0.1:6380> get k1
"v1"

会话三:

127.0.0.1:6381> get k2
"v1"
  • -注意:
    • 主机断开连接,此时不可以写入,但是从机还是可以读到数据,主机重新连接之后仍然能写入。
    • 从机断开连接之后再重新连接就不能读到主机写入的数据了,此时看到info replication看到自己是master,再次作为6379的从机之后就可以再次读取的数据。
  • 复制原理

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

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

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

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

但是只要重新连接master,一次完全同步(全量复制)将被自动执行

14.2、层层链路(第二种)

在这里插入图片描述

14.3、主机断开,分机转主机
1.“谋朝篡位”

一主二从的情况下,如果主机断了,从机可以使用命令 SLAVEOF NO ONE 将自己改为主机!这个时

候其余的从机链接到这个节点。对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器

关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。

在这里插入图片描述

主机再回来,也只是一个光杆司令了,从机为了正常使用跑到了新的主机上!

2.哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工

干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑

哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。

谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独

立运行。其原理是哨兵通过发送命令等待Redis服务器响应,从而监控运行的多个Redis实例。

在这里插入图片描述

  • 这里的哨兵有两个作用

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

  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服

    务器,修改配置文件,让它们切换主机。

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

在这里插入图片描述

假设主服务器宕(dang)机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认

为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一

定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。

切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为

客观下线

配置测试:

  • 在这三个配置文件的同级目录下新建一个sentinel.conf文件(启动时会自动扫描)
[root@lxf lconfig]# vim sentinel.conf
  • 并写入
#sentinel 监控  被监控的名称 host     port 1
sentinel monitor myredis 127.0.0.1 6379 1 
#后面这个数字1代表主机挂了之后slave投票看让谁接替成为主机,票数最多的就会成为主机

启动哨兵

[root@lxf bin]# redis-sentinel lconfig/sentinel.conf  #注意文件位置
3646:X 16 Aug 2020 21:48:55.962 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3646:X 16 Aug 2020 21:48:55.962 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=3646, just started
3646:X 16 Aug 2020 21:48:55.962 # Configuration loaded
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.6 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 3646
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

3646:X 16 Aug 2020 21:48:55.963 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
3646:X 16 Aug 2020 21:48:55.968 # Sentinel ID is f2c969e8088720c4d6e50acba6e5b6287dab662f
3646:X 16 Aug 2020 21:48:55.968 # +monitor master myredis 127.0.0.1 6379 quorum 1

此时分机:6380、6381,主机:6379。将6379直接关闭

哨兵监控到6379关闭,然后随机投票一个分机作为主机

如果主机此时回来了,只能归并到新的主机下当作从机,这就是哨兵模式的规则!

哨兵模式的优缺点:


优点

  1. 哨兵集群模式是基于主从模式的,所有主从的优点,哨兵模式同样具有。

  2. 主从可以切换,故障可以转移,系统可用性更好。

  3. 哨兵模式是主从模式的升级,系统更健壮,可用性更高。

缺点

  1. Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

  2. 实现哨兵模式的配置也不简单,甚至可以说有些繁琐

# Example sentinel.conf

# 哨兵sentinel实例运行的端口 默认26379 port 26379 

# 哨兵sentinel的工作目录 
dir /tmp

# 哨兵sentinel监控的redis主节点的 ip port 
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。 
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了 
# sentinel monitor <master-name> <ip> <redis-port> <quorum> 
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都 要提供密码 
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码 
#sentinel auth-pass <master-name> <password> 
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒 
# sentinel down-after-milliseconds <master-name> <milliseconds> 
sentinel down-after-milliseconds mymaster 30000

# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同 步,
#这个数字越小,完成failover所需的时间就越长, 
#但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。 
#可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。 
# sentinel parallel-syncs <master-name> <numslaves> 
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: 
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。 
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的 master那里同步数据时。 
#3.当想要取消一个正在进行的failover所需要的时间。 
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超 时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了 
# 默认三分钟 # sentinel failover-timeout <master-name> <milliseconds> 
sentinel failover-timeout mymaster 180000


十五、缓存穿透和雪崩

帮你解读什么是Redis缓存穿透和缓存雪崩(包含解决方案)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值