Redis 学习笔记

1、配置主从机器

需要修改redis.windows.conf的slaveof配置文件,在后面添加主机ip与端口 ,由于已经配置好了6379是从机,6380是主机

2、redis的启动和停止:

如果开两个redis,需要copy一下redis.windows.conf文件,并且需要修改其端口(默认为6379)

然后打开redis的安装目录,对其进行命令行 服务端启动:redis-server.exe redis.windows.conf

客户端连接redis: redis-cli.exe -h ip -p 6379

退出redis命令行:redis-cli.exe -p 6379 shutdown save 这样就可以退出redis了。

默认存储的数据库为db0;如果想换其他的数据库需要转换其他的数据库,用select 1 就转换成db1数据库了。

flushall是清空所有的数据库的数据

3、在windows上将redis作为本地服务启动

在含有redis-server.exe 的根目录中启动命令行窗口,执行

redis-server --service-install --service-name 服务名称 redis.windows.conf,
产生Redis successfully installed as a service提示即表示安装成功。
删除服务:sc delete redis6379

4、用法

命令:

1、切换数据库:
eg1:select 1
eg2: select 2

2、keys * //查看所有的键值
3、dbsize //查看当前数据库的大小
4、flushdb //清除当前数据库
5、flushall //清除所属有数据库
6、exists key //有则返回1,反之为0
7、move key 1 //移除key键 其中1为当前数据库
8、set key name //设置键值对

9、expire key 10 //将值设置为10秒过期
10、ttl key //可以查看当前key还剩下几秒
11、type key //查看当前的key对应value的类型
12、append key str //向key键中追加字符串 如果不存在key,相当于获set key
13、strlen key // 获取当前key中的value的长度

14、incr key //对key键对应的value值加1
15、desr key //对key键对应的value值减1
16、incrby key 10 //表示对key键对应的value值加10
17、desrby key 10 //表示对key键对应的value值加10

18、getrange key start end //截取字符串 end:-1表示全部
19、setrange key start end //替换字符串
20、setex key seconds value //将key值设置多少秒过期
21、setnx key name //当key不存在时,再设置值,反之不设置(分布式锁值)返回值0:失败;1:成功
22、mset key value [key value …] //设置多个key与value
eg:mset name “huanglin” age 12
23、msetnx key value [key value …] // msetnx 是一个原子性的操作,要么同时成功、要么同时失败

设置对象
24、set user:1 {username:hl,age:23}
先get再set
25、getset key value //如果不存在值则返回nil。

5、List

在redis里,我们可以把list用作栈、队列,阻塞队列!
所有的list命令都是l开头的
个人理解:l是栈顶的方向,r是栈底的方向

#################################
lpush
rpush
lrange
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                #获得全部的list的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1               #获得list区间的值
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"
#################################
lpop
rpop
127.0.0.1:6379> lpop list                  #移除头部(左)的第一个元素
"three"
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 0               #通过下标获取某一个值
"two"
127.0.0.1:6379> lindex list 5
(nil)
#################################
Llen
127.0.0.1:6379> llen list                       #返回列表的长度
(integer) 2
#################################
移除指定的值
Lrem 
lren key count value 
eg:
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"
#################################
trim修剪,list 截断127.0.0.1:6379> rpush mylist "hello1" "hello2" "hello3"
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
3) "hello3"
127.0.0.1:6379> ltrim mylist 1 2   #通过下标截取指定的长度,这个list已经发生改变,只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"
2) "hello3"
######################################
rpoplpush    #移除列表的最后一个元素,并将他移动到新的列表中
127.0.0.1:6379> rpush mylist hello1 hello2 hello,
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist
"hello3"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "hello3"
######################################
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 value1 
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "value1"
127.0.0.1:6379> lset list 0 0     # 如果存在,更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 -1   
1) "0"
127.0.0.1:6379> lset list 1 other
(error) ERR index out of range
######################################
linsert 插入值
linsert key before|after pivot value # 将某个值的value插入到你指定的某个元素前面或者后面
eg:
127.0.0.1:6379> rpush mylist hello world
(integer) 2
127.0.0.1:6379> linsert mylist before world huanglin
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "huanglin"
3) "world"

