一、了解
1、Nosql概述(同sql的区别)
1、存储方式
SQL数据存在特定结构的表中;而NoSQL则更加灵活和可扩展,存储方式可以省是JSON文档、哈希表或者其他方式。
2、表/数据集合的数据的关系
SQL中,必须定义好表和字段结构后才能添加数据,例如定义表的主键(primary key),索引(index),触发器(trigger)
存储过程(stored procedure)等。表结构可以在被定义之后更新,但是如果有比较大的结构变更的话就会变得比较复杂。
在NoSQL中,数据可以在任何时候任何地方添加,不需要先定义表。
3、外部数据存储
SQL中如何需要增加外部关联数据的话,规范化做法是在原表中增加一个外键,关联外部数据表
在NoSQL中除了这种规范化的外部数据表做法以外,我们还能用如下的非规范化方式把外部数据直接放到原数据集中
以提高查询效率。缺点也比较明显,更新审核人数据的时候将会比较麻烦。
4、SQL中的JOIN查询
SQL中可以使用JOIN表链接方式将多个关系数据表中的数据用一条简单的查询语句查询出来。
NoSQL暂未提供类似JOIN的查询方式对多个数据集中的数据做查询。所以大部分NoSQL使用非规范化的数据存储方式存储数据。
5、数据耦合性
SQL中不允许删除已经被使用的外部数据,以保证数据完整性
NoSQL中则没有这种强耦合的概念,可以随时删除任何数据。
6、事务
SQL中如果多张表数据需要同批次被更新,即如果其中一张表更新失败的话其他表也不能更新成功。
NoSQL中没有事务这个概念,每一个数据集的操作都是原子级的。
7、查询性能
在相同水平的系统设计的前提下,因为NoSQL中省略了JOIN查询的消耗,故理论上性能上是优于SQL的。
2、NoSql的四大分类
(1)、Key/value键值对存储
redis tair memecache
(2)、文档型数据库(Bson格式和Json类似)
MongoDB:是一个基于分布式文件存储的数据库,由C++编写,主要用来处理大量文档
介于关系型和非关系型数据库中间,属于非关系型数据库
(3)、列存储数据库
Habse 分布式文件系统
(4)、图关系数据库
Neo4j InfoGrid
村的并不是图形,是关系 例:朋友圈社交网络,广告推荐
二、了解redis
1、redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C
语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
2、redis能干吗?
(1)、内存存储、持久化,内存中是断电即失的,所以持久化很重要(edb,aof)
(2)、效率高,可以用于高速缓存
(3)、发布订阅系统(能干基本的消息队列工作)
(4)、地图信息分析
..................
3、redis特性
1、多样化数据类型 2、持久化 3、集群 4、事务...
三、下载Redis
(1)、下载并解压
(2)、进入redis安装目录 使用make命令 失败的话使用make MALLOC=libc 再make
(3)、启动redis
make完后 redis-2.8.17目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli,
两个程序位于安装目录 src 目录下
方法一:./redis-server
方法二:./redis-server ../redis.conf(注意这种方式启动redis 使用的是默认配置。也可以通过启动参数告诉redis
使用指定配置文件使用下面命令启动。)
(4)、修改配置文件使其后台运行
进入redis目录 vi redis.conf 把daemonize no 改为daemonize yes
启动 ./redis-server ../redis.conf
(5)、登录redis服务
./redis-clic
(6)、关闭并退出
(1)shutdown (2)exit
2、测试redis
3、redis基础知识
(1)、redis有16个数据库 //查看配置文件就知道 databases16 默认使用第0个可由Select切换
select 3 //切换到第三个数据库
dbsize //查看大小
keys * //查看所有的key
flushdb/flushall //清空当前数据库/全部数据库
(2)、redis是单线程的
redis是基于内存操作的,CPU并不是redis的内存瓶颈(多线程是是)。redis的瓶颈是根据机器的内存和带宽,既然
可以使用单线程,为什么使用多线程呢(redis官方说明)
redis基于C语言编写,官方提供数据为100000QPS,完全不比Memecache差
(3)、redis为什么单线程还那么快
误区:
1、高性能的服务器一定是多线程?
2、多线程一定比单线程效率高?
核心:
redis是把所有的数据放进内存中,所以单线程操作效率就是最高的,(多线程CPU上下文会切换!是耗时操作)
对于内存来说,没有上下文切换就是效率最高的。
四、五大数据类型
特殊:Redis-key
1 | del key | 该命令用于在 key 存在时删除 key。 |
---|---|---|
2 | dump key | 序列化给定 key ,并返回被序列化的值。 |
3 | exists key | 检查给定 key 是否存在。 |
4 | expire key seconds | 为给定 key 设置过期时间,以秒计。 |
5 | expireat key timestamp | EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
6 | pexpire key milliseconds | 设置 key 的过期时间以毫秒计。 |
7 | pexpireat key milliseconds-timestamp | 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
8 | keys pattern | 查找所有符合给定模式( pattern)的 key |
9 | move key db | 将当前数据库的 key 移动到给定的数据库 db 当中。 |
10 | persist key | 移除 key 的过期时间,key 将持久保持。 |
11 | ptll key | 以毫秒为单位返回 key 的剩余的过期时间。 |
12 | ttl key | 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
13 | randomkey | 从当前数据库中随机返回一个 key 。 |
14 | rename key newkey | 修改 key 的名称。 |
15 | renamenx key newkey | 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
16 | scan cursor [match pattern]_[count count] | 迭代数据库中的数据库键。 |
17 | type key | 返回 key 所储存的值的类型。 |
(1)、String
1 | set key value | 设置key,value值 |
---|---|---|
2 | get key | 获取指定key值 |
3 | getrange key stand end | 返回key中指定一段字符 |
4 | getset key value | key值社为value,返回key旧值 |
5 | getbit key offset | 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
6 | mget key[key2] | 获取一个或多个key的值 |
7 | setbit key offset value | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
8 | setex key seconds value | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
9 | setnx | 只有在 key 不存在时设置 key 的值。 |
10 | setrange key offset value | 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
11 | strlen key | 返回 key 所储存的字符串值的长度。 |
12 | mset key value [key value ...] | 同时设置一个或多个 key-value 对。 |
13 | msetnx key value [key value ...] | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
14 | psetex key milliseconds value | 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
15 | incr key | 将 key 中储存的数字值增一。 |
16 | incrby key increment | 将 key 所储存的值加上给定的增量值(increment) 。 |
17 | incrbyfloat key increment | 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
18 | decr key | 将 key 中储存的数字值减一。 |
19 | decrby key decrement | key 所储存的值减去给定的减量值(decrement) 。 |
20 | append key value | 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 |
(2)、List
1 | blpop key[key2] timeout | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
---|---|---|
2 | brpop key1[2] timeout | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
3 | brpoplpush source destination timeout | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
4 | lindex key index | 通过索引获取列表中的元素 |
5 | linsert key before/after pivot value | 在列表的元素前或者后插入元素 |
6 | llen key | 获取列表长度 |
7 | lpop key | 移出并获取列表的第一个元素 |
8 | lpush key value [value2] | 将一个或多个值插入到列表头部 |
9 | lpushx key value | 将一个值插入到已存在的列表头部 |
10 | lrange key start stop | 获取列表指定范围内的元素 |
11 | lrem key count value | 移除列表元素 |
12 | lset key idex value | 通过索引设置列表元素的值 |
13 | ltrim key stop | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
14 | rpop key | 移除列表的最后一个元素,返回值为移除的元素。 |
15 | proplpush source destination | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
16 | prush key value[value2] | 在列表中添加一个或多个值 |
17 | rpushx key value | 为已存在的列表添加值 |
(3)、Set
1 | SADD key member1 [member2] | 向集合添加一个或多个成员 |
---|---|---|
2 | SCARD key | 获取集合的成员数 |
3 | SDIFF key1 [key2] | 返回第一个集合与其他集合之间的差异。 |
4 | SDIFFSTORE destination key1 [key2] | 返回给定所有集合的差集并存储在 destination 中 |
5 | SINTER key1 [key2] | 返回给定所有集合的交集 |
6 | SINTERSTORE destination key1 [key2] | 返回给定所有集合的交集并存储在 destination 中 |
7 | SISMEMBER key member | 判断 member 元素是否是集合 key 的成员 |
8 | SMEMBERS key | 返回集合中的所有成员 |
9 | SMOVE source destination member | 将 member 元素从 source 集合移动到 destination 集合 |
10 | SPOP key | 移除并返回集合中的一个随机元素 |
11 | SRANDMEMBER key [count] | 返回集合中一个或多个随机数 |
12 | SREM key member1 [member2] | 移除集合中一个或多个成员 |
13 | SUNION key1 [key2] | 返回所有给定集合的并集 |
14 | SUNIONSTORE destination key1 [key2] | 所有给定集合的并集存储在 destination 集合中 |
15 | SSCAN key cursor [MATCH pattern] [COUNT count] | 迭代集合中的元素 |
(4)、Hash
1 | HDEL key field1 [field2] | 删除一个或多个哈希表字段 |
---|---|---|
2 | HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在。 |
3 | HGET key field | 获取存储在哈希表中指定字段的值。 |
4 | HGETALL key | 获取在哈希表中指定 key 的所有字段和值 |
5 | HINCRBY key field increment | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
6 | HINCRBYFLOAT key field increment | 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
7 | HKEYS key | 获取所有哈希表中的字段 |
8 | HLEN key | 获取哈希表中字段的数量 |
9 | HMGET key field1 [field2] | 获取所有给定字段的值 |
10 | HMSET key field1 value1 [field2 value2 ] | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
11 | HSET key field value | 将哈希表 key 中的字段 field 的值设为 value 。 |
12 | HSETNX key field value | 只有在字段 field 不存在时,设置哈希表字段的值。 |
13 | HVALS key | 获取哈希表中所有值。 |
14 | HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代哈希表中的键值对。 |
(5)、ZSet
1 | ZADD key score1 member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
---|---|---|
2 | ZCARD key | 获取有序集合的成员数 |
3 | ZCOUNT key min max | 计算在有序集合中指定区间分数的成员数 |
4 | ZINCRBY key increment member | 有序集合中对指定成员的分数加上增量 increment |
5 | ZINTERSTORE destination numkeys key [key ...] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中 |
6 | ZLEXCOUNT key min max | 在有序集合中计算指定字典区间内成员数量 |
7 | ZRANGE key start stop [WITHSCORES] | 通过索引区间返回有序集合指定区间内的成员 |
8 | ZRANGEBYLEX key min max [LIMIT offset count] | 通过字典区间返回有序集合的成员 |
9 | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | 通过分数返回有序集合指定区间内的成员 |
10 | ZRANK key member | 返回有序集合中指定成员的索引 |
11 | ZREM key member [member ...] | 移除有序集合中的一个或多个成员 |
12 | ZREMRANGEBYLEX key min max | 移除有序集合中给定的字典区间的所有成员 |
13 | ZREMRANGEBYRANK key start stop | 移除有序集合中给定的排名区间的所有成员 |
14 | ZREMRANGEBYSCORE key min max | 移除有序集合中给定的分数区间的所有成员 |
15 | ZREVRANGE key start stop [WITHSCORES] | 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
16 | ZREVRANGEBYSCORE key max min [WITHSCORES] | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
17 | ZREVRANK key member | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
18 | ZSCORE key member | 返回有序集中,成员的分数值 |
19 | ZUNIONSTORE destination numkeys key [key ...] | 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
20 | ZSCAN key cursor [MATCH pattern] [COUNT count] | 迭代有序集合中的元素(包括元素成员和元素分值) |
五、三大特殊数据类型
1、geospatial 地理位置
Redis3.2推出的。可以用来朋友定位、附近的人、打车计算等、推算地理信息,计算两地距离。。。
Redis GEO
(1)geoadd:添加地理位置的坐标。
(2)geopos:获取地理位置的坐标。
(3)geodist:计算两个位置之间的距离。
(4)georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
(5)georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
(6)geohash:返回一个或多个位置对象的 geohash 值。
实例:
(1)geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)
添加到指定的 key 中。
语法: GEOADD key longitude latitude member [longitude latitude member ...]
例: GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(2)geopos用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。
语法: GEOPOS key member [member ...]
例: GEOPOS Sicily Palermo Catania NonExisting
(3)geodist用于返回两个给定位置之间的距离。
语法: GEODIST key member1 member2 [m|km|ft|mi]
例: GEODIST Sicily Palermo Catania
(4)georadius以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
语法: GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH]
[COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
例:redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
2) "190.4424"
2) 1) "Catania"
2) "56.4413"
redis> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
2) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis>
(5)georadiusbymembergeoradiusbymember 和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是
georadiusbymember的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。
语法:GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
[ASC|DESC] [STORE key] [STOREDIST key]
例:redis> GEOADD Sicily 13.583333 37.316667 "Agrigento"
(integer) 1
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEORADIUSBYMEMBER Sicily Agrigento 100 km
1) "Agrigento"
2) "Palermo"
redis>
(6)geohash 来保存地理位置的坐标。
语法: GEOHASH key member [member ...]
例:redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"
redis>
2、Hyperloglog基数统计算法(是数据结构)
(1)、什么是基数?(网页UV一个人访问多次算作1次)
基数(不重复的元素)可以接受误差。Hyperloglog用来做基数统计的算法,每个 HyperLogLog 键只需要花费 12 KB 内存
就可以计算接近 2^64 个不同元素的基数。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身
所以HyperLogLog 不能像集合那样,返回输入的各个元素。(优点:占用内存特别小。)
(2、)语法
PFADD key element [element ...] 添加指定元素到 HyperLogLog 中。
PFCOUNT key [key ...] 返回给定 HyperLogLog 的基数估算值。
PFMERGE destkey sourcekey [sourcekey ...] 将多个 HyperLogLog 合并为一个 HyperLogLog
例:
PFADD runoobkey "redis"
PFCOUNT runoobkey
PFMERGE 自定义集合3 集合1 集合2
3、Bitmap位图(是数据结构)
只有0和1用来统计两种状态的场景,比如打卡,登录与未登录等、
Redis 命令:
(1)、setbit 例:setbit 打卡 1 0 //第一天未打卡
(2)、getbit 例:getbit 打卡 1 //查看第一天是否打卡
(3)、bitcount 例:bitcount 打卡 //查看打卡全部次数
六、事务
事务的本质(ACDI)本质:执行一组命令操作,一个事务中所有的命令都会被序列化,在事务执行过程中,会按照先后顺序执行。
Redis单条命令保存原子性,事务不保存原子性。也没有隔离级别的概念
1、Redis事务
(1)、Redis事务步骤
--开启事务(multi) //打开事务
--命令入队() //做IO操作会显示QUEUED表示命令入队了
--执行事务(exec) //执行此命令会执行命令入队包含的命令
--取消事务(discard) //取消
//执行完毕事务消失,再次使用需要开启新的事物。
(2)、Redis事务异常
--编译时异常(没写对,代码错误) //所有命令都不会执行
--运行时异常(写对了,运行时候出问题) //其他命令正确人性,异常命令抛异常
(3)、watch监控(当作乐观锁)
--悲观锁 //比较悲观,做什么操作都会加锁。
--乐观锁 //数据更新的时候去检索以下,如果发现冲突返回错误信息。
例:
watch 字段a ==>> 开启事务,字段a进行操作但是并没有exec执行事务
突然有个线程插队修改了字段a的值 ==>> 此时执行事务会成功但是并没有对字段a进行修改。
watch锁失效 ==>> unwatch解锁 ==>> watch重新检视
2、jedis操作Redis
(1)、Redis下载jedis的jar包
(2)、实例化jedis //Jedis jedis = new Jedis(host,port,....);
(3)、直接调用Api //jedis.flush() ||jedis.set().....
3、jedis操作事务
Jedis jedis = new Jedis();
Transaction tran = jedis.multi();
jedis.watch();
try{
//操作
tran.set(...,...);
tran.exec();
}catch(Execption e){
tran.discard();
}finaly{
tran.clos();
}
七、整合Redis
1、SpringBoot整合redis
SpringBoot数据操作:全部在SpringData里(redis、mongodb、jdbc...),SpringBoot在2.x之后不用jedis而是用lettuce:
区别:jedis: 底层采用直连,多个线程操作是不安全的,可以用连接池解决。问题比较多。
lettuce: 底层采用netty,实例可以在多线程中共享,不存在线程安全问题,可以减少线程数据。
步骤:
(1)、导入RedisJar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.0</version>
</dependency>
(2)、进行Redis配置
spring.redis.port=6379;
......
(3)、通过RedisTemplate进行Redis的操作:
@Autowired
private RedisTemplate redisTemp
redisTemp.opsForValue().进行String的相关操作 || .opsForSet().进行set操作....
redisTemp.multi(); //事务操作
//进行数据库操作
RedisConnection rs = redis.getConnectionFactory().getConnection();
rs.flushAll();
2、自定义RedisTemplate
(1)、RedisAutoConfiguraton中源码(默认采用JdkSerializationRedisSerializer,往Redis保存时都加了一串字符)
@Bean //下面这个注解的意思是如果redisTemplate这个Bean存在那么这里的redisTemplate就会失效。
//这是默认的redisTemplate,我们可以自己定义一个redisTemplate来替换这个默认的
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
//默认的RedisTemplate,没有过多的设置,使用Redis对象都需要序列化
//两个泛型都是Object,Object类型,我们使用时需要进行强制转换<String,Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
//由于String是Redis中最常使用的类型,所以说单独提取出来了一个Bean。如果要使用String类型直接调用这个方法就可以了。
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
(2)、自定义RedisTempLate(默认配置较少,还需要手动序列化)
@Configuration
public class RedisConfig {
//配置我们自己的redisTemplate 固定模板
@Bean
@SuppressWarnings("all") //告诉编译器忽略全部的警告,不用在编译完成后出现警告信息
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
throws UnknownHostException {
//我们为了自己开发方便,一般直接使用<String, Object>类型
RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
//连接工厂
template.setConnectionFactory(factory);
//Json的序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer
(Object.class);
ObjectMapper om = new ObjectMapper(); //JackSon对象
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//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);
//配置完之后将所有的properties设置进去
template.afterPropertiesSet();
return template;
}
}
3、RedisUtil工具类
(一般来说,我们redisTemplate对象去操作数据类型比较麻烦,我们一般会将操作封装成一个工具类,用的时候直接调用就可以了)
@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(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;
}
}
}
八、持久化
1、RDB
RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。
触发条件:
1 在指定的时间间隔内,执行指定次数的写操作
2 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令
3 执行flushall 命令,清空数据库所有数据,意义不大。
4 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据,意义...也不大。
通过RDB文件恢复数据:
将dump.rdb 文件拷贝到redis的安装目录的bin目录下,重启redis服务即可。
优缺点:
优点:效率比AOF高,恢复大数据集比AOF快,数据完整性低。
缺点:因为会fork一个子进程用来持久化所以数据量大时候消耗比较大,不能保证数据完整性。
2、AOF(Append Only File)
以日志的形式记录每个写的操作,将Redis执行过的命令记录下来(不包括读)只需追加文件,不许改写文件。Redis启动之初会读取该文件重新构建数据
``` 默认使用RDB启用的话需要在配置文件: appendonly no ==》》yes
生成appendonly.aof里面是写过的命令,但是这个命令会被破坏(登录会报错刷新失败)
解决办法:
使用redis-check-aof --fix appendonly.aof
重写规则:
配置文件默认超过64m,就会fork一个新的进程来将我们的文件重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
优缺点:
优点:
每一次修改都同步,文件完整性更高
每秒同一次,可能会丢失一秒数据
缺点:
丢弃错误的数据
修复数据时数据量大的情况特别慢
运行效率特别慢
注:同时开启两种会使用aof,因为aof数据完整性更高,rdb一般用来做备份。