【享学Redis】Redis典型应用及原理分析

Redis 使用场景与典型案例全面解析 Redis涵盖:缓存、锁、队列、限流、计数、地理位置、过滤器、位图、流式计算等大量场景,是现代高并发系统的核心组件。本文完整覆盖 Redis 全部典型应用场景。 阅读详情

引言

Redis全称Remote Dictionary Server,直译为“远程字典服务器”。Redis使用ANSI C语言编写,是一款开源的、基于单线程模型,且支持主从模式的nosql。使用内存提供主存储支持、也支持将内存中的数据以快照或日志的形式持久化到硬盘,提供多种语言的API。

Redis因其高性能、丰富的数据结构等特性在互联网中有着广泛应用:应用最多的领域是缓存,同时也是分布式锁常用的实现方式,除此之外Redis还可以应用于实现消息队列、排行榜、网站统计等,总结如下图:
在这里插入图片描述

Redis缓存

缓存是Redis最常见的应用场景,主要是基于其高效的读写性能,且Redis内部支持事务,在使用时能够有效保证数据的一致性,同时持久化和主备机制保证了数据的高可用性。

举例,网站发布一个促销活动,每天有百万人次访问,如果每次都从数据库查询,则需要消耗百万次数据库链接请求,耗费资源且低效。如果将这些数据存储到Redis(内存)中,每次查询时直接从内存中读取,不仅大大节约了系统访问数据库造成的资源开销,而且可以极大地提高查询返回效率,提升用户体验。

Redis用作缓存性能高、读写速度快,Redis高效的原因总结如下:

Redis是基于内存数据库,绝大部分请求都是纯粹的内存操作,因此非常快速;

  • Redis多样的数据结构,每种数据结构处理适合的数据应用,相对于数据库只有Table,MongogoDB只有JSON一种结构处理速度自然比较好;

  • Redis使用的是非阻塞IO,IO多路复用:将数据库的开、关、读、写都转换成了事件,利用 select、poll、epoll 具备监听事件流的能力;

  • Redis采用单线程处理命令,避免了不必要的上下文切换和竞争条件,也不存在多进程切换导致的CPU消耗。这里稍作解释:多线程的本质是CPU模拟出多个线程来,模拟的代价就是CPU要进行上下文切换;举例,假设CPU主频为2.5GHZ,即每秒可以执行2.5*10^9个指令,每个指令执行时间也仅0.4ns,而一次上下文切换时间却大概需要2000ns;在涉及下层存储等慢速情况下,如磁盘I/O交互时,线程等待磁盘数据读取或写入,空闲的CPU就可以再服务于其它线程,这种情况下多线程效率才会高于单线程。但是Redis是纯粹内存操作的,引入多线程带来的上下文切换时间要远大于串行执行指令的时间,所以Redis采用单线程可以带来更高的效率。

Redis相比其他缓存,另一个非常大的优势,就是支持多种数据类型。

  • String(字符串)

常用命令:GET、SET、INCR、SETNX、GETSET、MGET等;

  • List(列表)

常用命令:LPUSH、RPUSH、LPOP、RPOP、LRANGE等;

  • Hash(字典)

常用命令:HGET、HSET、HGETALL等;

  • Set(集合)

常用命令:SADD、SPOP、SMEMBERS、SUNION等;

  • Sorted Set(有序集合)

Sorted Set是一种有序集合,和Set相比,增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。

常用命令:ZADD、ZRANGE、ZREM、ZCARD等。

2 Redis分布式锁

互联网信息技术不断发展,传统集中式系统已逐渐无法满足要求,分布式系统被利用在越来越多的场景中。分布式系统会面临共享资源、同步访问等问题,传统的锁或者synchronized代码块只能解决单机同步访问问题。但对于如电商平台秒杀库存扣减、共同组队玩一个游戏等,多系统共同访问会伴随脏数据的产生,这个时候就需要分布式锁来解决。

分布式锁:为分布式应用多个节点对共享资源的排他式访问而设定的锁就是分布式锁,其实就是保证共享资源某一时刻只能被某台机器JVM进程下某个线程访问。

分布式锁可以基于很多种方式实现,数据库锁、ZooKeeper、Redis…不管哪种方式,基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

2.1 Redis分布式锁的原理

Redis做分布式锁实现起来比较简单,实现方式:

(1)获取锁的时候,使用SETNX加锁,同时设置一个超时时间(SET命令加NX选项,加上超时时间),超过该时间则自动释放锁,锁的value值可以设置为客户端的标识,释放锁的时候进行判断;

(2)释放锁的时候,通过客户端标识判断是不是该锁,若是该锁,则执行DEL进行锁释放。

2.1.1 加锁

加锁原理,利用SET命令:

SET KEY VALUE [NX|XX] [EX seconds] [PX milliseconds]
由于SET命令加上选项已经可以完全取代SETNX, SETEX, PSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。

如果key不存在就设置key,同时设置NX和EX(或PX)属性以及过期时间。

方法参数解释:

  1. 第一个为key,即锁名称,key是唯一的;

  2. 第二个为value,传的是请求Id,通过给value赋值为requestId,就知道这把锁是哪个客户端请求加的,在解锁的时候可以利用该值判断是否为同一客户端;

  3. 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,当key不存在时,进行set操作;若key已经存在,则不做任何操作,保证了只有一个客户端持有锁,即满足互斥性;

  4. 第四个为expx,这个参数传EX或PX,意思是要给这个key加一个过期的设置,单位为秒或毫秒;

  5. 第五个为time,与第四个参数相呼应,代表key的过期时间,设置过期时间,是为了避免死锁的发生。

那么SET命令是如何执行的呢?setCommand函数是server端解析客户端的SET命令,不论是单纯的SET,还是有NX或PX附加属性,或是附加了过期时间参数,都是一条命令发到server端,而后server端在setCommand方法里,对传进来的参数进行遍历,判断是不是要set if not existed,是不是设置了过期时间,单位是秒还是毫秒等等。最后再去调用setGenericCommand方法,往内存设置值。Redis server虽然也是分步去设置key值和过期时间,但Redis是单线程处理命令的,Redis执行这段函数的过程中,不可能有精力去执行其他函数,所以,就算是分成多个个动作去执行,也不影响命令的原子性。
在这里插入图片描述
再来看setGenericCommand函数的处理流程:

在这里插入图片描述
不想看源码?没关系,细心的少侠帮你们能总结了对应的处理流程,即:
在这里插入图片描述
执行上面的SET方法会有两种结果:1)当前没有锁(key不存在),那么就执行加锁操作,并对锁设置有效期;2)已经有锁存在,不做任何操作。

这里要注意一定不要把设置key和过期时间分成SETNX和EXPIRE两个命令执行,由于这是两条Redis命令,不具有原子性,如果在 NX 之后程序突然崩溃,导致锁没有设置过期时间,那么将会发生死锁。

2.1.2 解锁

1 解锁的“错误姿势”
"DEL key"命令

DEL key [key …]

加锁是SET key,那么解锁用"DEL key"命令,将表示锁的key删除。但考虑到实际情况,通常不能简单的删除key,否则会导致任何客户端都可以解锁,即使这把锁不是它的。

