PHP 实现 Redis 高并发解决方案

PHP 实现 Redis 高并发解决方案 使用 PHP 实现 Redis 在缓存加速、分布式锁和队列场景中的应用。 首先确保已安装 PHP Redis 扩展 一、缓存加速实现 1. 基本缓存操作 <?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 设置缓存 function setCache($key, $value, $expire = 3600) { global $redis; $serialized = serialize($value); return $redis->setex($key, $expire, $serialized); } // 获取缓存 function getCache($key) { global $redis; $serialized = $redis->get($key); return $serialized ? unserialize($serialized) : false; } // 删除缓存 function deleteCache($key) { global $redis; return $redis->del($key); } // 示例:用户数据缓存 function getUser($userId) { $cacheKey = "user:{$userId}"; $user = getCache($cacheKey); if ($user === false) { // 模拟数据库查询 $user = [ 'id' => $userId, 'name' => 'User ' ....

March 28, 2025 · 5 min · Leanku

Redis分布式锁

Redis分布式锁 一、 Redis锁基础概念 1. 为什么需要Redis锁 Redis锁是一种基于内存数据库Redis实现的分布式锁机制,主要解决分布式系统中的资源竞争问题。相比数据库锁,Redis锁具有以下优势 高性能:基于内存操作,响应速度快 原子性保证:Redis单线程模型天然支持原子操作 分布式支持:可跨多台服务器使用 丰富的数据结构:支持多种锁实现方式 2. 基本实现原理 最简单的Redis锁实现方式: SET resource_name my_random_value NX PX 30000 NX:仅当key不存在时设置 PX:设置过期时间(毫秒) my_random_value:唯一标识,用于安全释放锁 二、Redis锁的实现方式 1. SETNX实现(基本方式) 实现步骤: 尝试获取锁:SETNX lock_key 1 获取成功则设置过期时间:EXPIRE lock_key 30 执行业务逻辑 释放锁:DEL lock_key 问题: 非原子操作,SETNX和EXPIRE之间可能崩溃导致死锁 2. SET扩展参数实现(推荐) Redis 2.6.12+版本支持扩展参数,可原子性完成设置 SET lock_key unique_value NX PX 30000 PHP实现示例: $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $lockKey = 'order_lock_123'; $uniqueId = uniqid(); $expire = 30000; // 30秒 // 尝试获取锁 $acquired = $redis->set($lockKey, $uniqueId, ['NX', 'PX' => $expire]); if ($acquired) { try { // 执行业务逻辑 processOrder(); } finally { // 使用Lua脚本保证原子性释放 $script = " if redis....

April 23, 2024 · 3 min · Leanku

Redis高可用

Redis高可用 1. 事务机制和IO多路复用 1.1 事务 1.1.1 事务特点 事务提交前,先检查命令语法是否正确 提交后的命令一定会执行 有命令报错,也会执行完 不能回滚 1.1.2 命令 multi,告诉Redis服务器开启一个事务。只是开启而不是执行。 exec, 告诉Redis开始执行事务 discard,告诉Redis取消事务 watch,监视某一个键值对,它的作用是在事务执行之前如果监视的键值被修改,事务会被取消 1.2 IO多路复用 redis 是单线程,单线程只能在一个CPU内核上执行,假如是4核的,只会占用一个,其它三个不参与。 worker线程串行 read读->计算->write返回 在Redis6.0加入了io-threads, 主线程worker只进行计算,并行读取 io-threads 4 io-threads-do-reads yes 2. 持久化和过期淘汰策略 2.1 持久化 Redis是存储在内存中的, 服务器重启数据会丢失。持久化方案可以保存数据到磁盘文件,可以恢复到内存中。Redis提供的持久化方案有: rdb:生成某一时刻的快照,保存到二进制文件中 优点: 容灾性好,方便备份 性能最大化,fork出一个子进程来操作,对主进程没有影响 数据较多时,相对于aof启动效率更高 缺点: 会造成数据丢失 aof:实时记录每一条写命令,追加到文件中,打开可以看到具体的操作记录 同步策略:appendfsync everysec(默认),每秒同步一次 always,每次操作后都要同步一次 no,由操作系统调度进行同步 重写策略: 手动触发,执行bgrewiteaof命令 自动触发:auto-rewrite-percentage 100, auto-rewrite-min-size 64mb 优点 数据安全,不会造成数据的丢失 缺点 比rdb重启效率低;运行效率比rdb低 混合模式:上面两种方式的结合 触发方式有两种: 手动触发: save命令,会让Redis处于阻塞的状态,直到rdb持久化完成,线上环境谨慎使用 bgsave命令,它会fork出一个子进程(有短暂阻塞),用来执行持久化,主进程继续响应客户端请求 自动触发: 配置文件修改(save n m),在n秒内,有m个key发生变化就会触发,执行命令最总执行的是bgsave 2.2 过期键删除策略 Redis设置key时,都会设置一个过期时间,Redis同时使用了两种过期删除策略,惰性过期和定时过期...

