Redis学习笔记

Redis基本命令

[http://www.redis.cn/commands.html]

Ubuntu:启动redis

使用密码连接redis:redis-cli -h 127.0.0.1 -p 6379 -a 123456

#redis默认有16个数据库
#连接redis数据库:redis-server.exe redis.windows.conf
#选择数据库:select 3
#查看数据库中所有的key: keys *
#清除当前数据库: flushdb
#清除全部数据库的内容: flushall
#查看当前数据库大小:dbsize
#判断某个字段是否存在: exists key
#把某个字段移出数据库: move name(键名) 1
#设置过期时间: expire name(键名) 10(s)
#查看当前key的过期时间:ttl name(键名)
#获取key的值: get name
#查看当前value的值得类型: type name(键名)

redis使用的是单线程!


Redis-Key

String(字符串类型)

#设置值: set key1 v1
#获取值: get key1
#获取所有值: keys *
#判断某个值是否存在: exists key1
#append key1 "hello":如果key1存在则在value后追加,否则新建key为key1,value为“hello”的键值对
#获取字符串长度: strlen key1

###########################################################
#127.0.0.1:6379> set views 0
#OK
#127.0.0.1:6379> get views
#"0"
#127.0.0.1:6379> incr views #实现自增1
#(integer) 1
#127.0.0.1:6379> get views
#"1"
#127.0.0.1:6379> decr views #实现自减1
#(integer) 0
#127.0.0.1:6379> get views
#"0"
#127.0.0.1:6379> incrby views 10 #可以设置步长为10的自增
#127.0.0.1:6379> decrby views 5 #可以设置相应步长的自减
#(integer) 5
#127.0.0.1:6379> get views
#"5"
##########################################################
#字符串范围 range
127.0.0.1:6379> set key1 "hello world"#设置字符串
OK
127.0.0.1:6379> get key1
"hello world"
127.0.0.1:6379> getrange key1 0 3 #截取字符串【0,1,2,3】
"hell"
127.0.0.1:6379> getrange key1 0 -1 #查看整个字符串
"hello world"
##########################################################
#替换指定位置的字符串
127.0.0.1:6379> set key2 "abcdefghijklmn"
OK
127.0.0.1:6379> get key2
"abcdefghijklmn"
127.0.0.1:6379> setrange key2 1 xxxx #替换指定位置开始的字符串
(integer) 14
127.0.0.1:6379> get key2
"axxxxfghijklmn"
##########################################################
#setex(set with expire) 设置过期时间
#setnx(set if not exist) 不存在再设置(在分布式锁中常用)
127.0.0.1:6379> setex key3 30 "hello" #设置key3的值为“hello",30s过期
OK
127.0.0.1:6379> ttl key3 #查看key3的过期时间
(integer) 26
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis"  #如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key1"
127.0.0.1:6379> setnx mykey "MongDB" #如果mykey存在,创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"
##########################################################
mset
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx是一个原子操作,如果其中有个存在则都创建不成功
(integer) 0
127.0.0.1:6379> get key4
(nil)
127.0.0.1:6379>
##########################################################
#对象
set user :1(name:zhangsan,age:3) #设置一个user:1对象,值为json字符串保存一个对象
#这里的key是一个巧妙的设计: user:{id}:{filed}
127.0.0.1:6379> mset user:1:name cxh user:1:age 18
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "cxh"
2) "18"
##########################################################
getset #先get再set
127.0.0.1:6379> getset db redis #如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongedb #如果存在值,则返回获取原来的值并设置新的值
"redis"
127.0.0.1:6379> get db
"mongedb"
##########################################################

List