小结

  • 他实际是一个链表。
  • 如果不存在,创建新的链表
  • 如何存在,新增内容
  • 如果移除了所有的值,空链表,也代表不存在!
  • 在两边插入或者改动,效率最高,中间元素,相对来说效率会第一点。
  • 消息队列(Lpush Rpop),栈(Lpush Rpop)

6、Set(集合)

set是不可重复的.
set的开头都是s

127.0.0.1:6379> sadd myset hello huang lin #添加元素
(integer) 3
127.0.0.1:6379> smembers myset   #查看成员
1) "hello"
2) "lin"
3) "huang"
127.0.0.1:6379> SISMEMBER myset hello  #判断是否包含成员 1是包含
(integer) 1
127.0.0.1:6379> SISMEMBER myset nihao #判断是否包含成员 0是不包含
(integer) 0
#######################################################
127.0.0.1:6379> scard myset #获取set集合中的内容元素的个数!
(integer) 3
#######################################################
127.0.0.1:6379> SREM myset hello  #移除set集合中的指定元素
(integer) 1
#######################################################
set 无序不重复集合 。抽随机!
SRANDMEMBER  key [count]
127.0.0.1:6379> SRANDMEMBER myset  #随机抽选一个元素
"1"
127.0.0.1:6379> SRANDMEMBER myset 2  #随机抽取两个元素
"2"
"3"
#######################################################
spop 随机删除key
spop key [count]
eg:
127.0.0.1:6379> SPOP myset
"10"
#######################################################
将指定的值,移动到另一个set集合!
 smove source destination member
 eg:
127.0.0.1:6379> sadd myset hello world hl
(integer) 3
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 hl          #将指定的值移动到另一个集合中
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "hl"
2) "set2"
#######################################################
交集:sinter
并集:SUNION
差集:sdiff
127.0.0.1:6379> SDIFF key1 key2   #差集
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2  #交集        共同好友就可以这样实现
1) "c"
127.0.0.1:6379> SUNION key1 key2 #并集
1) "e"
2) "c"
3) "b"
4) "d"
5) "a"

7、Hash(哈希)

Map集合,key -map 的值是一个map集合!
##############################################
hset key field value
127.0.0.1:6379> hset myhash name anlisha   #set一个值 key -value 
(integer) 1
##############################################
127.0.0.1:6379> hget myhash name #获取一个字段值
"anlisha"
##############################################
127.0.0.1:6379> hmset key field value [field value ...] #设置多个值
127.0.0.1:6379> hmset user name anlisha age 25 gender girl  #设置多个字段值
OK
##############################################
127.0.0.1:6379> hmget user name age #获取多个字段值
1) "anlisha"
2) "25"
##############################################
127.0.0.1:6379> hgetall user # 获取全部的数据
1) "name"
2) "anlisha"
3) "age"
4) "25"
5) "gender"
6) "girl"
##############################################
127.0.0.1:6379> hdel user name #删除hash指定的key 对应的field 
(integer) 1
127.0.0.1:6379> hgetall user
1) "age"
2) "25"
3) "gender"
4) "girl"
##############################################
127.0.0.1:6379> HLEN user #获取链表的长度
(integer) 2
##############################################
127.0.0.1:6379> HEXISTS user name #判断hash中的key是否存在某个属性
(integer) 0
##############################################
127.0.0.1:6379> hkeys user #获取所有的field
1) "age"
2) "gender"
127.0.0.1:6379> HVALS user #获取所有的value
1) "25"
2) "girl"
##############################################
HINCRBY key field increment #指定增量

127.0.0.1:6379> HSETNX user age 26  #如果不存在可以 设置,如何存在,则不能设置
(integer) 0
127.0.0.1:6379> HSETNX user address chengdu
(integer) 1
##############################################
hash变更的数据 user name age ,用户信息的保存,是个存储对象
hset user:1 name anlisha 
127.0.0.1:6379> hset user:1 name hl
(integer) 1
127.0.0.1:6379> hget user:1 name
"hl"

8、Zset(有序集合)