April 23, 2024 · 1 min · Leanku

Redis主从复制

Redis主从复制 一、什么是Redis主从复制 1. 从复制的架构: Redis Replication是一种 master-slave 模式的复制机制,这种机制使得 slave 节点可以成为与 master 节点完全相同的副本,可以采用一主多从或者级联结构。 主从复制的配置要点: 配从库不配主,从库配置:slaveof 主库IP 主库端口 查看redis的配置信息:info replication 2. Redis为什么需要主从复制? 使用Redis主从复制的原因主要是单台Redis节点存在以下的局限性: Redis虽然读写的速度都很快,单节点的Redis能够支撑QPS大概在5w左右,如果上千万的用户访问,Redis就承载不了,成为了高并发的瓶颈。 单节点的Redis不能保证高可用,当Redis因为某些原因意外宕机时,会导致缓存不可用 CPU的利用率上,单台Redis实例只能利用单个核心,这单个核心在面临海量数据的存取和管理工作时压力会非常大。 3. 主从复制的好处: 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。 故障恢复:如果master宕掉了,使用哨兵模式,可以提升一个 slave 作为新的 master,进而实现故障转移,实现高可用 负载均衡:可以轻易地实现横向扩展,实现读写分离,一个 master 用于写,多个 slave 用于分摊读的压力,从而实现高并发; 4. 主从复制的缺点: 由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave服务器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重 二、主从复制的原理 从总体上来说,Redis主从复制的策略就是:当主从服务器刚建立连接的时候,进行全量同步;全量复制结束后,进行增量复制。当然,如果有需要,slave 在任何时候都可以发起全量同步。 1、主从全量复制的流程: Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份,具体步骤如下: slave服务器连接到master服务器,便开始进行数据同步,发送psync命令(Redis2.8之前是sync命令) master服务器收到psync命令之后,开始执行bgsave命令生成RDB快照文件并使用缓存区记录此后执行的所有写命令 如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是每个连接都执行一次,然后再把这一份持久化的数据发送给多个并发连接的slave。 如果RDB复制时间超过60秒(repl-timeout),那么slave服务器就会认为复制失败,可以适当调节大这个参数 master服务器bgsave执行完之后,就会向所有Slava服务器发送快照文件,并在发送期间继续在缓冲区内记录被执行的写命令 client-output-buffer-limit slave 256MB 64MB 60,如果在复制期间,内存缓冲区持续消耗超过64MB,或者一次性超过256MB,那么停止复制,复制失败 slave服务器收到RDB快照文件后,会将接收到的数据写入磁盘,然后清空所有旧数据,在从本地磁盘载入收到的快照到内存中,同时基于旧的数据版本对外提供服务。 master服务器发送完RDB快照文件之后,便开始向slave服务器发送缓冲区中的写命令 slave服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令; 如果slave node开启了AOF,那么会立即执行BGREWRITEAOF,重写AOF 2、增量复制: Redis的增量复制是指在初始化的全量复制并开始正常工作之后,master服务器将发生的写操作同步到slave服务器的过程,增量复制的过程主要是master服务器每执行一个写命令就会向slave服务器发送相同的写命令,slave服务器接收并执行收到的写命令。 3、断点续传: 什么是断点续传: 当master-slave网络连接断掉后,slave重新连接master时,会触发全量复制,但是从2.8版本开始,slave与master能够在网络连接断开重连后,只从中断处继续进行复制,而不必重新同步,这就是所谓的断点续传。 断电续传这个新特性使用psync命令,旧的实现中使用sync命令。Redis2.8版本可以检测出它所连接的服务器是否支持PSYNC命令,不支持的话使用SYNC命令。master服务器收到slave发送的psync命令后,会根据自身的情况做出对应的处理,可能是FULLRESYNC runid offset触发全量复制,也可能是CONTINUE触发增量复制 命令格式:psync runid offset 工作原理:...

March 20, 2024 · 2 min · Leanku

Redis持久化及过期删除策略

