1、NoSQL
1.1 什么是NoSQL
NoSQL(Not Only SQL):泛指非关系型数据库,区别与关系型数据库,且保持了关系型数据库的ACID原则;NoSQL数据库的产生就是为了解决大规模数据集合、多重数据种类带来的挑战,特别是大数据应用难题。
1.2 NoSQL特点
方便扩展(数据之间没有关系、很好扩展)
大数据量高性能(Redis一秒写8万、读11万)
数据类型是多样型的(不需要事先设计数据库)
传统的RDBMS和NoSQL ``` 传统的 RDBMS(关系型数据库)
- 结构化组织
- SQL
- 数据和关系都存在单独的表中 row col
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展 ```
1.3 3V、3高
大数据时代的3V :主要是描述问题的
- 海量Velume
- 多样Variety
- 实时Velocity
大数据时代的3高 : 主要是对程序的要求
- 高并发
- 高可扩
- 高性能
1.4 NoSQl的分类
KV键值对:
- 新浪:Redis
- 美团:Redis+Tair
- 阿里、百度:Redis+memeache
文档型数据库(bson、json)
MongoDB
- MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用于存储大量的文档
- MongoDB是一个介于关系型数据库和非关系型数据库之间的产品
- CouchDb
列存储型数据库:
- HBase
- 分列式文件系统
图关系型数据库:
Neo4j、infoGrid
不是存图片,放的是图关系 如社交关系
总结:
2、Redis安装与测试
2.1 Windows下安装
Redis官网:https://redis.io
github下载链接:https://github.com/tporadowski/redis/releases
菜鸟安装教程:https://www.runoob.com/redis/redis-install.html
下载zip压缩包并解压到指定目录
解压完成后,分别打开服务端和客户端。
在客户端输入ping命令,可以检测连接是否成功,成功会显示PONG
Redis按KV键值对存储数据,使用set get
2.2 Linux安装(重要)
1.去官网下载Linux的安装包
2.将压缩包移动到opt文件夹下并解压
yum install gcc-c++ #安装gcc编译
make #解压之后进入redis目录使用make指令进行编译
make install #make后进入src目录进行安装
3.在/usr/local目录下创建一个redis目录,并创建一个myconf子目录
mkdir -p/usr/local/redis
4.将redis的默认配置文件复制到/usr/local/redis/myconf/目录下:redis.conf,以后就使用此目录下的redis.conf配置文件,这样就可以随时还原
mv /opt/redis-5.0.13/redis.conf /usr/local/redis/myconf/
cd /opt/redis-5.0.13/src
mv mkreleasdhdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server /usr/local/redis/
4.Redis后台默认不启动,需要进入redis.conf配置文件修改daemonize为yes,默认后台启动
5.在/usr/local/redis/bin目录路径下启动redis服务端:./redis-server myconf/redis.conf
6.在/usr/local/redis/bin目录路径下使用redis-cli -p 6379 命令启动Redis客户端 使用ping检测是够连通
7.在/bin/src目录下进行性能测试:./redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000
注:测试时必须退出shell指令,如下图
7.退出:shutdowm->exit
3、Redis基础知识
redis有16个数据库,默认使用的是第0号数据库(0-15)
DBSIZE:查看当前数据库中的数据个数
select index:选择使用第几个数据库
keys *:查看所有的Key
flushdb:清空当前数据库
flushall:清空所有数据库
Redis是单线程的,基于内存操作,CPU不是Redis的性能瓶颈,Redis的瓶颈是基于机器的内存和网络的带宽
4、五大数据类型
4.1 Redis-Key
127.0.0.1:6379> set name qing #set添加键值对【name:qing】 默认为String类型
OK
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 #exists判断指定键是否存在 存在显示1,不存在显示0
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
127.0.0.1:6379> move name 1 #move移除指定键 1表示确定执行
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> get age #get 获取指定键的值
"1"
127.0.0.1:6379> EXPIRE age 5 #EXPIRE设置指定键的存活时间 后面跟秒
(integer) 1
127.0.0.1:6379> ttl age #ttl 查看指定键的存活时间
(integer) 1
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> type age #type 查看指定键的数据类型
string
127.0.0.1:6379> type name
string
4.2 String
127.0.0.1:6379> append name fan #append 指定键值追加字符串
(integer) 7
127.0.0.1:6379> get name
"qingfan"
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> APPEND name2 shuai #append时,若指定键不存在,则相当于set新建键值对
(integer) 5
127.0.0.1:6379> keys *
1) "name2"
2) "age"
3) "name"
127.0.0.1:6379> strlen name #strlen 获取指定键值的长度
(integer) 7
127.0.0.1:6379> get age
"1"
127.0.0.1:6379> incr age #incr:使指定键值+1
(integer) 2
127.0.0.1:6379> get age
"2"
127.0.0.1:6379> decr age #decr:使指定键值-1
(integer) 1
127.0.0.1:6379> get age
"1"
127.0.0.1:6379> incrby age 10 #incrby:使指定键值增加指定值
(integer) 11
127.0.0.1:6379> get age
"11"
127.0.0.1:6379> getrange name 0 1 #getrange 截取指定字符串 start end:0-1 前2个字符 0-0 第一个字符 0~-1 整个字符串
"qi"
127.0.0.1:6379> getrange name 0 0
"q"
127.0.0.1:6379> getrange name 0 -1
"qingfan"
127.0.0.1:6379> setrange name 1 xx #setrange 替换指定字符串的值 1:从第一个字符开始 xx:将第一个字符后的两个字符替换成xx
(integer) 7
127.0.0.1:6379> get name
"qxxgfan"
127.0.0.1:6379> setex key1 30 "qing" #setex:设置键的过期时间(秒),如果键不存在,则设置成功,若键已存在则设置失败
OK
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # mset 设置多个键值对
OK
127.0.0.1:6379> mget k1 k2 k3 # mget 获取多个键值对
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx:设置多个键值对,是一个原子性操作,因为k1已经存在,所有导致k4也设置失败
(integer) 0
127.0.0.1:6379> mset user:1:name qing user:1:age 5 user:2:name fan user:2:age 6 #mset设置对象 user的2个对象user:1和user:2
OK
127.0.0.1:6379> mget user:1:name user:1:age user:2:name #mget对象
1) "qing"
2) "5"
3) "fan"
127.0.0.1:6379> getset k4 v4 #getset:若果键不存在则返回nil并创建键值
(nil)
127.0.0.1:6379> get k4
"v4"
127.0.0.1:6379> getset k4 v44 #如果键存在,则返回旧值,并设置指定的新值
"v4"
4.3 List
Redis中可以用List来实现栈、队列、阻塞队列
#Lpush、Rpush、Lrange
127.0.0.1:6379> LPUSH list1 one #Lpush:将元素插入到list的头部(左)
(integer) 1
127.0.0.1:6379> LPUSH list1 two
(integer) 2
127.0.0.1:6379> LPUSH list 7 8 9 #添加多个值
(integer) 5
127.0.0.1:6379> LRANGE list1 0 -1 #LRANGE:显示list中的值,0~1 从左到右的两个元素,0~-1 所有元素
1) "two"
2) "one"
127.0.0.1:6379> Rpush list2 one two #Rpush:将一个或多个元素插入到list的尾部(右)
(integer) 2
127.0.0.1:6379> LRANGE list2 0 -1
1) "one"
2) "two"
#Lpop Rpop
127.0.0.1:6379> LRANGE list1 0 -1
1) "5"
2) "two"
3) "one"
127.0.0.1:6379> Lpop list1 #Lpop:将list头部第一个元素移除
"5"
127.0.0.1:6379> Rpop list1 #Rpop:将list尾部的第一个元素移除
"one"
#Lindex
127.0.0.1:6379> LRANGE list2 0 -1
1) "one"
2) "two"
127.0.0.1:6379> Lindex list2 0 #Lindex 通过下标获取list的值,从头部开始,起始下标为0
"one"
#Llen
127.0.0.1:6379> Llen list2 #Llen:返回指定list的长度
(integer) 2
#Lrem
127.0.0.1:6379> Lrem list2 1 one #Lrem:移除list中的指定值,1代表移除1个one
(integer) 1
#Ltrim
127.0.0.1:6379> Lpush list 1 2 3 4
(integer) 4
127.0.0.1:6379> Ltrim list 0 1 #Ltrim:根据指定下标和上标截断list,0~1代表第一个和第二个元素
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "4"
2) "3"
#RpopLpush
127.0.0.1:6379> LRANGE list 0 -1
1) "4"
2) "3"
127.0.0.1:6379> RpopLpush list list2 #RpopLpush:将前一个list的尾部元素移到另一个list的头部
"3"
#Lset
127.0.0.1:6379> LRANGE list 0 -1
1) "4"
127.0.0.1:6379> Lset list 0 5 #Lset:替换list指定下标的元素 0:将第一个元素替换为5
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "5"
#Linsert
127.0.0.1:6379> LRANGE list 0 -1
1) "5"
2) "6"
3) "7"
4) "8"
127.0.0.1:6379> Linsert list before 6 50 #在指定值的前面或后面插入值 在元素6的前面插入50
(integer) 5
127.0.0.1:6379> Linsert list after 8 90
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "5"
2) "50"
3) "6"
4) "7"
5) "8"
6) "90"
4.4 Set
127.0.0.1:6379> sadd myset "qing" #sadd:向set集合中添加指定值
(integer) 1
127.0.0.1:6379> sadd myset "fan"
(integer) 1
127.0.0.1:6379> sadd myset "chen"
(integer) 1
127.0.0.1:6379> smembers myset #smembers:查看指定set的所有值
1) "qing"
2) "chen"
3) "fan"
127.0.0.1:6379> sismember myset qing #sismember:判断指定值是否存在,返回1表示存在,0 不存在
(integer) 1
127.0.0.1:6379> sismember myset qing2
(integer) 0
127.0.0.1:6379> scard myset #scard:查看set中的数据个数
(integer) 3
127.0.0.1:6379> srem myset qing #srem:删除set中的指定值
(integer) 1
127.0.0.1:6379> smembers myset
1) "chen"
2) "fan"
127.0.0.1:6379> SRANDMEMBER myset #SRANDMEMBER:从set中随机抽取一个值
"fan"
127.0.0.1:6379> SRANDMEMBER myset
"chen"
127.0.0.1:6379> spop myset #spop:随机移除一个元素
"1"
127.0.0.1:6379> sadd myset 1 2 3 4
(integer) 4
127.0.0.1:6379> smove myset myset2 3 #smove:将一个set中的值移动到另外一个set中
(integer) 1
127.0.0.1:6379> SMEMBERS myset2
1) "3"
127.0.0.1:6379> sdiff myset myset2 #sdiff:查看两个集合的差集,set1中不包含set2的值
1) "2"
2) "4"
127.0.0.1:6379> sinter myset myset2 #sinter:交集
1) "1"
2) "5"
127.0.0.1:6379> sunion myset myset2 #sunion:并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
4.5 Hash
Map集合,键值对存储,key-value
127.0.0.1:6379> Hset myhash name qing #Hset:向集合中添加键值对
(integer) 1
127.0.0.1:6379> Hget myhash name #Hget:根据键获取指定值
"qing"
127.0.0.1:6379> Hset myhash name qing name2 qing2 #Hset:同时添加多个键值对
(integer) 1
127.0.0.1:6379> Hget myhash name2
"qing2"
127.0.0.1:6379> hmset myhash age1 50 age2 80 #Hmset:同时添加多个键值对
OK
127.0.0.1:6379> hmget myhash age1 age2 name name2 #Hmget:同时获取多个键的值
1) "50"
2) "80"
3) "qing"
4) "qing2"
127.0.0.1:6379> Hgetall myhash #获取集合中所有键值对,下标单数为键 偶数为值
1) "name"
2) "qing"
3) "name2"
4) "qing2"
5) "age1"
6) "50"
7) "age2"
8) "80"
127.0.0.1:6379> Hdel myhash name2 #Hdel:删除集合中指定键值对
(integer) 1
127.0.0.1:6379> Hlen myhash #Hlen:获取集合的长度-键值对的个数
(integer) 3
127.0.0.1:6379> Hexists myhash name #Hexists:判断指定的键是否存在 存在返回1,不存在返回0
(integer) 1
127.0.0.1:6379> Hexists myhash name2
(integer) 0
127.0.0.1:6379> Hkeys myhash #Hkeys:获取集合中的所有键
1) "name"
2) "age1"
3) "age2"
127.0.0.1:6379> Hvals myhash #Hvals:获取集合中的所有值
1) "qing"
2) "50"
3) "80"
127.0.0.1:6379> hget myhash age1
"50"
127.0.0.1:6379> Hincrby myhash age1 1 #Hincrby:给指定键的值增加指定值,若键不存在则会直接创建
(integer) 51
127.0.0.1:6379> Hincrby myhash age1 10
(integer) 61
127.0.0.1:6379> Hsetnx myhash age3 10 #Hsetnx:增加指定键值对,如果不存在则添加成功
(integer) 1
127.0.0.1:6379> Hsetnx myhash age3 9 #若键已存在则添加失败
(integer) 0
4.6 Zset(有序集合)
在set的基础上增加了一个字段,可以理解为排序数,可以根据此字段的大小进行排序
127.0.0.1:6379> Zadd myset 2 two 1 one #Zadd:增加一个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1 #Zrange:会根据具体值前面的数进行升序排序
1) "one"
2) "two"
127.0.0.1:6379> Zrevrange salary 0 -1 #Zrevrange:按降序排列
1) "chen"
2) "qing"
127.0.0.1:6379> Zadd salary 1000 qing
(integer) 1
127.0.0.1:6379> Zadd salary 10000 fan
(integer) 1
127.0.0.1:6379> Zadd salary 8000 chen
(integer) 1
127.0.0.1:6379> Zrange salary 0 -1
1) "qing"
2) "chen"
3) "fan"
127.0.0.1:6379> ZRANGEBYSCORE salary -0 +10000 #ZrangeByScore:可以指定排序的范围 -1~10000 负无穷~正无穷:-inf~+inf
1) "qing"
2) "chen"
3) "fan"
127.0.0.1:6379> ZRANGEBYSCORE salary -0 +10000 withscores #ZrangeByScore-withscores 打印排序的值和排序数大小
1) "qing"
2) "1000"
3) "chen"
4) "8000"
5) "fan"
6) "10000"
127.0.0.1:6379> Zrem salary fan #Zrem:移除集合中的指定元素
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "qing"
2) "chen"
127.0.0.1:6379> Zcard salary #Zcard:查询集合中的元素个数
(integer) 2
127.0.0.1:6379> Zcount myset 1 2 #Zcount:获取指定区间的元素个数
(integer) 3
5、三种特殊类型
5.1 geospatial
地理位置,可以推算地理位置的信息,计算两地之间的距离
#geoadd:添加key,一般使用java一键导入 城市经纬度 有效经度范围:-180~180 纬度范围:-85~85
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 shanghai
(integer) 0
127.0.0.1:6379> geoadd china:city 106.50 29.53 shenzhen 120.16 30.24 hangzhou
(integer) 2
#geopos:获取指定城市的经纬度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
#geodist:查看两个城市的直线距离 可切换单位:千米km、英里mi、英尺ft
127.0.0.1:6379> geodist china:city beijing shanghai km
"1464.0708"
#georadius:返回以指定经纬度为中心 指定半径内的城市 #withdist:显示直线距离 withcoord:显示经纬度参数 count:显示多少个城市
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "shanghai"
2) "shenzhen"
3) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 1
1) 1) "shanghai"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
#georadiusbymember:返回指定城市周围的城市
127.0.0.1:6379> georadiusbymember china:city shanghai 1000 km
1) "shanghai"
2) "shenzhen
#其余命令与set类似:Zrange、Zrem
127.0.0.1:6379> Zrange china:city 0 -1
1) "shanghai"
2) "shenzhen"
3) "hangzhou"
4) "beijing"
5.2 Hyperloglog
基数:不重复的元素,如:
A{1,2,,3,5,8} B{1,3,5,8} A、B集合的基数就是2
Reis Hyperloglog数据结构用于统计基数,允许有容错
127.0.0.1:6379> Pfadd key1 a b c d e f g #Pfadd:创建一组元素
(integer) 1
127.0.0.1:6379> PFcount key1 #Pfcount:统计一组元素中的基数数量
(integer) 7
127.0.0.1:6379> pFadd key2 b c d e h i
(integer) 1
127.0.0.1:6379> PFmerge key3 key1 key2 #Pfmerge:合并两组数到一个新数组,求出对应的基数
OK
127.0.0.1:6379> Pfcount key3
(integer) 9
5.3 Bitmaps(位图)
统计用户的信息,活跃、不活跃,打卡信息等
#例:统计用户一周的打卡信息
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 0
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
127.0.0.1:6379> getbit sign 5 #getbit 获取某天的打卡记录
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
127.0.0.1:6379> bitcount sign #bitcount:统计打卡天数
(integer) 3
6、事务
Redis单条命令保证原子性,但事务不保证原子性,Redis事务没有隔离级别的概念
Redis事务有一次性、顺序性、排他性
流程:
- 开启事务(multi):事务开启后,会形成一个队列
- 命令入队:所有命令会按照先后顺序进入队列,等待统一执行命令
- 执行事务(exec):
127.0.0.1:6379> multi #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 #exec:执行事务
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379> discard - #discard:取消事务
编译型异常:如get命令 后面不加指定键,命令出错导致报错,事务队列中的所有命令都不会执行!
127.0.0.1:6379> get
(error) ERR wrong number of arguments for 'get' command
运行时异常:如1/0,事务队列中存在语法性错误,则执行事务时,该条命令不会执行,其他正确的命令仍然会执行!
Redis实现事务监控:
乐观锁、悲观锁
Redis中使用 watch 来实现乐观锁,可以监控一个对象,在执行一个事务时,如果在当前事务没有提交(exec)之前有另外一个线程插入,修改了监控对象的数据,那么本事务一定会执行失败。 如果发现事务执行失败,就先使用unwatch解锁,再次使用watch加锁,重新监视
127.0.0.1:6379> watch money #watch:加锁
OK
127.0.0.1:6379> unwatch #unwatch解锁
OK
7、Jedis
Jedis是Redis官网推荐的java连接工具
7.1 导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
7.2 idea连接redis
1> 修改redis.conf配置文件,将“bind 127.0.0.1”注释掉并添加“bind 0.0.0.0”,并将“protect-mode yes”修改为:protect-mode no
2> 将reids端口加入防火墙: firewall-cmd —zone=public —add-port=6379/tcp —permanent
重启防火墙:systemctl restart firewalld !(也可以直接关闭防火墙,一劳永逸)
如果使用的云服务器,则需要在云服务器上打开6379端口
3> “./redis-server myconf/redis.conf ” 启动云服务器的Redis服务端
4> idea测试连接
public class TestRedis {
public static void main(String[] args) {
//创建Jedis对象
//host:我的阿里云ip地址,如使用的是本地redis,则用127.0.0.1即可
Jedis jedis = new Jedis("47.108.59.58",6379);
System.out.println(jedis.ping());
System.out.println(jedis.get("money"));
System.out.println(jedis.keys("*"));
}
}
8、SpringBoot集成Redis
在SpringBoot2.x之后,将Redis替换为了lettuce
jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式
8.1 集成测试
启动reids服务端
1> 导包
<!--Redis导入依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2> 配置连接
spring.redis.host=47.128.59.58
#spring.redis.host=127.0.0.1 本地redis
spring.redis.port=6379
3> 测试
@SpringBootTest
class Redis02SpringbootApplicationTests {
//导入redisTemplate模板
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//获取redis的连接对象,可对redis数据库进行操作
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.flushAll();
//opsForValue:封装了所有操作字符串的方法
//opsForList:封装了所有操作list的方法
//...
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("name","qing");
System.out.println(valueOperations.get("name"));
}
}
8.2 出现问题
当在idea中操作了Redis数据库后,再去Redis远程端查询时,出现乱码,如下图:
出现乱码原因:Redis源码中,自动进行了JDK序列化,我们需要使用Json来序列化,见8.3
8.3 自定义RedisTemplate
自定义redsiTemplate来实现对应的序列化,解决乱码
@Configuration
public class RedisConfig {
//编写自己的redsiTemplate 固定模板
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> myRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
// 序列化配置 解析任意对象
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// json序列化利用ObjectMapper进行转义
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 2.序列化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();
return template;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
//在企业开发中,所有的pojo实体类都需要序列化!
public class User implements Serializable {
private String name;
private Integer age;
}
8.4 自定义Redis工具类
/*
*File Name: RedisUtil
*Created By: QingFan
*Created Time:2021/9/22 12:42
*Desc: null
*/
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.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final 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(String.valueOf(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
*/
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 对应多个键值
*/
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 值
*/
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 时间(秒)
*/
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;
}
}
}
9、Redis.conf
10、Redis持久化
10.1 RDB
- redis 是内存数据库,数据断电及失,因此需要持久化,默认使用RDB,一般情况下我们无需修改RDB配置,即可使用
- RDB的缺点就是最后一次持久化后的数据可能丢失
- Redis会单独创建一个fork子进程来进行持久化,子进程中循环所有的数据,将数据写入到二进制文件中,会先将数据 写入到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上次持久化好了的文件
- RDB保存的文件就是:dump.rdb
触发机制:
- 在配置文件中的save,例如60秒内修改了5个key就会触发RDB,生成一个dump.rdb文件
- 执行flushall 命令,也会触发我们的RDB规则
- 退出redis,输入命令shutdown,就会关闭redis服务,这个时候也会生成RDB文件。
恢复数据:
- 只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查demp.rdb恢复其中的数据
- 查看rdb文件保存的位置,输入命令
config get dir
如果在该目录下存在,就会自动恢复其中的数据
10.2 AOF
aof是将我们所有的命令都记录下来,然后在恢复的时候再重新执行一次,并且只记录写操作的命令,读命令不会记录
aof只追加文件,因此aof的速度会慢一些
aof保存的文件是: appendonly.aof
aof默认不开启aof,修改
appendonly no
为yes后 重启Redis开启aof持久化如果aof文件出错:redis提供了一个持久化文件修复工具,一个是
redis-chech-rdb
,另一个是redis-chech-aof
,因此如果出现这种情况,我们就可以使用这两个工具修复文件./redis-check-aof --fix appendonly.aof