命令	描述
LPUSH/RPUSH key value1[value2…]	从左边/右边向列表中PUSH值(一个或者多个)。
LRANGE key start end	获取list 起止元素==(索引从左往右 递增)==
LPUSHX/RPUSHX key value	向已存在的列名中push值(一个或者多个)
LINSERT key BEFORE	AFTER pivot value
LLEN key	查看列表长度
LINDEX key index	通过索引获取列表元素
LSET key index value	通过索引为元素设值
LPOP/RPOP key	从最左边/最右边移除值 并返回
RPOPLPUSH source destination	将列表的尾部()最后一个值弹出,并返回,然后加到另一个列表的头部
LTRIM key start end	通过下标截取指定范围内的列表
LREM key count value	List中是允许value重复的 count > 0:从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索… count = 0:删除列表中所有的指定value。
BLPOP/BRPOP key1[key2] timout	移出并获取列表的第一个/最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOPLPUSH source destination timeout	和RPOPLPUSH功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
###############################################################
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> lrange list 0 -1 # 获取list中的值
1) "two"
2) "one"
127.0.0.1:6379> lrange list 0 1 #通过区间获取具体的值
1) "two"
2) "one"
127.0.0.1:6379> rpush list three #将一个值或者多个值,插入到列表尾部
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "three"
###############################################################
lpop rpop
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "three"
127.0.0.1:6379> lpop list #移出list的第一个元素
"two"
127.0.0.1:6379> rpop list #移出list的最后一个元素
"three"
127.0.0.1:6379> lrange list 0 -1
1) "one"
###############################################################
lindex list 1 #通过下表获取list中的某一个值
###############################################################
llen list # 获取列表的长度
###############################################################
移出指定的值
lrem 
127.0.0.1:6379> lrem list 1 one # 移出list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
3) "1"
127.0.0.1:6379> lrem list 1 1
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
###############################################################
trim 修剪: list截断

127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist hello1
(integer) 2
127.0.0.1:6379>  rpush mylist hello2
(integer) 3
127.0.0.1:6379> ltrim mylist 1 2 #通过下标截取指定长度的list内容,原来的list也将被修改
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
127.0.0.1:6379>

###############################################################
rpoplpush 原列表名 目标列表名#移除列表的最后一个元素并将该元素放到另一个列表中

127.0.0.1:6379> rpush list hello
(integer) 1
127.0.0.1:6379> rpush list hello1
(integer) 2
127.0.0.1:6379> rpush list hello2
(integer) 3
127.0.0.1:6379> rpoplpush list mylist
"hello2"
127.0.0.1:6379> lrange list 0 -1 #查看原列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange mylist 0 -1 #查看目标列表
1) "hello2"
###############################################################
lset
127.0.0.1:6379> lrange list 0 -1 
1) "item"
2) "hello1"
127.0.0.1:6379> lset list 1 item #替换指定位置的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
2) "item"
###############################################################
linsert list after(after|before) 3(指定值) 4(目标值)#在某个list中的指定值得前后插入数据 /
127.0.0.1:6379> linsert list before 2 1
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
4) "item"
127.0.0.1:6379> linsert list after(after|before) 3(指定值) 4(目标值) #在指定值的后面插入目标值
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "item"

Set(集合值不能重复)

