redis随笔

基础知识

redis服务器的启动:

redis-server redis.conf #启动服务器
redis-cli -p 6379 #启动客户端

redis默认有16个数据库

1616849276305

可以使用select进行切换:

127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> dbsize  #查看db大小
(integer) 0
127.0.0.1:6379[3]> 
127.0.0.1:6379[3]> keys * #查看数据库所有的key
1) "name"
127.0.0.1:6379[3]> 

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

127.0.0.1:6379[3]> FLUSHALL #清除全部数据库的内容

思考:为什么redis的端口号是6379?(明星名字的缩写)

Redis是单线程!

官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的额内存和网络带宽,既然可以使用单线程来实现,就是用单线程了!所有就使用单线程了!

Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样都是使用key-value的Memecache差!

Redis为什么单线程还这么快?

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

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

先去CPU>内存>硬盘的速度要有所了解

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

五大数据类型

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions, and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

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

Redis-Key

127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> set name ming
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> exists name #判断当前的key是否存在
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> move name 1 #把name这个key转移到1数据库
(integer) 1
127.0.0.1:6379> 
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name ming
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"ming"
127.0.0.1:6379> expire name 10 #设置key的过期时间,单位是秒
(integer) 1
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> del name #删除当前的key
(integer) 1
127.0.0.1:6379[1]> keys *
(empty array)

127.0.0.1:6379> type name  #查看key的类型
string
127.0.0.1:6379> type age
string
127.0.0.1:6379> 

String(字符串)

127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> get key1
"v1"
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379> append key1 "hello" #追加字符串,如果当前key不存在,就相当于set key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 # 获取字符串长度
(integer) 7
127.0.0.1:6379> append key1 ",ming"
(integer) 12
127.0.0.1:6379> strlen key1
(integer) 12
127.0.0.1:6379> get key1
"v1hello,ming"
###########################################################
# i++,i--,步长i+=
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views #自减
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> incrby views 10 #可以设置步长,指定增量
(integer) 9
127.0.0.1:6379> incrby views 10
(integer) 19
127.0.0.1:6379> decrby views 5
(integer) 14
127.0.0.1:6379> decrby views 5
(integer) 9
127.0.0.1:6379> 
###########################################################
# 字符串范围:range
127.0.0.1:6379> set key1 "hello,ming" #设置key1的值
OK
127.0.0.1:6379> get key1
"hello,ming"
127.0.0.1:6379> getrange key1 1 3 #截取1到3
"ell"
127.0.0.1:6379> getrange key1 0 -1 #获取全部字符串,与get key一样
"hello,ming"
127.0.0.1:6379> 

# 替换 :setrange
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx #替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
############################################################
# setex(set with exprie) #设置过期时间
# setnx(set if not exist) #不存在则设置(在分布式锁中会常常使用)

127.0.0.1:6379> setex key3 30 "hello"#设置key3的值为hello,30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 26
127.0.0.1:6379> ttl key3
(integer) 22
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> ttl key3
(integer) 15
127.0.0.1:6379> setnx mykey "redis" #如果mykey不存在,创建们也可以
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "momgoDb" #如果mykey存在,创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"


############################################################
# mset
# mget
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 k4
(nil)

# 对象
set user:1{name:zhangsan,age:3} #设置一个user:1 对象 值为json字符来保存一个对象!

# 这里的key是一个巧妙的设计:user:{id}:{field},如此设计完全可以的!
# set article:10000:views 0 设置id为10000的文章的阅读量为0
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"

############################################################
getset #先get再det
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 mongodb #如果存在,返回原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"


String类似的使用场景:value除了是我们的字符串还可以是数字!

  • 计数器
  • 统计多单位的数量 (uid:99999996:follow 0)
  • 粉丝数
  • 对象缓存存储!

List

基本的数据类型,列表

1616902930850

redis里,可以把list做成栈和队列

############################################################
127.0.0.1:6379> lpush list one #将一个值或者多个值,插入到列表头部(左)
(integer) 1
127.0.0.1:6379> lpush list tow
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #注意顺序,这里就相当于栈、队列
1) "three"
2) "tow"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "tow"
127.0.0.1:6379> rpush list right #把right放到右边,可以在两边插值
(integer) 4
127.0.0.1:6379> lrange list 0 -1 #注意顺序
1) "three"
2) "tow"
3) "one"
4) "right"



