Redis笔记

Redis

cd /usr/local/bin
#启动redis
redis-server kconfig/redis.conf
#启动客户端
redis-cli
#关闭
shutdown
#退出
exit
#测试启动的线程
ps -ef|grep redis
#目录
cd /usr/local/bin
#性能测试(50个用户,100000个请求)
redis-benchmark -h localhost -p 6379 -c 50 -n 100000

1.redis-benchmark性能测试

写入

在这里插入图片描述

在这里插入图片描述

2.基础知识

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

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

[root@localhost bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> select 1 #切换数据库
OK
127.0.0.1:6379[1]> dbsize # 查看DB大小
(integer) 0
127.0.0.1:6379[1]> 

不同数据库可以存储不同的值

在这里插入图片描述

127.0.0.1:6379[5]> keys *         #查看所有的key
1) "wizard"
127.0.0.1:6379[5]> flushdb        #清除当前数据库
127.0.0.1:6379[5]> flushall       #清除所有数据库

3.Redis是单线程的

redis是很快的 ,是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽

为什么单线程还这么快

Redis是C语言写的,100000+的QPS

  1. 高性能的服务器不一定是多线程的

  2. 多线程(CPU上下文会切换)不一定比单线程效率高

    CPU>内存>硬盘速度

  3. 核心:redis是将所有的数据全部放在内存中,所以说使用单线程去操作效率就是高,多线程(CPU上下文会切换:耗时操作),对于内存系统来说,如果没有上下文切换效率就是最高的 ,多次读写都是在一个CPU上的,在内存情况下,这个就是最佳方案

4.五大数据类型

Redis-Key
127.0.0.1:6379> keys *          #查看所有的key值
1) "age"
2) "team"
3) "num"
4) "name"
127.0.0.1:6379> get name         #获得key的value值
"curry"
127.0.0.1:6379> expire team 20    #设置过期时间
(integer) 1
127.0.0.1:6379> ttl team          #查看当前key的剩余时间
(integer) 12
127.0.0.1:6379> ttl team
(integer) 9
127.0.0.1:6379> ttl team
(integer) 7
127.0.0.1:6379> ttl name
(integer) -1
127.0.0.1:6379> ttl team
(integer) -2
127.0.0.1:6379> exists name         #判断当前的key是否存在
(integer) 1
127.0.0.1:6379> move name 1         #移除当前的key
(integer) 1
127.0.0.1:6379> type name           #查看当前key的类型
(integer) 1

String(字符串)
127.0.0.1:6379> exists name        #是否存在key键
(integer) 1
127.0.0.1:6379> append name 30       #追加字符串
(integer) 7                          #追加成功返回长度
127.0.0.1:6379> append xingming whz  #如果追加的key不存在,就相当于set key
(integer) 3
127.0.0.1:6379> get xingming
"whz"
127.0.0.1:6379> get name            #获取key的value值
"curry30"
127.0.0.1:6379> strlen name          #获取key的长度
(integer) 7
127.0.0.1:6379> incr views           #自增1,相当于i++
(integer) 1
127.0.0.1:6379> incr views 
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views            #自减1
(integer) 1
127.0.0.1:6379> incrby views 5        #自增特定的长度(5)
(integer) 6
127.0.0.1:6379> decrby views 5        #自减特定的长度(5)
(integer) 1
################################################################
#字符串范围 range
127.0.0.1:6379> get key1
"hello,wizard30"
127.0.0.1:6379> getrange key1 0 3     #截取字符串[0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1    #获取全部的字符串
"hello,wizard30"
#替换
127.0.0.1:6379> get key2              
"abcdefghijk"
127.0.0.1:6379> setrange key2 1 xx    #字符串替换,指定位置开始1
(integer) 11
127.0.0.1:6379> get key2
"axxdefghijk"
################################################################
#setex(set with expire)   #设置过期时间
#setnx(set if not exist)  #不存在设置(在分布式锁中常用)

127.0.0.1:6379> setex key3 60 "hello,java"    #设置60秒后过期
OK
127.0.0.1:6379> ttl key3                      #查看key剩余时间
(integer) 52
127.0.0.1:6379> get key3
"hello,java"
127.0.0.1:6379> setnx mykey "redis"          #如果mykey不存在,创建myke
(integer) 1
127.0.0.1:6379> setnx mykey "java"           #如果mykey已经存在,创建失败
(integer) 0
127.0.0.1:6379> keys *
1) "key2"
2) "mykey"
3) "key1"
##########################################################
#mset  
#mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3        #同时设置多个值
OK
127.0.0.1:6379> keys * 
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2                    #同时取出多个值
1) "v1"
2) "v2"
127.0.0.1:6379> msetnx k1 v1 k4 v4            #是原子性操作,如果不存在就创建,如果存在就报错,k1存在,k4不存在
(integer) 0
127.0.0.1:6379> get k4
(nil)