举例,若设置锁的超时时间不合理,客户端A锁过期时间为2s,锁过期之后客户端B获得了该锁,A执行完成任务用del命令解锁的话删除的就是客户端B的锁,如下图所示:
在这里插入图片描述
那么是否可以利用代码判断客户端标识呢?利用if语句进行判断,只有当锁的客户端标识与解锁命令发起的客户端相同,才能进行解锁,如下代码所示:

   public void unlock(String key, String requestId) {
        if (requestId.equals(jedis.getResource().get(key))) {
            jedis.getResource().del(key);
        }
    }

上面代码的实现解锁也是错误的,如果客户端A持有锁,并现在执行解锁操作,if判断标识相等;但此时A的锁过期了,且客户端B与此同时获得了锁,A再执行del的操作,实际上是删除了客户端B的锁,示意图如下:
在这里插入图片描述
2 “正确姿势”–利用lua命令解锁
Redis 2.6之后支持的功能,可以将一段Lua脚本发送到Redis服务器运行。Redis本身可提供丰富的指令集之外,同时还支持自定义扩充指令,即用户可以向服务器发送 Lua 脚本来执行自定义动作。Redis 服务器单线程原子性地执行 Lua 脚本,保证 Lua 脚本在处理的过程中不会被其它任意请求打断。

Redis使用lua脚本的好处:

  • 减少网络开销,可以将多个请求通过脚本的形式一次发送,减少网络时延;

  • 原子操作,Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入,因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务;

  • 复用,客户端发送的脚本可以存在Redis服务器上,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。

下面就是用来解锁的Lua脚本:
在这里插入图片描述
上述参数中,KEYS[1]赋值为锁名称lockKey,get命令获取lockKey对应的value值(即该锁对应的客户端标识),ARGV[1]赋值为解锁请求的客户端标识,若二者相等才执行del命令解锁。在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令,实现原子性解锁。举例,当前有个库存锁“stockLock”,客户端A已持有,加锁时设置的value值为客户端A的标识"client-a";那么解锁的时间就会判断“stockLock”的value值是否为"client-a",确认标识之后才能del命令删除key值,解锁成功。

2.2 Redisson分布式锁

Redis分布式锁性能高效、利用集群可避免单点故障;Redis命令支持友好、实现方便。上面加解锁已经可以满足一般场景下分布式锁的需求,但仍有些方面可以进一步优化:

  • 锁的失效时间(过期时间)设置,在高并发下分布式锁实现中,key的过期时间肯定不能设置的太长,否则会影响后续线程持有该锁;但如果设置过期时间很短,直到key过期,持有该锁的线程还未执行完任务;

  • 获取锁是非阻塞的,无论成功还是失败就直接返回,需要加额外的逻辑实现阻塞锁;

  • 若需要锁具备可重入性,也需要额外的逻辑实现;

Redisson是Redis官网推荐的java语言实现分布式锁的项目,是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。对于上面提出的问题,Redisson都提供了相应的优化方案。项目中可以直接引入Redisson,使用其已经实现的各种分布式锁。当然也可以借鉴其实现思路,在自己代码中用Redis实现多种类的分布式锁。

针对上面Redis实现过程中需要注意的问题,Redisson通过以下方式来优化解决:

  • 失效时间:如果某个线程在失效时间内,还未执行完成,守护线程自动设置一个新失效时间,直到该线程执行完成或释放;

  • 阻塞锁: 通过订阅消息和while循环去实现阻塞锁代码;

  • Redisson锁本身自带可重入性质,相同线程可以对同一把锁递归调用。

1 加锁
以无参的trylock()函数来进行举例说明:
在这里插入图片描述
其中,获取锁调用的是tryLockInnerAsync函数,源码如下:
在这里插入图片描述
上面tryLockInnerAsync函数中用到的Redis命令有:

HSET命令

HSET dict key value

与SET命令不同的是操作的数据类型不同,SET操作的是字符串,而HSET操作的是哈希(字典)类型。将哈希表 dict 中的key对应值设为value ;如果哈希表dict不存在,一个新的哈希表被创建。

HINCRBY命令

HINCRBY dict key increment

为哈希表dict中key对应的值加上增量increment 。

举例来说明,我们新建一个Redisson的锁用来锁商品库存,RLock lock0 = redisson.getLock(“stockLock”); lock0对应的锁名称getName()即为“stockLock”,Redisson对应分布锁内存结构如下:

在这里插入图片描述
Redisson锁有可重入机制,即同一个线程在外层方法获取锁的时候再进入该线程的内层方法自动获取锁,无需再次申请,但需要记录重入次数。

那么如何判断是同一个线程呢?Redisson中是用Redisson客户端ID+线程ID,getLockName(long threadId)方法获取。其中Redisson客户端ID在创建该Redisson实例时就唯一标识了;在分布式环境中,不同节点的线程ID可能会相同,但是用Redisson客户端ID+线程ID就不会出现重复。

函数主体是一段lua脚本来尝试获取锁。上面tryLockInnerAsync函数lua脚本中参数KEY[1]即getName方法获取的锁名称,对应举例中的“stockLock”。两个变量,ARGV[1]leaseTime,锁失效时间;ARGV[2]即为Redisson客户端ID+线程ID。执行lua脚本tryLockInnerAsync获取锁的逻辑图如下:

在这里插入图片描述
如果通过 exists 命令发现当前 key 不存在,即锁没被占用,则执行 hset 写入 Hash 类型数据 key:全局锁名称;field: Redisson客户端ID+线程ID,;value:锁重入次数。并执行 pexpire 对该 key 设置失效时间,返回空值 nil,至此获取锁成功;如果通过 hexists 命令发现 Redis 中已经存在当前 key 和 field 的 Hash 数据,说明当前线程之前已经获取到锁,这里的锁是可重入的,执行 hincrby 对当前 key field 的值加一,并重新设置失效时间,返回空值,至此重入获取锁成功;最后是锁已被占用的情况,即当前 key 已经存在,但是 Hash 中的 field 与当前值不同,则执行 pttl 获取锁的剩余ttl并返回,至此获取锁失败。

延时任务函数scheduleExpirationRenewal,源码如下:
在这里插入图片描述
添加一个netty的Timeout回调任务,注册延时任务,在1/3 失效时间时触发,将锁的失效时间进行重置。

2 解锁
在这里插入图片描述
最终调用的是unlockInnerAsync函数进行解锁,源码如下:
在这里插入图片描述
函数主体也是一段lua脚本来释放锁。

两个key,key[1]全局锁名称,key[2]是getChannelName获取的指定的信息频道channel;

变量有三个,ARGV[1]是发布在频道channel的解锁消息,值为0;ARGV[2] 锁失效时间;ARGV[3]是Redisson客户端ID+线程ID。
在这里插入图片描述
通过 exists 命令发现该锁不存在,即已被释放,发布信息解锁消息通知到指定的频道channel,同时返回1;如果锁存在,则判断锁是否被当前线程使用,如果不是直接返回nil;如果锁被当前线程使用,则将field域对应的value值减1;减1后的value值和0进行比较,如果大于0,重置失效时间,返回0;否则则认为该锁已被当前线程释放,删除锁,发布解锁消息,返回1;如果上面条件都没有命中返回nil。