############################################################
lpop
rpop

127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "tow"
3) "one"
4) "right"
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) "tow"
2) "one"

############################################################
lindex

127.0.0.1:6379> lrange list 0 -1
1) "tow"
2) "one"
127.0.0.1:6379> lindex list 1 # 通过下标获取某一个值
"one"
127.0.0.1:6379> lindex list 0
"tow"
# 生产者,消费者模式!


############################################################
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  # 返回列表的长度
(integer) 3

############################################################
移除指定的值!
取关 uid

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 #移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 1 three
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"

############################################################

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> rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 #通过下标截取指定长度的,这个list已经被改变了,截断了只剩下截取的元素(截取指定范围的元素)
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"

############################################################
rpoplpush #移除列表最后一个元素,并且将它移动到新的列表中

127.0.0.1:6379> rpush mylist "hello" "hello1" "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist  #移除列表最后一个元素,并且将它移动到新的列表中
"hello2"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "hello2"
############################################################
lset #将列表中指定下标的值替换为另外一个值,更新操作
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 item #如果存在,更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other #如果下标不存在值,就会报错
(error) ERR index out of range
############################################################

linsert #将某个具体的value插入到列表中某哥具体元素的前面或后面

127.0.0.1:6379> rpush mylist "hello" "world"
(integer) 2
127.0.0.1:6379> linsert mylist before "world" "other"
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert mylist after world new 
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

小结:

  • 实际上是一个链表,before Node after,left,right都可以插入值

  • 如果key不存在,创建新的链表

  • 如果key存在,新增内容

  • 如果移除了所有值,空链表,也代表不存在

  • 在两边插入或改动值,效率最高!中间元素,相对来说效率会低一点

    消息排队!消息队列(Lpush,Rpop),栈(Lpush,Lpop)

Set(集合)

set中的值不能重复

###########################################################
127.0.0.1:6379> sadd myset "hello" "ming" "loveming" #set集合中添加元素
(integer) 3
127.0.0.1:6379> smembers myseet
(empty array)
127.0.0.1:6379> smembers myset  #查看指定set的所有值
1) "ming"
2) "loveming"
3) "hello"
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 myset #获取set集合中内容元素个数
(integer) 4


############################################################
srem

127.0.0.1:6379> srem myset hello #移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> smembers myset
1) "ming"
2) "loveming"
3) "loveming2"

############################################################
set 无序不重复集合 ,抽随机!

127.0.0.1:6379> smembers myset
1) "ming"
2) "loveming"
3) "loveming2"
127.0.0.1:6379> srandmember myset #随机抽取一个元素
"loveming2"
127.0.0.1:6379> srandmember myset
"loveming"
127.0.0.1:6379> srandmember myset
"ming"
127.0.0.1:6379> srandmember myset 2 #随机抽取出多个指定个数的元素
1) "ming"
2) "loveming"

############################################################
删除指定的key,随机删除key

127.0.0.1:6379> smembers myset
1) "ming"
2) "loveming"
3) "loveming2"
127.0.0.1:6379> spop myset #随机删除一个set集合中的元素
"loveming"
127.0.0.1:6379> spop myset
"loveming2"
127.0.0.1:6379> smembers myset
1) "ming"

############################################################
将一个指定的值,移动到另外一个set集合!
127.0.0.1:6379> sadd myset "hello" "world" "ming"
(integer) 3
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "ming" #将一个指定的值,移动到另外一个set集合!
(integer) 1
127.0.0.1:6379> smembers myset
1) "world"
2) "hello"
127.0.0.1:6379> smembers myset2
1) "ming"
2) "set2"


############################################################

微博,b站,共同关注!(并集)
数字集合类:
	- 差集 sdiff
	- 交集 sinter
	- 并集 sunion
	
127.0.0.1:6379> sadd key1 a b c
(integer) 3
127.0.0.1:6379> sadd key2 c d e
(integer) 3
127.0.0.1:6379> 
127.0.0.1:6379> sdiff key1 key2 #差集
1) "b"
2) "a"
127.0.0.1:6379> sinter key1 key2 #交集,共同好友
1) "c"
127.0.0.1:6379> sunion key1 key2 #并集
1) "b"
2) "c"
3) "a"
4) "e"
5) "d"