在set的基础上,增加了一个值,set k1 v1  ; zset k1 score1 v1
127.0.0.1:6379> zadd salary 2500 xiaoming #添加用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan #添加用户
(integer) 1
127.0.0.1:6379> zadd salary 70000 als #添加用户
(integer) 1
###########################################
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf    #显示所有用户升序
1) "xiaoming"
2) "zhangsan"
3) "als"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores  #显示全部用户并附带成绩
1) "xiaoming"
2) "2500"
3) "zhangsan"
4) "5000"
5) "als"
6) "70000"
###########################################
127.0.0.1:6379> ZRANGE salary 0 -1 
1) "xiaoming"
2) "zhangsan"
3) "als"
127.0.0.1:6379> ZREM salary xiaoming   #移除有序集合的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1  #遍历元素
1) "zhangsan"
2) "als"
###########################################
127.0.0.1:6379> zcard salary   #获取有序集合中的个数
(integer) 2
###########################################
127.0.0.1:6379> ZREVRANGE salary 0 -1  #从大到小进行排序
1) "als"
2) "zhangsan"
###########################################
127.0.0.1:6379> zadd myset 1 hello 
(integer) 1
127.0.0.1:6379> zadd myset 2 world
(integer) 1
127.0.0.1:6379> zadd myset 3 iloveyou
(integer) 1
127.0.0.1:6379> zcount myset 1 3  #获取指定区间的指定数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
 
 案例思路:set排序存储班级成绩表,工资表排序。
 普通消息,1重要消息,2权重进行判断
 排行榜应用实现,取top N测试。
 

9、 三种特殊数据类型

geospatial地理位置

朋友的定位,附近的人,打车距离计算?
redis的Geo在redis3.2版本就推出了!这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人!

相关命令

GEOADD
将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。这些数据将会存储到sorted set这样的目的是为了方便使用             GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。
GEODIST
GEOHASH
GEOPOS
GEORADIUS
GEORADIUSBYMEMBER
#############################################
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
##############################################
GEOPOS
GEOPOS key member [member ...] 
从key里返回所有给定位置元素的位置(经度和纬度)。
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.39999896287918"
   2) "39.900000091670925"
##############################################
GEODIST 
GEODIST key member1 member2 [unit] 
返回两个给定位置之间的距离。
指定单位的参数 unit 必须是以下单位的其中一个:
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"

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

附近的人?(获得所有附近的人的定位)通过半径查询
所有的数据应该录入:china:city,才会让结果更加清楚

127.0.0.1:6379> GEORADIUS china:city 110 30 1500 km withcoord withdist
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885"
      2) "29.529999579006592"
2) 1) "shanghai"
   2) "1105.9098"
   3) 1) "121.47000163793564"
      2) "31.229999039757836"
3) 1) "beijing"
   2) "1245.2858"
   3) 1) "116.39999896287918"
      2) "39.900000091670925"
   ##############################################
   127.0.0.1:6379> GEORADIUS china:city 110 30 1500 km withcoord withdist count 2  #count显示个数
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885"
      2) "29.529999579006592"
2) 1) "shanghai"
   2) "1105.9098"
   3) 1) "121.47000163793564"
      2) "31.229999039757836"

GEORADIUSBYMEMBER

找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点指定成员的位置被用作查询的中心。

 GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] 
 127.0.0.1:6379>  GEORADIUSBYMEMBER china:city beijing 1500 km
1) "chongqing"
2) "shanghai"
3) "beijing"

GEO的底层的实现原理其实就是Zset!我们可以使用Zset来操作geo

127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "shanghai"
3) "beijing"
127.0.0.1:6379> zrem china:city beijing
(integer) 1

10、Hyperloglog

什么是基数

A{1,3,5,7,8,7} B{1,3,5,7,8}
基数(不重复的元素)=5,可以接受误差!

简介

Redis2.8.9 版本就更新了Hyperloglog数据结构!
Redis Hyperloglog 基数统计算法!
优点:占用的内存是固定,2的64次方不同的元素基数,只需要耗费12KB内存!
网页的UV(一个人访问一个网站多次,但是还是算作一个人!)
传统的方式,set保存用户的id,然后就可以统计set种的元素数量作为标准判断
这个方式如果存在大量的用户id,就会比较麻烦,我们的目的就是为了计数,而不是保存用户id。