#对象
 set user:1 {name:zhangsan,age:4}            #设置一个user:1 对象 值为json对象来保存对象
 #这里的key是一个巧妙的设计;  user:{id}:{filed}
 127.0.0.1:6379> mset user:1:name Kobe user:1:age 38
OK
127.0.0.1:6379> mget user:1:name
1) "Kobe"


#################################################
#getset  先获取一个值再设置一个值
127.0.0.1:6379> getset db redis           #如果不存在值返回null
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db java            #如果存在,返回存在的值并设置新的值
"redis"
127.0.0.1:6379> get db
"java"
List

基本数据类型,列表

在redis里面,可以吧list变成,栈,队列,阻塞队列

所有的list命令都是用l开头(redis不区分大小写)

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
1) "three"
2) "two"
127.0.0.1:6379> Rpush list 555          #将一个值或者多个值 插入到列表的尾部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "555"
#################################################	
#移除
#lpop
#rpop
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "555"
127.0.0.1:6379> lpop list           #左边移除
"three"
127.0.0.1:6379> rpop list           #右边移除
"555"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
#################################################	
lindex 
127.0.0.1:6379> lrange list 0 -1    
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1       #通过下标获取list 中的 某一个值
"one"
#################################################
llen
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> llen list               #获取当前list下的值
(integer) 3
#################################################	
lrem   #移除指定的值
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one            #移除一个one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 2 three            #移除2个three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
#################################################	
trim 修剪  list 截断
127.0.0.1:6379> lrange mylist 0 -1      
1) "hello1"
2) "hello2"
3) "hello3"
4) "hello4"
127.0.0.1:6379> ltrim mylist 1 2         #通过下标截取指定的长度
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"
2) "hello3"
#################################################
 rpoplpush                                #移除列表中的最后一个元素,并移动到新的列表中
127.0.0.1:6379> rpoplpush mylist myotherlist
"hello3"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"
#################################################
lset  #将列表中指定下标的值替换为另一个值
127.0.0.1:6379> exists list                    #是否存在某个list   0:不存在
(integer) 0
127.0.0.1:6379> lset list 0 item                #如果不存在就去更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value                #往列表中添加某个值
(integer) 1 
127.0.0.1:6379> lrange list 0 0                 #查看列表第0 个元素的值
1) "value" 
127.0.0.1:6379> lset list 0 item                 #更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
#############################################################
linsert   #将某个具体的value插入到列表中某个元素的前面或者后面
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world value        #往world元素前面插入value
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "value"
3) "world"
127.0.0.1:6379> linsert mylist after world curry        #往world元素后面插入curry
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "value"
3) "world"
4) "curry"
Set(集合)
#############################################################
127.0.0.1:6379> sadd myset hello       #set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset wizard
(integer) 1
127.0.0.1:6379> sadd myset wizard30
(integer) 1
127.0.0.1:6379> smembers myset        #查看指定set中所有值
1) "wizard30"
2) "wizard"
3) "hello"
127.0.0.1:6379> sismember myset hello  #判断某个值是不是在set集合中
(integer) 1
127.0.0.1:6379> sismember myset word
(integer) 0

#############################################################
scard
127.0.0.1:6379> scard myset      #获取set集合中的内容元素个数
(integer) 3
#############################################################
rem
127.0.0.1:6379> srem myset hello     #移除指定元素
(integer) 1
127.0.0.1:6379> smembers myset         #查看所有元素
1) "wizard30"
2) "wizard"
#############################################################
set    无序不重复集合   抽随机  srandmember
127.0.0.1:6379> smembers myset
1) "wizard30"
2) "wizard"
3) "whz"
4) "hello"
127.0.0.1:6379> srandmember myset     #随机抽取出myset中的一个元素
"hello"
127.0.0.1:6379> srandmember myset
"wizard"
127.0.0.1:6379> srandmember myset 2    #随机抽取指定个数的元素
1) "wizard30"
2) "whz"
#############################################################
删除指定的key   随机删除key	
127.0.0.1:6379> smembers myset
1) "wizard30"
2) "wizard"
3) "whz"
4) "hello"
127.0.0.1:6379> spop myset              #随机删除一些set元素 
"hello"
#############################################################
将一个指定的值,移动到另外一个set集合
127.0.0.1:6379> smove myset myset2 whz   #移动myset中的whz到myset2中
(integer) 1
127.0.0.1:6379> smembers myset
1) "wizard30"
2) "wizard"
127.0.0.1:6379> smembers myset2
1) "whz"
2) "set2"
#############################################################
数字集合类:
	-差集 sdiff
	-交集 sinter
	-并集 sunion