微博,A用户将所有关注的人放在一个set集合中,将它的粉丝也放在一个集合中!

共同关注,共同爱好,二度好友,推荐好友!(六度分割理论)

Hash(哈希)

Map集合,key-Map<key,value>集合!本质和String类型没有太大区别,还是一个简单的key-value

set myhash field ming

127.0.0.1:6379> hset myshash field1 ming #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myshash field1 #获取一个key-value
"ming"
127.0.0.1:6379> hmset myshash field1 hello field2 world #set多个key-value
OK
127.0.0.1:6379> hmget myshash field1 field2 #获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myshash #获取全部的数据
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myshash field1 #删除hash指定的key字段,对应的value值就消失了
(integer) 1
127.0.0.1:6379> hgetall myshash
1) "field2"
2) "world"

############################################################

hlen

127.0.0.1:6379> hmset myshash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myshash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myshash #获取hash表字段数量
(integer) 2

############################################################

127.0.0.1:6379> hexists myshash field1 #判断hash中指定字段是否存在
(integer) 1
127.0.0.1:6379> hexists myshash field3
(integer) 0

############################################################
# 只获得所有的field
# 只获得所有的value
127.0.0.1:6379> hkeys myshash # 只获得所有的field
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myshash # 只获得所有的value
1) "world"
2) "hello"

############################################################
incr decr(没有decrby)

127.0.0.1:6379> hset myshash field3 5 #指定增量
(integer) 1
127.0.0.1:6379> hincrby myshash field3 1
(integer) 6
127.0.0.1:6379> hincrby myshash field3 -1
(integer) 5
127.0.0.1:6379> hsetnx myshash field4 world #如果不存在则可以设置
(integer) 1
127.0.0.1:6379> hsetnx myshash field4 hello #如果存在则不可以设置
(integer) 0



hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更适合字符串的存储

Zset(有序集合)

在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1

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 xiaohong #添加一个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 ming
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf #显示全部用户,从小到大
1) "ming"
2) "xiaohong"
3) "zhangsan"

127.0.0.1:6379> zrevrange salary 0 -1 #从大到小排序
1) "zhangsan"
2) "ming"

127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #显示全部用户并且附带成绩从小到大
1) "ming"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores #显示工资小于2500员工的升序排列
1) "ming"
2) "500"
3) "xiaohong"
4) "2500"


############################################################

# 移除rem中的元素

127.0.0.1:6379> zrange salary 0 -1
1) "ming"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除有序集合中指定的元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "ming"
2) "zhangsan"
127.0.0.1:6379> zcard salary #获取有序集合中的个数
(integer) 2
############################################################

127.0.0.1:6379> zadd myset 1 hello 2 world 3 ming
(integer) 3
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测试!

三种特殊数据类型

geospatial

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

Redis的Geo在Redis3.2版本就推出了,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人

可以查询一些数据:http://www.hao828.com/chaxun/zhongguochengshijingweidu/index.asp?key=����&submit=��ѯ

只有留个命令:

1616912579840

文档:https://www.redis.net.cn/order/3685.html

#geoadd 添加地位置
# 规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
#有效的经度从-180度到180度。
#有效的纬度从-85.05112878度到85.05112878度。
#当坐标位置超出上述指定范围时,该命令将会返回一个错误。
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqin 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.26 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:ciry 108.96 34.26 xian
(integer) 1

############################################################

# geopos 获取当前定位:一定是一个坐标值!
127.0.0.1:6379> geopos china:city beijing chongqin #获取指定城市的经度和纬度!
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"

   
############################################################
   
# goedist 两个人之间的距离
单位:
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 1000 km #以100 ,30这个经纬度为中心,寻找方圆1000km内的城市
1) "chongqin"
2) "xian"
3) "shenzhen"
127.0.0.1:6379>  georadius china:city 110 30 500 km
1) "chongqin"
2) "xian"
127.0.0.1:6379>  georadius china:city 110 30 500 km withdist  #显示到中心的距离
1) 1) "chongqin"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
127.0.0.1:6379>  georadius china:city 110 30 500 km withcoord #显示其他城市经纬度
1) 1) "chongqin"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379>  georadius china:city 110 30 500 km withdist withcoord count 1 #筛选出指定的结果
1) 1) "chongqin"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379>  georadius china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqin"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379>  georadius china:city 110 30 500 km withdist withcoord count 3
1) 1) "chongqin"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"
      