分析完Redisson加锁解锁的过程,再回到上面Redis实现分布式锁需要注意的问题,Redisson解决方式总结如下:

- 锁过期时间设置

Redisson设置监控程序,在scheduleExpirationRenewal函数中,使用Netty的Timeout回调任务,每隔1/3失效时间(internalLockLeaseTime )毫秒执行一次,检查线程是否执行完毕,并重置超时时间。

- 可重入

通上述加锁、解锁过程中的源码分析,Redisson的可重入是通过哈希表类型维护的,Redisson客户端ID+线程ID是分布式锁的唯一标识,value值是对应的重入次数。

- 阻塞锁

非阻塞获取锁,如果获取不到锁就马上返回;阻塞锁是指当线程尝试获取锁失败时,在设置的超时时间内进行等待阻塞。上面无参的trylock即是非阻塞锁,阻塞锁可以通过Redisson中带参数的trylock来实现,源码如下:
在这里插入图片描述
在上面阻塞锁代码需要等待的场景主要有两个个地方,首先是在订阅时,等待订阅命令必须在指定的时间内完成;

其次是在while循环里,getEntry(threadId).getLatch()是获取RedissonLockEntry对象的信号量Semaphore属性,后面的tryAcquire是在指定时间内获取信号量消息,即通过信号量进行阻塞等待。

RedissonFairLock是基于Redis的分布式可重入公平锁,它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。此外基于Redisson实现的还有RedissonMultiLock、RedissonRedLock、RReadWriteLock等等,限于篇幅本文就不在此一一介绍,有兴趣的同学可以阅读相关源码学习。

2.3 集群环境Redis分布式锁RedLock

上面的分布式锁在只有一个Redis实例的情况下安全性足以保证,但在集群环境中,却不能做到足够安全。
在这里插入图片描述
如图所示,若client1申请分布式锁成功,主节点故障挂掉,但是锁信息还未及时同步到从节点;此时Slave通过选举竞争成为新的主节点,若client2此时申请同一把分布锁,由于New Master内部没有该锁,于是client2申请锁成功,同一把锁被两个客户端拥有,违反了分布锁的互斥性,带来了不安全性。

上述情况只有在客户端申请锁成功瞬间,主机宕掉且未及时同步锁信息到从节点才会发生,发生概率很低且持续时间短,多数业务系统都可以容忍。

不过,Redis作者Antirez不甘于此,提出了RedLock算法来解决集群环境下Redis分布锁安全性问题,Distributed locks with Redis 官网对算法过程有详细介绍,本文简单描述一下:
在这里插入图片描述
RedLock算法的核心是“大多数机制”,需要提供多个Redis主节点,并且这些Redis节点相互独立;加锁时半数以上节点加锁成功才会认为申请锁成功。

如图我们假设N=5,另外图中的从节点只是示意,实际情况中一个Master可能对应不止一个Slave节点。

  1. 以毫秒为单位获取当前时间startTime;

  2. 按顺序依次向上面5个主Redis节点发送获取锁命令,客户端在等待每个Redis响应时都会有超时时间,当然设置的这个超时时间要相较于锁的过期时间小很多。例如,锁的过期时间LeaseTime为10s,那么客户端等待的超时时间可以设置为5~50ms之间,这样防止在某个故障的master节点上长时间阻塞,如果当前Redis实例未能在超时时间范围内获取到锁,就认为当前节点获取锁失败,立即与下一个节点交互;

  3. RedLock利用“大多数机制”,需要获取至少超过半数主节点Redis实例的锁,N=5的时,至少需要获取3个实例的锁;记录下获取到3个锁时的时间endTime,endTime - startTime < LeaseTime,即获取到锁的时间小于锁的过期时间,则认为获取锁成功;

  4. 若获取不到超过半数节点的锁,或者获取到锁的时间大于锁的过期时间,则都认为获取该分布锁失败;

  5. 如果获取锁失败,则需要向所有的Master节点发送Lua脚本解锁命令。

RedLock算法比较复杂,不过已经有很多开源库对其进行了封装,用户可以比较方便的使用,如Redlock-rb、Redlock-py、以及2.2节提到的Redisson。

如果对效率和性能要求较高,可用性没有那么严苛,那么使用Redis单机分布式锁加上 Master Slave;使用RedLock需要更多的Redis实例,需要考虑出错重试、时钟漂移等实现细节,且运维等也需要成本;Redlock 并不是设计用来保持强一致性的,对于强一致性需求可以选择更合适的框架,例如 Zookeeper。

3 Redis队列

3.1 消息队列简介

在这里插入图片描述
队列是单个服务实例内部使用,如一个jvm实例内部可以使用Deque、LinkedList、PriorityQueue、ArrayBlockingQueue、LinkedBlockingQueue等等。消息队列一般适用于分布式系统的消息存储和传递,目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等,它们有不同的技术实现,适用于各种场景的消息任务分发,但本质都是为生产者和消费者消息传递建立桥梁。

在业务的实现过程中,即使没有大量流量,解耦和异步化几乎也是处处需要,此时MQ就显得尤为重要。但与此同时MQ也是一个较重的组件,例如使用RabbitMQ就必须为它搭建一个服务器,如果要考虑可用性,就要为服务端建立一个集群,且发送消息的过程也较复杂。所幸的是,Redis提供的list数据结构非常适合做消息队列,如果中小型业务中已经使用Redis缓存,就可以直接使用Redis实现轻量级消息队列。

使用Redis实现消息队列有以下优势:

  • Redis已经广泛运用于各大系统中,无需再次引入其他第三方框架;

  • Redis是基于内存存储的,生产者和消费者的存取速度都非常快;

  • Redis消息队列,发送和接收消息的实现都很简单。

3.2 Redis实现轻量级消息队列

3.2.1 Redis的push/pop机制

列表类型(List)用于存储一个有序的字符串列表,常用的操作是向队列两端添加元素或者获得列表的某一片段。列表内部使用的是双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度是O(1),获取越接近列表两端的元素的速度越快。Redis队列添加或获取元素都是十分高效的。

生产消息LPUSH命令

LPUSH key value [value …]

LPUSH的作用是将一个或多个值 value 插入到列表 key 的表头。如将Tony添加到朋友列表:redis> LPUSH friends Tony。RPUSH与LPUSH作用是一样的,区别是操作列表key 的表尾。
消费消息RPOP命令

RPOP key [key …]

RPOP 移除并返回列表 key 的尾元素;LPOP移除并返回列表 key 的头元素。

综上,可以使用LPUSH/RPUSH操作入队列,使用LPOP/RPOP命令出队列。
在这里插入图片描述
阻塞读取消息BRPOP命令

阻塞读取消息BRPOP命令

BRPOP是列表的阻塞式(blocking)弹出, 当给定列表内没有任何元素可供弹出的时候,连接将被BRPOP命令阻塞,直到等待超时或发现可弹出元素为止,示例:redis>BRPOP mylist 1000

使用RPOP命令读取消息时,客户端需要不停地调用RPOP命令查看是否又消息,如果队列为空,会陷入到死循环,会造成资源的空耗;使用BRPOP命令,在消息队列为空时阻塞休眠,当有消息时又被唤醒开始进行消息的读取。

