欢迎光临
我们一直在努力

Redis学习专栏(基础知识):Redis数据结构和常用实现

本站教程收集整理的这篇文章主要介绍了Redis学习专栏(基础知识):Redis数据结构和常用实现,本站教程本站觉得挺不错的,现在分享给大家,也给大家做个参考。

一. redis数据结构

String

最基本的数据类型,其值最大可存储512M,二进制安全(redis的String可以包含任何二进制数据,包含jpg对象等)。

注:如果重复写入key相同的键值对,后写入的会将之前写入的覆盖。

Hash

String元素组成的字典,适用于存储对象。

List

列表,按照String元素插入顺序排序。其顺序为后进先出。由于其具有栈的特性,所以可以实现如“最新消息排行榜”这类的功能。

Set

String元素组成的无序集合,通过哈希表实现(增删改查时间复杂度为O(1)),不允许重复。

另外,当我们使用smembers遍历set中的元素时,其顺序也是不确定的,是通过hash运算过后的结果。redis还对集合提供了求交集、并集、差集等操作,可以实现如同共同关注,共同好友等功能。

sorted set

通过分数来为集合中的成员进行从小到大的排序。

更高级的redis类型

用于计数的HyperLogLog、用于支持存储地理位置信息的Geo。

二. Keys命令

KEYS [pattern]:查找所有符合给定模式pattern的key

使用 keys [pattern] 指令可以找到所有符合pattern条件的key,但是keys会一次性返回所有符合条件的key,所以会造成redis的卡顿,假设redis此时正在生产环境下,使用该命令就会造成隐患,另外如果一次性返回所有key,对内存的消耗在某些美国高防vps条件下也是巨大的。

例:

keys test* //返回所有以test为前缀的key

SCAN cursor [MATCH pattern] [COUNT count]

cursor:游标
MATCH pattern:查询key的条件
count:返回的条数

SCAN是一个基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程。SCAN以0作为游标,开始一次新的迭代,直到命令返回游标0完成一次遍历。此命令并不保证每次执行都返回某个给定数量的元素,甚至会返回0个元素,但只要游标不是0,程序都不会认为SCAN命令结束,但是返回的元素数量大概率符合count参数。另外,SCAN支持模糊查询。

例:

SCAN 0 MATCH test* COUNT 10 //每次返回10条以test为前缀的key

三. redis常用实现

1.redis实现分布式锁

分布式锁是控制分布式系统之间共同访问共享资源的一种锁的实现。如果一个系统,或者不同系统的不同主机之间共享某个资源时,往往需要互斥,来排除干扰,满足数据一致性

分布式锁需要解决的问题如下:

互斥性:任意时刻只有一个客户端获取到锁,不能有两个客户端同时获取到锁。

安全性:锁只能被持有该锁的客户端删除,不能由其它客户端删除。

死锁:获取锁的客户端因为某些原因而宕机继而无法释放锁,其它客户端再也无法获取锁而导致死锁,此时需要有特殊机制来避免死锁。

容错:当各个节点,如某个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();
}

2. 实现异步队列

  • 使用redis中的List作为队列

使用上文所说的redis的数据结构中的List作为队列 Rpush生产消息,LPOP消费消息。

此时我们可以看到,该队列是使用rpush生产队列,使用lpop消费队列。在这个生产者-消费者队列里,当lpop没有消息时,证明该队列中没有元素,并且生产者还没有来得及生产新的数据

缺点:lpop不会等待队列中有值之后再消费,而是直接进行消费。

弥补:可以通过在应用层引入Sleep机制去调用LPOP重试。

  • 使用BLPOP key [key…] timeout

BLPOP key [key …] timeout:阻塞直到队列有消息或者超时。

缺点:按照此种方法,我们生产后的数据只能提供给各个单一消费者消费,能否实现生产一次就能让多个消费者消费呢?

  • pub/sub:主题订阅者模式

发送者(pub)发送消息,订阅者(sub)接收消息。

订阅者可以订阅任意数量的频道

pub/sub模式的缺点:

消息的发布是无状态的,无法保证可达。对于发布者来说,消息是“即发即失”的,此时如果某个消费者在生产者发布消息时下线,重新上线之后,是无法接收该消息的,要解决该问题需要使用专业的消息队列,如kafka…此处不再赘述。

本站总结

以上是本站教程为你收集整理的Redis学习专栏(基础知识):Redis数据结构和常用实现全部内容,希望文章能够帮你解决Redis学习专栏(基础知识):Redis数据结构和常用实现所遇到的程序开发问题。

如果觉得本站教程网站内容还不错,欢迎将本站教程推荐给好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。

赞(0)
【声明】:本博客不参与任何交易,也非中介,仅记录个人感兴趣的主机测评结果和优惠活动,内容均不作直接、间接、法定、约定的保证。访问本博客请务必遵守有关互联网的相关法律、规定与规则。一旦您访问本博客,即表示您已经知晓并接受了此声明通告。