############################################################

#georadiusbymenber
#找出位于指定元素周围的其他元素
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
2) "xian"

############################################################

# geohash 返回一个或多个位置元素的 Geohash 表示 
#该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示。
#将二维的额经纬度转化为一维的字符串,如果两个字符串越接近,距离越接近
127.0.0.1:6379> geohash china:city beijing chongqin
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

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

127.0.0.1:6379> zrange china:city 0 -1 #查看地图中全部元素
1) "chongqin"
2) "xian"
3) "shenzhen"
4) "shanghai"
5) "beijing"
127.0.0.1:6379> zrem china:city beijing #移除指定元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqin"
2) "xian"
3) "shenzhen"
4) "shanghai"

Hyperloglog

什么是基数?

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

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

基数(不重复的元素的个数->1,3,5,7,8) = 5 ,可以接受误差

简介:Redis2.8.9更新了Hyperloglog数据结构!

Redis Hyperloglog 基数统计的算法!

优点:占用的内存是固定,2^64不同的元素的技术,只需要12kb内存,如果从内存角度的话,Hyperloglog是首选!

网页的UV(一个人访问一个网站多次,但是还是算作一个人!)

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

这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是为了保存用户id;

0.81%错误率!统计UV任务,可以接受!

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

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

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

Bitmaps

位存储

统计疫情感染人数:0 1 0 0 1

统计用户信息:活跃,不活跃!登录、未登录!打卡,365打卡!userid states date?

两个状态的,都可以使用Bitmaps!

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

365 天 = 365 bit 1字节 = 8bit 46个字节左右就可以存储一个一年的打卡情况!

使用bitmaps来记录周一到周日的打卡!

周一:1 ,周二:0,周三: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 0
(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) 0

统计打卡的天数:

127.0.0.1:6379> bitcount sign #统计这周的打卡记录,就可以查看是否有全勤
(integer) 3
bitcount sign start end(这里的start和end代表的是字节)(六天的数据就存储在一个字节上)
(一字节 = 8 bit ,可以存储8天的数据)

事务

MySql:ACID

要么同时成功,要么同时失败,原子性!

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

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

一次性、顺序性、排他性!执行一些列的命令

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

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

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

Redis的事务:

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

锁:Redis可以实现乐观锁

正常执行事务!

127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec  #命令执行
1) OK
2) OK
3) "v2"
4) OK

放弃事务!

127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard #取消事务
OK
127.0.0.1:6379> get k4 #事务中命令不会被执行
(nil)

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3    #错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> exec  #执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5  #所有的命令都不会执行
(nil)

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

127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1 #执行的时报错,字符串不能自增
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range #虽然第一条事务报错了,但是其他依旧正常执行成功了
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

监控! Watch

悲观锁:

  • 什么时候都可能出现问题,无论做什么都会加锁

乐观锁:

  • 很乐观,认为什么时候都不会出现问题,更新数据的时候去判断一下,再次期间是否有人修改过这个数据,
  • 获取version
  • 更新的时候比较version

Redis的监视测试i

正常执行成功!

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(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec  #执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败(会先比较一下money)
(nil)

如果修改失败,获取最新值就好!

1.如果发现事务执行失败,就先解锁,

2.获取最新的值,再次1监视,selectversion

3.比对监视的值是否发生了变化如果没有变化,那么可以执行成功,如果变了就执行失败!

127.0.0.1:6379> unwatch #1.如果发现事务执行失败,就先解锁,
OK
127.0.0.1:6379> watch money #2.获取最新的值,再次1监视,selectversion
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 1
QUEUED
127.0.0.1:6379(TX)> incrby out 1
QUEUED
127.0.0.1:6379(TX)> exec #3.比对监视的值是否发生了变化如果没有变化,那么可以执行成功,如果变了就执行失败!
1) (integer) 999
2) (integer) 21

Jedis

我们使用java来操作Redis

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

知其然知其所以然!

测试

1.导入对应的依赖

 <!--导入jedis包-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.5.2</version>
        </dependency>

        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

