Redis
NoSQL概述
NoSQL = Not Only SQL
关系型数据库:相当于表格,行列对应
NoSQL泛指非关系型数据库
很多的数据类型用户的个人信息,社交网络,地理位置…这些数据类型的存储不需要一个固定的格式。不需要多余的操作就可以横向扩展。想Map<String,Object> 使用键值对来控制
NoSQL特点
-
方便扩展(数据之间没有关系,很好扩展)
-
大数据量高性能(NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)
-
数据类型是多样型的(不需要事先设计数据库,随取随用)
-
传统RDBMS和NoSQL
传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 数据操作语言,数据定义语言
- 严格的一致性
- 基础的事务
- …
Nosq
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE(异地多活)
- 高性能,高可用,高可扩
3V+3高
大数据时代的3V:主要是描述问题的
- 海量Volume
- 多样Variety
- 实时Velocity
大数据时代的3高:主要是对程序的要求
- 高并发
- 高可扩(随时水平拆分)
- 高性能(保证用户体验和性能)
NoSQL+RDBMS一起使用无敌!
NoSQL的四大分类
KV键值对:
- 新浪:Redis
- 美团:Redis+Tair
- 阿里、百度:Redis+memcache
文档型数据库(bson格式和json一样)
-
MongoDB
MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
MongoDB是一个介于关系型数据库和非关系型数据库中间的产品,MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的
-
ConthDB
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
存的不是图形,放的是关系
- Neo4j,InfoGrid;
四者对比
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eFLYAQGQ-1649916833089)(F:\Study_Note\自学\Redis\1.png)]
Redis入门
概述
Redis是什么
Redis(Remote Dictionary Server)远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nnYEy0NO-1649916833090)(F:\Study_Note\自学\Redis\image-20220407111856853.png)]
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
免费和开源 被人们称之为结构化数据库
Redis能干什么
- 内存存储、持久化,内存是断电即失,持久化很重要(rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
- …
特性
- 多样化的数据类型
- 持久化
- 集群
- 事务
- …
准备
- 官网:https://redis.io/
- 中文网:http://www.redis.cn/
性能测试
reids-benchmark是一个压力测试工具
官方自带的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N4qZOtPG-1649916833091)(F:\Study_Note\自学\Redis\image-20220407153558401.png)]
基础知识
redis默认有16个数据库
默认使用第0个
可以使用select进行切换
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]>
查看数据库大小
127.0.0.1:6379> dbsize
(integer) 4
127.0.0.1:6379>
查看数据库所有的key
127.0.0.1:6379[1]> keys *
(empty array)
127.0.0.1:6379[1]>
清空当前数据库
127.0.0.1:6379[1]> flushdb
OK
127.0.0.1:6379[1]>
清空所有数据库
127.0.0.1:6379[1]> flushall
OK
127.0.0.1:6379[1]>
Redis是单线程的
redis是基于内存操作,CPU不是redis的性能瓶颈,redis对的瓶颈是根据机器对的内存和网络带宽
Redis为什么单线程还那么快
误区:
- 高性能的服务器不一定是多线程的
- 多线程不一定比单线程效率高
速度:CPU>内存>硬盘
核心:redis是将所有的数据全部放在内存中,所以使用单线程操作效率最高,多线程(CPU上下文会切换:耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的
五大数据类型
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> keys * #查看所有key
(empty array)
127.0.0.1:6379> set name wm #set key
OK
127.0.0.1:6379> set age 11
OK
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> expire age 10 #设置key的存在时间
(integer) 1
127.0.0.1:6379> ttl age #查看key的剩余时间
(integer) 7
127.0.0.1:6379> ttl age
(integer) 4
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
String
localhost:6379> set name w #添加key
OK
localhost:6379> get name #获取key
"w"
localhost:6379> exists name #判断是否存在
(integer) 1
localhost:6379> append name m #追加字符串
(integer) 2
localhost:6379> type name #判断类型
string
localhost:6379> strlen name #获得value长度
(integer) 2
localhost:6379> append age 11 #如果可以不存在 则新建key
(integer) 2
localhost:6379> keys *
1) "age"
2) "name"
localhost:6379>
localhost:6379> incr age #增加
(integer) 12
localhost:6379> decr age #减少
(integer) 11
localhost:6379>
localhost:6379> incrby age 10 #指定步长增加
(integer) 21
localhost:6379> DECRBY age 10 #指定步长减少
(integer) 11
localhost:6379> set name "hello"
OK
localhost:6379> GETRANGE name 0 3 #获取指定的字符串
"hell"
localhost:6379> GETRANGE name 0 -1 #获取全部
"hello"
localhost:6379> SETRANGE name 1 22 #替换指定位置开始的字符串
(integer) 5
localhost:6379> get name
"h22lo"
######
# setex (set with expire)设置过期时间
# setnx (set if not exist)不存在再设置 (在分布式锁中常常使用)
localhost:6379> SETEX key1 30 wm #设置key1 30秒后过期
OK
localhost:6379> keys *
1) "key1"
2) "name"
localhost:6379> SETNX key2 wm #如果key2不存在 则创建 如果存在 则创建失败
(integer) 1
localhost:6379> keys *
1) "name"
2) "key2"
localhost:6379> MSET k1 n1 k2 n2 k3 n3 #同时设置多个
OK
localhost:6379> keys *
1) "k1"
2) "k3"
3) "k2"
localhost:6379> MGET k1 k2 k3 # 同时获取多个
1) "n1"
2) "n2"
3) "n3"
localhost:6379> MSETNX k1 v2 k4 v4 #msetnx是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
localhost:6379> get k4
(nil)
#对象
localhost:6379> MSET use:1:name zhangsan use:1:age 11 #设置一个use:1对象,值为json字符来保存一个对象
OK
localhost:6379> MGET use:1:name use:1:age
1) "zhangsan"
2) "11"
######
localhost:6379> GETSET db redis #如果不存在值 则返回nil
(nil)
localhost:6379> get db
"redis"
localhost:6379> GETSET db mongodb # 如果存在值 获取原来的值 并替换掉
"redis"
localhost:6379> get db
"mongodb"
String类似的使用场景:value除了可以是字符串还可以是数字
- 计数器
- 统计多单位的数量
- 对象缓存存储
List(列表)
基本的数据类型,列表
在redis里面,可以把list使用作栈、队列、阻塞队列
所有的list命令都是用l开头的
127.0.0.1:6379> LPUSH list tom #从左侧插入
(integer) 1
127.0.0.1:6379> LPUSH list cat
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "cat"
2) "tom"
127.0.0.1:6379> LRANGE list 0 1
1) "cat"
2) "tom"
127.0.0.1:6379> RPUSH list #从右侧插入luck
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "cat"
2) "tom"
3) "luck"
127.0.0.1:6379> LPOP list #删除头
"cat"
127.0.0.1:6379> rpop list #删除尾
"luck"
127.0.0.1:6379> LINDEX list 0 #获取指定下标的值
"tom"
127.0.0.1:6379> LLEN list #获取列表长度
(integer) 1
127.0.0.1:6379> LREM list 1 #移除指定个数的值
tom
(integer) 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> LPUSH list four
(integer) 4
127.0.0.1:6379> LTRIM list 1 2 #根据下标截取指定的长度 list已经被修改了
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
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> RPOPLPUSH list mylist #移除最后一个元素到新的列表
"one"
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "one"
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> lset list 0 #替换指定下标的值 如果不存在报错
tom
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "tom"
2) "two"
3) "one"
127.0.0.1:6379> LINSERT list before "two" one #向指定元素的前面或后面插入指定值
(integer) 4
127.0.0.1:6379> LRANGE lsit 0 -1
(empty array)
127.0.0.1:6379> LRANGE list 0 -1
1) "tom"
2) "one"
3) "two"
4) "one"
- 实际上是一个链表,before Node after,left,right都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高,中间元素,相对来说效率会低一点
消息排队、消息队列、栈
Set(集合)
set中的值是不能重复的
127.0.0.1:6379> SADD list "hello" #添加
(integer) 1
127.0.0.1:6379> SADD list "world"
(integer) 1
127.0.0.1:6379> SMEMBERS list #查看
1) "world"
2) "hello"
127.0.0.1:6379> SISMEMBER list hello #查看是否存在
(integer) 1
127.0.0.1:6379> SISMEMBER list w
(integer) 0
127.0.0.1:6379> SCARD list #获取set中的元素个数
(integer) 2
127.0.0.1:6379> SREM list hello #移除元素
(integer) 1
127.0.0.1:6379> SMEMBERS list
1) "world"
################
127.0.0.1:6379> SADD list one
(integer) 1
127.0.0.1:6379> SADD list two
(integer) 1
127.0.0.1:6379> SADD list three
(integer) 1
127.0.0.1:6379> SADD list four
(integer) 1
127.0.0.1:6379> SRANDMEMBER list #随机获取元素
"one"
127.0.0.1:6379> SRANDMEMBER list
"three"
127.0.0.1:6379> SRANDMEMBER list
"three"
127.0.0.1:6379> SRANDMEMBER list
"one"
127.0.0.1:6379> SRANDMEMBER list
"two"
127.0.0.1:6379> SRANDMEMBER list
"two"
127.0.0.1:6379> SRANDMEMBER list
"four"
127.0.0.1:6379> SRANDMEMBER list 2 #抽取指定个数的随机元素
1) "three"
2) "one"
127.0.0.1:6379> SPOP list #随机删除元素
"two"
127.0.0.1:6379> SPOP list
"one"
127.0.0.1:6379> SMEMBERS list
1) "four"
2) "three"
127.0.0.1:6379> SADD list01 one
(integer) 1
127.0.0.1:6379> SMOVE list01 list one #将指定元素移动到指定集合
(integer) 1
127.0.0.1:6379> SMEMBERS list
1) "four"
2) "three"
3) "one"
#################
127.0.0.1:6379> SADD one a
(integer) 1
127.0.0.1:6379> SADD one b
(integer) 1
127.0.0.1:6379> SADD two a
(integer) 1
127.0.0.1:6379> SADD two c
(integer) 1
127.0.0.1:6379> SADD two d
(integer) 1
127.0.0.1:6379> SMEMBERS one
1) "b"
2) "a"
127.0.0.1:6379> SMEMBERS two
1) "a"
2) "c"
3) "d"
127.0.0.1:6379> SINTER one two #交集
1) "a"
127.0.0.1:6379> SDIFF one two #差集
1) "b"
127.0.0.1:6379> SUNION one two #并集
1) "b"
2) "a"
3) "c"
4) "d"
127.0.0.1:6379> SDIFF two one #差集
1) "c"
2) "d"
Hash(哈希)
127.0.0.1:6379> HSET list field1 one #设置具体的key-value
(integer) 1
127.0.0.1:6379> HMSET list field1 one field2 two #设置多个
OK
127.0.0.1:6379> HGET list field1 #获取
"one"
127.0.0.1:6379> HGETALL list #获取所有
1) "field1"
2) "one"
3) "field2"
4) "two"
127.0.0.1:6379> HDEL list field1 #删除
(integer) 1
127.0.0.1:6379> HGETALL list
1) "field2"
2) "two"
127.0.0.1:6379> HLEN list #查看长度
(integer) 1
127.0.0.1:6379> HEXISTS list field2 #判断是否存在
(integer) 1
127.0.0.1:6379> HVALS list #获取所有的value
1) "two"
127.0.0.1:6379> HKEYS list #获取所有的key
1) "field2"
############
127.0.0.1:6379> HSET list field1 1
(integer) 1
127.0.0.1:6379> HINCRBY list field1 1 #指定增量
(integer) 2
127.0.0.1:6379> HSETNX list field1 2 #如果存在Field 则添加失败
(integer) 0
Zset(有序集合)
127.0.0.1:6379> ZADD list 1 one #添加 并按照k排序
(integer) 1
127.0.0.1:6379> ZADD list 2 two
(integer) 1
127.0.0.1:6379> ZADD list 4 four
(integer) 1
127.0.0.1:6379> ZADD list 3 three
(integer) 1
127.0.0.1:6379> ZRANGE list 0 -1 #查看
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> ZRANGEBYSCORE list -inf +inf #从小到大排序
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> ZREVRANGE list 0 -1 #从大到小
1) "four"
2) "three"
3) "two"
127.0.0.1:6379> ZRANGEBYSCORE list -inf +inf withscores #附带值
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
7) "four"
8) "4"
127.0.0.1:6379> ZRANGEBYSCORE list -inf 3 withscores # 指定范围的值
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
127.0.0.1:6379> ZREM list one #删除指定值
(integer) 1
127.0.0.1:6379> ZCARD list #获取个数
(integer) 3
127.0.0.1:6379> ZCOUNT list 1 3 #获取指定区间的集合数量
(integer) 2
三种特殊数据类型
geospatial 地理位置
这个功能可以推算地理位置信息,两地之间的距离,方圆几里的人
geoadd 添加
# 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中
# 有效的经度从-180度到180度。
# 有效的纬度从-85.05112878度到85.05112878度。
# 当坐标位置超出上述指定范围时,该命令将会返回一个错误。
127.0.0.1:6379> GEOADD china:city 116.4 39.9 beijing # 添加
(integer) 1
127.0.0.1:6379> GEOADD china:city 120.16 30.24 hanghzou 114.05 22.52 shenhzen
(integer) 2
geopos 获取
127.0.0.1:6379> GEOPOS china:city beijing hanghzou shenhzen #获取
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "120.1600000262260437"
2) "30.2400003229490224"
3) 1) "114.04999762773513794"
2) "22.5200000879503861"
geodist
两人之间的距离
单位:m、km、mi(英里)、ft(英尺)
127.0.0.1:6379> GEODIST china:city beijing hanghzou km
"1127.3378"
georadius 以给定的经纬度为中心,找出某一半径内的元素
附近的人(通过半径来查询)
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord # 到中间的距离 他人的定位信息
1) 1) "shenhzen"
2) "924.6408"
3) 1) "114.04999762773513794"
2) "22.5200000879503861"
2) 1) "hanghzou"
2) "977.5143"
3) 1) "120.1600000262260437"
2) "30.2400003229490224"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord count 1 #指定数量
1) 1) "shenhzen"
2) "924.6408"
3) 1) "114.04999762773513794"
2) "22.5200000879503861"
georadiusbymenber
# 找出指定元素周围指定距离的元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
geohash
返回11个字符的geohash字符串
# 将二维的经纬度转换为一维的字符串,如果字符串越接近,距离越近
127.0.0.1:6379> GEOHASH china:city beijing shenhzen
1) "wx4fbxxfke0"
2) "ws10578st80"
geo的底层实现原理是Zset 可以使用Zset命令操作geo
Hyperloglog
基数
不重复的元素的个数
简介
Redis Hyperloglog基数统计的算法 是一种数据结构
例:一个人多次访问一个网页 算作一个人
优点:占用的内存是固定的,2^64不同的元素的基数,只需要费12kb内存,从内存角度比较首选
127.0.0.1:6379> PFADD key a d g e t h g
(integer) 1
127.0.0.1:6379> PFCOUNT key
(integer) 5
127.0.0.1:6379> PFADD key1 d o n j w
(integer) 1
127.0.0.1:6379> PFMERGE key2 key key1 #合并
OK
127.0.0.1:6379> PFCOUNT key2
(integer) 9
允许容错 可以使用
Bitmaps
位存储
只有两个状态的可以使用
Bitmaps位图 是一种数据结构 都是操作二进制位进行记录,就只有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 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 1
(integer)
# 查看是否打卡
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) 4
事务
Redis事务的本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行
一次性、顺序性、排他性
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行
Redis单条命令是保证原子性的,但是事务不保证原子性
redis的事务:
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
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 k1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
3) "v1"
4) "v2"
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 v3
QUEUED
127.0.0.1:6379(TX)> DISCARD #取消事务
OK
127.0.0.1:6379> get k4 3队列中的事务都不会执行
(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)> getset k1
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常,如果事务队列中存在语法性错误,其他命令是可以正常执行的,错误命令会抛异常
127.0.0.1:6379> set k1 "k1"
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 k2
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) "v2"
5) "v3"
监控 Watch
悲观锁:
- 很悲观,认为什么时候都会出问题,无论做什么都加锁
乐观锁:
- 很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候会去判断一下,再次期间是否有人修改过这个数据
- 获取version
- 更新的时候比较version
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 #监视
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可以当做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
OK
127.0.0.1:6379> MULTI #开启事务之后 另一个线程修改了money的值
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
(nil)
127.0.0.1:6379> UNWATCH #解锁
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) 180
2) (integer) 20
修改失败获取最新的值
Jedis
jedis是Redis官方推荐的java连接开发工具,使用java操作Redis中间件
测试
-
导入对应的依赖
<dependencies> <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> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
-
编码测试
- 连接数据库
- 操作命令
- 结束连接
public class TestPing { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1",6379); System.out.println(jedis.ping()); } }
常用API
String
Set
List
Hash
Zset
和redis命令一样
事务
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","wm");
//开启事务
Transaction multi = jedis.multi();
String s = jsonObject.toJSONString();
try {
multi.set("user1",s);
multi.set("user2",s);
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
SpringBoot整合
SpringBoot操作数据:spring-boot–>jpa jdbc MongoDB redis
SpringData也是和SpringBoot齐名的项目
在SpringBoot2.x之后,原来使用的jedis被替换为了letture
jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池 BIO模式
letture:采用netty,实例可以使用在多个线程中共享,不存在线程不安全的情况,可以减少线程数量 NIO模式
源码分析:
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")//可以自定义一个redisTemplate来替换这个默认的
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的RedisTemplate 没有过多地配置,redis对象都是需要序列化
//两个泛型都是object 的类型,我们后续使用需要强制类型转换
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//由于String类型时redis最常使用的类型,单独出来一个bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
测试
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
配置连接
# SpringBoot所有的配置类,都有一个自动配置类 # 自动配置类都会绑定一个properties 配置文件 # 配置redis spring.redis.host=127.0.0.1 spring.redis.port=6379
-
测试
@SpringBootTest class RedisStudySpringbootApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { /** //redisTemplate 操作不同的数据类型,api和redis一样 //opsForValue() 操作字符串 类似String //opsForList() 操作List 类似List //opsForSet() //opsForHash() //opsForZSet() //opsForGeo() //... redisTemplate.opsForValue().set("list","wm"); **/ /** //除了基本的操作,常用的方法都可以直接通过RedisTemplate操作 RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); connection.flushAll(); **/ } }
序列化配置
默认的序列化是jdk序列化
所有的对象需要序列化
自定义RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
//为了方便 直接使用String Object
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value采用json的方式序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value采用json的方式序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
RedisUtil工具类
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 {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((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 delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @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)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @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 值
*/
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 时间(秒)
*/
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 值
*/
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;
}
}
}
测试
@SpringBootTest
class RedisStudySpringbootApplicationTests {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Autowired
private RedisUtil redisUtil;
@Test
public void test02(){
redisUtil.set("user","wm");
System.out.println(redisUtil.get("user"));
}
@Test
void contextLoads() {
/**
//redisTemplate 操作不同的数据类型,api和redis一样
//opsForValue() 操作字符串 类似String
//opsForList() 操作List 类似List
//opsForSet()
//opsForHash()
//opsForZSet()
//opsForGeo()
//...
redisTemplate.opsForValue().set("list","wm");
**/
/** //除了基本的操作,常用的方法都可以直接通过RedisTemplate操作
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushAll();
**/
}
@Test
public void test01() throws JsonProcessingException {
User user = new User("zhangsna", 123);
//String objectMapper = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
}