3.2.2 Redis的RPOPLPUSH

消费者用RPOP或BRPOP命令取出队列中的元素,若消费者程序在取出元素之后崩溃了,此时消息已被取出且没有正确处理,会造成该消息的丢失,因此会带来业务数据的丢失或状态不一致。

可以通过Redis的RPOPLPUSH命令提供队列的备份机制:消费者程序在从主消息队列中取出消息之后再将其插入到备份队列中,直到消费者程序完成正常的处理逻辑后再将该消息从备份队列中删除。同时还可以提供一个守护进程,当发现备份队列中的消息过期时,可以重新将其再放回到主消息队列中,以便其它的消费者程序继续处理。RPOPLPUSH命令执行过程示意图如下:
在这里插入图片描述
RPOPLPSUH命令

RPOPLPUSH source destination

命令RPOPLPUSH在一个原子时间内,执行以下两个动作:

  • 将列表 source 最后一个元素(尾元素)弹出,返回给客户端调用者;

  • 将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。

举例:

两个列表,source列表里包含的元素是a、b、c;destination为备份列表;执行“RPOPLPUSH source destination”命令后,source中c元素被弹出,返回给客户端;同时会将c弹入destination列表,此时destination列表元素为c。

3.2.3 Redis的PubSub机制

上面的PUSH/POP机制不支持消息多播传递,Redis使用了PubSub发布订阅模式来支持多播消息的传递,下面进行介绍。

订阅频道SUBSCRIBE命令

SUBSCRIBE channel [channel …]

SUBSCRIBE用于订阅一个或多个频道的信息,即subscribe channel1,channel2…。

Redis将频道的订阅关系保存到pubsub_channels字典结构里,如下图:
在这里插入图片描述
订阅频道的源码如下:
在这里插入图片描述
执行SUBSCRIBE命令,如果频道channel已有其它订阅者,Redis将客户端client添加到订阅者链表尾部;如果频道首次被订阅,那么在pubsub_channels创建对应channel的键,然后将client添加到链表中,举例如下图:
在这里插入图片描述

发布消息PUBLISH命令

PUBLISH channel msg

PUBLISH命令用于将消息msg发布到相应的频道,命令执行的源码如下:

在这里插入图片描述
Redis如果处于集群模式,则将消息向所有集群节点进行广播;Redis如果是非集群(即一般的主备机模式),则需要在客户端中增加REDIS_FORCE_REPL标记,以便PUBLISH命令之后能传递给从节点。

具体发现消息的函数是pubsubPublishMessage,源码如下。在发送完所有订阅的client列表之后,如果有和当前频道匹配的其它频道,那么订阅这些模糊匹配的频道的客户端也会收到通知消息。关于模糊匹配的知识本文不再过多介绍。
在这里插入图片描述

3.3 Redis消息队列的局限

1、Redis不是专业的消息队列,没有ACK机制;

2、Redis数据库容量受到物理内存的限制,不能用作海量数据的高性能读写;

3、Redis不支持消费者的负载均衡、不提供队列监控和流量控制等消息队列的高级功能,均需要自己实现;

4、Redis的PubSub发布消息不提供消息持久化,如果订阅者client不在线,该消息就会丢失;

综上,Redis消息队列仅适用于轻量级、对时延有高要求、但是对消息可靠性不做高要求的场景。

4 Redis排行榜

排行榜是业务应用开发中常见的一个场景,如:游戏中的玩家等级排名、分数排名;球赛竞猜积分排行榜;以及电商应用中商品销量、好评等排行。排行榜要求系统能够满足实时、高效的要求,如果每次去查数据库,显然很难满足性能要求,同时会对数据库造成较大压力。

对于排行榜需求,Redis中有一种数据结构—有序集合(sorted set)非常适合,且Redis自带的ZADD命令可以直接实现按得分排序、ZREVRANGE命令能够按照得分来获取前N名用户,ZRANK可以用来获取用户排名,所以能够利用Redis十分方便地实现排行榜的功能。

4.1 sorted set简介

sorted set和set类型极为相似,它们都是字符串集合、都不允许相同元素出现在同一集合里。不同的是sorted set每个元素都会关联一个double类型的分数,Redis正是通过关联的分数对集合中的成员进行排序的,尽管sorted set成员必须唯一,但是关联的分数却是可以重复的。

sorted set通过SkipList(跳跃表)和HashTable(哈希表)的双端口数据结构实现,删除、添加和更新一个元素的时间复杂度为O(log(N)),都是非常快速的操作;又由于成员的位置都是有序的,所以访问一个元素并获取其分数值时间复杂度为O(1);当要求排序的时候,Redis无需要做任何工作了,因为已经全部排好序了。

4.2 Redis排序相关命令

Redis中与sorted set集合相关的命令有ZADD、ZCARD、ZCOUNT、ZINCRBY、ZRANGE、ZRANGEBYSCORE、ZRANK 、ZREM、ZREMRANGEBYRANK、ZREMRANGEBYSCORE、ZREVRANGE、ZREVRANGEBYSCORE、ZREVRANK、ZSCORE、ZUNIONSTORE、ZINTERSTORE、ZSCAN、ZRANGEBYLEX、ZLEXCOUNT、ZREMRANGEBYLEX等。鉴于篇幅限制,本文对几个最常用的排序命令进行阐述并举例,其他相关命令有兴趣同学可在Redis官网上进行阅读学习。

ZADD 设置元素分数

ZADD key score member [[score member] [score member] …]

将一个或多个 member 元素及其 score 值加入到有序集 key 当中,如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值。

举例:

127.0.0.1:6379> ZADD pageRank 89 user1
(integer) 1
127.0.0.1:6379> 
127.0.0.1:6379> ZADD pageRank 90 user1 88 user2 100 user3 80 user4
(integer) 3
127.0.0.1:6379> 

ZSCORE 查看元素分数

ZSCORE key member

返回有序集 key 中,成员 member 的 score 值。

举例:

127.0.0.1:6379> ZSCORE pageRank user1
"90"
127.0.0.1:6379> ZSCORE pageRank user2
"88"
127.0.0.1:6379> ZSCORE pageRank user3
"100"
127.0.0.1:6379> ZSCORE pageRank user4
"80"
127.0.0.1:6379> 

ZREVRANGE 按名次查看排行榜

ZREVRANGE key start stop [WITHSCORES]

返回有序集 key 中指定区间内的成员,其中成员的位置按 score 值递减(从大到小)来排列,具有相同 score 值的成员按字典序的逆序排列。

由于排行榜一般按照分数由高到低排序,所以我们用ZREVRANGE;如果需要按照分数由低到高排序,可以使用ZRANGE命令。除了排序方式不同外,ZRANGE与ZREVRANGE命令完全一致。

举例:

127.0.0.1:6379> ZREVRANGE pageRank 0 2 WITHSCORES
1) "user3"
2) "100"
3) "user1"
4) "90"
5) "user2"
6) "88"
127.0.0.1:6379> ZREVRANGE pageRank 0 2
1) "user3"
2) "user1"
3) "user2"
127.0.0.1:6379> ZREVRANGE pageRank 0 -1 WITHSCORES
1) "user3"
2) "100"
3) "user1"
4) "90"
5) "user2"
6) "88"
7) "user4"
8) "80"
127.0.0.1:6379> 