2.编码测试

  • 连接数据库

  • 操作命令

  • 断开连接!

    package com.ming;
    
    import redis.clients.jedis.Jedis;
    
    public class TestPing {
        public static void main(String[] args) {
            //new Jedis对象即可
            Jedis jedis = new Jedis("127.0.0.1",6379);
            //jedis所有的命令就是我们之前学习的指令
            System.out.println(jedis.ping());
        }
    
    }
    
    

    常用API

    String

    List

    Set

    Hash

    Zset

    Bitmaps

    Hyperloglog

事务

package com.ming;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;

public class TestTX {

    public static boolean mymulit(Jedis jedis) throws InterruptedException {

        int balance = Integer.valueOf(jedis.get("balance"));
        int out = Integer.valueOf(jedis.get("out"));
        int pay = 10;
        jedis.watch("balance");
        if(balance<pay){
            jedis.unwatch();
            return false;
        }
        else{
            Transaction multi = jedis.multi();
            try{
                multi.decrBy("balance",pay);
                multi.incrBy("out",pay);
                Thread.sleep(2000);
                List<Object> exec =multi.exec();
                System.out.println(exec);
                if(exec == null) return false; // 如果其他线程操作了balance,那么就不执行
            }catch (Exception e){
                multi.discard();//放弃事务
                System.out.println(e.getStackTrace());
                return false;
            }finally {
                jedis.unwatch();
                jedis.close();
            }
            return true;

        }

    }

    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = new Jedis();
        jedis.set("balance","100");
        jedis.set("out","0");
        boolean b = mymulit(jedis);
        System.out.println(b);
    }


}

SpringBoot整合

springboot操作数据层:spring-data jpa jdbc mongodb redis!

springData 也是和springboot齐名的项目~

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

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

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

源码分析:

public class RedisAutoConfiguration {

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

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

}

测试一下

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class RedisSpringboot01ApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {


        //redisTemplate
        //opsForValue 操作字符串,类似于String,该方法之后的操作api与我们学习的指令相同了
        //opsForList 操作List
        //opsForSet  操作Set
        //opsForHash
        //opsForZSet
        //opsForGeo
        //opsForHyperLogLog
        redisTemplate.opsForHyperLogLog();

        //除了基本的操作,常用的方法都可以直接通过RedisTemplate操作,比如事务的操作
        redisTemplate.discard();

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

        redisTemplate.opsForValue().set("mykey","关注林嘉铭公众号");
        System.out.println(redisTemplate.opsForValue().get("mykey"));

    }

}

关于对象的保存:

1617005640066

1617006523873

package com.ming.redisspringboot01;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ming.redisspringboot01.Utils.RedisUtils;
import com.ming.redisspringboot01.opoj.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class RedisSpringboot01ApplicationTests {

    @Autowired
    @Qualifier("redisTempalte")//使用这个注解使用我们自己定义的的RedisTemplate,
    // 这个注解相当于:<bean id=redisTempalte class=RedisTemplate></bean>
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtils redisUtils;

    @Test
    void contextLoads() {


        //redisTemplate
        //opsForValue 操作字符串,类似于String,该方法之后的操作api与我们学习的指令相同了
        //opsForList 操作List
        //opsForSet  操作Set
        //opsForHash
        //opsForZSet
        //opsForGeo
        //opsForHyperLogLog
        /*redisTemplate.opsForHyperLogLog();*/

        //除了基本的操作,常用的方法都可以直接通过RedisTemplate操作,比如事务的操作
        /*redisTemplate.discard();*/

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

        redisTemplate.opsForValue().set("mykey","关注林嘉铭公众号");
        System.out.println(redisTemplate.opsForValue().get("mykey"));

    }


    //在企业开发中,80%情况下,都不会使用这个原生的方式去写代码
    //我们会自己写自己的工具类,RedisUtils
    @Test
    void test() throws JsonProcessingException {
        User user = new User("ming",20);
        //需要把user类进行序列化,不然会报错
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
        //当然,这里implements Serializable是jdk自带的序列化,到控制台挥别转移,我们需要自己自定义RedisTemplate
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));

        //真实的开发一般使用json来传递对象
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonUser = objectMapper.writeValueAsString(user);
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
        
        //真实的开发,使用工具类
        redisUtils.set("name","ming");
        System.out.println(redisUtils.get("name"));

    }

}