Redis持久化及过期删除策略 Redis 持久化之RDB和AOF RDB 详解 快照(snapshotting,RDB) RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据 从配置文件了解RDB 打开 redis.conf 文件,找到 SNAPSHOTTING 对应内容 RDB核心规则配置(重点) save <seconds> <changes> #save "" save 900 1 save 300 10 save 60 10000 说明:save <指定时间间隔> <执行指定次数更新操作>,满足条件就将内存中的数据同步到硬盘中。官方出厂配置默认是 900秒内有1个更改,300秒内有10个更改以及60秒内有10000个更改,则将内存中的数据快照写入磁盘。 若不想用RDB方案,可以把 save “” 的注释打开,下面三个注释。 指定本地数据库文件名,一般采用默认的 dump.rdb dbfilename dump.rdb 指定本地数据库存放目录,一般也用默认配置 dir ./ 默认开启数据压缩 rdbcompression yes 解说:配置存储至本地数据库时是否压缩数据,默认为yes。Redis采用LZF压缩方式,但占用了一点CPU的时间。若关闭该选项,但会导致数据库文件变的巨大。建议开启。 触发RDB快照 在指定的时间间隔内,执行指定次数的写操作 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令 执行flushall 命令,清空数据库所有数据,意义不大。 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据,意义…也不大。 注意: save备份过程:save备份是同步的,如果备份的数据量过大的话,服务器会暂停几百毫秒甚至是1秒 bgsave备份过程:bgsave备份会单独创建一个子进程,将备份的数据写入一个临时文件 RDB数据还原 找到备份临时文件的指令是 config get dir 指令执行成功以后将备份临时文件的目录拷贝到Redis的安装目录下 然后重新启动Redis服务就成功还原数据了...

March 13, 2024 · 1 min · Leanku

Redis中的Lua

Redis中的Lua 一. Redis中的Lua脚本的作用 Redis中的Lua脚本主要解决复杂操作的原子性和性能优化两大核心问题。它允许你在Redis服务器端直接执行自定义的逻辑,而不是把数据拉到客户端处理后再写回去。 核心作用 1. 保证原子性操作 Redis执行Lua脚本时,脚本中的所有命令会作为一个整体被执行,中间不会被其他命令插入。这完美解决了多命令组合操作时的并发安全问题。 经典场景: 实现分布式锁、库存扣减、限流计数器等。比如秒杀场景下,检查库存、扣减库存、记录订单这三个操作必须原子执行,用Lua脚本就能确保数据一致性。 2. 减少网络开销 将多个Redis命令合并到一个Lua脚本中,原本需要多次网络请求的操作变成一次请求完成。数据在Redis服务器本地处理,避免了大量数据在客户端和服务器之间来回传输。 3.扩展Redis指令集 你可以用Lua实现Redis原生不支持的复杂业务逻辑。比如自定义数据结构操作、复杂条件判断、循环处理等,让Redis变成一个具备计算能力的"小数据库"。 4. 提高代码复用率 脚本可以被缓存到Redis服务器,客户端通过SHA1摘要重复调用,无需每次都发送完整的脚本代码。多个应用可以共享同一套脚本逻辑 总结 Redis Lua脚本让你能把多个操作打包成一个原子性的、在服务器本地执行的任务,既保证了数据一致性,又提升了性能。特别适合秒杀、分布式锁、计数器、复杂条件更新等需要"读-改-写"原子操作的场景。 二、Lua安装 windows下载地址 :https://luabinaries.sourceforge.net/ 三 、 Lua在Redis中最常用、最经典的几个应用场景 1. 在Redis中操作Lua脚本的常用命令 命令 作用 一句话解释 EVAL 执行给定的Lua脚本 直接把脚本和参数传给Redis运行一次。 SCRIPT LOAD 将脚本缓存到Redis 把脚本存到Redis里,会返回一个SHA1值作为“脚本ID”。 EVALSHA 根据SHA1值执行缓存的脚本 拿着“脚本ID”去执行之前存好的脚本,省流量,效率更高。 SCRIPT EXISTS 检查脚本是否已缓存 看看某个“脚本ID”对应的脚本还在不在Redis里。 SCRIPT KILL 终止正在运行的脚本 如果脚本执行太久卡住了,可以用这个命令尝试杀掉它。 SCRIPT FLUSH 清空所有缓存的脚本 一键清除Redis里缓存的所有脚本,操作要小心\-1。 2. 五大经典应用场景 分布式锁 这是Lua脚本在Redis中最经典的应用。获取锁和释放锁的多个步骤必须是一个原子操作,否则会出现并发问题。 获取锁:使用 SET NX PX 命令,一步到位地尝试设置一个带过期时间的键,保证只有一个客户端能拿到锁-8。 释放锁:释放锁时,需要先检查当前持有锁的客户端是否就是自己(通过一个唯一值比如UUID判断),然后再删除锁。这两个动作必须通过Lua脚本一起执行,才能避免误删别人的锁-2-8。 -- 安全的释放锁脚本 -- KEYS[1]: 锁的名称 -- ARGV[1]: 客户端的唯一标识 if redis....