SADD key member1[member2…]	向集合中无序增加一个/多个成员
SCARD key	获取集合的成员数
SMEMBERS key	返回集合中所有的成员
SISMEMBER key member	查询member元素是否是集合的成员,结果是无序的
SRANDMEMBER key [count]	随机返回集合中count个成员,count缺省值为1
SPOP key [count]	随机移除并返回集合中count个成员,count缺省值为1
SMOVE source destination member	将source集合的成员member移动到destination集合
SREM key member1[member2…]	移除集合中一个/多个成员
SDIFF key1[key2…]	返回所有集合的差集 key1- key2 - …
SDIFFSTORE destination key1[key2…]	在SDIFF的基础上,将结果保存到集合中==(覆盖)==。不能保存到其他类型key噢!
SINTER key1 [key2…]	返回所有集合的交集
SINTERSTORE destination key1[key2…]	在SINTER的基础上,存储结果到集合中。覆盖
SUNION key1 [key2…]	返回所有集合的并集
SUNIONSTORE destination key1 [key2…]	在SUNION的基础上,存储结果到及和张。覆盖
SSCAN KEY [MATCH pattern] [COUNT count]	在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分
########################################################
127.0.0.1:6379> sadd myset hello #向set中添加数据
(integer) 1
127.0.0.1:6379> sadd myset cxh
(integer) 1
127.0.0.1:6379> smembers myset #查看set中的数据
1) "hello"
2) "cxh"
127.0.0.1:6379> sismember myset hello #判单某个值在set中是否存在
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
#################################################
127.0.0.1:6379> scard my #查询集合长度
(integer) 3
#################################################
127.0.0.1:6379> srem my hello #移出set中的元素ss
(integer) 1
127.0.0.1:6379> scard my
(integer) 2
127.0.0.1:6379> smember
(error) ERR unknown command 'smember'
127.0.0.1:6379> smembers my
1) "love"
2) "set"
#################################################
127.0.0.1:6379> srandmember my #随机抽取元素
"love"
127.0.0.1:6379> srandmember my 2
1) "love"
2) "set"
#################################################
127.0.0.1:6379> spop my#随机移出元素
"set"
127.0.0.1:6379> smembers my
1) "love"
#################################################
127.0.0.1:6379> smembers my
1) "love"
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset cxh
(integer) 1
127.0.0.1:6379> sadd myset 1
(integer) 1
127.0.0.1:6379> smove myset my cxh #将myset中的指定元素移到my中
(integer) 1
127.0.0.1:6379> smembers my
1) "love"
2) "cxh"
#################################################
数字集合类:差集 交集 并集
127.0.0.1:6379> sdiff key1 key2 #差集
1) "1"
127.0.0.1:6379> sinter key1 key2 #交集
1) "2"
2) "3"
127.0.0.1:6379> sunion key1 key2 #并集
1) "1"
2) "2"
3) "3"

hash(哈希)

Map集合,key-map!

HSET key field value	将哈希表 key 中的字段 field 的值设为 value 。重复设置同一个field会覆盖,返回0
HMSET key field1 value1 [field2 value2…]	同时将多个 field-value (域-值)对设置到哈希表 key 中。
HSETNX key field value	只有在字段 field 不存在时,设置哈希表字段的值。
HEXISTS key field	查看哈希表 key 中,指定的字段是否存在。
HGET key field value	获取存储在哈希表中指定字段的值
HMGET key field1 [field2…]	获取所有给定字段的值
HGETALL key	获取在哈希表key 的所有字段和值
HKEYS key	获取哈希表key中所有的字段
HLEN key	获取哈希表中字段的数量
HVALS key	获取哈希表中所有值
HDEL key field1 [field2…]	删除哈希表key中一个/多个field字段
HINCRBY key field n	为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段
HINCRBYFLOAT key field n	为哈希表 key 中的指定字段的浮点数值加上增量 n。
HSCAN key cursor [MATCH pattern] [COUNT count]	迭代哈希表中的键值对。
127.0.0.1:6379> hset myhash field1 hello #添加值
(integer) 1
127.0.0.1:6379> hset myhash field2 cxh
(integer) 1
127.0.0.1:6379> hget myhash field1 #通过键获取value
"hello"
127.0.0.1:6379> hmset myhash 1 1 2 2 #通时设置多个键值对
OK
127.0.0.1:6379> hmget myhash 1 2	#同时获取多个值
1) "1"
2) "2"
127.0.0.1:6379> hgetall myhash #获取某个hash的说有键值对
1) "field1"
2) "hello"
3) "field2"
4) "cxh"
5) "1"
6) "1"
7) "2"
8) "2"
127.0.0.1:6379> hdel myhash 1 #删除指定的键值对
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "cxh"
5) "2"
6) "2"
127.0.0.1:6379> hlen myhash#获取hash的长度
(integer) 3
127.0.0.1:6379> hexists myhash 1 #判断指定的key是否存在
(integer) 0
127.0.0.1:6379> hexists myhash field1
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> hkeys myhash #获取hash中的所有key
1) "field1"
2) "field2"
3) "2"
127.0.0.1:6379> hvals myhash #获取hash中的所有值
1) "hello"
2) "cxh"
3) "2"

适合对象的存储

zset