该方法有0.81%的错误率,统计UV数量,可以忽略不计!

测试使用

127.0.0.1:6379> pfadd mykey a b c a s d f q w e r t  #创建第一组元素
(integer) 1
127.0.0.1:6379> PFCOUNT mykey  #统计 mykey元素的基数数量
(integer) 11
127.0.0.1:6379> pfadd mykey2 1 2 3 4 5 6 78 43 32
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2  #合并两组mykey 和 mykey2  
OK
127.0.0.1:6379> PFCOUNT mykey3  #看并集数量
(integer) 20

11、Bitmaps

位存储

统计用户信息,活跃,不活跃!登陆,未登录,!打卡、365打卡!
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!
365天 = 365bit 1字节=8bit 46个字节左右

开始打卡
127.0.0.1:6379> setbit sign 0 0
(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 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
#########################
查看某一天是否有打卡!
127.0.0.1:6379> getbit sign 5
(integer) 1
统计操作,统计打卡天数
127.0.0.1:6379> BITCOUNT sign
(integer) 1

12、事务

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

-------队列 set set set 执行-----------------

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

== 所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行! ==

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> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec           #执行事务
1) OK
2) OK
3) OK
4) "v3"

放弃事务

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> DISCARD       #取消事务 事务队列中命令都不会执行
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 k3 v3
QUEUED
127.0.0.1:6379> setget k4 k4    #命令错误
(error) ERR unknown command 'setget'
127.0.0.1:6379> EXEC       #执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2      #全部不成功
(nil)

运行时异常(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> get k3
QUEUED
127.0.0.1:6379> exec                #虽然第一条失败,但是其他命令成功
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"

监控! Watch

悲观锁

  • 很悲观,什么时候都会出问题,无论做什么都会加锁。

乐观锁

  • 很乐观,任何时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,再次期间是否有人修改过这个数据
  • 获取version
  • 更新的时候比较version

redis监视测试

正常执行成功

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> 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

测试多线程修改值,使用watch可以当作redis的乐观锁操作!

127.0.0.1:6379> watch money    #监视  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)

如果修改失败,获取最新的值
在这里插入图片描述

13、Jedis

导入包

    <!--导入jedis的包 -->
    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
   <dependencies>
       <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.73</version>
       </dependency>
   </dependencies>

java链接redis

public class redisTest {
    public static void main(String[] args) {
        //new jedis 对象即可
        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.auth("123456");
        //jedis 所有的命令就是我们之前学习的所有命令!
        System.out.println(jedis.ping());
    }
}

java事务:

public class TestTX {
    public static void main(String[] args) {
        //new jedis 对象即可
        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.auth("123456");
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","huanglin");
        //开启事务
        Transaction transaction = jedis.multi();
        String jsonString = jsonObject.toJSONString();
        try {
            transaction.set("user1",jsonString);
            transaction.set("user2",jsonString);
            //执行事务
            transaction.exec();
        }catch (Exception e){
            //放弃事务
            transaction.discard();
            e.printStackTrace();
        }finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            //关闭链接
            jedis.close();
        }
    }
}

14、SpringBoot整合

SpringBoot 操作数据:spring-data jpa jdbc mongodb redis!
SpringData也是和SpringBoot齐名的项目!
说明:在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce?
在这里插入图片描述

jedis:采用的直连,多线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池!BIO
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数量,更像 NIO 模式

SpringBoot所有的配置类,都有一个自动配置类:RedisAutoConfiguration
自动配置类都会绑定一个properties配置文件 :redisProperties

redis源码:

  //ConditionalOnMissingBean 表示我们可以自己定义一个redisTemplate来替换这个默认的!
    @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;
    }

    @Bean
    @ConditionalOnMissingBean  //由于String是redis最常使用的类型,所以单独提出来一个bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

整合测试

1、导入依赖:

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

2、配置连接

spring.redis.host=127.0.0.1
spring.redis.port=6379

3、测试