127.0.0.1:6379> smembers key1
1) "d"
2) "b"
3) "c"
4) "a"
127.0.0.1:6379> smembers key2
1) "f"
2) "e"
3) "d"
4) "c"
5) "g"
127.0.0.1:6379> sdiff key1 key2          #key1和key2的差集
1) "b"
2) "a"
127.0.0.1:6379> sinter key1 key2          #key1和key2的交集
1) "d"
2) "c"
127.0.0.1:6379> sunion key1 key2          #key1和key2的并集
1) "g"
2) "e"
3) "c"
4) "f"
5) "d"
6) "b"
7) "a"
#############################################################
Hash(哈希)

Map集合,key-map, 值是map集合

127.0.0.1:6379> hset myhash field1 curry    #set一个具体的key-value
(integer) 0
127.0.0.1:6379> hget myhash field1          #获取一个字段值
"curry"
127.0.0.1:6379> hmset myhash field1 wizard field2 wizard30  #set多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field2                  #获取多个字段值
1) "wizard"
2) "wizard30" 
127.0.0.1:6379> hgetall myhash               #获取全部的数据
1) "field1"
2) "wizard"
3) "wizard30"
4) "curry"
5) "field2"
6) "wizard30"
127.0.0.1:6379> hdel myhash field1 wizard30    #删除指定的key字段,对应value的值也就消失了
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "wizard30"
#############################################################
hlen   #获取hash的内容长度
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "wizard"
3) "field2"
4) "wizard30"
5) "field3"
6) "curry"
7) "field4"
8) "ys"
127.0.0.1:6379> hlen myhash         #获取myhash的内容长度
(integer) 4
######################################################
127.0.0.1:6379> hexists myhash field1    #判断哈市中指定字段是否存在
(integer) 1
127.0.0.1:6379> hexists myhash field5
(integer) 0
######################################################
#只获得所有的field
#只获得所有的value
127.0.0.1:6379> hkeys myhash   #只获得所有的field
1) "field1"
2) "field2"
3) "field3"
4) "field4"
127.0.0.1:6379> hvals myhash    #只获得所有的value	
1) "wizard"
2) "wizard30"
3) "curry"
4) "ys"
######################################################
#自增 incr  自减 decr
127.0.0.1:6379> hset myhash field5 30 
(integer) 1
127.0.0.1:6379> hincrby myhash field5 5       #自增步长为5
(integer) 35
127.0.0.1:6379> hsetnx myhash field5 hello     #如果存在则不能设置
(integer) 0
127.0.0.1:6379> hsetnx myhash field10 999      #如果不存在则可以设置
(integer) 1

hash变更的数据user name age ,用户信息保存,经常变动的信息(参考String)

127.0.0.1:6379> hset user:1 name wizard age 30
(integer) 2
127.0.0.1:6379> hmget user:1 name age
1) "wizard"
2) "30"
Zset(有序集合)

在set的基础上增加了一个值

127.0.0.1:6379> zadd myset 1 one                    #添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three            #添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
#####################################################################
#排序如何实现
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 100 lp
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf             #显示全部用户 从小到大排序
1) "lp"
2) "xiaoming"
3) "zhangsan"
127.0.0.1:6379> zrevrange salary 0 -1                       #显示所有用户  从大到小
1) "zhangsan"
2) "xiaoming"
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "zhangsan"
2) "5000"
3) "xiaoming"
4) "2500"

127.0.0.1:6379> zrangebyscore salary -inf +inf withscores    #显示所有用户,并附带成绩
1) "lp"
2) "100"
3) "xiaoming"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores    #显示工资小于2500的员工升序排列
1) "lp"
2) "100"
3) "xiaoming"
4) "2500"
#####################################################################
移除rem中的元素	
127.0.0.1:6379> zrange salary 0 -1
1) "lp"
2) "xiaoming"
3) "zhangsan"
127.0.0.1:6379> zrem salary lp            #移除一个元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiaoming"
2) "zhangsan"
127.0.0.1:6379> zcard salary              #获取集合中的个数
(integer) 2
#####################################################################
127.0.0.1:6379> zadd myset 1 hello 2 world 3 wizard
(integer) 3
127.0.0.1:6379> zcount myset 1 3            #获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 2 3
(integer) 2
#####################################################################

5.三种特殊数据类型

geospatial 地理位置

朋友的定位,附近的人

getadd

#规则:两级无法直接添加

#参数 key值(纬度、经度、名称)
127.0.0.1:6379> geoadd chian: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 114.05 22.52 shenzheng
(integer) 2