ZREVRANK查看元素排名

ZREVRANK key member

返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序, score 值最大的成员排名为 0 。使用命令ZRANK可以获得成员按 score 值递增(从小到大)排列的排名。

举例:

127.0.0.1:6379> ZREVRANK pageRank user3
(integer) 0
127.0.0.1:6379> ZREVRANK pageRank user4
(integer) 3
127.0.0.1:6379>

ZINCRBY 增减元素分数

ZINCRBY key increment member

为有序集 key 的成员 member 的 score 值加上增量 increment 。可以通过传递一个负数值 increment ,让 score 减去相应的值,比如 ZINCRBY key -5 member ,就是让 member 的 score 值减去 5 。

举例:

127.0.0.1:6379> ZINCRBY pageRank 16 user4
"96"
127.0.0.1:6379> ZREVRANGE pageRank 0 -1 WITHSCORES
1) "user3"
2) "100"
3) "user4"
4) "96"
5) "user1"
6) "90"
7) "user2"
8) "88"
127.0.0.1:6379> 

ZREM 移除元素

ZREM key member [member …]

移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。

举例:

127.0.0.1:6379> ZREM pageRank user4
(integer) 1
127.0.0.1:6379> ZREVRANGE pageRank 0 -1 WITHSCORES
1) "user3"
2) "100"
3) "user1"
4) "90"
5) "user2"
6) "88"
127.0.0.1:6379> 

具体在工程中实现排行榜时还要考虑到:

  • 在分数相同时,Redis按照根据集合成员自身的字典顺序来排序,但这可能不能满足实际应用需要,需要额外实现;

  • 可以利用Redis的主备机制,实现负载均衡。排行榜一般读多写少,可以对 master 进行写入操作,然后对多个 slave 进行读取操作。

总结来看,Redis的有序集合是一个非常高效的数据结构,排行榜是其典型应用,用来替代数据库的实现方式可以大大提升性能。

5 Redis项目列表

在Web应用中“列出动态列表”、“列出回复列表”、“关注列表”、“粉丝列表”等查询非常普遍。

SELECT * FROM foo WHERE … ORDER BY time DESC LIMIT 10

上面SQL语句常用来显示最新条目,但随着数据的增多,访问速度会越来越慢。Redis使用的是常驻内存的缓存,速度非常快,我们可以用Redis来实现主页的项目列表展示。

列表中的条目都设定一个唯一的递增ID字段,每次发表新评论或动态时,将条目ID通过LPUSH命令添加到Redis列表里(LPUSH命令在前文消息队列中已有介绍,在此不再赘述),同时可以用Redis的LTRIM命令来限制缓存容量,如果用户检索数量超过这个阈值,再向数据库发起请求访问。

LTRIM

LTRIM key start stop

对一个列表进行范围限制,不在start和stop下标指定区间范围内的元素将被删除。

举例:

LTRIM comments 0 4999

Redis的评论列表comments保留最新5000条记录,超过这个数量的才需要向数据库发起查询请求。

每次我们需要获取最新评论的项目范围时,利用LRANGE命令。

LRANGE

LRANGE key start stop

返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。

下面是获取最新评论的伪代码:

FUNCTION get_latest_comments(start, num_items):  
    id_list = redis.lrange("latest.comments",start,start+num_items - 1)  
    IF id_list.length < num_items  
        id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")  
    END  
    RETURN id_list  
END  

6 网站的PV/UV统计

在各种大型网站中一般都需要进行PV、UV的统计;PV(Page View)即页面浏览量或点击量,统计页面的累计访问量;UV(Unique visitor)是统计浏览某个网页的独立访客数量,可以通过用户Id+cookie的方式对独立访客进行标识。

统计当日PV是网站计数器功能,可以利用Redis的Incr命令来实现,将key值设置为当天日期;统计当日UV的话,同一个用户一天之内的多次请求只能增加一个UV值,因此需要去重,我们分别用Redis的BitMap和HyperLogLog结构来实现。

6.1 计数器统计PV

Redis Incr 命令将 key 中储存的数字值增一。

INCR key

如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。

PV一般要按时间统计,如页面访问数量需要按天统计,只需将日期带入计数器 key 就可以。

# 假定操作 2019-03-26 数据
 get total_pv:2019-03-26
 # 2018-12-26 注册用户数增加一位
 incr total_pv:2019-01-26
 # 设置 48 小时过期时间 172800 = 48 * 60 * 60
 expire total_pv:2019-01-26 172800

限速器是特殊化的计算器,通过统计网站在最近N分钟内点击次数、数据库最近N秒内处理的读写请求数量,能够注意到流量增减情况,可以对系统进行流量限制或者升级,从而防止系统因为负荷超载而崩溃。

6.2 Redis BitMap统计UV

Redis的位图就是一个由二进制位组成的数组,数组的每个单元只能存储 0和1,数组的下标在Bitmap中叫做偏移量。通过将数组中的每个二进制位与用户 ID 进行一一对应, 可以使用位图去记录每个用户当日是否访问,存储的1的个数就是UV数量。
首先以当天的日期加固定的前缀作为key,建立一个Bitmap。每一位二进制的offset做为一个用户ID的标识,当今天用户访问时就将Bitmap中标识此用户的二进制(value)从0置为1。当统计某一天用户访问数量时,直接BITCOUNT那天对应的key,Redis BitMap中常用命令如下:
SETBIT 设置偏移量

SETBIT key offset value

对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。

GETBIT 获取偏移量

GETBIT key offset

对 key 所储存的字符串值,获取指定偏移量上的位(bit)。当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。

BITCOUNT 获取指定范围值为1的位个数

BITCOUNT key [start] [end]

统计指定key范围区间值为1的位个数,不设置 [start] [end],给定的整个字符串都会被进行计数;指定额外的 start 或 end 参数,可以让计数只在特定的范围内进行。

SETBIT key offset value

对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。

GETBIT 获取偏移量

GETBIT key offset

对 key 所储存的字符串值,获取指定偏移量上的位(bit)。当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。

BITCOUNT 获取指定范围值为1的位个数

BITCOUNT key [start] [end]

统计指定key范围区间值为1的位个数,不设置 [start] [end],给定的整个字符串都会被进行计数;指定额外的 start 或 end 参数,可以让计数只在特定的范围内进行。

6.3.1 HyperLogLog实现原理

1 伯努利过程

投掷一次硬币出现正、反两面的概率均为1/2。如果不断的投掷硬币,直到出现一次正面,在这样的一个过程中,投掷一次得到正面的概率为1/2,投掷两次才得到正面的概率为(1/2)2。依次类推,投掷k次才得到一次正面的概率为(1/2)k,将这种抛硬币多次直到出现正面的过程记为一次伯努利过程。对于n次伯努利过程,我们会得到n个出现正面的投掷次数值k1,k2……kn,其中最大值记为kmax,那么可以得到下面结论:

  • 进行n次伯努利过程,所有投掷次数都不大于(小于等于)Kmax的概率 :在一次伯努利过程中,投掷次数大于k的概率为(1/2)的k次方,也就是投了k次反面的概率。因此,在一次过程中投掷次数不大于k的概率为1−(1/2)^k,因此n次伯努利过程所有投掷次数都不大于k的概率为
    P(X <= Kmax)=(1−1/2Kmax)n
  • 进行n次伯努利过程,至少有一次大于等于Kmax的概率:
    P(X  >= Kmax)=1−(1−1/2Kmax−1)n