序列化方式有多种:

1617006844322

我们来编写一个自己的RedisTemplate:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.org.apache.xpath.internal.objects.XObject;
import lombok.val;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class RedisConfig{

    //编写自己的redistemplate
    @Bean
    public RedisTemplate<String,Object> redisTempalte(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String,Object> template = new RedisTemplate<>();
        //配置具体的序列化方式
        //Json序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //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();

        //template.setKeySerializer(jackson2JsonRedisSerializer);

        return template;
    }
}

附加工具类:

package com.ming.redisspringboot01.Utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

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

@Component
public final class RedisUtils {
    @Autowired
    @Qualifier("redisTempalte")
    private RedisTemplate<String,Object> redisTemplate;

    // ===============common=======================

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

    /**
     * 根据key,获取过期时间
     * @param key
     * @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 可以传一个或者多个
     */

    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 如果小于等于0 将设置无限期
     * @return
     */
    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(Hash)==============================


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


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

    /**
     * HashSet
     * @param key 键
     * @param map 多个值
     * @return
     */
    public boolean hmset(String key,Map<Object,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<Object,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 时间(秒)大于0,如果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 键
     * @param item 项 可以多个,不能为null
     */
    public void hdel(String key,Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key
     * @param item
     * @return
     */
    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,判断value是否在set中
     * @param key
     * @param value
     * @return
     */
    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 value 值
     * @return
     */
    public long sSet(String key,Object... value){
        try {
            return redisTemplate.opsForSet().add(key,value);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存,并设置时间
     * @param key 键
     * @param time 时间(秒) 大于0
     * @param value 值 可以多个
     * @return 成功的个数
     */

    public long sSetAndTime(String key,long time,Object... value){
        try {
            long count = redisTemplate.opsForSet().add(key,value);
            if(time>0)
                expire(key,time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


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

    /**
     * 移除值为value
     * @param key 键
     * @param value 值 可以多个
     * @return 移除的个数
     */
    public long setRemove(String key,Object... value){
        try {
            return redisTemplate.opsForSet().remove(key,value);
        } 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 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 时间 大于0
     * @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,Object... value){
        try {
            redisTemplate.opsForList().rightPushAll(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将多少个数据放入List缓存,并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) 大于0
     * @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 {
            return redisTemplate.opsForList().remove(key,count,value);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    
}

Redis.conf详解

启动的时候通过配置文件启动!

单位

1617027559546

1.配置文件 unit单位 对大小写不敏感

包含

1617027791209

就是好比我们学习spring、import,include 可以多个配置文件都配置进来

网络

bind 127.0.0.1 -::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 "/usr/local/redis/redis_log.log" #日志文件存储的地方
databases 16 #数据库的数量,默认16个数据库
set-proc-title yes #是否总是显示logo

快照

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

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

# 如果3600秒内,如果至少有一个key进行了修改,我们就进行持久化操作
save 3600 1
# 如果300秒内,如果至少有100个key进行了修改,我们就进行持久化操作
save 300 100
# 如果60秒内,如果至少有10000个key进行了修改,我们就进行持久化操作
save 60 10000
#我们之后学习持久化,会自己定义这个测试


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

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

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

dbfilename dump.rdb #rdb文件保存的目录

REPLICATION复制,我们后面讲解

SECURITY 设置密码

1617029262210

限制 CLIENTS

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

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

maxmemory-policy noeviction #内存达到上限之后的处理策略
	# 移除一些过期的key
	# 报错
	# 。。。
	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 #默认是不开启的aof模式,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用

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

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

Redis持久化

重点!

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

RDB(Redis DataBase)

什么是RDB

1617032340725

1617030327686

我们默认就是RDB,一班情况下不需要修改这个设置!

有时候在生产环境我们会将这个文件进行备份!

RBD保存的文件是dump.rdb,都是在我们的配置文件中快照进行配置的!

1617031451357

测试一下:60秒内修改了5次key,就会触发rdb操作

1617031506404

触发机制

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

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

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

备份就会自动生成一个dump.rdb(我自己保存在/usr/local/redis/redis_dbfile文件里)

1617031718087

如何恢复rdb文件!

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

2、查看需要存在的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" #如果在这个目录存在dump.rdb文件,启动就会自动恢复其中的数据


几乎就他自己默认的配置就够用了,但是我们还是需要去学习!

  • 优点:

1、适合大规模的数据恢复!dump.db

2、对数据的完整性要求不高!

  • 缺点:

1、需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有了!

2、fork进程的时候,会占用一定的内存空间!!

AOF (Append Only File)

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

是什么

1617065946927

1617066017544

AOF保存的是appendonly.aof文件

append

1617066262310

默认是不开启的,我们需要手动进行配置!我们只需要讲appendonly改为yes就开启了aof!

重启,redis就可以生效了!

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

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

1617067224800

如果文件正常,重启就可以直接恢复了!但是把错误的数据删掉了!

1617067397631

重写规则

1617067895146

如果aof文件大于64m,就会fork一个新的进程来将Wimbledon的文件进行重写,

  • 优点:

    1、每一次修改都同步,文件的完整性会更加好!

    2、每秒同步一次,可能会丢失一秒的数据!

    3、从不同步,效率最高的!

  • 缺点

    1、相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢!

    2、aof运行效率也要比rdb慢,所以我们redis默认配置就是rdb持久化!

扩展

1617068432249

Redis发布订阅

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

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

订阅/发布消息图:

1617074468362

1617074530574

1617074572819

测试

127.0.0.1:6379> subscribe ming   #订阅者订阅频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ming"
3) (integer) 1  #接收到的数据会显示在下面
1) "message"
2) "ming"
3) "hello,mingming"
1) "message"
2) "ming"
3) "are you ok?"



127.0.0.1:6379> publish ming "hello,mingming" #发布者发布数据,会发布到所有的订阅者
(integer) 1
127.0.0.1:6379> publish ming "are you ok?"
(integer) 1



1617075097532

1617075193196

使用场景:

1、实时消息系统!

2、实时聊天!(频道当做聊天室,将信息回显给所有人即可!)

3、订阅,关注系统都是可以的!

稍微复杂的场景我们就会使用消息中间件MQ()

主从复制

1617075484072

1617075507112

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

环境配置

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

127.0.0.1:6379> info replication #查看信息 
# Replication
role:master      #默认是主库
connected_slaves:0
master_failover_state:no-failover
master_replid:26fbdd5255bb815f553c8ceefe7e3e063ea0a21e
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名字

修改完毕之后,启动我们的3个redis服务器,可以通过进程信息查看!

1617077463774

一住二从

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

认老大!一主(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:2
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:69004b4e9369218fc97d2bab4df22ea68b0a4345
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:1  #多了从机我的配置
slave0:ip=127.0.0.1,port=6380,state=online,offset=238,lag=1
master_failover_state:no-failover
master_replid:69004b4e9369218fc97d2bab4df22ea68b0a4345
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:238
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:238

如果连个都配置完就有两个从机的信息

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2  #2 
slave0:ip=127.0.0.1,port=6380,state=online,offset=490,lag=0 #信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=490,lag=0
master_failover_state:no-failover
master_replid:69004b4e9369218fc97d2bab4df22ea68b0a4345
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:490
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:490

如果在配置文件中配置,则需配置这两项即可:

1617078254149

细节

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

127.0.0.1:6379> set k1 v1 # 主
OK

127.0.0.1:6380> get k1 # 从1
"v1"

127.0.0.1:6381> get k1 # 从2
"v1"

从机只能读取内容!

127.0.0.1:6380> set k2 v2 #从1
(error) READONLY You can't write against a read only replica.

测试:主机断开连接,从机依旧连接到主机,但是没有写操作,这个时候,主机回来了,从机依旧可以直接获取到主机写的信息

如果是命令行,配置的主从,这个时候如果重启了,就会变回主机!只要变为从机,立马就会从主机获取值!

1617079278795

——1617079393703

=== 以上两种主从方式,工作中都不会用到!=== +_+

1617079535668

哨兵模式(自动)

概述

1617079650210

1617079727311

1617079865868

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

测试!

我们目前的状态是一主二从

1、配

置哨兵配置文件 sentinel.conf

#sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1

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

2、启动哨兵

sudo redis-sentinel mconfig/sentinel.conf
posted @ 2021-04-28 18:37  小明明吗  阅读(57)  评论(0)    收藏  举报