getpos

127.0.0.1:6379> geopos china:city beijing    #获取指定城市经度纬度
1) 1) "116.39999896287918091"
  2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city shanghai
1) 1) "121.47000163793563843"
  2) "31.22999903975783553"

geodist

两地之间的距离

127.0.0.1:6379> geodist china:city beijing shanghai    #查看北京到上海的直线距离
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing chongqing km   #查看北京到重庆的距离
"1464.0708"  

georadius

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

获得指定数量 2

127.0.0.1:6379> georadius china:city 110 30 1000 km   #以100 30 这个经纬度为中心,寻找方圆1000km内的城市
1) "chongqing"
2) "shenzheng"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist   #显示到中心距离的位置
1) 1) "chongqing"
  2) "341.9374"
2) 1) "shenzheng"
  2) "924.6408"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord   #显示他人的定位信息,即经度纬度
1) 1) "chongqing"
  2) 1) "106.49999767541885376"
     2) "29.52999957900659211"
2) 1) "shenzheng"
  2) 1) "114.04999762773513794"
     2) "22.5200000879503861"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 2  #筛选指定数量的用户
1) 1) "chongqing"
  2) "341.9374"
  3) 1) "106.49999767541885376"
     2) "29.52999957900659211"
2) 1) "shenzheng"
  2) "924.6408"
  3) 1) "114.04999762773513794"
     2) "22.5200000879503861"

georadiusbymember

127.0.0.1:6379> georadiusbymember china:city beijing 1000 km   #找出位于指定元素周围的其他元素
1) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 10000 km
1) "chongqing"
2) "shenzheng"
3) "shanghai"
4) "beijing"

GEO底层实现原理其实就是Zset,可以使用Zset命令来操作GEO

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

Hyperloglog(基数计数)

什么是基数

A{1,3,5,7,8,7}

B{1,3,5,7,8}

基数(不重复的元素)=5,可以接受误差

Hyperloglog基数统计的算法:

​ 优点:占用的内存是固定的2^64不同元素的基数,只要12KB内存

网页UV(一个人访问一个网站多次,但是只算一次浏览)

传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断

127.0.0.1:6379> PFadd mykey a b c d e f g h i j k l   #创建第一组元素mykey
(integer) 1
127.0.0.1:6379> pfcount mykey                         #统计mykey基数数量
(integer) 12
127.0.0.1:6379> pfadd mykey2 i j k z x c v b n         #创建第二组元素mykey2
(integer) 1
127.0.0.1:6379> pfcount mykey2                        #统计mykey2基数数量
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2            #合并两组,并集
OK
127.0.0.1:6379> pfcount mykey3                         #查看并集数量
(integer) 16

如果允许容错,使用Hyperloglog,不允许容错,使用set或自己的数据类型

Bitmaps(位图)

位存储

统计用户信息,活跃,不活跃;登录,未登录;打卡(是否打卡),两个状态使用Bitmaps

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

使用Bitmaps来记录周一到周日的打卡情况,打卡:1 迟到:0

127.0.0.1:6379> setbit sign 0 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 1
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0

查看某一天是否打卡

127.0.0.1:6379> getbit sign 3     #周四打卡
(integer) 1
127.0.0.1:6379> getbit sign 6     #周日打卡
(integer) 1

统计操作,统计打卡天数

127.0.0.1:6379> bitcount sign        #统计打卡记录	
(integer) 4

6.事务

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

一次性、顺序性、排他性

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> get k1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec                #执行事务
1) OK
2) "v1"
3) OK
4) OK
5) "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 k4 v4
QUEUED
127.0.0.1:6379> discard            #放弃事务
OK
127.0.0.1:6379> get k1             #事务队列中的命令都不会被执行
(nil)
127.0.0.1:6379> get k4
(nil)

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> 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> get k2
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 k1             #所有命令都不会执行
(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 k4 v4
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> get k4
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range  #虽然第一条命令报错了,但是依然可以执行成功
2) OK
3) OK
4) "v1"
5) "v4"

监控

悲观锁:

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

乐观锁:

  • 很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下在此期间是否有人修改过数据,version
  • 获取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   #监视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             #执行之前,另外一个线程修改了值,就会导致事务执行失败
(nil)

127.0.0.1:6379> get money    #另外一个线程修改了
"80"
127.0.0.1:6379> set money 1000
OK

解决办法

#如果事务执行失败,先解锁
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch money   #再重新加监视(加锁)
OK
#再执行事务
exec              #比对监视的值是否发生变化,如果没变 ,可以执行成果,如果变化了就重复此步骤

7.Jedis

