最基本的数据类型,其值最大可存储512M,二进制安全(Redis的String可以包含任何二进制数据,包含jpg对象等)。
注:如果重复写入key相同的键值对,后写入的会将之前写入的覆盖。
列表,按照String元素插入顺序排序。其顺序为后进先出。由于其具有栈的特性,所以可以实现如“最新消息排行榜”这类的功能。
String元素组成的无序集合,通过哈希表实现(增删改查时间复杂度为O(1)),不允许重复。
另外,当我们使用smembers遍历set中的元素时,其顺序也是不确定的,是通过hash运算过后的结果。Redis还对集合提供了求交集、并集、差集等操作,可以实现如同共同关注,共同好友等功能。
用于计数的HyperLogLog、用于支持存储地理位置信息的Geo。
使用 keys [pattern] 指令可以找到所有符合pattern条件的key,但是keys会一次性返回所有符合条件的key,所以会造成redis的卡顿,假设redis此时正在生产环境下,使用该命令就会造成隐患,另外如果一次性返回所有key,对内存的消耗在某些条件下也是巨大的。
例:
keys test* //返回所有以test为前缀的key
cursor:游标
MATCH pattern:查询key的条件
count:返回的条数
SCAN是一个基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程。SCAN以0作为游标,开始一次新的迭代,直到命令返回游标0完成一次遍历。此命令并不保证每次执行都返回某个给定数量的元素,甚至会返回0个元素,但只要游标不是0,程序都不会认为SCAN命令结束,但是返回的元素数量大概率符合count参数。另外,SCAN支持模糊查询。
例:
SCAN 0 MATCH test* COUNT 10 //每次返回10条以test为前缀的key
分布式锁是控制分布式系统之间共同访问共享资源的一种锁的实现。如果一个系统,或者不同系统的不同主机之间共享某个资源时,往往需要互斥,来排除干扰,满足数据一致性。
分布式锁需要解决的问题如下:
互斥性:任意时刻只有一个客户端获取到锁,不能有两个客户端同时获取到锁。
安全性:锁只能被持有该锁的客户端删除,不能由其它客户端删除。
死锁:获取锁的客户端因为某些原因而宕机继而无法释放锁,其它客户端再也无法获取锁而导致死锁,此时需要有特殊机制来避免死锁。
容错:当各个节点,如某个redis节点宕机的时候,客户端仍然能够获取锁或释放锁。
使用SETNX实现
SETNX key value:如果key不存在,则创建并赋值。该命令时间复杂度为O(1),如果设置成功,则返回1,否则返回0。
由于SETNX指令操作简单,且是原子性的,所以初期的时候经常被人们作为分布式锁,我们在应用的时候,可以在某个共享资源区之前先使用SETNX指令,查看是否设置成功,如果设置成功则说明前方没有客户端正在访问该资源,如果设置失败则说明有客户端正在访问该资源,那么当前客户端就需要等待。
但是如果真的这么做,就会存在一个问题,因为SETNX是长久存在的,所以假设一个客户端正在访问资源,并且上锁,那么当这个客户端结束访问时,该锁依旧存在,后来者也无法成功获取锁,这个该如何解决呢?
由于SETNX并不支持传入EXPIRE参数,所以我们可以直接使用EXPIRE指令来对特定的key来设置过期时间。
用法:EXPIRE key seconds
RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key,"1");
if(status == 1){
redisService.expire(key,expire);
doOcuppiedWork();
}
这段程序存在的问题:假设程序运行到第二行出现异常,那么程序来不及设置过期时间就结束了,则key会一直存在,等同于锁一直被持有无法释放。出现此问题的根本原因为:原子性得不到满足。
解决:从Redis2.6.12版本开始,我们就可以使用Set操作,将Setnx和expire融合在一起执行,具体做法如下。
SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
EX second:设置键的过期时间为second秒。
PX millisecond:设置键的过期时间为millisecond毫秒。
NX:只在键不存在时,才对键进行设置操作。
XX:只在键已经存在时,才对键进行设置操作。
注:SET操作成功完成时才会返回OK,否则返回nil。
有了SET我们就可以在程序中使用类似下面的代码实现分布式锁了:
RedisService redisService = SpringUtils.getBean(RedisService.class);
String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);
if("OK.equals(result)"){
doOcuppiredWork();
}
使用上文所说的Redis的数据结构中的List作为队列 Rpush生产消息,LPOP消费消息。
此时我们可以看到,该队列是使用rpush生产队列,使用lpop消费队列。在这个生产者-消费者队列里,当lpop没有消息时,证明该队列中没有元素,并且生产者还没有来得及生产新的数据。
缺点:lpop不会等待队列中有值之后再消费,而是直接进行消费。
弥补:可以通过在应用层引入Sleep机制去调用LPOP重试。
BLPOP key [key …] timeout:阻塞直到队列有消息或者超时。
缺点:按照此种方法,我们生产后的数据只能提供给各个单一消费者消费,能否实现生产一次就能让多个消费者消费呢?
发送者(pub)发送消息,订阅者(sub)接收消息。
消息的发布是无状态的,无法保证可达。对于发布者来说,消息是“即发即失”的,此时如果某个消费者在生产者发布消息时下线,重新上线之后,是无法接收该消息的,要解决该问题需要使用专业的消息队列,如kafka…此处不再赘述。