March 12, 2024 · 2 min · Leanku

Redis分布式

Redis分布式 一、 Redis分布式概述 1. 什么是Redis分布式 Redis分布式是指将Redis数据分布到多个Redis节点上,通过集群方式提供更高性能、更大容量和更好可用性的解决方案。 1.2 为什么需要Redis分布式 数据量增长:单机内存容量有限 高并发需求:单机性能瓶颈 高可用性:避免单点故障 可扩展性:支持业务动态扩容 1.3 Redis分布式方案对比 方案 优点 缺点 使用场景 主从复制 部署简单,读写分离 写操作单点,故障需手动切换 读多写少,数据备份 Redis Sentinel 自动故障转移,高可用 配置复杂,扩容不便 生产环境高可用 Redis Cluster 数据分片,自动故障转移 客户端兼容性要求,迁移复杂 大数据量,高并发 二、 Redis分布式方案搭建 2.1 主从复制搭建 配置文件示例 主节点 (master.conf): port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile "/var/log/redis_6379.log" 从节点 (slave.conf): port 6380 daemonize yes pidfile /var/run/redis_6380.pid logfile "/var/log/redis_6380.log" slaveof 127.0.0.1 6379 启动命令 # 启动主节点 redis-server master.conf # 启动从节点 redis-server slave.conf 2.2 Redis Sentinel搭建 Sentinel配置文件...

March 12, 2024 · 3 min · Leanku

Redis的一些常见题

Redis的一些常见题 redis数据类型及应用场景 见 Redis的五种数据类型及使用场景 redis持久化及过期策略 见 Redis持久化及过期删除策略 redis 常见问题及解决方案 缓存雪崩:同一时间大量缓存失效,导致请求直接查询数据库,数据库内存和CPU压力增加甚至宕机 解决: 热点数据永不过期或者分布到不同实例,降低单机故障问题 缓存时间添加随机数,防止大量缓存同时失效 做二级缓存或者双缓存,A为原始缓存 短时效,B为备份缓存 ,长期有效。更新时候双写缓存 缓存穿透:缓存和数据库都没有数据,大量请求下,所有请求直接击穿到数据库,导致宕机。 解决: 布隆过滤器:长度为m的位向量或者位列表组成(仅包含0或1位值的列表) 使用多个不用的hash函数,产生多个索引值,填充对应多个位置的值为1 布隆过滤器可以检查值是 “可能在集合中” 还是 “绝对不在集合中” 可能误判但是基础过滤效率高 极端情况,当布隆过滤器没有空闲位置的时候每次查询返回true 空缓存(短时效) 业务层参数过滤 缓存击穿:数据库中有数据,但是缓存突然失效之后发生大量请求导致数据库压力增加甚至打垮宕机 解决: 热点数据永不过期 互斥锁:获取锁之后不管成功还是失败都要释放锁 Redis主从复制原理 Redis 在复制时底层采用的是 psync 命令完成的数据主从同步,同步主要分为:全量复制和增量复制两种 全量复制:顾名思义也就是一次性把主节点数据全部发送给从节点,所以这种情况下,当数据量比较大时,会对主节点和网络造成很大的开销。 部分复制:用于处理主从复制时因网络中断等原因造成数据丢失的场景。当从节点再次和主节点连接时,主节点会补发丢失的数据。因为是补发,所以在发送的数据量一定是小于全量的数据。 详细见 Redis的一些常见题 Redis缓存和数据库数据一致性问题: 读数据 尝试查询缓存,存在缓存则直接返回数据 缓存不存在时则查询数据库,并保存到缓存(合适设置过期时间),返回数据 写数据 先更新缓存再更新数据库 或者 先删除缓存再更新数据库 先更新数据库再更新缓存 或者 先更新数据库再删除缓存 使用删除还是更新? 这里无非是删除还是更新Redis,直接删除的方式比较简单,在下一次查询时会生成缓存(更推荐);更新缓存则相较复杂 先操作Redis还是先操作数据库? 先操作数据库 (推荐) 流程:先更新数据库,再删除缓存 问题:比如线程1执行写操作(修改数据库->删除缓存),线程2执行读操作(查询缓存->旧数据),就会出现旧数据返回,或者删除缓存失败也会返回旧数据 先操作缓存 流程:修改操作先删除缓存再修改数据库 问题: 比如线程1去执行写操作: (删除缓存->更新数据库)同时有线程2执行读操作(查询缓存(未查到缓存)->查数据库->保存到缓存)因为数据库更新有可能网络延迟等问题,这里保存到缓存的数据就是旧的数据,只有再过期之后才会读到新数据 解决方案: (延迟双删)在数据库更新之后再进行一次缓存删除(加适当延迟,比如延迟500ms),这时再有查询过来也会查询到数据库并保存到缓存。但是线程2在这个删除缓存500ms内请求仍然可能会出现一次获取到旧数据 (推荐方式)

