常用中间件Redis详解
一、Redis概述
1.2、NoSQL
1、什么是NoSQL
-
Not Only SQL :不仅仅是sql,泛指非关系型数据库 。
-
NoSQL不依赖于业务逻辑方式存储,而以简单的key—value 模式存储。大大增加了扩展能力
2、NoSQL特点
- 方便扩展(数据之间没有关系,很好扩展!)
- 大数据量,高性能(Redis 一秒写八万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
- 数据类型是多样型的!(不需要事先设计数据库!随取随用!)
1.2、NoSQL的四大分类
- KV键值对:
- 新浪:Redis
- 美团:Redis+Tair
- 阿里、百度:Redis +memecache
- 文档型数据库 :(bjson和json一样)
- MongdDB(一般必须要掌握)
- MongdDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
- MongdDB是一个介于关系型数据库和非关系型数据库中间的产品!MongdDB是非关系型数据库中功能最丰富、最像关系型数据库的!
- ConthDB
- 列存储数据库 :
- HBase
- 分布式文件系统
- 图关系数据库 :
- 不是存图形的,放的是关系,比如:朋友圈社交网络,广告推荐
- Neo4j,InfoGrid
1.3、Redis概述
- Redis是什么
Redis(Remote Dictionary Server) ,即远程服务字典
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言的API。
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
- Redis能干什么?
- 内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
- …
- Redis特性
- 多样的数据类型
- 持久化
- 集群
- 事务
1.4、安装
Linux下安装
1、去官网下载安装包。后缀.tar.gz
2、将压缩包放入到linux文件中
3、解压
tar -zxvf redis-xxxx
4、基本的环境安装
yum install gcc-c++
make
make install
5、默认安装路径:/ usr/local/bin
6、复制一份redis.conf到当前目录config下
7、设置配置文件里daemonize yes
8、启动
# 服务端启动
redis-server configs/redis.conf
# 客户端启动
redis-cli -h 主机号 -p 端口号
auth 密码#查看redis启动状态
ps -ef | grep redis
二、五大数据类型
Redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和信息中间件MQ。它支持多种类型的数据结构,如 字符串,散列,列表,集合,有序集合与范围查询和地理空间索引半径查询。Reids内置了复制,LUA脚本,LRU驱动事件,事务和不同级别的磁盘持久化,并通过Redis哨兵和自动分区提供高可用性。
2.1、Reids-Key
127.0.0.1:6379> ping #测试连接
PONG
127.0.0.1:6379> FlushALL #清空所有数据库
OK
127.0.0.1:6379> keys * #查看所有key
(empty list or set)
127.0.0.1:6379> set name xqh #新建一个key
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) "age"
2) "name"
127.0.0.1:6379> EXISTS name #判断当前的key是否存在
(integer) 1
127.0.0.1:6379> move name 1 #移除当前的key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> get age
"1"
127.0.0.1:6379> EXPIRE age 10 #使这个key十秒后过期,单位是秒
(integer) 1
127.0.0.1:6379> ttl age #查看当前key的剩余时间
(integer) -2
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> type name #查看当前key的类型
string
127.0.0.1:6379> type age
string
2.2、String(字符串)
127.0.0.1:6379> set key1 v1 #设置值
OK
127.0.0.1:6379> keys * #查看所有key
1) "key1"
127.0.0.1:6379> APPEND key1 "hello" # 拼接字符串,如果当前字符串不存在就相当于新建一个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 ",xqh"
(integer) 11
127.0.0.1:6379> STRLEN KEY1
(integer) 0
127.0.0.1:6379> STRLEN key1
(integer) 11
127.0.0.1:6379> get key1
"v1hello,xqh"
127.0.0.1:6379>
------------------------------------------------------------------------------------------------
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增1 ,浏览量变为1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views #获得当前浏览量
"2"
127.0.0.1:6379> decr views #自减1 ,浏览量-1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> INCRBY views 10 #设置步长,指定增量
(integer) 10
127.0.0.1:6379> INCRBY views 10
(integer) 20
127.0.0.1:6379>DECRBY views 5
(integer) 15------------------------------------------------------------------------------------------------
127.0.0.1:6379> get key1
"hello,xqh"
127.0.0.1:6379> GETRANGE key1 0 3 #截取字符串 【0,3】闭区间
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 #截取所有字符串和 get key 是一样的
"hello,xqh"
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> SETRANGE key2 1 xx # 从下标1开始替换为xx
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
------------------------------------------------------------------------------------------------
127.0.0.1:6379> setex key3 30 "hello" #设置过期时间
OK
127.0.0.1:6379> ttl key3
(integer) 24
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis" #不存在这个key的话才设置这个值(在分布锁中会常常使用)
(integer) 1 #设置成功
127.0.0.1:6379> keys *
1) "key2"
2) "mykey"
3) "key1"
127.0.0.1:6379> setnx mykey "mongdb"
(integer) 0 #设置失败,因为已经存在mykey
127.0.0.1:6379>------------------------------------------------------------------------------------------------127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #设置失败,k1存在,k4不存在,设置失败,说明msetnx是一个原子性操作,要么都成功,要么都失败,不能k1创建失败,而k4创建成功
(integer) 0
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379>
------------------------------------------------------------------------------------------------
127.0.0.1:6379> mset user:1:name zhangshan user:1:age 2 #对象作为值
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangshan"
2) "2"
127.0.0.1:6379>------------------------------------------------------------------------------------------------
#getset :先获取再设置。先获取,如果不存在就返回nil并创建
127.0.0.1:6379> getset db redis #获取db,db不存在所以返回nil,并创建一个db:redis
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongdb # 获取db,db存在先获取原来的值,再赋新的值覆盖
"redis"
127.0.0.1:6379> get db
"mongdb"
127.0.0.1:6379>------------------------------------------------------------------------------------------------
- String类型的使用场景:value除了是我们的字符串还可以是我们的数字!
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存存储
2.3、List
- 基本的数据类型,列表。
在redis里面,我们可以把list玩成栈、队列、阻塞队列
redis不区分大小写命令
127.0.0.1:6379> ping #测试连接
PONG
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> LPUSH list one #将一个值或者多个值插入到列表的头部
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
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 righr #将一个值或者多个值 插入到列表的尾部
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"
127.0.0.1:6379> lpop list #移除列表list第一个元素
"three"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "righr"
127.0.0.1:6379> rpop list #移除列表list最后一个元素
"righr"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"127.0.0.1:6379> lindex list 1 #通过下标获取list中某一个值
"one"127.0.0.1:6379> llen list #获取列表长度
(integer) 2#移除指定元素
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"
127.0.0.1:6379> lrem list 1 one #移除列表中1一个one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "zero"
127.0.0.1:6379> lpush list three
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "zero"
127.0.0.1:6379> lrem list 2 three #移除列表中两个three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "zero"#trim:截取,保留部分元素
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> lrange mylist 0 -1
1) "hello"
2) "hello1"
3) "hello2"
4) "hello3"
127.0.0.1:6379> ltrim mylist 1 2 #通过下标截取指定长度 只保留下标1到2
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"#rpoplpush 移除列表的最后一个元素并添加到新的列表
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> rpoplpush mylist myotherlist #移除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"
127.0.0.1:6379> exists list #列表是否存在
(integer) 0
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 -1
1) "item"
127.0.0.1:6379> lset list 1 other #列表不存在下标1,所以报错
(error) ERR index out of range#linsert 将某个值插入到列表中某个值的前面或者后面
127.0.0.1:6379> lpush mylist "hello"
(integer) 1
127.0.0.1:6379> lpush mylist "hello1"
(integer) 2
127.0.0.1:6379> linsert mylist before "hello1" "other" #在hello1的前面插入other
(integer) 3
127.0.0.1:6379> lrange list 0 -1
(empty list or set)
127.0.0.1:6379> lrange mylist 0 -1
1) "other"
2) "hello1"
3) "hello"
127.0.0.1:6379> linsert mylist after "hello1" "other" #在hello1的后面插入other
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "other"
2) "hello1"
3) "other"
4) "hello"
2.4、Set(集合)
- set中的值是不能重复的
127.0.0.1:6379> sadd myset "hello" #set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "xqh"
(integer) 1
127.0.0.1:6379> sadd myset "love"
(integer) 1
127.0.0.1:6379> smembers myset #查看指定set的所有值(无序、不重复的集合)
1) "xqh"
2) "love"
3) "hello"
127.0.0.1:6379> sismember myset hello #判断一个元素是否在set集合中,有---1 ,没有---0
(integer) 1
127.0.0.1:6379> sismember myset xxx
(integer) 0127.0.0.1:6379> scard myset #获取set中值的个数
(integer) 3127.0.0.1:6379> srem myset "hello" #移除集合中某一个元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "xqh"
2) "love"#随机抽出指定个数的元素
127.0.0.1:6379> smembers myset
1) "love"
2) "hello"
3) "hello3"
4) "xqh"
5) "hello2"
6) "hello1"
127.0.0.1:6379> srandmember myset
"hello1"
127.0.0.1:6379> srandmember myset
"hello3"
127.0.0.1:6379> srandmember myset
"hello"
127.0.0.1:6379> srandmember myset 2
1) "hello"
2) "hello2"
127.0.0.1:6379> srandmember myset 2
1) "hello1"
2) "hello2"#随机删除
127.0.0.1:6379> smembers myset
1) "hello3"
2) "xqh"
3) "love"
4) "hello2"
5) "hello1"
6) "hello"
127.0.0.1:6379> spop myset #随机删除集合中的一个元素
"love"
127.0.0.1:6379> smembers myset
1) "hello3"
2) "xqh"
3) "hello2"
4) "hello1"
5) "hello"#将一个指定的值移除并且添加到另一个集合中127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "xqh"
(integer) 1
127.0.0.1:6379> smembers myset2
1) "xqh"
2) "set2"#交集,并集,差集(不同)
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> smembers key1
1) "c"
2) "a"
3) "b"
127.0.0.1:6379> smembers key2
1) "d"
2) "c"
3) "e"
127.0.0.1:6379> sdiff key1 key2 #差集,不同的元素
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2 #交集 相同的部分 ,共同的好友可以这样实现
1) "c"
127.0.0.1:6379> sunion key1 key2 #并集
1) "b"
2) "a"
3) "d"
4) "e"
5) "c"
2.5、Hash(哈希)
- Map集合,key-map集合 ,key-< key-vlaue> ,这时候的值是一个map集合。本质和String类型没有太大区别
127.0.0.1:6379> hset myhash field1 xqh #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 # 取出值
"xqh"
127.0.0.1:6379> hmset myhash field1 hello field2 world #set多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 #取出多个值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #查看所有key-value
1) "field1"
2) "hello"
3) "field2"
4) "world"127.0.0.1:6379> hdel myhash field1 #删除hash指定key字段,对应的value值也消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"#获取长度
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
5) "field3"
6) "xqh"
7) "field"
8) "xqhx"
127.0.0.1:6379> hlen myhash #获取哈希表的字段数量
(integer) 4127.0.0.1:6379> hexists myhash field1 #判断指定字段是否存在
(integer) 1#获取所有的key,获取所有的value
127.0.0.1:6379> hkeys myhash
1) "field2"
2) "field1"
3) "field3"
4) "field"
127.0.0.1:6379> hvals myhash
1) "world"
2) "hello"
3) "xqh"
4) "xqhx"127.0.0.1:6379> hgetall myhash
1) "field1"
2) "4"
127.0.0.1:6379> hincrby myhash field1 -2 # hincrby自增,正是就增加,负数就减少
(integer) 2
127.0.0.1:6379> hsetnx myhash field4 hello #如果field4不存在就添加这个元素,所以这里添加成功
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 xqh #因为field4已经存在了,所以这里添加失败
(integer) 0
- hash应用
- hash变更数据,和string类型类似,但是比string更方便。尤其是用户信息的保存,还有经常变动的信息。
- hash更适合对象的存储,而string更适合字符串的存储
2.6、Zset(有序集合)
- 在set的基础上,增加了一个值 zset k1 score1 v1 score1 2 …优先级
127.0.0.1:6379> zadd myset 1 one #添加一个值 (中间要加一个数字,代表优先级,从1到后面,排序越后)
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three 4 four
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
4) "four" 127.0.0.1:6379> zrange salary 0 -1 #从小到大
1) "xiaohong"
2) "zhangshan"
3) "xiaolan"
127.0.0.1:6379> zrevrange salary 0 -1 #从大到小
1) "xiaolan"
2) "zhangshan"
3) "xiaohong"127.0.0.1:6379> zrange salary 0 -1 withscores #升序排列显示全部的用户并带上成绩值
1) "xiaohong"
2) "2500"
3) "zhangshan"
4) "5000"
5) "xiaolan"
6) "20000"
127.0.0.1:6379> zrevrange salary 0 -1 withscores #降序排列显示全部的用户并带上成绩值
1) "xiaolan"
2) "20000"
3) "zhangshan"
4) "5000"
5) "xiaohong"
6) "2500"127.0.0.1:6379> zrangebyscore salary -inf 10000 withscores #小于10000工资的用户升序排列
1) "xiaohong"
2) "2500"
3) "zhangshan"
4) "5000"127.0.0.1:6379> zrevrangebyscore salary +inf 3000 withscores #大于3000工资的降序排列
1) "xiaolan"
2) "20000"
3) "zhangshan"
4) "5000"#删除指定元素
127.0.0.1:6379> zrange salary 0 -1
1) "xiaohong"
2) "zhangshan"
3) "xiaolan"
127.0.0.1:6379> zrem salary xiaohong #删除xiaohong
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zhangshan"
2) "xiaolan"#获取有序集合中的个数
127.0.0.1:6379> zcard salary
(integer) 2#获取指定区间的成员数量
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "zhangshan"
2) "5000"
3) "xiaochen"
4) "10000"
5) "xiaolan"
6) "20000"
127.0.0.1:6379> zcount salary 5000 20000
(integer) 3
- 这些只是一些常用的api,其他的遇到再去查官方文档!
三、三种特殊数据类型
3.1、geospatial (地理位置)
- 朋友定位,附近的人,打车距离计算
Redis的Geo 这个功能可以推算地理位置的信息,两地之间的距离等
可以查询一些测试数据
- 6个命令
################################################################################################
#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 chongqi 114.05 22.52 shenzhen
(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> geopos china:city beijing #获取指定城市的经度和纬度
1) 1) "116.39999896287918091"2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing shanghai
1) 1) "116.39999896287918091"2) "39.90000009167092543"
2) 1) "121.47000163793563843"2) "31.22999903975783553"
################################################################################################
#两人之间的距离
#单位
#m 表示单位为米。
#km 表示单位为千米。
#mi 表示单位为英里
#ft 表示单位为英尺。
#如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
127.0.0.1:6379> geodist china:city beijing shanghai km #查看上海到北京的直线距离,单位km
"1067.3788"
#################################################################################################
#查找附近的人(需要获得所有附近的人的地址,定位!)通过半径来查询
#获取指定数量的人
#前提:所有数据应该都录入:china:city
127.0.0.1:6379> georadius china:city 110 30 1000 km #以110,30这个经纬度为中心,寻找方圆1000km内的城市
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 500 km #以110,30这个经纬度为中心,寻找方圆500km内的城市
1) "chongqi"
2) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist #以110,30这个经纬度为中心,寻找方圆500km内的城市,顺带输出离中心距离(110,30)的位置
1) 1) "chongqi"2) "341.9374"
2) 1) "xian"2) "483.8340"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord #以110,30这个经纬度为中心,寻找方圆500km内的城市,顺带输出他人的定位信息(主要是经纬度)
1) 1) "chongqi"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 withcoord count 1 #以110,30这个经纬度为中心,寻找方圆500km内的城市,筛选出指定数量的数据
1) 1) "chongqi"2) 1) "106.49999767541885376"2) "29.52999957900659211"
################################################################################################
#找出位于指定元素周围的其他元素
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km #查找北京附近1000km的城市
1) "beijing"
2) "xian"
#################################################################################################
#geohash该命令将返回11个字符的geohash字符串
#将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近
127.0.0.1:6379> geohash china:city beijing chongqi
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
#################################################################################################
#GEO底层的实现原理其实就是zset!我们可以使用zset命令来操作geo
127.0.0.1:6379> zrange china:city 0 -1 #查看地图中全部的元素,之前通过geoadd添加进去的
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city beijing #移除指定元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
4.2、Hyperloglog
- 基数:不重复的元素
A{1,3,5,7,8,7}
B{1,3,5,7,8}
基数(不重复的元素) =5
- 简介:redis 2.8.9版本就更新了Hyperloglog数据结构
基数统计的算法!网页的UV(一个人访问一个网站多次,但是还是算作一个人)
传统的方式,set保存用户的id ,然后就可以统计set中的元素数量作为标准判断
这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id
优点 :占用的内存是固定,2^64 不同的元素的技术,只需要费 12kb内存,所以如果要从内存角度来比较的话 Hyperloglog 首选
- 测试使用
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 #统计第一组元素数量
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j k l m n b v
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 8
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 #将第一组和第二组合并到第三组中,不能有重复的元素,所以一共是15个
OK
127.0.0.1:6379> pfcount mykey3
(integer) 15
- 允许容错的话,可以使用这个Hyperloglog。如果不允许误差就不能用这个。
4.3、Bitmaps
- 位存储:
统计用户信息,活跃、不活跃!登录、未登录!打开等,两个状态的都可以使用bitmaps
Bitmaps 位图,数据结构 !都是操作二进制来进行记录,就只有0和1两个状态
- 测试
#记录周一到周日的打卡情况。下标0-6代表周一至周日,0代表未打卡,1代表打卡
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 1
(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 1
(integer) 0#查看某一天是否打卡
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0#统计打卡天数
127.0.0.1:6379> bitcount sign #统计这周打卡记录
(integer) 5
四、事务
1、Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!一次性、顺序性、排他性!Redis中没有隔离级别的概念。
2、redis的事务:
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
3、正常执行事务:
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 k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务,一步一步
1) OK
2) OK
3) "v2"
4) OK127.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 #事务队列中命令都不会被执行
(nil)
- 编译型异常(代码有问题,命令有错),事务中所有的命令都不会被执行
- 运行时异常(1/0) ,如果事务队列中存在语法性错误,那么执行命令的时候,其他命令可以正常执行,错误命令会抛出异常
#运行时异常
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 "v1"
QUEUED
127.0.0.1:6379> incr k1 #代码没问题,只是用错了,这里字符串不能递增
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec #执行不报错,错误的命令行不能执行,而其他命令行继续执行
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) OK
5) "v3"
127.0.0.1:6379> get k2 #其他的命令行成功执行
"v2"
4、监控(watch)
- 悲观锁:当一个线程获得该资源锁时,其他任何线程都不能获得。只有等该线程执行完释放锁,其他线程才有机会获得锁
- 乐观锁:当一个线程获得该资源锁时,其他线程也可以获取,只是在更新数据的时候去判断一下,在此期间是否有人修改过和这个数据,CAS/版本号机制
五、Jedis
我们要使用Java来操作Redis
- 什么是Jedis
- 是Redis官方推荐的java连接开发工具!使用java操作Redis中间件,如果你要使用java操作redis,那么一定要对jedis十分的熟悉。
- 测试
- 导入依赖
<dependencies><!-- 导入jedis的包 --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.2.3</version></dependency>
<!-- fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.2</version></dependency></dependencies>
- 编码测试
package com.qian;import redis.clients.jedis.Jedis;public class TestPing {public static void main(String[] args) {//1.new Jedis 对象即可Jedis jedis =new Jedis("127.0.0.1",6379);//jedis所有的命令就是我们之前学习的所有命令System.out.println(jedis.ping());//测试连接}
}
//输出:pong
- 常用API(所有的api和前面的命令一模一样)
package com.qian;
import redis.clients.jedis.Jedis;
import java.util.Set;public class TestKey {public static void main(String[] args) {Jedis jedis = new Jedis("127.0.0.1",6379);System.out.println("清空数据:"+jedis.flushDB());System.out.println("判断某个键是否存在:"+jedis.exists("username"));System.out.println("新增键值对:"+jedis.set("username","xqh"));System.out.println("新增键值对:"+jedis.set("password","123456"));System.out.println("系统中所有的键如下:");Set<String> keys = jedis.keys("*");System.out.println(keys);System.out.println("删除password"+jedis.del("password"));System.out.println("判断键password是否存在:"+jedis.exists("password"));System.out.println("判断username所存储的值的类型:"+jedis.type("username"));System.out.println("新增键值对:"+jedis.set("project","chinese"));System.out.println("随机返回key空间的一个:"+jedis.randomKey());System.out.println("重命名key:"+jedis.rename("username","name"));System.out.println("取出改后的name的值:"+jedis.get("name"));System.out.println("按索引查询:"+jedis.select(0));System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());}
}
...//和之前的命令一模一样,需要使用可以直接翻前面的笔记喔
- 通过Jedis再次理解事务
package com.qian;import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;public class TestTX {public static void main(String[] args) {Jedis jedis = new Jedis("127.0.0.1", 6379);jedis.flushDB();JSONObject jsonObject = new JSONObject();jsonObject.put("hello","world");jsonObject.put("name","xqh");//开启事务Transaction multi = jedis.multi();String result = jsonObject.toJSONString();try {multi.set("user1", result);multi.set("user2", result);int i = 1/0; //代码抛出异常事务,执行失败multi.exec(); //成功就执行事务}catch (Exception e){multi.discard();//如果失败就放弃事务e.printStackTrace();}finally {System.out.println(jedis.get("user1"));System.out.println(jedis.get("user2"));jedis.close(); //关闭连接}}
}
//代码错误,事务执行失败,get不到,输出null
六、SpringBoot集成Redis
SpringBoot操作数据
- 整合测试
- 导入依赖
<!-- 操作redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
- 配置连接
redis:# 地址host: 192.168.180.100# 端口,默认为6379port: 6379# 密码password: 123456# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 5# 连接池的最大数据库连接数max-active: 5# #连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1msdatabase: 6
- 测试
package com.example;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 Redis02SpringbootApplicationTests {@Autowiredprivate RedisTemplate redisTemplate;@Testvoid contextLoads() {redisTemplate.opsForValue().set("mukey","xqh");System.out.println(redisTemplate.opsForValue().get("mukey"));}}
redisTemplate.opsForValue().set(“mukey”,“xqh”);
- 传递对象要序列化
package com.example.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;import java.io.Serializable;@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
//在企业中,我们的所有pojo都会序列化
public class User implements Serializable {private String name;private int age;
}
//test
@Testpublic void test() throws JsonProcessingException {//真实的开发一般都使用json来传递对象User user = new User("java", 3);//String jsonUser = new ObjectMapper().writeValueAsString(user);redisTemplate.opsForValue().set("user",user);Object user1 = redisTemplate.opsForValue().get("user"); //直接传递对象会报错,所有的对象传递要先序列化!System.out.println(user1);}
- 在企业开发中,我们大部分情况下都不会使用这种原生的方式去编写代码,而是封装成工具类RedisUtils
package com.example.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;@Component
public class RedisUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}// =============================common============================/*** 指定缓存失效时间** @param key 键* @param time 时间(秒)* @return*/public boolean expire(String key, long time) {try {if (time > 0) {redisTemplate.expire(key, time, TimeUnit.SECONDS);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 判断key是否过期** @param key* @return*/public boolean isExpire(String key) {return getExpire(key) > 1 ? false : true;}/*** 根据key 获取过期时间** @param key 键 不能为null* @return 时间(秒) 返回0代表为永久有效*/public long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 判断key是否存在** @param key 键* @return true 存在 false不存在*/public boolean hasKey(String key) {try {return redisTemplate.hasKey(key);} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除缓存** @param key 可以传一个值 或多个*/@SuppressWarnings("unchecked")public void del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {redisTemplate.delete(key[0]);} else {redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));}}}// ============================String=============================/*** 普通缓存获取** @param key 键* @return 值*/public Object get(String key) {return key == null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入** @param key 键* @param value 值* @return true成功 false失败*/public boolean set(String key, Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置时间** @param key 键* @param value 值* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean set(String key, Object value, long time) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);} else {set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置时间** @param key 键* @param value 值* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean set(String key, Object value, long time, TimeUnit timeUnit) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, timeUnit);} else {set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 递增** @param key 键* @param delta 要增加几* @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 要减少几* @return*/public long decr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递减因子必须大于0");}return redisTemplate.opsForValue().increment(key, -delta);}// ================================Map=================================/*** HashGet** @param key 键 不能为null* @param item 项 不能为null* @return 值*/public Object hget(String key, String item) {return redisTemplate.opsForHash().get(key, item);}/*** 获取hashKey对应的所有键值** @param key 键* @return 对应的多个键值*/public Map<Object, Object> hmget(String key) {return redisTemplate.opsForHash().entries(key);}/*** HashSet** @param key 键* @param map 对应多个键值* @return true 成功 false 失败*/public boolean hmset(String key, Map<String, Object> map) {try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** HashSet 并设置时间** @param key 键* @param map 对应多个键值* @param time 时间(秒)* @return true成功 false失败*/public boolean hmset(String key, Map<String, Object> map, long time) {try {redisTemplate.opsForHash().putAll(key, map);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key 键* @param item 项* @param value 值* @return true 成功 false失败*/public boolean hset(String key, String item, Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key 键* @param item 项* @param value 值* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间* @return true 成功 false失败*/public boolean hset(String key, String item, Object value, long time) {try {redisTemplate.opsForHash().put(key, item, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除hash表中的值** @param key 键 不能为null* @param item 项 可以使多个 不能为null*/public void hdel(String key, Object... item) {redisTemplate.opsForHash().delete(key, item);}/*** 判断hash表中是否有该项的值** @param key 键 不能为null* @param item 项 不能为null* @return true 存在 false不存在*/public boolean hHasKey(String key, String item) {return redisTemplate.opsForHash().hasKey(key, item);}/*** hash递增 如果不存在,就会创建一个 并把新增后的值返回** @param key 键* @param item 项* @param by 要增加几(大于0)* @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中查询,是否存在** @param key 键* @param value 值* @return true 存在 false不存在*/public boolean sHasKey(String key, Object value) {try {return redisTemplate.opsForSet().isMember(key, value);} catch (Exception e) {e.printStackTrace();return false;}}/*** 将数据放入set缓存** @param key 键* @param values 值 可以是多个* @return 成功个数*/public long sSet(String key, Object... values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 将set数据放入缓存** @param key 键* @param time 时间(秒)* @param values 值 可以是多个* @return 成功个数*/public long sSetAndTime(String key, long time, Object... values) {try {Long count = redisTemplate.opsForSet().add(key, values);if (time > 0) {expire(key, time);}return count;} catch (Exception e) {e.printStackTrace();return 0;}}/*** 获取set缓存的长度** @param key 键* @return*/public long sGetSetSize(String key) {try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 移除值为value的** @param key 键* @param values 值 可以是多个* @return 移除的个数*/public long setRemove(String key, Object... values) {try {Long count = redisTemplate.opsForSet().remove(key, values);return count;} catch (Exception e) {e.printStackTrace();return 0;}}// ===============================list=================================/*** 获取list缓存的内容** @param key 键* @param start 开始* @param end 结束 0 到 -1代表所有值* @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 index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推* @return*/public Object lGetIndex(String key, long index) {try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {e.printStackTrace();return null;}}/*** 将list放入缓存** @param key 键* @param value 值* @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 时间(秒)* @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, List<Object> value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** @param key 键* @param value 值* @param time 时间(秒)* @return*/public boolean lSet(String key, List<Object> value, long time) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据索引修改list中的某条数据** @param key 键* @param index 索引* @param value 值* @return*/public boolean lUpdateIndex(String key, long index, Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 移除N个值为value** @param key 键* @param count 移除多少个* @param value 值* @return 移除的个数*/public long lRemove(String key, long count, Object value) {try {Long remove = redisTemplate.opsForList().remove(key, count, value);return remove;} catch (Exception e) {e.printStackTrace();return 0;}}}
测试,有了工具类后使用更加方便
@Autowiredprivate RedisUtils redisUtils;@Testpublic void test1(){redisUtils.set("name","xqh");System.out.println(redisUtils.get("name"));}