不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
score相同:按字典顺序排序
有序集合的成员是唯一的,但分数(score)却可以重复。
命令:
	1、ZADD key [NX|XX] [CH] [INCR] score member [score member ...] #向key中添加 分数/成员(score/member)数据
	2、ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] ##默认按照score对数据进行重小到大排序,并输出给定范围的数据,【并且可以在输出时带上分数】,【也可以指定显示那一页数据】
    例: ZRANGEBYSCORE zset (1 5
        返回所有符合条件1 < score <= 5的成员;
        ZRANGEBYSCORE zset (5 (10
        返回所有符合条件5 < score < 10 的成员。
     3、zrem key member #移出key中的成员
     4、zrange key index1 index2 #查询范围为[index1,index2]之间的成员
	5、zcard key #获取有序集合成员数量
	6、zrevrange key 0 -1 #将有序集合key的成员倒序输出,【可以选择是否输出分数】(从大到小)
	7、zcount salary -inf +inf #获取key中在指定socore区间的成员数量	
127.0.0.1:6379> zadd salary 1 cxh #向salary中添加数据
(integer) 1
127.0.0.1:6379> zadd salary 10 ccc
(integer) 1
127.0.0.1:6379> zadd salary -1 zs
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf #默认按照score对数据进行重小到大排序,并输出给定范围的数据
1) "zs"
2) "cxh"
3) "ccc"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #输出时带上score(可以理解为权重、大小等)
1) "zs"
2) "-1"
3) "cxh"
4) "1"
5) "ccc"
6) "10"
127.0.0.1:6379> zrangebyscore salary -inf 5 withscores
1) "zs"
2) "-1"
3) "cxh"
4) "1"
###################################################################################################################
127.0.0.1:6379> zrange salary 0 -1
1) "zs"
2) "cxh"
3) "ccc"
127.0.0.1:6379> zrem salary ccc #移出元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zs"
2) "cxh"
127.0.0.1:6379>
###################################################################################################################
127.0.0.1:6379> zcard salary #获取有序集合中的成员数量
(integer) 2
###################################################################################################################
127.0.0.1:6379> zrevrange salary 0 -1 #将有序集合的成员倒序输出,【可以选择是否输出分数】
1) "cxh"
2) "zs"
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "cxh"
2) "1"
3) "zs"
4) "-1"
###################################################################################################################
127.0.0.1:6379> zcount salary -inf +inf #获取指定区间的成员数量
(integer) 3

三种特殊的数据类型

geospatial 地理位置

使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用

https://map.yanue.net/

geoadd key longitud(经度) latitude(纬度) member []	将具体经纬度的坐标存入一个有序集合
geopos key member [member…]	获取集合中的一个/多个成员坐标
geodist key member1 member2 [unit]	返回两个给定位置之间的距离。默认以米作为单位。
‘georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count]’	以给定的经纬度为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
GEORADIUSBYMEMBER key member radius…	功能与GEORADIUS相同,只是中心位置不是具体的经纬度,而是使用结合中已有的成员作为中心点。
geohash key member1 [member2…]	返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。
  • GEOADD

    将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。

    ##############################################
    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.52 shenzheng
    (integer) 1
    
  • GEOPOS

    获取指定城市的经纬度

    127.0.0.1:6379> geopos china:city beijing #获取北京的精度纬度
    1) 1) "116.39999896287918"
       2) "39.900000091670925"
    
  • GEODIST

    计算两个城市间的距离

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

    单位:m、km 、mi(英里)、ft(英尺)

  • GEOHASH

    该命令将返回11个字符的geohash表示

    127.0.0.1:6379> geohash china:city beijing chongqing #将额二维的经纬度转换为一维
    1) "wx4fbxxfke0"
    2) (nil)
    
  • GEORADIUS

    以给定的城市经纬度,按着半径大小查询附近的经纬度

    127.0.0.1:6379> georadius china:city 110 30 1000 km #以100,30这个经纬度为中心,寻找方圆1000km内的城市
    1) "shenzheng"
    127.0.0.1:6379> georadius china:city 110 30 1000 km withdist #显示出到中心距离的位置
    1) 1) "shenzheng"
       2) "342.1258"
    127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord #显示出城市的经纬度 
    1) 1) "shenzheng"
       2) 1) "106.49999767541885"
          2) "29.520000104032995"
    127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist count 1 #筛选出指定的结果!
    1) 1) "shenzheng"
       2) "342.1258"
       3) 1) "106.49999767541885"
          2) "29.520000104032995"
    
  • GEORADIUSBYMEMBER

    127.0.0.1:6379> georadiusbymember china:city beijing 1000 km #找出位于指定位置,距离的城市
    1) "beijing"
    

    geo的底层是使用zset实现的

    127.0.0.1:6379> zrem china:city beijing
    (integer) 1
    127.0.0.1:6379> zrange china:city 0 -1 #查看地图中全部元素
    1) "shenzheng"
    2) "shanghai"
    

    Hyperloglog(基数的统计的算法)

    Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
    花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
    因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
    其底层使用string数据类型