@Test
    void contextLoads() {
        // opsForValue 操作字符串 类似String
        //opsForList 操作List 类似List
        //opsForSet
        //opsForHash
        //opsForZSet
        //opsForGeo

        //常用的方法可以直接通过redisTemplate进行操作

        /*获取redis的链接对象
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        connection.flushDb();
        connection.flushAll();*/

        redisTemplate.opsForValue().set("name","huanhuan");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

自己定义redisTemplate

默认实现jdk序列化,控制台存储对象时会显示乱码,通常我们重写redisTemplate来覆盖
系统默认的redisTemplate

@Configuration
public class RedisConfig {

    //自己定义一个redisTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);
        // value序列化方式采用jackson
        template.setValueSerializer(jacksonSeial);
        // key采用String的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        // 对hash的key采用String的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        // 对hash的value采用jackson的序列化方式
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();
        return template;
    }
}

redisUtil工具类

/**
 * redisTemplate封装
 *
 *  @author yinxp@dist.com.cn
 */
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key,long time){
        try {
            if(time>0){
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String ... key){
        if(key!=null&&key.length>0){
            if(key.length==1){
                redisTemplate.delete(key[0]);
            }else{
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key){
        return key==null?null:redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key,Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key,Object value,long time){
        try {
            if(time>0){
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }else{
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key,String item){
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String,Object> map){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String,Object> map, long time){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,Object value,long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item){
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item,double by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item,double by){
        return redisTemplate.opsForHash().increment(key, item,-by);
    }

    //============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key){
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key,Object value){
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object...values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key,long time,Object...values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if(time>0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object ...values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end){
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key){
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key,long index){
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index,Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key,long count,Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 模糊查询获取key值
     * @param pattern
     * @return
     */
    public Set keys(String pattern){
        return redisTemplate.keys(pattern);
    }

    /**
     * 使用Redis的消息队列
     * @param channel
     * @param message 消息内容
     */
    public void convertAndSend(String channel, Object message){
        redisTemplate.convertAndSend(channel,message);
    }


    //=========BoundListOperations 用法 start============

    /**
     *将数据添加到Redis的list中(从右边添加)
     * @param listKey
     * @param expireEnum 有效期的枚举类
     * @param values 待添加的数据
     */
    public void addToListRight(String listKey, Status.ExpireEnum expireEnum, Object... values) {
        //绑定操作
        BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
        //插入数据
        boundValueOperations.rightPushAll(values);
        //设置过期时间
        boundValueOperations.expire(expireEnum.getTime(),expireEnum.getTimeUnit());
    }
    /**
     * 根据起始结束序号遍历Redis中的list
     * @param listKey
     * @param start  起始序号
     * @param end  结束序号
     * @return
     */
    public List<Object> rangeList(String listKey, long start, long end) {
        //绑定操作
        BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
        //查询数据
        return boundValueOperations.range(start, end);
    }
    /**
     * 弹出右边的值 --- 并且移除这个值
     * @param listKey
     */
    public Object rifhtPop(String listKey){
        //绑定操作
        BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
        return boundValueOperations.rightPop();
    }

    //=========BoundListOperations 用法 End============

}

15.redis.config详解

启动的时候,就通过配置文件来启动

单位
在这里插入图片描述

1、配置文件unit对大小写不敏感!

包含

在这里插入图片描述
可以包含多个文件,像spirng的配置文件import。

网络
在这里插入图片描述

 bind 127.0.0.1  #绑定的ip
 port 6379   #端口
 protected-mode yes #保护模式

通用General

daemonize yes    #后台运行,默认为no,我们需要手动开启!

pidfile /var/run/redis.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 #默认的数据库数量

快照 SNAPSHOTTING

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

redis是内存数据库,如果没有持久化,数据断电即失!

save <seconds> <changes>   #在下面的示例中,行为将是保存:
save 900 1    #900秒(15分钟)后,如果至少有1把钥匙改变
save 300 10   #300秒(5分钟)后,如果至少有10个按键发生变化
save 60 10000 #60秒后,如果至少有10000个钥匙被更改
#我们之后学习持久化,会定义这个测试

stop-writes-on-bgsave-error yes #持久化如果出错,是否继续进行操作

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

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

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

REPLICATION 复制

SECURITY 安全

可以在这里设置redis的密码,默认是没有密码!

requirepass 123456 #设置密码

限制 LIMITS

maxclients 10000 #设置能链接上redis的最大客户端的数量
maxmemory <bytes> #redis 配置最大的内存容量
maxmemory-policy noeviction #内存到达上限之后的处理策略

maxmemory-policy 六种方式
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
2、allkeys-lru : 删除lru算法的key   
3、volatile-random:随机删除即将过期key   
4、allkeys-random:随机删除   
5、volatile-ttl : 删除即将过期的   
6、noeviction : 永不过期,返回错误

APPEND ONLY MODE aof配置

appendonly no #默认不开启,默认是使用rdb方式持久化,在大部分情况下,rdb完全够用!
appendfilename "appendonly.aof" #持久化的文件的名字

# appendfsync always  #每次修改都会写入。 消耗性能
appendfsync everysec  #每秒执行一次 ,可能会丢失1s的数据
# appendfsync no      #不执行sync

16、Redis持久化

1、RDB(redis DataBase)

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
在生产环境中,我们会将dump.rdb进行备份
在这里插入图片描述如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效,RDB的缺点时最后一次持久化后的数据可能丢失,我们默认的就是RDB,一般情况下不需要修改这个配置!

rdb保存的文件是dump.rdb
在这里插入图片描述

触发机制

1、save的规则满足的情况下,会自动触发rdb规则
2、执行flushall命令,也会触发我们的rdb规则!
3、退出redis,也会产生rdb文件!
备份就会自动生生一个dump.rdb

如何恢复rdb文件!

只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据!

RDB优缺点:

优点:
1、 适合大规模的数据恢复
2、对数据的完整性不高
缺点:
1、需要一定的时间间隔进程操作!如果redis意外宕机,这个最后一次修改的数据就,就没有了!
2、fork进程的时候,会占用一定的内容空间!!!

2、AOF(Append Only File)

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。(写入操作采用的是append模式

将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍!
在这里插入图片描述
Aof保存的是appendonly.aof文件

APPEND ONLY MODE

在这里插入图片描述
默认是不开启的,我们需要手动进行配置!我们需要appendonly改为yes
重启redis就可以生效了
如果这个aof文件有错误,这时候redis启动不起来,我们需要修复这个aof文件,redis给我们提供了一个工具,redis-check-aof 去修复aof文件.

重写规则

aof默认就是文件的无限追加,文件会越来越大!
在这里插入图片描述

如果aof文件大于64M,太大了!,fork一个新的进程来将我们的文件进行重写!

AOF 优点和缺点!

appendonly no #默认不开启,默认是使用rdb方式持久化,在大部分情况下,rdb完全够用!
appendfilename "appendonly.aof" #持久化的文件的名字

# appendfsync always  #每次修改都会写入。 消耗性能
appendfsync everysec  #每秒执行一次 ,可能会丢失1s的数据
# appendfsync no      #不执行sync

优点:
1、每一次修改都同步,文件的完整性会更好!
2、每秒同步一次,可能会丢失1s的数据
3、不同步。
缺点:
1、相对于数据文件来说,aof的远远大于rdb,修复速度也比rdb慢!
2、aof运行效率也比rdb慢。

17、Redis发布订阅

Redis发布订阅(pub/sub)是一种消息通信模式 :发送者(pub)发送消息,订阅者(sub)接受消息。eg:微信,微博,关注系统!
redis客户端可以订阅任意数量的频道。
订阅/发布消息图:
第一个:消息发送者,第二个:频道,第三个:消息订阅者。
在这里插入图片描述在这里插入图片描述

命令

在这里插入图片描述

测试

订阅端:

127.0.0.1:6379> SUBSCRIBE HLTest          #订阅一个频道   HLTest          
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "HLTest"
3) (integer) 1
#等待读取推送的信息
1) "message"               #消息
2) "HLTest"					#那个频道的消息
3) "hello world"          #消息的具体内容

1) "message"
2) "HLTest"
3) "hello redis"