使用java 操作redis

  1. 导入对应依赖

     <!--导入Jedis包-->
        <dependencies>
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.2.0</version>
            </dependency>
    
            <!--fastjson-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.62</version>
            </dependency>
        </dependencies>
    
  2. 编码测试

    • 连接数据库
    • 操作命令
    • 断开连接
    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);
            //所有指令就是方法
            System.out.println(jedis.ping());//输出PONG
        }
    }
    
    

    Test.Key

    import redis.clients.jedis.Jedis;
    import java.util.Set;
    
    public class TestKey {
        public static void main(String[] args) {
            //1. new Jedis 对象
            Jedis jedis = new Jedis("127.0.0.1",6379);
            //所有指令就是方法
            System.out.println(jedis.ping());
            System.out.println("清空教据:"+jedis.flushDB());
            System.out .println("判断某个键是否存在: "+jedis.exists(  "username" ));
            System.out.println("新增< 'username' , ' wizard'>的键值对:"+jedis.set("username","wizard"));
            System.out.println("新增< 'password' , ' password'>的键值对:"+jedis.set("password","password"));
            System.out.print("系统中所有的键如下: ");
            Set<String> keys = jedis.keys(  "*");
            System.out.println(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("随机返回key空间的一个:"+jedis.randomKey());
            System.out.println("重命名key: "+jedis.rename(  "username" , "name"));
            System.out.println("取出改后的name: "+jedis.get("name") );
            System.out.println("切换数据库:"+jedis.select(0));
            System.out.println("删除当前选择数据库中的所有key: "+jedis.flushDB());
            System.out.println("返回当前数据库中key的数日: "+jedis.dbSize());
            System.out.println("删除所有数据库中的所有key: "+jedis.flushAll());
        }
    }
    

    TestString

    import redis.clients.jedis.Jedis;
    
    import java.util.concurrent.TimeUnit;
    
    public class TestString {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("127.0.0.1",6379);
    
            jedis.flushDB( );
            System.out.println( "----------增加数据----------");
            System.out.println(jedis.set( "key1" , "value1"));
            System.out.println(jedis.set( "key2" , "value2"));
            System.out.println(jedis.set( "key3" , "value3"));
            System.out.println("删除健key2 : "+jedis.del( "key2"));
            System.out.println("获取键key2 : "+jedis.get( "key2") );
            System.out.println("修改key1:"+jedis.set( "key1" ,"value1Changed"));
            System.out.println("获取key1的值:"+jedis.get("key1"));
            System.out.println("在:key3后面加入值:"+jedis.append("key3","End"));
            System.out.println("key3的值:"+jedis.get("key3"));
            System.out.println("增加多个键值对:"+jedis.mset ("key01","value01","key02","value02","key03","value03")) ;
            System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));
            System.out.println("获取多个键值对: "+jedis.mget("key01","key02","key03","key04"));
            System.out.println("剧除多个诎值对:"+jedis.del("key01","key02"));
            System.out.println("获取多个键值对: "+jedis.mget("key01","key02","key03"));
    
            jedis.flushDB();
            System.out.println( "===========新增键值对防止覆盖原先值===============");
            System.out.println(jedis.setnx ( "key1", "value1"));
            System.out.println(jedis.setnx( "key2", "value2" ));
            System.out.println(jedis.setnx ( "key2", "value2-new" ));//如果键不存在就创建,如果存在就创建失败
            System.out.println(jedis.get( "key1"));
            System.out.println(jedis.get("key2"));
    
    
            System.out.println( "==============新增键值对并设置有效时间================");
            System.out.println(jedis.setex( "key3" , 2, "value3"));
            System.out.println(jedis.get("key3"));
            try {
            TimeUnit.SECONDS.sleep(3) ;
            }catch ( InterruptedException e) {
                e.printStackTrace( );
            }
            System.out.println(jedis.get( "key3"));
    
            System.out.println( "===========获取原值,更新为新值==========");
            System.out.println(jedis.getSet( "key2", "key2GetSet") );
            System.out.println(jedis.get( "key2" ) );
            System.out.println("截取key2的值的字串:"+jedis.getrange(  "key2",2,4));
    
        }
    }
    

    TestShiWu

    import com.alibaba.fastjson.JSONObject;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.Transaction;
    
    public class TestTX {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("127.0.0.1", 6379);
    
            jedis.flushDB();
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("hello","world");
            jsonObject.put("name","wizard");
    
            //开启事务
            Transaction multi = jedis.multi();
            String string = jsonObject.toJSONString();
    
            //jedis.watch(string)
    
            try {
                multi.set("user1",string);
                multi.set("user2",string);
                multi.exec();//执行事务
            } catch (Exception e) {
                multi.discard();//如果失败就放弃事务
                e.printStackTrace();
            } finally {
                System.out.println(jedis.get("user1"));
                System.out.println(jedis.get("user2"));
                jedis.close();//关闭连接
            }
    
        }
    }
    