什么是基数?

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

#向hyperloglog中添加数据
127.0.0.1:6379> pfadd mykey a b c d e f g h i j k
(integer) 1
#统计mykey中元素基数数量
127.0.0.1:6379> pfcount mykey
(integer) 11
127.0.0.1:6379> pfadd mykey2 f h j i j k
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 5
#合并两组mykey mykey2 为mykey3
127.0.0.1:6379> pfmerge mykey3 mykey mykey2
OK
127.0.0.1:6379> pfcount mykey3
(integer) 11

不允许容错就不能用

Bitmaps(位图)

位存储

应用场景:统计疫情感染人数、统计用户信息(活跃、不活跃)、(登录、未登录)操作二进制数来进行记录

#设置打卡记录
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
#查询某天打卡情况
127.0.0.1:6379> getbit sign 2
(integer) 
#统计打卡记录
127.0.0.1:6379> bitcount sign
(integer) 2

事务(一次性、顺序性、排他性)

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

redis单条命令式保存原子性,但是事务不保证原子性

redis的事务:

1、开启事务(mutil)

2、命令入队(…)

3、执行事务(exec)

正常执行事务!

127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set key v1
QUEUED
127.0.0.1:6379> set key1 2
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "2"

放弃事务!

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> set aaaa 1
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
127.0.0.1:6379> get aaaa
(nil)
127.0.0.1:6379>

编译型异常(命令有问题),所有事务都不会被运行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set 1 1
QUEUED
127.0.0.1:6379> getset 1
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set 2 2
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get 1
(nil)

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> set key3 hello
QUEUED
127.0.0.1:6379> incr key3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
4) (error) ERR value is not an integer or out of range

悲观锁:无论做什么都加锁

乐观锁:什么时候都不会上锁

redis监视(watch)

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 #执行事务时,另一个现场,修改了money值
(nil)
127.0.0.1:6379> set money 1000
OK

当客户端1开启了监视后,客户端2修改了对应的数据,客户1提交事务会失败。发现事务失败后需要使用unwatch解锁,再开启新的监视

Jedis

jedis时JAVA连接开发工具!使用java操作redis的中间件!

1、创建项目

2、导入依赖

 <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.7.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>

3、连接测试

package com.cxh;

        import redis.clients.jedis.Jedis;

/**
 * ClassName:TestPing
 * Package:com.cxh
 * Description:
 *
 * @Date:2021/11/27 16:15
 * @Author:cxh
 */
public class TestPing {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println(jedis.ping());
    }
}

4、jedis中的相关api跟redis中的命令相同

5、事务处理

package com.cxh;

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * ClassName:TestMulti
 * Package:com.cxh
 * Description:
 *
 * @Date:2021/11/27 16:18
 * @Author:cxh
 */
public class TestMulti {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username","cxh");
        jsonObject.put("age",18);
        Transaction multi = jedis.multi();
        try {
            multi.set("user1",jsonObject.toString());
            multi.set("user2",jsonObject.toString());
            int i = 1/0;
            multi.exec();
        }catch (Exception e){
            multi.discard();
            e.printStackTrace();
        }
        System.out.println(jedis.get("user1"));
    }
}

SpringBoot整合redis

