redis随笔
基础知识
redis服务器的启动:
redis-server redis.conf #启动服务器
redis-cli -p 6379 #启动客户端
redis默认有16个数据库
可以使用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) 与范围查询, bitmaps, hyperloglogs 和 地理空间(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
基本的数据类型,列表
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=��ѯ
只有留个命令:
文档: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"));
}
}
关于对象的保存:
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"));
}
}
序列化方式有多种:
我们来编写一个自己的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详解
启动的时候通过配置文件启动!
单位
1.配置文件 unit单位 对大小写不敏感
包含
就是好比我们学习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 设置密码
限制 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
我们默认就是RDB,一班情况下不需要修改这个设置!
有时候在生产环境我们会将这个文件进行备份!
RBD保存的文件是dump.rdb,都是在我们的配置文件中快照进行配置的!
测试一下:60秒内修改了5次key,就会触发rdb操作
触发机制
1、save的规则满足的情况下,会自动触发rdb规则
2、执行flushall命令,也会触发我们的rdb规则
3、退出redis,也会产生rdb文件!
备份就会自动生成一个dump.rdb(我自己保存在/usr/local/redis/redis_dbfile文件里)
如何恢复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,恢复的时候就会把这个文件全部再执行一遍!
是什么
AOF保存的是appendonly.aof文件
append
默认是不开启的,我们需要手动进行配置!我们只需要讲appendonly改为yes就开启了aof!
重启,redis就可以生效了!
如果这个aof文件有错位这时候redis是启动不起来的,我们需要修复这个aof文件
redis给我们提供了一个工具 redis-check-aof --fix
如果文件正常,重启就可以直接恢复了!但是把错误的数据删掉了!
重写规则
如果aof文件大于64m,就会fork一个新的进程来将Wimbledon的文件进行重写,
-
优点:
1、每一次修改都同步,文件的完整性会更加好!
2、每秒同步一次,可能会丢失一秒的数据!
3、从不同步,效率最高的!
-
缺点
1、相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢!
2、aof运行效率也要比rdb慢,所以我们redis默认配置就是rdb持久化!
扩展
Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅(sub)接受消息。
Redis客户端可以订阅任意数量的频道。
订阅/发布消息图:
测试
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
使用场景:
1、实时消息系统!
2、实时聊天!(频道当做聊天室,将信息回显给所有人即可!)
3、订阅,关注系统都是可以的!
稍微复杂的场景我们就会使用消息中间件MQ()
主从复制
主从复制,读写分离!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服务器,可以通过进程信息查看!
一住二从
默认情况下,每台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
如果在配置文件中配置,则需配置这两项即可:
细节
主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存!
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.
测试:主机断开连接,从机依旧连接到主机,但是没有写操作,这个时候,主机回来了,从机依旧可以直接获取到主机写的信息
如果是命令行,配置的主从,这个时候如果重启了,就会变回主机!只要变为从机,立马就会从主机获取值!
——
=== 以上两种主从方式,工作中都不会用到!=== +_+
哨兵模式(自动)
概述
假设主服务器宕机,哨兵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

浙公网安备 33010602011771号