8.SpringBoot整合

springboot操作数据:springdata

springdata也是和springboot齐名的项目

在springBoot2.x之后,原来使用的jedis被替换为了lettuce

  • jedis:采用的是直连,多个线程操作,不安全,想要避免不安全,使用jedis pool连接池,更像BIO模式
  • lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据,更像NIO模式
  1. 导入依赖

    <!--操作redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置properties

    #SpringBoot所有的配置类,都有一个自动配置类     RedisAutoConfiguration
    #自动配置类,都会绑定一个properties配置文件     RedisProperties
    #配置redis
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    
  3. 测试

    @SpringBootTest
    class Redis02SpringbootApplicationTests {
    
        @Autowired
        private RedisTemplate redisTemplate;
        
        @Test
        void contextLoads() {
            //redisTemplate.opsForValue();  操作字符串,类似String
            //redisTemplate.opsForList();  操作List  类似List
    
            //获取Redis的连接对象
            //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
            //connection.flushAll();
            //connection.flushDb();
    
            redisTemplate.opsForValue().set("mykey","wizard30");
            System.out.println(redisTemplate.opsForValue().get("mykey"));
    
        }
    
    @SpringBootTest
    class Redis02SpringbootApplicationTests {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        public void test(){
            //真实的开发一般都用json来传递对象
            User user = new User("农夫山泉", 3);
           // String jsonUser = new ObjectMapper().writeValueAsString(user);
            redisTemplate.opsForValue().set("user",user);
            System.out.println(redisTemplate.opsForValue().get("user"));
        }
    

User对象需要序列化,否则会报错,为什么需要序列化?

  • 序列化是一种用来处理对象流的机制
  • 所谓对象流:就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间
  • 为了解决在对对象流进行读写操作时所引发的问题
  • 为了数据传输,在你的代码的里是对象格式,而在传输的时候不可能还保持这对象的样子
  • redis的持久化需要写入硬盘

自定义redisTemplate

@Configuration
public class RedisConfig {
    //编写自己的配置
    @Bean
    @SuppressWarnings("all")//用于抑制编译器产生警告信息。
    public RedisTemplate<String,Object> myredisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //为了开发方便,一般直接使用<String,Object>
        RedisTemplate<String,Object> template=new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        //Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om= new ObjectMapper();//转义
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        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;
    }
}

测试

@Test
public void test() throws JsonProcessingException {
    //真实的开发一般都用json来传递对象
    User user = new User("农夫山泉", 3);
   // String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user",user);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

自定义工具类RedisUtil(RedisUtil.java)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    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((Collection<String>) 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)
     */
    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)
     */
    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
     */
    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 对应多个键值
     */
    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)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


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


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    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 键
     */
    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代表所有值
     */
    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 键
     */
    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倒数第二个元素,依次类推
     */
    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 值
     */
    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  时间(秒)
     */
    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;
        }

    }

}

测试

@Test
public void test1(){
    redisUtil.set("name","wizard50");
    System.out.println(redisUtil.get("name"));
}

9.Redis.config详解

