关系型数据库 | Mysql、Oralce |
---|---|
特点 | 数据之间有关联;数据存储在硬盘上 |
效率 | 操作关系型数据库非常耗时 |
非关系型数据库 | redis、hbase |
---|---|
存储 | key:value |
特点 | 数据之间没有关联关系;数据存储在内存中 |
缓存思想 | 从缓存中获取数据,有数据时,直接返回;没有数据时,从数据库中查询,将数据放入到缓存,返回数据 |
总结:
与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度非常快,
每秒可以处理超过10万次读写操作。因此redis被广泛应用于缓存。
另外,Redis也经常用来做分布式锁。
除此之外,Redis支持事务、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。
redis存储的是:key,value 格式的数据,其中Key都是字符串,value有5种不同的数据结构。
命令举例:
字符串类型(String)
存储 set key value
获取 get key
删除 del key
哈希类型(map格式)
存储 hset key field value
获取指定的field对应的值 hget key field
获取所有的field和value hgetall key
删除 hdel key field
列表类型(list,linkedlist格式)
将元素加入列表左表 lpush key value
将元素加入列表右边 rpush key value
范围获取 lrange key start end
删除列表最左边的元素,并将元素返回 lpop key
删除列表最右边的元素,并将元素返回 rpop key
集合类型(set)
存储 sadd key value
获取所有元素 smembers key
删除集合中某个元素 srem key value
有序集合类型(sortedset)
存储 zadd key score value
获取 zrange key start end
删除 zrem key value
通用命令
查询所有键 key *
获取键对应的value类型 tepe key
删除指定的Key,value del key
redis事务的本质就是一组命令的集合。
一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。
redis事务的特性包含一次性、顺序性、排他性。
注意
1、redis事务没有隔离级别的概念。所有的命令在事务中,并没有直接执行,只有发起执行命令的时候才会执行;
2、redis单条命令是保证原子性的,但是事务不保证原子性。
1、基于内存存储实现
内存读写是比在磁盘快很多的,Redis基于内存存储实现的数据库,相对于数据存在磁盘的MySQL数据库,
省去磁盘I/O的消耗。
2、高效的数据结构
Mysql索引为了提高效率,选择了B+树的数据结构。其实合理的数据结构,就是让应用/程序更快。
3、合理的数据编码
Redis支持多种数据数据类型,每种基本类型,可能对多种数据结构。什么时候,使用什么样数据结构,
使用什么样编码,是redis设计者总结优化的结果。
4、合理的线程模型
Redis是单线程模型的,而单线程避免了CPU不必要的上下文切换和竞争锁的消耗。也正因为是单线程,
如果某个命令执行过长(如hgetall命令),会造成阻塞。Redis是面向快速执行场景的数据库。所以要慎用如smembers和lrange、hgetall等命令。
Redis 6.0 引入了多线程提速,它的执行命令操作内存的仍然是个单线程。
5、虚拟内存机制
Redis直接自己构建了VM机制 ,不会像一般的系统会调用系统函数处理,会浪费一定的时间去移动和请求。
(Redis的虚拟内存机制是啥呢?)
虚拟内存机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据)。
通过VM功能可以实现冷热数据分离,使热数据仍在内存中、冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。
常见的缓存使用方式:
读请求来了,先查下缓存,缓存有值命中,就直接返回;
缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回。
缓存穿透指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,
这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
通俗点说,读请求访问时,缓存和数据库都没有某个值,这样就会导致每次对这个值的查询请求都会穿透到数据库,
这就是缓存穿透。
(缓存穿透一般都是这几种情况产生的):
1、业务不合理的设计。
2、业务/运维/开发失误的操作,比如缓存和数据库的数据都被误删除了。
3、黑客非法请求攻击,比如黑客故意捏造大量非法请求,以读取不存在的业务数据。
(如何避免缓存穿透呢? 一般有三种方法。)
1、如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。
2、如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。
3、使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。
(布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。)
缓存雪奔:指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,
引起数据库压力过大甚至down机。
1、缓存雪奔一般是由于大量数据同时过期造成的,对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+0到1800秒。
2、Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群。
缓存击穿: 指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,
从而大量的请求打到数据库。
(解决方案就有两种):
1、使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
2、“永不过期”,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间。
缓存击穿和缓存雪奔区别
缓存击穿看着有点像缓存雪奔,其实它两区别是:
缓存雪奔是指数据库压力过大甚至down机,
缓存击穿只是大量并发请求到了DB数据库层面。
可以认为击穿是缓存雪奔的一个子集吧。
有些文章认为它俩的区别在于击穿针对某一热点key缓存,雪奔则是很多key。
什么是热Key问题,如何解决热key问题
(什么是热Key呢?)在Redis中,我们把访问频率高的key,称为热点key。
如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务。
(而热点Key是怎么产生的呢?主要原因有两个:)
1、用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
2、请求分片集中,超过单Redis服务器的性能,比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题。
(在日常开发中,如何识别到热点key呢?)
1、凭经验判断哪些是热Key;
2、客户端统计上报;
3、服务代理层上报。
(如何解决热key问题?)
1、Redis集群扩容:增加分片副本,均衡读流量;
2、将热key分散到不同的服务器中;
3、使用二级缓存,即JVM本地缓存,减少Redis的读请求。
1、定时过期
每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
2、惰性过期
只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
3、定期过期
每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。
该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。
volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;
allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰。
volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key。
allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰;
volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据;。
allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰;
noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。
Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了持久化,即把数据保存到磁盘。
RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。
执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。
RDB 的优点:适合大规模的数据恢复场景,如备份,全量复制等。
RDB缺点:没办法做到实时持久化/秒级持久化;新老版本存在RDB格式兼容问题。
【触发机制】
三种情况下会触发RDB规则:
1.sava的规则满足的情况下,会自动触发rdb规则
2.执行fushall命令,也会触发我们的rdb规则
3.退出redis,也会产生rdb文件
【恢复机制】
只需要将rdb文件放在redis启动目录下即可,redis启动的时候回自动检测dump.rdb,恢复数据。
RDB默认方式,不需要进行配置,默认就使用这种机制,在一定的间隔时间中,检测key的变化情况,然后持久化数据
AOF(append only file) 持久化,采用日志的形式来记录每个写操作,
将redis执行过的所有指令记录下来(读操作不记录),追加到文件中,且只许追加文件但不可以改写文件,
重启时再重新执行AOF文件中的命令来恢复数据。
换句话说,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
它主要解决数据持久化的实时性问题。
AOF模式保存的文件名是appendonly.aof。在redis配置中,默认是不开启的,开发者需要手动配置,
只需要将appendonly改为yes即可开启、再进行重启redis,就可以生效了。
AOF默认就是文件的无限追加,文件会越来越大,如果aof文件大于64m,redis会fork一个新的进程来将文件进行重写。
AOF的优点:数据的一致性和完整性更高。
AOF的缺点:AOF记录的内容越多,文件越大,数据恢复变慢。
注意:
1.redis是一个内存数据库,当redis 服务器重启,或者电脑重启,数据会丢失,我们可以将redis 内存中的数据持久化保存到硬盘的文件中。
2.修改后,重启redis服务器,并制定配置文件的名称。
在项目中使用Redis,肯定不会是单点部署Redis服务的。因为,单点部署一旦宕机,就不可用了。为了实现高可用,通常的做法是,将数据库复制多个副本以部署在不同的服务器上,其中一台挂了也可以继续提供服务。
Redis 实现高可用有三种部署模式:主从模式,哨兵模式,集群模式。
主从模式中,Redis部署了多台机器,有主节点,负责读写操作,有从节点,只负责读操作。
从节点的数据来自主节点,实现原理就是主从复制机制。
主从复制包括全量复制,增量复制两种。
一般当slave第一次启动连接master,或者认为是第一次连接,就采用全量复制,全量复制流程如下:
1.slave发送sync命令到master。
2.master接收到SYNC命令后,执行bgsave命令,生成RDB全量文件。
3.master使用缓冲区,记录RDB快照生成期间的所有写命令。
4.master执行完bgsave后,向所有slave发送RDB快照文件。
5.slave收到RDB快照文件后,载入、解析收到的快照。
6.master使用缓冲区,记录RDB同步期间生成的所有写的命令。
7.master快照发送完毕后,开始向slave发送缓冲区中的写命令;
8.salve接受命令请求,并执行来自master缓冲区的写命令
主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。显然,多数业务场景都不能接受这种故障处理方式。Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。
哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的Redis主节点和从节点,
并在被监视的主节点进入下线状态时,自动将下线主服务器属下的某个从节点升级为新的主节点。
但是呢,一个哨兵进程对Redis节点进行监控,就可能会出现问题(单点问题),
因此,可以使用多个哨兵来进行监控Redis节点,并且各个哨兵之间还会进行监控。
Sentinel哨兵模式
简单来说,哨兵模式就三个作用:
1)发送命令,等待Redis服务器(包括主服务器和从服务器)返回监控其运行状态;
2)哨兵监测到主节点宕机,会自动将从节点切换成主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机;
3)哨兵之间还会相互监控,从而达到高可用。
(故障切换的过程是怎样的呢?)
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵1主观的认为主服务器不
可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进
行一次投票,投票的结果由一个哨兵发起,进行 failover 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自
己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
(哨兵的工作模式如下:)
1、每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他Sentinel实例发送一个 PING命令。
2、如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel标记为主观下线。
3、如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
4、当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线。
5、在一般情况下, 每个 Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
6、当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
7、若没有足够数量的 Sentinel同意Master已经下线, Master的客观下线状态就会被移除;若Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。因此,Cluster集群应运而生,它在Redis3.0加入的,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它也提供复制和故障转移的功能。
Cluster集群节点的通讯 (一个Redis集群由多个节点组成,各个节点之间是怎么通信的呢?通过Gossip协议)
Redis Cluster集群通过Gossip协议进行通信,节点之前不断交换信息,交换的信息内容包括节点出现故障、新节点加入、主从节点变更信息、slot信息等等。
常用的Gossip消息分为4种,分别是:ping、pong、meet、fail。
1)meet消息:通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换。
2)ping消息:集群内交换最频繁的消息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。
3)pong消息:当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。
4)fail消息:当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。
为了保证高可用,Cluster集群引入了主从复制,一个主节点对应一个或者多个从节点。
当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与 A 通信超时,那么认为主节点 A 宕机了。
如果主节点宕机时,就会启用从节点。
Redis集群实现了高可用,当集群内节点出现故障时,通过故障转移,以保证集群正常对外提供服务。
redis集群通过ping/pong消息,实现故障发现。这个环境包括主观下线和客观下线。
1)主观下线: 某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。
2)客观下线: 指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。
(流程)
1、假如节点A标记节点B为主观下线,一段时间后,节点A通过消息把节点B的状态发到其它节点,当节点C接受到消息
并解析出消息体时,如果发现节点B的pfail状态时,会触发客观下线流程;
2、当下线为主节点时,此时Redis Cluster集群为统计持有槽的主节点投票,看投票数是否达到一半,当下线报告统计数
大于一半时,被标记为客观下线状态。
故障恢复:故障发现后,如果下线节点的是主节点,则需要在它的从节点中选一个替换它,以保证集群的高可用。
流程如下:
1)资格检查:检查从节点是否具备替换故障主节点的条件。
2)准备选举时间:资格检查通过后,更新触发故障选举时间。
3)发起选举:到了故障选举时间,进行选举。
4)选举投票:只有持有槽的主节点才有票,从节点收集到足够的选票(大于一半),触发替换主节点操作
1、缓存延时双删
延时双删流程:先删除缓存;再更新数据库;休眠一会(比如1秒),再次删除缓存。
2、删除缓存重试机制
因为延时双删可能会存在第二步的删除缓存失败,导致的数据不一致问题。
可以使用这个方案优化:删除失败就多删除几次,保证删除缓存成功就可以。所以可以引入删除缓存重试机制。
删除缓存重试流程:写请求更新数据库;缓存因为某些原因,删除失败;把删除失败的key放到消息队列;
消费消息队列的消息,获取要删除的key;重试删除缓存操作。
3、读取biglog异步删除缓存
通过数据库的binlog来异步淘汰key。
(以mysql为例)
可以使用阿里的canal将binlog日志采集发送到MQ队列里面
然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性。
为什么Redis 6.0 之后改多线程呢?
redis使用多线程并非是完全摒弃单线程,redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程。这样做的目的是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。
RedisTemplate是一个key和value都是泛型的模板类,一般情况下key为String类型,如:RedisTemplate
(此外,如果没特殊情况,切勿定义成RedisTemplate
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
RedisTemplate中定义了对5种数据结构操作:
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
其实这里的ops相当于options, 是RedisTemplate对各种不同的Redis数据类型进行操作。其实还有另外的方法:
redistempalate.boundValueOps
redistempalate.boundSetOps
redistempalate.boundListOps
redistempalate.boundHashOps
redistempalate.boundZSetOps
【opsForXXX和boundXXXOps的区别?】
XXX为value的类型,前者获取一个operator,但是没有指定操作的对象(key),可以在一个连接(事务)内操作多个key以及对应的value;
后者获取了一个指定操作对象(key)的operator,在一个连接(事务)内只能操作这个key对应的value。
@Autowired
private RedisTemplate redisTemplate;
// 删除key
public void delete(String key){
redisTemplate.delete(key);
}
// 删除多个key
public void deleteKey (String ...keys){
redisTemplate.delete(keys);
}
// 指定key的失效时间
public void expire(String key,long time){
redisTemplate.expire(key,time,TimeUnit.MINUTES);
}
// 根据key获取过期时间
public long getExpire(String key){
Long expire = redisTemplate.getExpire(key);
return expire;
}
// 判断key是否存在
public boolean hasKey(String key){
return redisTemplate.hasKey(key);
}
直接用redisTemplate操作redis,需要很多行代码,因此直接封装好一个RedisUtil ,这样写代码更方便点。这个RedisUtil 交给spring容器实例化,使用时直接注解注入。
【RedisUtil 使用示例:】
@Autowired
private RedisUtil redisUtil;
List
/**
* @component (把普通pojo实例化到spring容器中,相当于配置文件中的)
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),
我们就可以使用@Component来标注这个类。
1、@Service用于标注业务层组件
2、@Controller用于标注控制层组件(如struts中的action)
3、@Repository用于标注数据访问组件,即DAO组件.
4、@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
*/
@Component
public class RedisUtil {
/**
* @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的
* 使用来消除 set ,get方法。在使用@Autowired之前,我们对一个bean配置起属性时,是这用用的
* 通过这种方式来,配置比较繁琐,而且代码比较多。在Spring 2.5 引
* 入了 @Autowired 注释
*/
@Autowired
private RedisTemplate redisTemplate;
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return true / false
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据 key 获取过期时间
* @param key 键
* @return
*/
public 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;
}
}
/**
* 删除缓存
* @SuppressWarnings("unchecked") 忽略类型转换警告
* @param key 键(一个或者多个)
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
// 传入一个 Collection 集合
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 则设置无限时间
* @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 递增大小
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于 0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 递减大小
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于 0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
// ============================== Map ==============================
/**
* HashGet
* @param key 键(no null)
* @param item 项(no null)
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取 key 对应的 map
* @param key 键(no null)
* @return 对应的多个键值
*/
public Map