Redis
1、Redis简介
Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
Redis 优势
-
性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
-
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
-
原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
-
丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
2、Redis基本知识说明
在进兴之前,我们首先要知道redis不区分大小写的
Redis有16个数据库,默认使用第一个
可以使用select进行切换数据库
127.0.0.1:6379> dbsize #查看数据库大小
(integer) 1
127.0.0.1:6379> select 3 #选择数据库
OK
127.0.0.1:6379[3]>
127.0.0.1:6379> keys * #查看数据库中所有的key
1) "key"
127.0.0.1:6379>
清除当前数据库flushdb
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>
清楚所有数据库flushall
Redis 是单线程的!
Redis是基于内存操作的,CPU不是redis的性能瓶颈
后来6.0之后的版本就支持多线程了
3、五大数据类型
Redis-Key
127.0.0.1:6379> set name nahan
OK
127.0.0.1:6379> get name
"nahan"
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get name
"nahan"
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> exits name #判断是否存在,存在返回1,不存在返回0
(error) ERR unknown command 'exits'
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> move name #移除
(error) ERR wrong number of arguments for 'move' command
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379>
设定一个时间限定消失
可以用于网站的用户登录存放cookie这些(单点登录)
**expire name 10 **
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> expire name 10 #expire 设置name10秒后消失
(integer) 1
127.0.0.1:6379> ttl
(error) ERR wrong number of arguments for 'ttl' command
127.0.0.1:6379> ttl name #查看key剩余的时间
(integer) 4
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>
####################################################
127.0.0.1:6379> type age #查看当前key的数据类型
string
127.0.0.1:6379>
####################################################
127.0.0.1:6379> set name nahan
OK
127.0.0.1:6379> del name #删除当前的key值
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379>
####################################################
Redis Dump 命令
Redis DUMP 命令用于序列化给定 key ,并返回被序列化的值。
127.0.0.1:6379> set greeting "hello,dumping world"
OK
127.0.0.1:6379> dump greeting
"\x00\x13hello,dumping world\x06\x00\x06\xd4\x19\xf07\x95\xc3\xbf"
127.0.0.1:6379>
**Redis RANDOMKEY 命令从当前数据库中随机返回一个 key **
语法:
redis RANDOMKEY 命令基本语法如下:
redis 127.0.0.1:6379> RANDOMKEY
实例
127.0.0.1:6379> mset fruit "apple" drink "beer" food "cookies" #设置多个key值
OK
127.0.0.1:6379> randomkey #随机从数据库中返回值
"food"
127.0.0.1:6379> randomkey
"age"
127.0.0.1:6379> randomkey
"food"
127.0.0.1:6379>
Redis Renamenx 命令用于在新的 key 不存在时修改 key 的名称 。
语法
redis Renamenx 命令基本语法如下:
redis 127.0.0.1:6379> RENAMENX OLD_KEY_NAME NEW_KEY_NAME
String(字符串)
1、append ,strlen
语法
append key // 追加 如果当前key不存在,就相当于set key
strlen key //字符串长度
实例
127.0.0.1:6379> set key v1 #设置值
OK
127.0.0.1:6379> get key #获得值
"v1"
127.0.0.1:6379> append key ",hello" #追加
(integer) 8
127.0.0.1:6379> get key
"v1,hello"
127.0.0.1:6379> strlen key #获取当前字符串长度
(integer) 8
127.0.0.1:6379>
2、incr 、decr、incrby、decrby、
此类主要可以用于网站的浏览量和播放量之类的功能
语法
incr key //加1
decr key //减1
incyby key value //一次加value值
decrby key value //一次减value值
#实例
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 vieews
(nil)
127.0.0.1:6379> get views
"2"
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) 11
127.0.0.1:6379> incrby views 10
(integer) 21
127.0.0.1:6379> decrby views 5
(integer) 16
127.0.0.1:6379>
3、获取指定区间的值
语法
getrange key 0 3 //截取字符串[0,3]
getrange key 0 -1 //全部字符串
setrange key 1 hh //替换指定位置的字符串 下标1 后面的字符串
实例
127.0.0.1:6379> set key hello,nahan
OK
127.0.0.1:6379> get key
"hello,nahan"
127.0.0.1:6379> getrange key 0 3
"hell"
127.0.0.1:6379> getrange key 0 -1
"hello,nahan"
127.0.0.1:6379> setrange key 1 hh
(integer) 11
127.0.0.1:6379> get key
"hhhlo,nahan"
127.0.0.1:6379>
4、设置值
setex (set with expire) #设置过期时间 setnx(set if no exist)
- 实例*
127.0.0.1:6379> setex key1 30 hello #设置30s过期
OK
127.0.0.1:6379> ttl key1
(integer) 25
127.0.0.1:6379> ttl key1
(integer) 23
127.0.0.1:6379> setnx mykey redis #不存在mykey就设置为redis
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key1"
3) "key"
127.0.0.1:6379> ttl key1
(integer) -2
127.0.0.1:6379> setnx mykey "mongodb" #mykey已经存在,所以设置不成功
(integer) 0
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379>
5、mset
mset设置多个值-
语法
mset k1 v1 k2 v2 k3 v3 //同时设置多个值
mget k1 k2 k3 //同时获取多个值
实例
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
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 #这是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379>
6、对象
语法:
mset user:1:name "zhangsan" user:1:age 20 //设置一个对象的值
mget user:1:name user:1:age //获取一个对象的值
127.0.0.1:6379> mset user:1:name "zhangsan" user:1:age 20
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "20"
127.0.0.1:6379>
7、getset
127.0.0.1:6379> getset db redis #先get然后再set,所以返回值为null
(nil)
127.0.0.1:6379> get db #get 就为redis了
"redis"
127.0.0.1:6379> getset db mongdb #如果存在值,则获取原来的值
"redis"
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379>
使用场景
可以使用于网站浏览量等计数器。。。。
List
基本的数据类型,列表。主要是看进出的方式来区别栈和队列、阻塞队列
所有的list命令都是l开头(redis不区分大小写)
1、插入操作
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> keys *
1) "list"
127.0.0.1:6379> lrange list 0 -1 #通过区间获取具体的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> rpush list right #从尾部插入(右边)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379>
从上面就可以看出,先进去的在右边,后进去的在左边,当把[0,1]这个范围的值取出来时是从左到右取的
2、移除列表元素
语法
lpop key //头部移除第一个
rpop key //右边移除最后一个
实例
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
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) "two"
2) "one"
3、获得某一个值
通过下标获取值
实例:
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1
"one"
127.0.0.1:6379> lindex list 0
"two"
127.0.0.1:6379>
4、获取列表的长度
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
127.0.0.1:6379>
5、移除指定的值
由于列表中允许同样的value,所以可以根据需求来移除n个value
实例:
127.0.0.1:6379> lrem list 1 one #移除列表中一个one
(integer) 1
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrem list 2 three #移除列表中的两个three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379>
6、修剪操作
即保留区间的元素
语法:
ltrim key [1,2] //将1 2区间的修剪
实例
127.0.0.1:6379> lpush list hello
(integer) 1
127.0.0.1:6379> lpush list hello1
(integer) 2
127.0.0.1:6379> lpush list hello12
(integer) 3
127.0.0.1:6379> lpush list hello3
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello12"
3) "hello1"
4) "hello"
127.0.0.1:6379> ltrim list 1 2 //通过下标截取指定的长度,这个list只剩下被改变了,只剩下了被截取的部分
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello12"
2) "hello1"
127.0.0.1:6379>
7、移除最后一个元素到新的列表中
语法:
rpoplpush key otherkey
实例:
127.0.0.1:6379> rpush list hello
(integer) 1
127.0.0.1:6379> rpush list hello1
(integer) 2
127.0.0.1:6379> rpush list hello2
(integer) 3
127.0.0.1:6379> rpush list hello3
(integer) 4
127.0.0.1:6379> rpoplpush list myotherlist
"hello3"
127.0.0.1:6379> lrange list 0 -1 #查看原来的列表
1) "hello"
2) "hello1"
3) "hello2"
127.0.0.1:6379> lrange myotherlist 0 -1 #查看目标列表
1) "hello3"
8、Lset
将列表中指定下标的值替换为另一个值,相当于更新操作
127.0.0.1:6379> lpush list value
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value"
127.0.0.1:6379> lset list 0 item #这里就相当于更新值,就是替换
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379>
9、Linsert
将一个元素插入到某一个元素的前面或者后面
语法:
linset key before/after value value2
实例:
127.0.0.1:6379> rpush list hello
(integer) 1
127.0.0.1:6379> rpush list world
(integer) 2
127.0.0.1:6379> linsert list before world other
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert list after world hello
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "other"
3) "world"
4) "hello"
127.0.0.1:6379>
10、小结
list
-
实际上是一个链表,before node after,left,right都可以插入值
-
如果key不存在,创建新的链表
-
如果key存在,新增内容
-
如果移除了所有的值,空链表,即是不存在
-
在两边插入或者改动值,效率最高!!中间相对来说低一点
Set
Set是无序的,而List是有序的
注意:set中的数据是不能重读的
简单体验
127.0.0.1:6379> sadd myset hello #向set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset nahan
(integer) 1
127.0.0.1:6379> sadd myset love
(integer) 1
127.0.0.1:6379> smembers myset #查看set中的所有值
1) "love"
2) "nahan"
3) "hello"
127.0.0.1:6379> sismember myset hello #判断hello是否在myset里面,是返回1,不是返回 0
(integer) 1
127.0.0.1:6379> sismember myset world #返回0
(integer) 0
127.0.0.1:6379> scard myset #查看当前set中的元素个数
(integer) 3
127.0.0.1:6379> srem myset hello #移除指定的value
(integer) 1
127.0.0.1:6379> smembers myset #查看所有值
1) "love"
2) "nahan"
1、随机挑选和随机删除
语法
srandmember key //随机挑选
spop key //随机删除
实例
127.0.0.1:6379> smembers myset
1) "hello"
2) "love"
3) "nahan"
4) "vans"
127.0.0.1:6379> srandmember myset
"love"
127.0.0.1:6379> srandmember myset
"vans"
127.0.0.1:6379> smembers myset
1) "hello"
2) "love"
3) "nahan"
4) "vans"
127.0.0.1:6379> spop myset
"vans"
127.0.0.1:6379> spop myset
"hello"
127.0.0.1:6379> smembers myset
1) "love"
2) "nahan"
127.0.0.1:6379>
2、将一个值移动到另一个集合中
语法:
smove 原key 目标key 元素
实例:
127.0.0.1:6379> smembers myset
1) "hello"
2) "love"
3) "nahan"
127.0.0.1:6379> sadd set world #新建一个集合
(integer) 1
127.0.0.1:6379> smove myset set hello
(integer) 1
127.0.0.1:6379> smembers set #查看目标集合
1) "hello"
2) "world"
3、数字集合类
这个可以实现我们平时关注的公众号的共同好友,共同爱好,共同关注
数字集合类中:
- 交集
- 并集
- 差集
实例
127.0.0.1:6379> sadd key a
(integer) 1
127.0.0.1:6379> sadd key b
(integer) 1
127.0.0.1:6379> sadd key c
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 e
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> sdiff key key1 #key和key1不同的值
1) "a"
2) "b"
127.0.0.1:6379> sinter key key1 #交集
1) "c"
127.0.0.1:6379> sunion key key1 #并集
1) "c"
2) "d"
3) "a"
4) "b"
5) "e"
127.0.0.1:6379>
Hash
Map集合,key-map 这个时候值就是一个map集合,其本质和string类型没有太大区别
实例
127.0.0.1:6379> hset hash filed nahan #设置值
(integer) 1
127.0.0.1:6379> hget hash filed #得到值
"nahan"
127.0.0.1:6379> hmset hash filed hello filed1 nahan #设置多个值
OK
127.0.0.1:6379> hmget hash filed filed1 #得到多个值
1) "hello"
2) "nahan"
127.0.0.1:6379> hgetall hash #得到全部的key-map值
1) "filed"
2) "hello"
3) "filed1"
4) "nahan"
127.0.0.1:6379>
1、删除
语法:
hdel key map #删除hash指定的key字段,对应的value值也消失的!!
实例
127.0.0.1:6379> hdel hash filed
(integer) 1
127.0.0.1:6379> hgetall hash
1) "filed1"
2) "nahan"
2、获得key的字段长度
127.0.0.1:6379> hdel hash filed
(integer) 1
127.0.0.1:6379> hgetall hash
1) "filed1"
2) "nahan"
127.0.0.1:6379> hlen hash
(integer) 1
3、判断是否存在
127.0.0.1:6379> hexists hash filed
(integer) 0
127.0.0.1:6379> hexists hash filed2
(integer) 1
4、只获得值或者key
127.0.0.1:6379> hkeys hash #获得key
1) "filed1"
2) "filed2"
3) "filed3"
127.0.0.1:6379> hvals value
(empty list or set)
127.0.0.1:6379> hvals hash #获得值
1) "nahan"
2) "hello"
3) "world"
5、指定增量或者减
127.0.0.1:6379> hset hash filed 3
(integer) 1
127.0.0.1:6379> hincrby filed 5
(error) ERR wrong number of arguments for 'hincrby' command
127.0.0.1:6379> hincrby hash filed 5
(integer) 8
127.0.0.1:6379> hincrby hash filed -2
(integer) 6
Zset
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
命令
zadd key score1 member1[score2 member2] #向集合中添加一个或者多个成员,或者更新已经存在的集合
zcard key #获取有序集合中的成员数
zcount 0 2#获取区间0 到 2之间的成员数
实例:
127.0.0.1:6379> zcount myzset 0 1
(integer) 1
####################################################
zincrby key increment member #给集合中指定的成员分数加上增量
实例:
127.0.0.1:6379> zincrby myzset 5 mongodb
"7"
zrange key 0 -1 withscores #把集合中所有值打印出来
实例:
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "reids"
2) "1"
3) "mysql"
4) "3"
5) "mongodb"
6) "7"
1、redis Zrevrank 命令
Redis Zrevrank 命令返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
排名以 0 为底,也就是说, 分数值最大的成员排名为 0 。
使用 ZRANK 命令可以获得成员按分数值递增(从小到大)排列的排名。
redis Zrevrank 命令基本语法如下:
redis 127.0.0.1:6379> ZREVRANK key member
实例
127.0.0.1:6379> zrevrank myzset reids
(integer) 2
127.0.0.1:6379> zrevrank myzset redis
(nil)
127.0.0.1:6379> zrevrank myzset mysql
(integer) 1
127.0.0.1:6379> zrevrank myzset mongodb
(integer) 0
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "reids"
2) "1"
3) "mysql"
4) "3"
5) "mongodb"
6) "7"
2、Zscore命令
语法
redis 127.0.0.1:6379> ZSCORE key member
实例
127.0.0.1:6379> zscore myzset mongodb
"7"
127.0.0.1:6379>
4、三种特殊数据类型
1、Gespatial
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。
Redis GEO 操作方法有:
- geoadd:添加地理位置的坐标。
- geopos:获取地理位置的坐标。
- geodist:计算两个位置之间的距离。
- georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
- georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
- geohash:返回一个或多个位置对象的 geohash 值。
1、添加地理位置
#规则:两级无法直接添加,我们一般会下载城市数据,通过java读进redis
#参数:key 值(维度、进度、名称)
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> GEOADD china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> GEOADD china:city 106.50 29.53 chongqing 114.05 22.52 shenzheng
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
127.0.0.1:6379>
2、获得指定城市的经纬度
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.39999896287918" #获得当前定位,是一个坐标值
2) "39.900000091670925"
127.0.0.1:6379> GEOPOS china:city hangzhou
1) 1) "120.16000002622604"
2) "30.240000322949022"
127.0.0.1:6379>
3、两个人之间的距离
geodist
geodist 用于返回两个给定位置之间的距离。
geodist 语法格式如下:
GEODIST key member1 member2 [m|km|ft|mi]
member1 member2 为两个地理位置。
最后一个距离单位参数说明:
-
m :米,默认单位。
-
km :千米。
-
mi :英里。
-
ft :英尺。
实例:
127.0.0.1:6379> GEODIST china:city beijing shanghai #北京到上海的距离(单位 :km)
"1067378.7564"
127.0.0.1:6379> GEODIST china:city beijing chongqing km #重庆到上海的距离
"1464.0708"
4、我附近的人?
获得附近所有人的定位,通过半径来查询
获得指定数量的人
将所有数据都录入china:city 让结果更加精确
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km #以110 30为中心,半径为1000km来查找
1) "chongqing"
2) "xian"
3) "shenzheng"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km #以110 30为中心,半径为5000km来查找
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist #以110 30为中心,半径为5000km来查找,并且将纬度找出来
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord count 2 #以110 30为中心,半径为5000km来查找两个
1) 1) "chongqing"
2) 1) "106.49999767541885"
2) "29.529999579006592"
2) 1) "xian"
2) 1) "108.96000176668167"
2) "34.2599996441893"
GEORADIUSBYMEMBER
找出位于指定元素的周围的其他元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
2、Hyperloglog
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
1、什么是基数
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
可以用于计算网站的访问人数,一个人访问十次该网站,但是只记为一次
实例
127.0.0.1:6379> PFADD mykey a b c d f g h #创建第一组元素
(integer) 1
127.0.0.1:6379> PFCOUNT mykey #统计key元素的数量
(integer) 7
127.0.0.1:6379> PFADD mykey2 a o i j k l m n
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 8
127.0.0.1:6379> PFMERGE key mykey mykey2 #合成到一个key中去,重复的就会去重。
OK
127.0.0.1:6379> PFCOUNT key
(integer) 14
3、Bitmap
统计用户信息,活跃数,不活跃数,和打卡
只有0 1状态
语法
setbit key 0 0 #前面第一个参数为星期一,后面为不打卡,1为打卡
setbit key 1 0
。。。。。
getbit key 1 0 #得到某一天的信息
bitcout key #统计为1的天数
5、Redis事务
Redis单条命令式保存原子性,但是事务不保证原子性
Redis事务本质:一组命令的集合! 一个事务的所有命令都会被序列化,在事务执行过程中,会按照顺序执行
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
实例
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v1"
放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard #取消事务
OK
127.0.0.1:6379> get k4 #那么k4就没有入队
(nil)
127.0.0.1:6379> get k2
"v2"
6、Redis实现乐观锁
实例
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监视money对象
OK
127.0.0.1:6379> multi #事务正常结束,数据期间没有发生变动,这个时候就正常执行
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>
测试多线程修改值,使用watch可以当作redis的乐观锁操作。
7、使用jedis操作redis
首先创建一个空项目
然后添加一下依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
测试连接
package com.heng;
import redis.clients.jedis.Jedis;
public class Test {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
System.out.println();
}
}
更多的操作
package com.heng;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
public class TestRedis{
private Jedis jedis;
@Before
public void setup() {
//连接redis服务器,192.168.0.100:6379
jedis = new Jedis("127.0.0.1", 6379);
}
/**
* redis存储字符串
*/
@Test
public void testString() {
//-----添加数据----------
jedis.set("name","xinxin");//向key-->name中放入了value-->xinxin
System.out.println(jedis.get("name"));//执行结果:xinxin
jedis.append("name", " is my lover"); //拼接
System.out.println(jedis.get("name"));
jedis.del("name"); //删除某个键
System.out.println(jedis.get("name"));
//设置多个键值对
jedis.mset("name","liuling","age","23","qq","476777XXX");
jedis.incr("age"); //进行加1操作
System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq"));
}
/**
* redis操作Map
*/
@Test
public void testMap() {
//-----添加数据----------
Map<String, String> map = new HashMap<String, String>();
map.put("name", "xinxin");
map.put("age", "22");
map.put("qq", "123456");
jedis.hmset("user",map);
//取出user中的name,执行结果:[minxr]-->注意结果是一个泛型的List
//第一个参数是存入redis中map对象的key,后面跟的是放入map中的对象的key,后面的key可以跟多个,是可变参数
List<String> rsmap = jedis.hmget("user", "name", "age", "qq");
System.out.println(rsmap);
//删除map中的某个键值
jedis.hdel("user","age");
System.out.println(jedis.hmget("user", "age")); //因为删除了,所以返回的是null
System.out.println(jedis.hlen("user")); //返回key为user的键中存放的值的个数2
System.out.println(jedis.exists("user"));//是否存在key为user的记录 返回true
System.out.println(jedis.hkeys("user"));//返回map对象中的所有key
System.out.println(jedis.hvals("user"));//返回map对象中的所有value
Iterator<String> iter=jedis.hkeys("user").iterator();
while (iter.hasNext()){
String key = iter.next();
System.out.println(key+":"+jedis.hmget("user",key));
}
}
/**
* jedis操作List
*/
@Test
public void testList(){
//开始前,先移除所有的内容
jedis.del("java framework");
System.out.println(jedis.lrange("java framework",0,-1));
//先向key java framework中存放三条数据
jedis.lpush("java framework","spring");
jedis.lpush("java framework","struts");
jedis.lpush("java framework","hibernate");
//再取出所有数据jedis.lrange是按范围取出,
// 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有
System.out.println(jedis.lrange("java framework",0,-1));
jedis.del("java framework");
jedis.rpush("java framework","spring");
jedis.rpush("java framework","struts");
jedis.rpush("java framework","hibernate");
System.out.println(jedis.lrange("java framework",0,-1));
}
/**
* jedis操作Set
*/
@Test
public void testSet(){
//添加
jedis.sadd("user","liuling");
jedis.sadd("user","xinxin");
jedis.sadd("user","ling");
jedis.sadd("user","zhangxinxin");
jedis.sadd("user","who");
//移除noname
jedis.srem("user","who");
System.out.println(jedis.smembers("user"));//获取所有加入的value
System.out.println(jedis.sismember("user", "who"));//判断 who 是否是user集合的元素
System.out.println(jedis.srandmember("user"));
System.out.println(jedis.scard("user"));//返回集合的元素个数
}
@Test
public void test() throws InterruptedException {
//jedis 排序
//注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的)
jedis.del("a");//先清除数据,再加入数据进行测试
jedis.rpush("a", "1");
jedis.lpush("a","6");
jedis.lpush("a","3");
jedis.lpush("a","9");
System.out.println(jedis.lrange("a",0,-1));// [9, 3, 6, 1]
System.out.println(jedis.sort("a")); //[1, 3, 6, 9] //输入排序后结果
System.out.println(jedis.lrange("a",0,-1));
}
}
8、SpringBoot集成Redis
9、持久化之RDB(Redis DataBase)操作
优势:
- RDB文件紧凑,全量备份,非常适合用于进行备份和灾难性宕机后恢复
- 恢复大的数据集比AOF快
劣势:
- RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据,就是说,在最后一次修改数据时发生宕机机会导致数据丢失。
因为redis是一个内存数据库,但是内存中的数据变换很快,容易造成丢失,所以redis提供了持久化的机制,分别是RDB和AOF
Redis实现持久化的流程如下:
- 客户端向服务端发送写操作(数据在客户端的内存中)
- 数据库服务端接受到写请求的数据(数据在服务端的内存中)
- 服务端调用write这个系统系统调用,将数据往磁盘上写
- 操作系统将缓冲区中的数据转移到磁盘控制器上
- 磁盘控制器将数据写到磁盘的物理介质中
RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢?按我的理解就是将某一刻瞬间拍成照片。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘中
我们可以从配置文件中看到
是有一定的时间间隔的。
那么RDB是怎么实现写入这种过程的呢?
1、save触发方式
执行该命令时,redis不能执行其他命令,直到执行完这个命令为止,所以就会导致阻塞当前redis服务器
这种方法显然不可取。所以就产生了另一种触发方式
2、bgsave触发方式
该命令的特点就是可以异步进行快照操作,这个大大的提高了Redis服务器的工作效率
具体流程如下:
具体操作流程就是Redis进程执行fork操作创建子进程,RDB持久化由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上Redis 内部所有的RDB操作都是采用 bgsave 命令。
3、自动触发
改行为主要是通过配置文件进行执行的。
10、持久化之AOF(Append Only File)操作
对于RDB的全量备份的话,显然是很耗时的,所以提供了一种更为高效的AOF操作,工作机制很简单,redis会将每一个收到的写命令通过write函数追加到文件中。简单理解就相当于日志,每一步都记录下来了。
优势:
- AOF可以更好的保护数据不丢失,,每隔一秒,就会进行操作,最多只丢失一秒的数据
- 写入效率非常高,文件不容易破损
- AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写
- AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
劣势:
- 同一份数据来说,AOF日志文件一般比RDB数据快照文件大
- AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
AOF的三种触发机制
-
每修改同步always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
-
每秒同步everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失
-
不同no:从不同步
11、Redis发布订阅
测试
第一个客户端,订阅频道
127.0.0.1:6379> SUBSCRIBE nahan #订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "nahan"
3) (integer) 1
#等待读取推送的消息
1) "message" #消息
2) "nahan" #频道
3) "hello nahan" #频道消息
1) "message"
2) "nahan" #频道
3) "nahan like java" #频道消息
第二个客户端,发布消息
127.0.0.1:6379> PUBLISH nahan "hello nahan" #发布消息
(integer) 1
127.0.0.1:6379> PUBLISH nahan "nahan like java"
(integer) 1
127.0.0.1:6379>
触发方式
该命令的特点就是可以异步进行快照操作,这个大大的提高了Redis服务器的工作效率
具体流程如下:
[外链图片转存中…(img-EZaMChfP-1603771820547)]
具体操作流程就是Redis进程执行fork操作创建子进程,RDB持久化由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上Redis 内部所有的RDB操作都是采用 bgsave 命令。
3、自动触发
改行为主要是通过配置文件进行执行的。
10、持久化之AOF(Append Only File)操作
对于RDB的全量备份的话,显然是很耗时的,所以提供了一种更为高效的AOF操作,工作机制很简单,redis会将每一个收到的写命令通过write函数追加到文件中。简单理解就相当于日志,每一步都记录下来了。
优势:
- AOF可以更好的保护数据不丢失,,每隔一秒,就会进行操作,最多只丢失一秒的数据
- 写入效率非常高,文件不容易破损
- AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写
- AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
劣势:
- 同一份数据来说,AOF日志文件一般比RDB数据快照文件大
- AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
AOF的三种触发机制
-
每修改同步always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
-
每秒同步everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失
-
不同no:从不同步
11、Redis发布订阅
测试
第一个客户端,订阅频道
127.0.0.1:6379> SUBSCRIBE nahan #订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "nahan"
3) (integer) 1
#等待读取推送的消息
1) "message" #消息
2) "nahan" #频道
3) "hello nahan" #频道消息
1) "message"
2) "nahan" #频道
3) "nahan like java" #频道消息
第二个客户端,发布消息
127.0.0.1:6379> PUBLISH nahan "hello nahan" #发布消息
(integer) 1
127.0.0.1:6379> PUBLISH nahan "nahan like java"
(integer) 1
127.0.0.1:6379>