发送端:

127.0.0.1:6379> PUBLISH HLTest "hello redis"    #发布者发布消息到频道!
(integer) 1

原理
在这里插入图片描述

18、Redis主从复制 (读写分离)

在这里插入图片描述
在这里插入图片描述

主从复制,读写分离!80%的情况下都是在进行读操作!减缓服务器的压力!(一主二从)

环境配置

只配置从库,不用配置主库

127.0.0.1:6379> info replication   #查看当前库的信息
# Replication
role:master          #角色
connected_slaves:0   #没有从机
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>

复制3个配置文件,修改对应的信息
1、 端口
2、pid名字
3、log文件名字
4、dump.rdb名字

windows中用:
1、redis-server.exe redis.windows.conf
2、redis-server.exe redis.windows-80.conf
3、redis-server.exe redis.windows-81.conf
来启动3个redis.

19、一主二从

redis 默认都是主节点;一般情况下只需要配置从机

一主(79)二从(80、81)

slaveof 127.0.0.1 6379

#如果主机有密码必须设置masterauth 属性 ,其中值为主机密码
eg:masterauth 123456


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:2
master_sync_in_progress:0
slave_repl_offset:43
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6380>



#在主机中查看从机的信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1  #从机信息
slave0:ip=127.0.0.1,port=6380,state=online,offset=267,lag=1
master_repl_offset:267
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:266
127.0.0.1:6379>

