<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Lua on Leanku 的博客</title><link>https://www.leanku.com/tags/lua/</link><description>Recent content in Lua on Leanku 的博客</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 12 Mar 2024 20:46:01 +0800</lastBuildDate><atom:link href="https://www.leanku.com/tags/lua/index.xml" rel="self" type="application/rss+xml"/><item><title>Redis中的Lua</title><link>https://www.leanku.com/post/nosql/redis%E4%B8%AD%E7%9A%84lua/</link><pubDate>Tue, 12 Mar 2024 20:46:01 +0800</pubDate><guid>https://www.leanku.com/post/nosql/redis%E4%B8%AD%E7%9A%84lua/</guid><description>&lt;h1 id="redis中的lua"&gt;Redis中的Lua&lt;/h1&gt;&#10;&lt;h2 id="一-redis中的lua脚本的作用"&gt;一. Redis中的Lua脚本的作用&lt;/h2&gt;&#10;&lt;p&gt;Redis中的Lua脚本主要解决复杂操作的原子性和性能优化两大核心问题。它允许你在Redis服务器端直接执行自定义的逻辑，而不是把数据拉到客户端处理后再写回去。&lt;/p&gt;&#10;&lt;h3 id="核心作用"&gt;核心作用&lt;/h3&gt;&#10;&lt;h4 id="1-保证原子性操作"&gt;1. 保证原子性操作&lt;/h4&gt;&#10;&lt;p&gt;Redis执行Lua脚本时，脚本中的所有命令会作为一个整体被执行，中间不会被其他命令插入。这完美解决了多命令组合操作时的并发安全问题。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;经典场景&lt;/strong&gt;：&#10;实现分布式锁、库存扣减、限流计数器等。比如秒杀场景下，检查库存、扣减库存、记录订单这三个操作必须原子执行，用Lua脚本就能确保数据一致性。&lt;/p&gt;&#10;&lt;h4 id="2-减少网络开销"&gt;2. 减少网络开销&lt;/h4&gt;&#10;&lt;p&gt;将多个Redis命令合并到一个Lua脚本中，原本需要多次网络请求的操作变成一次请求完成。数据在Redis服务器本地处理，避免了大量数据在客户端和服务器之间来回传输。&lt;/p&gt;&#10;&lt;h4 id="3扩展redis指令集"&gt;3.扩展Redis指令集&lt;/h4&gt;&#10;&lt;p&gt;你可以用Lua实现Redis原生不支持的复杂业务逻辑。比如自定义数据结构操作、复杂条件判断、循环处理等，让Redis变成一个具备计算能力的&amp;quot;小数据库&amp;quot;。&lt;/p&gt;&#10;&lt;h4 id="4-提高代码复用率"&gt;4. 提高代码复用率&lt;/h4&gt;&#10;&lt;p&gt;脚本可以被缓存到Redis服务器，客户端通过SHA1摘要重复调用，无需每次都发送完整的脚本代码。多个应用可以共享同一套脚本逻辑&lt;/p&gt;&#10;&lt;h3 id="总结"&gt;总结&lt;/h3&gt;&#10;&lt;p&gt;Redis Lua脚本让你能把多个操作打包成一个原子性的、在服务器本地执行的任务，既保证了数据一致性，又提升了性能。特别适合秒杀、分布式锁、计数器、复杂条件更新等需要&amp;quot;读-改-写&amp;quot;原子操作的场景。&lt;/p&gt;&#10;&lt;h2 id="二lua安装"&gt;二、Lua安装&lt;/h2&gt;&#10;&lt;p&gt;windows下载地址 ：https://luabinaries.sourceforge.net/&lt;/p&gt;&#10;&lt;h3 id="三--lua在redis中最常用最经典的几个应用场景"&gt;三 、 Lua在Redis中最常用、最经典的几个应用场景&lt;/h3&gt;&#10;&lt;h4 id="1-在redis中操作lua脚本的常用命令"&gt;1. 在Redis中操作Lua脚本的常用命令&lt;/h4&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;命令&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;作用&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;一句话解释&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;EVAL&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;执行给定的Lua脚本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;直接把脚本和参数传给Redis运行一次。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;SCRIPT LOAD&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;将脚本缓存到Redis&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;把脚本存到Redis里，会返回一个SHA1值作为“脚本ID”。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;EVALSHA&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;根据SHA1值执行缓存的脚本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;拿着“脚本ID”去执行之前存好的脚本，省流量，效率更高。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;SCRIPT EXISTS&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;检查脚本是否已缓存&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;看看某个“脚本ID”对应的脚本还在不在Redis里。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;SCRIPT KILL&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;终止正在运行的脚本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;如果脚本执行太久卡住了，可以用这个命令尝试杀掉它。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;SCRIPT FLUSH&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;清空所有缓存的脚本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一键清除Redis里缓存的所有脚本，操作要小心&lt;a href="https://www.alibabacloud.com/help/tc/redis/support/usage-of-lua-scripts"&gt;\-1&lt;/a&gt;。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h4 id="2-五大经典应用场景"&gt;2. 五大经典应用场景&lt;/h4&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;分布式锁&#10;这是Lua脚本在Redis中最经典的应用。获取锁和释放锁的多个步骤必须是一个原子操作，否则会出现并发问题。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;获取锁&lt;/strong&gt;：使用 &lt;code&gt;SET NX PX&lt;/code&gt; 命令，一步到位地尝试设置一个带过期时间的键，保证只有一个客户端能拿到锁&lt;a href="https://support.huaweicloud.com/intl/zh-cn/redisug-nosql/redis_bestpractice_2_010.html"&gt;-8&lt;/a&gt;。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;释放锁&lt;/strong&gt;：释放锁时，需要先检查当前持有锁的客户端是否就是自己（通过一个唯一值比如UUID判断），然后再删除锁。这两个动作必须通过Lua脚本一起执行，才能避免误删别人的锁&lt;a href="https://bbs.huaweicloud.com/blogs/455583"&gt;-2&lt;/a&gt;&lt;a href="https://support.huaweicloud.com/intl/zh-cn/redisug-nosql/redis_bestpractice_2_010.html"&gt;-8&lt;/a&gt;。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;-- 安全的释放锁脚本&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;-- KEYS&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt;: 锁的名称&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;-- ARGV&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt;: 客户端的唯一标识&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; redis.call&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;, KEYS&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; ARGV&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; redis.call&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;del&amp;#34;&lt;/span&gt;, KEYS&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;end&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;&#10;&lt;li&gt;原子性计数器&#10;单纯的&lt;code&gt;INCR&lt;/code&gt;命令是原子的，但如果计数逻辑稍微复杂一点，比如要限制计数上限，就需要Lua了&lt;a href="https://bbs.huaweicloud.com/blogs/455583"&gt;-2&lt;/a&gt;。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;场景&lt;/strong&gt;：限制某个用户每分钟只能调用某个接口100次。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;实现&lt;/strong&gt;：在Lua脚本里，你可以先&lt;code&gt;GET&lt;/code&gt;当前的计数，判断是否超过阈值，没超过再用&lt;code&gt;INCR&lt;/code&gt;增加。这整个“读-判断-写”的过程由Lua保证原子性，避免了在高并发下计数超限的风险。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;-- 带阈值的原子计数器&#10;-- KEYS[1]: 计数器键名&#10;-- ARGV[1]: 递增步长&#10;-- ARGV[2]: 最大阈值&#10;local current = tonumber(redis.call(&amp;#39;GET&amp;#39;, KEYS[1]) or &amp;#39;0&amp;#39;)&#10;local new_value = current + tonumber(ARGV[1])&#10;&#10;if new_value &amp;gt; tonumber(ARGV[2]) then&#10; return 0 -- 超过阈值，返回失败&#10;else&#10; redis.call(&amp;#39;SET&amp;#39;, KEYS[1], new_value)&#10; return new_value&#10;end&#10;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;&#10;&lt;li&gt;限流器&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;在“原子计数器”的基础上，可以轻松实现一个简单的时间窗口限流器。&lt;/p&gt;</description></item></channel></rss>