启动通过配置文件来启动

  1. 配置文件大小写不敏感

  2. 网络

    bind 127.0.0.1     #绑定的ip
    protected-mode yes #保护模式
    port 6379          #端口设置
    
  3. 通用GENERAL

    daemonize yes       #以守护线程的方式运行,默认是no,需要手动修改为yes
    pidfile /var/run/redis_6379,pid  #如果以	后台的方式运行,我们就需要指定一个pid文件
    
    #日志
    # Specify the server verbosity level.
    # This can be one of:
    # debug (a lot of information, useful for development/testing)
    # verbose (many rarely useful info, but not a mess like the debug level)
    # notice (moderately verbose, what you want in production probably)
    # warning (only very important / critical messages are logged)
    loglevel notice
    logfile ""     #日志的文件位置名
    databases 16   #数据库的数量   默认是16个
    always-show-logo yes  #是否总是显示log
    
    
  4. 快照

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

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

    
    save 900 1               #如果900s内,如果至少有1个key进行了修改,我们就进行持久化操作
    save 300 10              #如果300s内,如果至少有10个key进行了修改,我们就进行持久化操作
    save 60 10000            #如果60s内,如果至少有10000个key进行了修改,我们就进行持久化操作
    #实际操作会自定义
    
    stop-writes-on-bgsave-error yes   #持久化出错是否继续工作
    
    rdbcompression yes                #是否压缩rdb文件,需要消耗一些CPU资源
    
    rdbchecksum yes                   #保存rdb文件的时候,进行错误的检查效验
    
    dir ./						    #rdb文件保存的目录
    
  5. REPLICATION复制

  6. SECURITY安全

    可以设置redis密码,默认是无密码

    127.0.0.1:6379> ping
    PONG
    127.0.0.1:6379> config get requirepass           #获取密码
    1) "requirepass"
    2) ""
    127.0.0.1:6379> config set requirepass 123456    #设置密码
    OK
    127.0.0.1:6379> config get requirepass           #无权限
    (error) NOAUTH Authentication required.
    127.0.0.1:6379> auth 123456                      #登录
    OK
    127.0.0.1:6379> ping
    PONG
    127.0.0.1:6379>
    
  7. 限制CLIENTS

    maxclients 10000            #设置能连接redis的最大客户端数量
    maxmemory <bytes>           #redis 配置最大的内存容量
    maxmemory-policy noeviction #内存达到上限之后的处理策略
    
    #1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
    #2、allkeys-lru : 删除lru算法的key   
    #3、volatile-random:随机删除即将过期key   
    #4、allkeys-random:随机删除   
    #5、volatile-ttl : 删除即将过期的   
    #6、noeviction : 永不过期,返回错误
    
  8. APPEND ONLY模式 aof配置

    appendonly no        #默认不开启aof模式,默认使用rdb方式持久化
    appendfilename "appendonly.aof"  #持久化的文件的名字
    
    # appendfsync always  #每次修改都会同步Sync
    appendfsync everysec  #每秒执行一次Sync,可能会丢失1s的数据
    # appendfsync no      #不执行Sync,这个时候操作系统自己同步数据,速度最快
    

10.Redis持久化

RDB(Redis DataBase)

rdb保存的文件是dump.rdb

触发机制:

  1. save的规则满足的情况下,会自动触发
  2. 执行了flushall命令,也会触发rdb规则
  3. 退出redis,也会产生rdb文件

备份就会自动生成一个dump.rdb文件

如何恢复rdb文件

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

  2. 查看需要存在的位置

    127.0.0.1:6379> config get dir
    1) "dir"
    2) "F:\\software\\Redis\\Redis-x64-3.0.504"   #如果在这个目录下存在dump.rdb文件,启动就会恢复其中的数据
    

优点:

  • 适合大规模的数据恢复 dump.rdb
  • 对数据的完整性要求不高

缺点:

  • 需要一定的时间间隔进行操作,如果redis意外宕机,最后一次修改数据就没了
  • fork进程的时候,会占用一定的内存空间
AOF(Append Only File)

将所有命令都记录下来,恢复的时候,就把这个文件全部执行一遍

AOF 保存的是appendonly.aof文件

append

默认是不开启的,需要手动进行配置,将appendonly改为yes就行了

重启,redis就可以生效

如果aof文件有错误,redis不能启动,我们需要修复配置文件

redis提供了一个工具redis-check-aof --fix

优点:

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

缺点

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

11.Redis发布订阅

是一种消息通信模式:发送者发送消息,订阅者接收消息。

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

在这里插入图片描述

测试

订阅端

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

发送端

127.0.0.1:6379> publish NBA curry    #发布者发布消息到频道 
(integer) 1
127.0.0.1:6379> publish NBA kobe
(integer) 1

12.Redis主从复制

数据的复制是单向的,只能从主节点到从节点。Maser以写为主,Slave以读为主

主从复制的作用

  • 数据冗余
  • 故障恢复
  • 负载均衡
  • 高可用(集群)基石

在这里插入图片描述

主从复制,读写分离,80%情况都在进行读操作,减缓服务器压力,架构中常使用,一主二从

环境配置

只配置从库 不配置主库

127.0.0.1:6379> info replication         #查看当前库的信息
# Replication
role:master      #角色   master   
connected_slaves:0    #没有从机
master_replid:7a2fa8c4a8c9f6452a507709a2ea65aa1747b9ec
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

复制三个配置文件,然后修改对应的信息

  1. 端口号
  2. pid名字
  3. log文件名字
  4. dump.rdb的文件名

修改完毕之后,启动三个redis服务,可以通过进程信息查看

ps -ef|grep redis

在这里插入图片描述

一主二从

默认情况下,每台Redis服务器都是主节点,一般情况下配置从机就好了

1.一主(79)二从(80、81)
127.0.0.1:6380> slaveof 127.0.0.1 6379      #在从机中进行配置   slaveof host 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:8
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a0bfad947edb79d69aa62d651f5a97f37114a7c2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14