真实的主从配置是在配置文件中配置,如果是用命令去配置只是暂时的
主机可以写,从机不可以写,主机所有的信息,从机都会自动保存。
测试:主机断开链接,从机依旧连接到主机的,但是没有写的操作,这个时候,主机如果恢复,从机依旧可以直接获取主机写的信息!
如果是命令行配置的主从关系,这个时候,如果从机重启,就会变成主机,主从关系就会自动断开,如果用命令变回了从机,就会立马从主机获取值

复制原理

在这里插入图片描述

层层链路

下图中的80端口redis无法写入。但是可以完成主从复制
在这里插入图片描述

如果主机宕机,如何让后续的从机变成主机?

1、手动:如果主机断开链接,我们可以使用slaveof no one 让自己变成主机!其他的节点就可以手动连接到最新的这个主节点上(注意:手动)

20、哨兵模式(自动选举老大的模式)

如何配置可参考: https://www.cnblogs.com/joeymary/p/11492791.html

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述 在这里插入图片描述

测试:

目前redis的状态是一主二从!
1、配置哨兵配置文件sentinel.conf:

# sentinel monitor 被监控的名字 host port 1   可以看哨兵配置
sentinel monitor myredis 127.0.0.1 6379  1

后面的这个数字1,代表主机挂了,slave投票看谁接替成为主机,票数最多,就会成为主机。
2、 启动哨兵:

# 启动sentinel
redis-server sentinel.conf --sentinel
# 或者
redis-sentinel sentinel.conf
# 登录sentinel后关闭
redis-cli -p 26379
shutdown
# 获取相关信息
info sentinel
# 手动故障切换
sentinel failover mymaster
# 获取当前主实例的信息
sentinel get-master-addr-by-name mymaster
# 获取所有被监控主实例的状态
sentinel masters
# 获取一个被监控主实例的所有从实例的信息
sentinel slaves mymaster

启动之后会显示如下界面:
在这里插入图片描述

哨兵日志,当主机6379宕机时,哨兵会从从机中随机选择一个服务器(通过算法投票)
在这里插入图片描述如果主机宕掉的主机重新连接,只能当从机,无法恢复主机地位。

优缺点

在这里插入图片描述

哨兵全部配置文件

如果有多个哨兵,需要开启多个配置文件,注意修改端口

# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 ip port 
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
  sentinel monitor mymaster 127.0.0.1 6379 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
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
一个是事件的类型,
一个是事件的描述。
如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
 sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

21、Redis缓存穿透和雪崩

1、缓存穿透描述(查不到)

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方法:

>布隆过滤器:

>缓存空对象:

2、缓存穿透描述(量太大,缓存过期!)

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

解决方法

设置热点数据用不过期

加互斥锁

3、缓存雪崩

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值