从上述公式中可得出结论:当n远小于2Kmax时,P(X≥Kmax)≈0,即所有投掷次数都小于k;当n远大于2Kmax时,P(X≤Kmax)≈0,即所有投掷次数都大于k。因此,我们似乎就可以用2^Kmax的值来粗略估计n的大小。 以上结论可以总结为:进行了n次抛硬币实验,每次分别记录下第一次抛到正面的抛掷次数k,那么可以用n次实验中最大的抛掷次数kmax 来预估实验组数量:n = 2Kmax。

2 比特串的基数估计

在UV统计里,我们需要统计一组集合中不重复元素的个数。可以利用哈希算法将集合中的数据转换成0和1构成的二进制数串,那么一个二进制串可以类比为一次抛硬币实验,1是抛到正面,0是反面。

Redis 里使用 MurmurHash2 算法来计算集合数据的哈希值,该算法有很好的均匀性,即使输入集合数据按规律排列,哈希之后仍能保证数据随机分布,因此可以保证每bit出现0或1的概率均为1/2,Redis中采用的是MurmurHash2固定64比特版本,另外该算法的计算速度也较快。
在这里插入图片描述
HLL算法思想的核心就在于通过保留少量的比特信息,来估计或观察消息流。二进制串中从低位开始第一个1出现的位置可以理解为抛硬币试验中第一次出现正面的抛掷次数k,那么基于上面的结论,我们可以通过多次抛硬币实验的最大抛到正面的次数来预估总共进行了多少次实验,同样可以通过第一个1出现位置的最大值Kmax来预估总共有多少个不同的数字(整体基数)。

网上很多博客或文章统计的都是末尾0的个数而非第一个1出现的位置,这样是有失准确的;关于k的值,Redis源码中(hllPatLen函数内部)也有备注,

Note that the final “1” ending the sequence of zeroes must be included in the count, so if we find “001” the count is 3, and the smallest count possible is no zeroes at all, just a 1 bit
at the first position, that is a count of 1.

3 分桶平均

如果直接应用上面的HLL方法进行基数估计会由于偶然性带来较大的误差,因此HLL算法采用分桶平均的方法来消减偶然性的误差、提高估计的准确度。先把数据分成若干个分组(桶bucket),估计每个分组的基数,然后用所有分组基数的平均数来估计总的基数。Redis中桶的个数是16384,对于每个哈希值(64比特),14位作为桶编号用来定位数据分布的桶位置,剩余的50比特即伯努利过程,每个桶对应6bit大小,记录kmax。

举例说明,若UV值通过Hash算法得到比特串“10110000 00000000 01101100 00000100 00101001 11000000 00000100 00011101”,后面14位确定桶编号,即bucket=1053,前面的50比特伯努利过程,该例中kmax=9,那么UV基数估计为29

多个桶用平均数计算,HLL采用的是调和平均方法,然后再基于因子修正公式计算得出,调和平均较之于几何平均有更高的精度,具体数学公式和原理分析有兴趣的读者可以参考阅读👇
解读Cardinality Estimation算法(第四部分:HyperLogLog Counting及Adaptive Counting)

4 空间使用

Redis中Hyperloglog的结构如下:
在这里插入图片描述
Redis中规定分分桶个数16384,每个桶的kmax用6bit空间来存放,6*16384/8 字节,再加上结构头等数据,加起来一共12304个字节;用12K字节的内存占用,即可计算接近 264 个不同元素的基数。这和基数越大占用内存越大的其它计算基数的方式形成鲜明对比。

但Redis对应内存的节约还不止于此,12K字节内存是encoding=HLL_DENSE(密集)模式下的内存占用;对于基数值比较少,大多数桶的计数值kmax为0的情况,Redis采用HLL_SPARSE稀疏模式的存储,稀疏存储的空间占用远小于12K字节。对密集存储和稀疏存储方式,本文做简单介绍:

密集存储的结构很简单,就是连续的16384个6比特连起来的位图。
在这里插入图片描述
稀疏存储针对的就是很多的桶计数值为0的情况,因此会有大量连续0的情况出现。

在这里插入图片描述

如果连续计数值为0的桶,Redis会存储为00xxxxxx,00是前缀标识,后面的6比特值加1(加1是因为数量全0是没有意义的)表示连续0的个数,6比特最多表示64个桶计数连续为0;为了表示更多位的连续0个数,Redis又设计了16位的01xxxxxx xxxxxxxx形式,01是前缀标识,后面14比特值加1表示连续0的个数,这样最大能表示16384个桶连续计数为0,即Hyperloglog初始化状态。

如果计数值为非0的桶,Redis用1vvvvvxx的形式表示,第一位1是前缀标识,中间5比特是桶的计数值,最后两位(值加1)是表示这样计数值连续的个数。一个1vvvvvxx记录的非0桶的最大连续个数是4,中间5比特计数值最大只能表示到32,如果当某个桶的计数值超过32,Redis会转换为密集模式进行存储。

只要以下条件满足其一,Redis就会从稀疏存储转换为密集存储:

1)任意一个桶的计数值超过32;

2)稀疏存储占用的字节数超过hll_sparse_max_bytes值,默认是3000字节(可配置调整)。

6.3.2 Redis Hyperloglog命令源码解析

加粗样式添加元素 PFADD 命令

PFADD key element [element …]

将任意数量的元素添加到指定的 HyperLogLog 里,名称为key。

HyperLogLog 内部可能会被更新, 若HyperLogLog 估计的基数在命令执行之后出现了变化返回 1,否则返回 0。

添加元素命令实现的Redis源码如下:
在这里插入图片描述
继续看添加元素的hllAdd函数实现:
在这里插入图片描述
根据encoding值进行判断,如果为HLL_DENSE(值为0)进入密集模式,为HLL_SPARSE(值为1)则进入稀疏模式,其他情况无效直接返回-1。

以HLL_DENSE密集模式为例:
在这里插入图片描述
调用hllPatLen函数计算元素经过哈希之后第一个1出现的位置,如果计数值发生变化,则进行更新并返回1,没有变化则返回0,hllPatLen函数源码如下:
在这里插入图片描述
上面是最新的Redis5.0的hllPatLen函数写法,hash右移和bit初始值较之前Redis版本稍作改动,增强了代码可读性,但逻辑仍一致。

计算基数 PFCOUNT命令

PFCOUNT key [key …]

当PFCOUNT命令作用于单个键时,返回储存在给定键的 HyperLogLog 的基数估计, 如果键不存在,那么返回 0;当PFCOUNT命令作用于多个键时,则需要合并所有的键(求并集),然后计算其基数,源码如下:
在这里插入图片描述

结语

Redis 这些年不断地增加新功能和优化改进,让它变得更灵活场景适应性更多的同时,也让我们在使用时需要更细致的思考。