February 20, 2024 · 1 min · Leanku

Redis的数据类型及使用场景

Redis的数据类型及使用场景 一、基本数据类型 1.字符串(String) string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。 string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。 string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。 常用命令: SET key value 设置指定 key 的值。 GET key 获取指定 key 的值。 应用场景: String 是最常用的一种数据类型,普通的 key/value 存储都可以归为此类,即可以完全实现目前 Memcached 的功能,并且效率更高。还可以享受 Redis 的定时持久化,操作日志及 Replication 等功能。除了提供与 Memcached 一样的 get、set、incr、decr 等操作外,Redis 还提供了下面一些操作: 获取字符串长度 往字符串 append 内容 设置和获取字符串的某一段内容 设置及获取字符串的某一位(bit) 批量设置一系列字符串的内容 使用场景: 常规 key-value 缓存应用。常规计数:微博数,粉丝数。 实现方式: String 在 redis 内部存储默认就是一个字符串,被 redisObject 所引用,当遇到 incr,decr 等操作时会转成数值型进行计算,此时 redisObject 的 encoding 字段为 int...

September 1, 2023 · 7 min · Leanku

Redis事务和锁

Redis事务 事务是指一个完整的动作,要么全部执行,要么什么也没有做。Redis 事务不是严格意义上的事务,只是用于帮助用户在一个步骤中执行多个命令。单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。 Redis 事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。 Redis 的事务不是原子性,但是Redi执行每一个命令都是原子性的 举例:INCR在redis中是自增,即使多个客户端对同一个密钥发出INCR,也永远不会进入竞争状态。例如,客户机1读取“10”,客户机2同时读取“10”,两者都增加到11,并将新值设置为11,这样的情况永远不会发生。最终的值将始终是12。 这个案例是官网提出来的:https://redis.io/docs/data-types/tutorial/ 事务一般都是为原子性而生,既然Redis事务没有原子性,那他存在的意义是什么? redis事务的主要作用就是串联多个命令防止 别的命令插队。 官网介绍:https://redis.com.cn/redis-transaction.html Redis事务 - 基本使用 Redis 在形式上看起来也差不多,MULTI、EXEC、DISCARD这三个指令构成了 redis 事务处理的基础: MULTI:用来组装一个事务,从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,redis会将之前的命令依次执行。 EXEC:用来执行一个事务 DISCARD:用来取消一个事务 所有的指令在 exec 之前不执行,而是缓存在服务器的一个事务队列中,服务器一旦收到 exec 指令,才开执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。因为 Redis 的单线程特性,不用担心自己在执行队列的时候被其它指令打搅,可以保证他们能得到的有顺序的执行。 取消事务,放弃执行事务块内的所有命令。 组队中某个命令出现了错误报告,执行时整个队列中所有的命令都会被取消。 命令组队的过程中没有问题,执行中出现了错误会导致部分成功部分失败。 悲观锁&乐观锁 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁。 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去那数据的时候都认为别人不会修改,所以不会上锁,但是在修改的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。redis就是使用这种check-and-set机制实现事务的。 watch监听 WATCH:在执行multi之前,先执行watch key1 [key2 …],可以监视一个或者多个key,若在事务的exec命令之前这些key对应的值被其他命令所改动了,那么事务中所有命令都将被打断,即事务所有操作将被取消执行。 unwatch:取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。 简单示例秒杀场景 <?php // 连接Redis $redis = new Redis(); $redis->connect('127.0.0.1',6379); $redis->watch('sales'); $sales = $redis->get('sales'); $store = 10; if($sales >= $store){ exit('结束'); } // 事务 $redis->multi(); $redis->incr('sales'); $res = $redis->exec(); if($res){ // 库存更新....

May 28, 2023 · 1 min · Leanku