说明:在springboot2.x之后原来使用的jedis被替换为了lettuce

jedis:采用的直连,多线程操作的话,是不安全的,如果想要避免不安全的则需要使用jedis pool连接池!BIO模式

lettuce:采用的netty,事例可以在多个线程中进行共享,不存在线程不安全的问题!可以减少线程数据,Nio模式

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 test(){
        //操作字符串,类似于String
        redisTemplate.opsForValue().set("name","cxh");
        System.out.println(redisTemplate.opsForValue().get("name"));
        
    }

4、传入对象的问题(需要将对象进行序列化)

解决方法1:使用ObjectMapper中的writeValueAsString的方法将对象进行序列化

 @SneakyThrows
    @Test
    public void test1(){
        User user = new User("cxh", 20);
        String userJson = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user",userJson);
        System.out.println(redisTemplate.opsForValue().get("user"));

    }

解决方法2:在pojo实体类上实现Serializable即可将对象进行序列化

@Component
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
public class User implements Serializable {
    private String name;
    private Integer age;
}

解决方法3:自己配置一个redisTemplate

@Configuration
public class RedisConfig {
    //自定义redisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //配置具体的序列化方式
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value序列化方式采用jackson
        template.setValueSerializer(objectJackson2JsonRedisSerializer);
        //hash的value序列化方式采用jackson
        template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

RedisUtil工具类:

@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(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;
        }

    }

}

Redis.conf详解

https://blog.csdn.net/lisen01070107/article/details/108507798

单位:配置文件对大小写不敏感

包含:可以把多个配置文件配置一起

网络

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

通用 GENERAL

daemonize yes # 以守护进程的方式运行,默认是no,我们需要自己开启为yes
pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个 pid 文件

# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境使用
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" # 日志的文件位置名
databases 16 # 数据库的数量,默认是16个
always-show-logo yes # 是否总是显示logo

快照:SNAPSHOTTING

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件【.rdb/.aof】
redis是内存数据库,如果没有持久化,那么数据断点即失

# 如果900s内至少有 1 个 key进行了修改,我们即进行持久化操作
save 900 1
# 如果300s内至少有 10 个 key进行了修改,我们即进行持久化操作
save 300 10
# 如果60s内至少有 10000 个 key进行了修改,我们即进行持久化操作
save 60 10000
# 我们可以自己定义这个


stop-writes-on-bgsave-error yes # 持久化如果出错,是否继续工作
rdbcompression yes # 是否压缩 rdb 文件,需要消耗一些CPU资源
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验
dir ./ # rdb 文件保存的目录

Resdis持久化

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

RDB

RDB(Redis DataBase)
什么是RDB ?

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。==RDB的缺点是最后一次持久化后的数据可能丢失。==我们默认的就RDB,一般情况下不需要修改这个配置!
rdb保存的文件是dump.rdb 都是在我们的配置文件中快照中进行配置的!

触发机制

  • 1、save的规则满足的情况下,会自动触发rdb规则

  • 2、执行 flushall 命令,也会触发我们的rdb规则!

  • 3、退出redis,也会产生 rdb 文件!

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

如果恢复rdb文件!

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

优点:

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

缺点:

  • 1、需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了!
  • 2、fork进程的时候,会占用一定的内容空间!!

AOF(Append Only File)

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

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

Aof保存的是 appendonly.aof 文件

什么是AOF

​ 快照功能(RDB)并不是非常耐久(durable): 如果 Redis因为某些原因而造成故障停机,那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。

如果要使用AOF,需要修改配置文件:

appendonly no yes则表示启用AOF

默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!

如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件

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

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

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

优点:

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

缺点:

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

Redis发布订阅

https://www.runoob.com/redis/redis-pub-sub.html

主从复制

概念

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

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

作用

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

为什么使用集群

​ 单台服务器难以负载大量的请求

​ 单台服务器故障率高,系统崩坏概率大

​ 单台服务器内存容量有限

启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息

  • 端口号 、pid文件名、日志文件名、rdb文件名

查看服务启动情况

		ps -ef|grep redis

缓存穿透和血崩

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值