本文只是希望能够抛砖引玉。

参考文献

Antirez博客

Redis官网

缓存那些事

Redis在实时大数据分析中的应用:原理、架构与代码实现 随着物联网、移动互联网的普及,企业每天产生的海量数据对实时分析能力提出极高要求。传统关系型数据库在高并发、低延迟场景下性能瓶颈显著,而Redis作为内存型NoSQL数据库,凭借其高效的数据结构和内存访问特性,成为实时大数据分析的理想选择。解析Redis适用于实时分析的核心技术原理构建端到端的实时分析系统架构提供完整的代码实现与性能优化方案覆盖日志分析、实时计数、用户行为分析等典型场景背景知识:明确核心概念与技术术语原理剖析:Redis内存模型与数据结构特性。 阅读详情

相关推荐

Android加密开发实战:Spongy Castle集成与国密算法应用指南

在移动应用开发中,数据加密是保障用户隐私和信息安全的基石。其原理基于密码学算法,通过对数据进行编码转换,确保即使数据被截获也无法被未授权方解读。这项技术的核心价值在于为敏感信息提供机密性、完整性和身份验证,广泛应用于用户登录、本地存储、网络通信等场景。对于Android开发者而言,系统自带的加密功能有时无法满足需求,特别是涉及国密算法或更丰富算法套件时。本文聚焦于Spongy Castle这一专为Android定制的加密库,它有效解决了与系统内置Bouncy Castle的兼容性问题,并提供了对SM4等国密

weixin_27722377的博客 309

Redis原理及应用

1.什么是redis redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言API, 支持的String(也可以是数字)、list(链表)、hash、set、zset数据结构。 2.redis的应用场景 2.1热数据缓存(减少数据库的压力) 热点数据(经常被查询,但不是经常修改或删除的数据)具体的处理过程如下...

misxu890312的博客 324

Latext安装(二)WinEdt安装+激活教程

常用的LaTeX安装方案是TeXLive + WinEdt。TeXLive 是LaTeX的运行环境,而 WinEdt 则是编写LaTeX文档的编辑器。安装好TexLive之后,再安装WinEdt。这里介绍的是WinEdt的安装和激活教程。

j_l_sheng的博客 4万+

Redis-使用和原理

redis有效期设置 可以通过expire key time来设置缓存有效期 如果没有设置有效期,即使内存用完,redis 自动回收机制也是看设置了有效期的,不会动没有设定有效期的,如果清理后内存还是满的,就不再接受写操作。 Redis无论有没有设置expire,他都会遵循redis的配置好的删除机制,在配置文件里设置: redis最大内存不足"时,数据清除策略,默认为"volatile-lru"。 volatile-lru ->对"过期集合"中的数据采取LRU(近期最少使用)算法.如果对key

u013217730的博客 2686

游戏玩家和排行数据抓取:使用 Python 爬虫抓取游戏中的玩家排名、角色数据和战绩

对于游戏开发者、分析师和玩家本身来说,抓取游戏中的玩家排名、角色数据和战绩信息是一项至关重要的任务。Python提供了强大的爬虫库和数据处理工具,可以帮助我们高效地抓取和分析游戏数据,为游戏分析师、开发者以及玩家本身提供有价值的数据支持。通过本篇博客,我们学习了如何使用Python爬虫技术抓取游戏中的玩家排名、角色数据和战绩。本篇博客将详细介绍如何使用Python编写爬虫来抓取游戏中的玩家排名、角色数据和战绩,并结合最新的技术来实现高效的数据抓取与处理。为了方便后续的数据处理,我们将抓取的数据保存到。

2201_76125261的博客 945

11.嵌入式控制器EC实战 KBC矩阵键盘Keyboard开发流程

文章目录笔记本矩阵键盘概述矩阵键盘的扫描方式行列扫描方式逐行逐列扫描方式EC中矩阵键盘是如何扫描并得到码值的(代码分析) 笔记本矩阵键盘概述 在EC中支持两种形式的键盘,一种是PS/2接口的键盘,这种键盘几乎已经被USB接口的键盘所取代,所以PS/2接口的键盘不在我们的讨论范围。 另一种键盘则是矩阵键盘,也是笔记本电脑的内置键盘,该键盘类似于学习单片机时使用的4*4的那种键盘,笔记本使用的矩阵键盘有三种规格,分别是16*8、17*8和18*8。16、17和18代表着该款键盘的列数,8代表着键盘的行数,我们这

qq_30135687的博客 3809

Redis setnx和set(key,value,nxxx,expx,time)

redis setnx和set(key,value,nxxx,expx,time)

qq_42459467的博客 2万+

「企业级智能体」LangChain4j Multi-Agent:角色分离 + 协同编排实战

在大部分 Java 项目里,“接入大模型Controller 收到一个 question调用大模型接口拿到 answer把结果原样丢回前端这种玩法可以叫「高配搜索框模式一个 Agent 要写 Prompt、要查数据库、还要改写文案,太忙了不同角色(创作者、编辑、审稿人)的职责混在一起,难以维护想复用其中一段能力(比如只要润色)也不方便把大任务拆成多个 “角色清晰的 Agent”,再编排它们像流水线一样协同完成任务。CreativeWriter:负责创作故事。

OFFTime_we的博客 1548

Redis

redis是什么 一个开源的、使用C语言编写的、支持网络交互的、可基内存也可持久化的Key-Value数据库。 redis安装 从redis.io下载最新版redis-X.Y.Z.tar.gz后解压,然后进入redis-X.Y.Z文件夹后直接make即可,安装非常简单。 make成功后会在src文件夹下产生一些二进制可执行文件,包括redis-server、redis-cli等等: 代码如下: $ find . -type f -executable ./redis-benchmark //用于进行redi

飞子的博客 440

十、Teigha.Net示例的源代码下载链接,以及英文的chm说明文件和相关案例

目录官方案例demo下载Teigha.net 英文版手册下载Teigha.net Entity实体中文翻译Teigha4.0 解决闪烁问题demo下载Teigha4.0 收录demo案例类似CAD看图王软件无AutoCAD环境下操作显示CAD文件的功能源码,整个示例模块源码 官方案例demo下载 在之前找到的官方案例3_03版本中包含32位与64位dll 其中的chm的说明文件内容也不完全,后面有更为详细的英文版的chm; 示例项目中,主要包括一个winfrom程序与控制台程序,OdWriteExMgd

... 3519

深度学习框架对比:AI开发如何选择

你是否遇到过这样的困惑:想做一个图像分类模型,朋友推荐PyTorch;公司要部署智能客服,同事说用TensorFlow;想快速验证想法,学长又提到Keras……深度学习框架种类繁多,功能重叠却各有侧重。本文将覆盖主流框架(PyTorch/TensorFlow/Keras/JAX/MXNet等),从开发者真实需求出发,总结选择框架的核心维度。本文将按照“认识框架→对比特性→实战选择”的逻辑展开:先通过生活比喻理解各框架特点,再用代码对比核心功能,最后结合真实场景给出选择建议。动态计算图。

AI 领航者的博客 1162

redis数据类型原理及应用

redis数据类型以及底层结构和原理

qqrrjj2011的博客 1101

