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/srcmv 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 *:查看所有的Keyflushdb:清空当前数据库flushall:清空所有数据库
Redis是单线程的,基于内存操作,CPU不是Redis的性能瓶颈,Redis的瓶颈是基于机器的内存和网络的带宽
4、五大数据类型
4.1 Redis-Key
127.0.0.1:6379> set name qing #set添加键值对【name:qing】 默认为String类型OK127.0.0.1:6379> set age 1OK127.0.0.1:6379> keys * #查看当前数据库的所有键值对1) "age"2) "name"127.0.0.1:6379> EXISTS name #exists判断指定键是否存在 存在显示1,不存在显示0(integer) 1127.0.0.1:6379> EXISTS name1(integer) 0127.0.0.1:6379> move name 1 #move移除指定键 1表示确定执行(integer) 1127.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) 1127.0.0.1:6379> ttl age #ttl 查看指定键的存活时间(integer) 1127.0.0.1:6379> ttl age(integer) -2127.0.0.1:6379> keys *(empty array)127.0.0.1:6379> type age #type 查看指定键的数据类型string127.0.0.1:6379> type namestring
4.2 String
127.0.0.1:6379> append name fan #append 指定键值追加字符串(integer) 7127.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) 5127.0.0.1:6379> keys *1) "name2"2) "age"3) "name"127.0.0.1:6379> strlen name #strlen 获取指定键值的长度(integer) 7127.0.0.1:6379> get age"1"127.0.0.1:6379> incr age #incr:使指定键值+1(integer) 2127.0.0.1:6379> get age"2"127.0.0.1:6379> decr age #decr:使指定键值-1(integer) 1127.0.0.1:6379> get age"1"127.0.0.1:6379> incrby age 10 #incrby:使指定键值增加指定值(integer) 11127.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) 7127.0.0.1:6379> get name"qxxgfan"127.0.0.1:6379> setex key1 30 "qing" #setex:设置键的过期时间(秒),如果键不存在,则设置成功,若键已存在则设置失败OK127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # mset 设置多个键值对OK127.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) 0127.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:2OK127.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、Lrange127.0.0.1:6379> LPUSH list1 one #Lpush:将元素插入到list的头部(左)(integer) 1127.0.0.1:6379> LPUSH list1 two(integer) 2127.0.0.1:6379> LPUSH list 7 8 9 #添加多个值(integer) 5127.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) 2127.0.0.1:6379> LRANGE list2 0 -11) "one"2) "two"#Lpop Rpop127.0.0.1:6379> LRANGE list1 0 -11) "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"#Lindex127.0.0.1:6379> LRANGE list2 0 -11) "one"2) "two"127.0.0.1:6379> Lindex list2 0 #Lindex 通过下标获取list的值,从头部开始,起始下标为0"one"#Llen127.0.0.1:6379> Llen list2 #Llen:返回指定list的长度(integer) 2#Lrem127.0.0.1:6379> Lrem list2 1 one #Lrem:移除list中的指定值,1代表移除1个one(integer) 1#Ltrim127.0.0.1:6379> Lpush list 1 2 3 4(integer) 4127.0.0.1:6379> Ltrim list 0 1 #Ltrim:根据指定下标和上标截断list,0~1代表第一个和第二个元素OK127.0.0.1:6379> LRANGE list 0 -11) "4"2) "3"#RpopLpush127.0.0.1:6379> LRANGE list 0 -11) "4"2) "3"127.0.0.1:6379> RpopLpush list list2 #RpopLpush:将前一个list的尾部元素移到另一个list的头部"3"#Lset127.0.0.1:6379> LRANGE list 0 -11) "4"127.0.0.1:6379> Lset list 0 5 #Lset:替换list指定下标的元素 0:将第一个元素替换为5OK127.0.0.1:6379> LRANGE list 0 -11) "5"#Linsert127.0.0.1:6379> LRANGE list 0 -11) "5"2) "6"3) "7"4) "8"127.0.0.1:6379> Linsert list before 6 50 #在指定值的前面或后面插入值 在元素6的前面插入50(integer) 5127.0.0.1:6379> Linsert list after 8 90(integer) 6127.0.0.1:6379> LRANGE list 0 -11) "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) 1127.0.0.1:6379> sadd myset "fan"(integer) 1127.0.0.1:6379> sadd myset "chen"(integer) 1127.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) 1127.0.0.1:6379> sismember myset qing2(integer) 0127.0.0.1:6379> scard myset #scard:查看set中的数据个数(integer) 3127.0.0.1:6379> srem myset qing #srem:删除set中的指定值(integer) 1127.0.0.1:6379> smembers myset1) "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) 4127.0.0.1:6379> smove myset myset2 3 #smove:将一个set中的值移动到另外一个set中(integer) 1127.0.0.1:6379> SMEMBERS myset21) "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) 1127.0.0.1:6379> Hget myhash name #Hget:根据键获取指定值"qing"127.0.0.1:6379> Hset myhash name qing name2 qing2 #Hset:同时添加多个键值对(integer) 1127.0.0.1:6379> Hget myhash name2"qing2"127.0.0.1:6379> hmset myhash age1 50 age2 80 #Hmset:同时添加多个键值对OK127.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) 1127.0.0.1:6379> Hlen myhash #Hlen:获取集合的长度-键值对的个数(integer) 3127.0.0.1:6379> Hexists myhash name #Hexists:判断指定的键是否存在 存在返回1,不存在返回0(integer) 1127.0.0.1:6379> Hexists myhash name2(integer) 0127.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) 51127.0.0.1:6379> Hincrby myhash age1 10(integer) 61127.0.0.1:6379> Hsetnx myhash age3 10 #Hsetnx:增加指定键值对,如果不存在则添加成功(integer) 1127.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) 2127.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) 1127.0.0.1:6379> Zadd salary 10000 fan(integer) 1127.0.0.1:6379> Zadd salary 8000 chen(integer) 1127.0.0.1:6379> Zrange salary 0 -11) "qing"2) "chen"3) "fan"127.0.0.1:6379> ZRANGEBYSCORE salary -0 +10000 #ZrangeByScore:可以指定排序的范围 -1~10000 负无穷~正无穷:-inf~+inf1) "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) 1127.0.0.1:6379> ZRANGE salary 0 -11) "qing"2) "chen"127.0.0.1:6379> Zcard salary #Zcard:查询集合中的元素个数(integer) 2127.0.0.1:6379> Zcount myset 1 2 #Zcount:获取指定区间的元素个数(integer) 3
5、三种特殊类型
5.1 geospatial
地理位置,可以推算地理位置的信息,计算两地之间的距离
#geoadd:添加key,一般使用java一键导入 城市经纬度 有效经度范围:-180~180 纬度范围:-85~85127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing(integer) 1127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai(integer) 1127.0.0.1:6379> geoadd china:city 106.50 29.53 shanghai(integer) 0127.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 beijing1) 1) "116.39999896287918091"2) "39.90000009167092543"#geodist:查看两个城市的直线距离 可切换单位:千米km、英里mi、英尺ft127.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 km1) "shanghai"2) "shenzhen"3) "hangzhou"127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 11) 1) "shanghai"2) "341.9374"3) 1) "106.49999767541885376"2) "29.52999957900659211"#georadiusbymember:返回指定城市周围的城市127.0.0.1:6379> georadiusbymember china:city shanghai 1000 km1) "shanghai"2) "shenzhen#其余命令与set类似:Zrange、Zrem127.0.0.1:6379> Zrange china:city 0 -11) "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) 1127.0.0.1:6379> PFcount key1 #Pfcount:统计一组元素中的基数数量(integer) 7127.0.0.1:6379> pFadd key2 b c d e h i(integer) 1127.0.0.1:6379> PFmerge key3 key1 key2 #Pfmerge:合并两组数到一个新数组,求出对应的基数OK127.0.0.1:6379> Pfcount key3(integer) 9
5.3 Bitmaps(位图)
统计用户的信息,活跃、不活跃,打卡信息等
#例:统计用户一周的打卡信息127.0.0.1:6379> setbit sign 0 1 #星期一 打卡(integer) 0127.0.0.1:6379> setbit sign 1 0 #星期二 未打卡(integer) 0127.0.0.1:6379> setbit sign 2 0(integer) 0127.0.0.1:6379> setbit sign 3 0(integer) 0127.0.0.1:6379> setbit sign 4 1(integer) 0127.0.0.1:6379> setbit sign 5 1(integer) 0127.0.0.1:6379> setbit sign 6 0(integer) 0127.0.0.1:6379> getbit sign 5 #getbit 获取某天的打卡记录(integer) 1127.0.0.1:6379> getbit sign 6(integer) 0127.0.0.1:6379> bitcount sign #bitcount:统计打卡天数(integer) 3
6、事务
Redis单条命令保证原子性,但事务不保证原子性,Redis事务没有隔离级别的概念
Redis事务有一次性、顺序性、排他性
流程:
- 开启事务(multi):事务开启后,会形成一个队列
- 命令入队:所有命令会按照先后顺序进入队列,等待统一执行命令
- 执行事务(exec):
127.0.0.1:6379> multi #multi:开启事务OK127.0.0.1:6379> set k1 v1 #命令入队QUEUED127.0.0.1:6379> set k2 v2QUEUED127.0.0.1:6379> get k2QUEUED127.0.0.1:6379> set k3 v3QUEUED127.0.0.1:6379> exec #exec:执行事务1) OK2) OK3) "v2"4) OK127.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:加锁OK127.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 本地redisspring.redis.port=6379
3> 测试
@SpringBootTestclass Redis02SpringbootApplicationTests {//导入redisTemplate模板@Autowiredprivate RedisTemplate redisTemplate;@Testvoid 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来实现对应的序列化,解决乱码
@Configurationpublic 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序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash的value序列化方式采用jacksontemplate.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;@Componentpublic final class RedisUtil {@Autowiredprivate 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