#在主机中可以看到
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2         #多了从机的配置
slave0:ip=127.0.0.1,port=6380,state=online,offset=350,lag=0 #从机的信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=350,lag=0 #从机的信息
master_replid:a0bfad947edb79d69aa62d651f5a97f37114a7c2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:350
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:350

真实的主从配置在配置文件中配置,这样的话是永久的,我们使用的命令,暂时

细节

主机可以写,从机不能写,只能读;主机中的所有信息和数据,都会被从机保存

主机断开连接,从机依旧连接到主机的,但是没有写操作了;如果主机回来了,从机依旧可以直接进获取到主机写的信息

如果是使用命令行,来配置的主从,如果重启了,就会变回主机;只要变为从机,数据立马就会写过来

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

  • 全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
    但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
2. 层层链路:上一个M连接下一个S

如果主机断开连接,从机使用slave no one使自己变成主机,其他节点手动连接到这个最新的主节点

13.哨兵模式

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

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

测试

一主二从:

  1. 配置哨兵配置文件sentinel.conf

    sentinel monitor myredis 127.0.0.1 6379 1
    

    后面的这个数字1,代表主机宕机了,slave投票看让谁接替成为主机,票数最多的,就会成为主机

  2. 启动哨兵

    [root@localhost bin]# redis-sentinel kconfig/sentinel.conf 
    6171:X 16 Nov 2020 03:50:21.710 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
    6171:X 16 Nov 2020 03:50:21.710 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=6171, just started
    6171:X 16 Nov 2020 03:50:21.710 # Configuration loaded
    6171:X 16 Nov 2020 03:50:21.766 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                    _._                                                  
               _.-``__ ''-._                                             
          _.-``    `.  `_.  ''-._           Redis 6.0.9 (00000000/0) 64 bit
      .-`` .-```.  ```\/    _.,_ ''-._                                   
     (    '      ,       .-`  | `,    )     Running in sentinel mode
     |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
     |    `-._   `._    /     _.-'    |     PID: 6171
      `-._    `-._  `-./  _.-'    _.-'                                   
     |`-._`-._    `-.__.-'    _.-'_.-'|                                  
     |    `-._`-._        _.-'_.-'    |           http://redis.io        
      `-._    `-._`-.__.-'_.-'    _.-'                                   
     |`-._`-._    `-.__.-'    _.-'_.-'|                                  
     |    `-._`-._        _.-'_.-'    |                                  
      `-._    `-._`-.__.-'_.-'    _.-'                                   
          `-._    `-.__.-'    _.-'                                       
              `-._        _.-'                                           
                  `-.__.-'                                               
    
    6171:X 16 Nov 2020 03:50:21.772 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
    6171:X 16 Nov 2020 03:50:21.800 # Sentinel ID is b3cbc184fd9bd294e378bf9093212a99adfebed9
    6171:X 16 Nov 2020 03:50:21.800 # +monitor master myredis 127.0.0.1 6379 quorum 1
    6171:X 16 Nov 2020 03:50:21.804 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
    6171:X 16 Nov 2020 03:50:21.809 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
    
    

    如果主机节点断开,这个时候会从从机中随机选择一个服务器:哨兵日志

在这里插入图片描述

如果主机再次连接,只能归并到新的主机下,当做从机,这就是哨兵模式的规则

在这里插入图片描述

优点:

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

缺点

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

14.Redis缓存穿透和雪崩

缓存穿透(查不到)

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

解决方案:

  1. 布隆过滤器

    布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

  2. 缓存空对象

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

    存在问题

    • 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
    • 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(量太大,缓存过期)

这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

解决方案:

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

    从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。

  2. 加互斥锁

    分布式锁∶使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

解决方案

  1. redis高可用:

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

  2. 限流降级

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

  3. 数据预热

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
狂神在他的Redis笔记中可能会涉及到一些内容,但是根据提供的引用内容,无法确定具体是哪些内容。可以使用自定义的配置文件来启动Redis服务,使用systemctl命令来启动和关闭服务。\[1\]在复杂的情况下,可以使用专业的消息中间件来进行订阅,但是需要注意如果订阅方的读取消息速度不够快,可能会导致消息积压,影响Redis的性能甚至导致崩溃。\[2\]Redis和Memcached一样,数据都是缓存在内存中,但是Redis会周期性地将更新的数据写入磁盘或记录文件,并通过主从复制实现数据同步。\[3\]如果你有具体的问题或需要更详细的信息,请提供更多的上下文。 #### 引用[.reference_title] - *1* *2* [Redis详细笔记](https://blog.csdn.net/qq_40087648/article/details/109808727)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [狂神说 Redis笔记](https://blog.csdn.net/DDDDeng_/article/details/108118544)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值