ROS & ROS2 机器人深度相机激光雷达多传感器标定工具箱入门教程(一)

multisensor_calibration 是一个积极维护的通用标定工具箱,用于辅助基于目标的多传感器标定,支持 ROS 1 和 ROS 2。它提供了多种方法和应用来标定复杂的多传感器系统,例如。

2506_90492529的博客 3261

Redis Bitmap详解:原理、应用与最佳实践

Redis中的Bitmap(位图)是一种基于String类型实现的特殊数据结构,它通过二进制位(bit)来高效存储和操作大量布尔值数据。

weixin_73385730的博客 2024

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

分布式锁的实现原理及典型应用场景?​​

分布式锁的核心价值在于​​跨节点互斥​​与​​资源安全访问​​。选型需权衡性能、一致性和复杂度:Redis适用于高并发容忍弱一致,ZooKeeper适合强一致关键业务,而数据库方案仅作轻量级替代。实践中需结合锁粒度控制(如按业务ID分片)、超时策略(业务平均耗时×3 + 缓冲时间)及监控(锁竞争频率、持有时长)以保障稳定性。

iddlw75046的博客 919

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

redis之典型应用-缓存cache

缓存是计算机系统中提升性能的关键技术,通过将热点数据存储在访问速度更快的介质中(如内存)来减少对慢速存储(如硬盘)的访问。Redis作为数据库缓存可显著降低MySQL压力,其核心原理是利用内存存储热点数据,遵循"二八定律"处理80%的请求。缓存策略包括LRU、LFU等淘汰机制,并需应对缓存穿透(非法请求)、雪崩(大规模缓存失效)和击穿(热点key失效)等问题。解决方案包括参数校验、布隆过滤器、随机过期时间和服务降级等措施,同时通过缓存预热提前加载热点数据。合理使用缓存能有效提升系统性能,

2301_79176091的博客 462

Redis 原理及应用(4)--Redis应用场景分析

来到了Redis系列的最后一篇,在前三篇中,介绍了Redis的数据类型及底层实现,持久化,集群分区,事务,缓存淘汰策略,HA哨兵机制等内容,其实关于Redis最重要的是其应用场景,只有知道Redis在什么场景下使用才是第一步。在这篇文章中,我们就来讨论一下Redis典型的应用场景。 缓存 提到redis,我们第一想到的应用场景肯定是缓存,因为redis是基于内存的数据库,具有很强大的读写性能。

怕什么真理无穷, 进一寸有一寸的欢喜 3323

《Redis五大数据结构源码剖析与典型应用场景》

Redis五大数据结构解析与优化实践 Redis作为高性能缓存的核心组件,其五大核心数据结构(String、List、Hash、Set、ZSet)通过SDS、Ziplist、Hashtable等底层实现提供O(1)到O(logN)的高效操作。本文深入剖析了各结构的源码实现,包括SDS的预分配机制、Ziplist的紧凑存储、跳表的层级查询等特性,并给出典型应用场景: 缓存穿透防护:通过空对象缓存和原子操作实现 排行榜:利用ZSet的分数排序特性 分布式限流:基于Lua脚本的令牌桶算法 优化建议包括: 避免Bi

qq_43414012的博客 1180

从原理、场景、解决方案深度分析Redis分布式Session

Redis分布式Session是解决分布式系统中Session共享的主流方案,通过将Session数据集中存储在Redis集群中,实现全局访问和高可用性。其核心原理包括:Session数据抽离到Redis、全局唯一SessionID、Redis键过期策略管理生命周期、原子性操作保证一致性。相比本地Session,具有全局共享、高容灾、易扩容等优势。

he_co的博客 1176

Redis互斥锁:原理与应用

在分布式系统中,数据的一致性和并发控制是至关重要的。以一个在线支付系统为例,当多个用户同时进行支付操作时,系统需要确保每个支付请求都能正确执行,不会因为并发操作而导致数据不一致或错误。这就需要一种机制来保证在多客户端环境下,对共享资源的访问是互斥的,即同一时间只有一个客户端可以访问该资源。在这样的背景下,Redis互斥锁应运而生。Redis互斥锁是一种基于Redis的分布式锁,它利用Redis的原子操作来保证在分布式环境中对共享资源的互斥访问。

我是Java程序员廖志伟,感谢朋友们的支持!不定期贡献一篇高品质、过万文字、图文并茂且附有视频解说、满载代码示例注释的良心之作,坚决杜绝粗制乱造。 275

【Redis概述与应用场景】

在现代计算机系统中,数据存储和访问的速度已经成为影响性能的关键因素之一。尤其是在构建高并发、高可用性分布式系统时,传统的关系型数据库在处理速度和扩展性方面的瓶颈逐渐显现。为了应对这一挑战,Redis作为一种高效、灵活的内存数据存储系统,逐渐成为了开发人员和系统架构师的首选工具。本文将全面介绍Redis的基本概念、数据结构、工作原理、常见应用场景以及如何在项目中高效利用Redis,以帮助开发者更好地理解并应用Redis。

2501_90373104的博客 1018

Redis原理—1.Redis数据结构

因为压缩列表比双端链表更节约内存,且在元素较少时,在内存中以连续块方式保存的压缩列表,比起双端链表可以更快地被载入到缓存中。比如从跳跃表的表头结点的第一层出发L1,表头L1会指向第一个结点的指针,到第一个结点的L1会指向第二个结点的指针,定位到第二个结点后,从其L1又可以获取第三个结点的指针,依此类推。当服务器打开了maxmemory选项,且服务器用于回收内存的算法为volatile-lru或allkeys-lru,则当服务器占用的内存数据超了maxmemory,空转时长较高的那部分键会优先释放回收内存。

mjunz的博客 1173

Redis原理之缓存

查询的key在Redis和MySQL数据库中都不存在,这样的key不会被实时缓存到Redis中,后续如果这类key频繁访问,依然会对MySQL造成较大压力。缓存穿透的特殊情况,缓存穿透是针对普遍的key(包括热点和非热点)突然同时失效的问题,而缓存击穿是指热点key突然过期,导致大量查询未命中去查数据库。当Redis刚启动或大批key同时过期,此时Redis中没有数据,查询就无法命中缓存,从而去查MySQL,给数据库带来大量查询压力。1.业务未进行合理的参数校验,导致非法的key被查询。

sniper_fandc的博客 1194

Redis分布式锁深度剖析:从原理到高可用实践

Redis分布式锁通过SET key random_value EX time NX实现原子加锁,配合Lua脚本保障安全释放。关键挑战在于锁续期和集群高可用——看门狗机制解决业务执行超时问题,RedLock算法应对主从切换场景。工业级实现首选Redisson,提供可重入锁、公平锁等高级特性。选型需权衡CAP理论:Redis满足AP高可用,Zookeeper/etcd提供CP强一致性。未来趋势聚焦Serverless适配和跨云锁服务,但核心仍是平衡互斥性、容错性与时效性。

JouJz的博客 2万+
上一篇: 【字节跳动面经】Java后台研发2020面试真题(商业变现部门一面)
下一篇: 【Kafka技术内幕】(一):认识MQ——典型应用场景和基本特性概述
少侠露飞
博客等级 码龄9年 214粉丝 117原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值