Redis做为一个很是成功的数据库,提供了很是丰富的数据类型和命令,使用这些,咱们能够轻易而高效地完成不少缓存操做,但是总有一些比较特殊的问题或需求须要解决,这时候可能就须要咱们本身定制本身的 Redis 数据结构和命令。css
文章欢迎转载,请尊重做者劳动成果,带上原文连接:http://www.cnblogs.com/zhenbianshu/p/8416162.htmlhtml
线程安全问题
咱们都知道 Redis 是单线程的,但是它怎么会有 线程安全 问题呢?nginx
咱们正常理解的线程安全问题是指单进程多线程
模型内部多个线程操做进程内共享内存
致使的数据资源充突。而 Redis 的线程安全问题的产生,并非来自于 Redis 服务器内部。web
Redis 做为数据服务器,就至关于多个客户端的共享内存,多个客户端就至关于同一进程下的多个线程,若是多个客户端之间没有良好的数据同步策略,就会产生相似线程安全的问题。redis
典型场景是:数据库
user5277=idle
;status = get("user5277")
;set("user5277", "busy")
;致使这个问题的缘由就是虽然 Redis 是单线程的,能保证命令的序列化,但因为其执行效率很高,多个客户端的命令之间不作好请求同步,一样会形成命令的顺序错乱。编程
固然这个问题也很好解决,给用户状态加锁就好了,使同一时间内只能有一个客户端操做用户状态。不过加锁咱们就须要考虑锁粒度、死锁等问题了,无疑添加了程序的复杂性,不利于维护。缓存
Redis 做为一个极其高效的内存数据服务器,其命令执行速度极快,以前看过阿里云 Redis 的一个压测结果,执行效率能够达到 10W写QPS, 60W读QPS,那么,它的效率问题又来自何处呢?安全
答案是网络,作 Web 的都知道,效率优化要从网络作起,服务端又是优化代码,又是优化数据库,不如网络链接的一次优化,而网络优化最有效的就是减小请求数。咱们要知道执行一次内存访问的耗时约是 100ns
,而不一样机房之间来回一次约须要 500000ns
,其中的差距可想而知。服务器
Redis在单机内效率超高,但工业化部署总不会把服务器和 Redis 放在同一台机器上,若是触碰到效率瓶颈的话,那就是网络。
典型场景就是咱们从 Redis 里读出一条数据,再使用这条数据作键,读取另一条数据。这样来来回回,便有两次网络往返。
致使这种问题的缘由就是 Redis 的普通命令没有服务端计算的能力,没法在服务器进行复合命令操做,虽然有 Redis 也提供了 pipeline
的特性,但它须要多个命令的请求和响应之间没有依赖关系。想简化多个相互依赖的命令就只能将数据拉回客户端,由客户端处理后再请求 Redis。
综上,咱们要更高效更方便的使用 Redis 就须要本身“定制”一些命令了。
万幸 Redis 内嵌了 Lua 执行环境,支持 Lua 脚本的执行,经过执行 Lua 脚本,咱们能够把多个命令复合为一个 Lua 脚本,经过 Lua 脚原本实现上文中提到的 Redis 命令的次序性和 Redis 服务端计算。
Lua 是一个简洁、轻量、可扩展的脚本语言,它的特性有:
并且彻底不须要担忧语法问题,Lua 的语法很简单,分分钟使用不成问题。
Redis 在 2.6 版本后,启动时会建立 Lua 环境、载入 Lua 库、定义 Redis 全局表格、存储 redis.pcall
等 Redis 命令,以准备 Lua 脚本的执行。
一个典型的 Lua 脚本执行步骤以下:
交互时序如图
虽然 Lua 脚本使用的是伪客户端,但 Redis 处理它会跟普通客户端同样,也会将执行的 Redis 命令进行 rdb aof 主从复制等操做。
Lua 脚本的使用能够经过 Redis 的 EVAL
和 EVALSHA
命令。
EVAL
适用于单次执行 Lua 脚本,执行脚本前会由脚本内容生成 sha1 校验和,在函数表内查询函数是否已定义,如未定义执行成功后 Redis 会在全局表里缓存这个脚本的校验和为函数名,后续再次执行此命令就不会再建立新的函数了。
而要使用 EVALSHA
命令,就得先使用 SCRIPT LOAD
命令先将函数加载到 Redis,Redis 会返回此函数的 sha1 校验和, 后续就能够直接使用这个校验和来执行命令了。
如下是使用上述命令的例子:
127.0.0.1:6379> EVAL "return 'hello'" 0 0 "hello" 127.0.0.1:6379> SCRIPT LOAD "return redis.pcall('GET', ARGV[1])" "20b602dcc1bb4ba8fca6b74ab364c05c58161a0a" 127.0.0.1:6379> EVALSHA 20b602dcc1bb4ba8fca6b74ab364c05c58161a0a 0 test "zbs"
EVAL 命令的原型是 EVAL script numkeys key [key ...] arg [arg ...]
,在 Lua 函数内部可使用 KEYS[N]
和 ARGV[N]
引用键和参数,须要注意 KEYS 和 ARGV 的参数序号都是从 1
开始的。
还须要注意在 Lua 脚本中,Redis 返回为空时,结果是 false
,而 不是 nil
;
下面写几个 Lua 脚本的实例,用来介绍语法的,仅供参考。
// 使用: EVAL script 2 A B local tmpKey = redis.call('HGET', KEYS[1], KEYS[2]); return redis.call('GET', tmpKey);
// 使用: EVAL script 2 list count local list = {}; local item = false; local num = tonumber(KEYS[2]); while (num > 0) do item = redis.call('LPOP', KEYS[1]); if item == false then break; end; table.insert(list, item); num = num - 1; end; return list;
local elements = redis.call('ZRANK', KEYS[1], 0, KEY[2]); local detail = {}; for index,ele in elements do local info = redis.call('HGETALL', ele); table.insert(detail, info); end; return detail;
基本使用语法就是如此,更多应用就看各个具体场景了。
实现以外,还要一些东西要思考:
首先来总结一下 Redis 中 Lua 的使用场景:
使用 Lua 脚本,咱们还须要注意:
local
关键字。pipeline
,比 Lua 脚本方便。最近工做有了较大的变更,从业务到技术栈都跟原来彻底不一样了,全部代码和业务都脱离了本身掌控的感受真的很不爽,工做中全是“开局一个搜索引擎,语法全靠查”,天天还要熬到很晚熟悉新的东西,有点小累,果真换工做就是找罪受啊。不过走出温馨区后的充实感也在提醒本身正在不停进步,倒也挺有成就感的。
刚接触新的东西没什么沉淀,又不想写一些《带你三天精通 Java》这种水文,工做之余的时间都被拿去补充工做须要的技术栈了,也没时间研究些本身以为有意思的东西,写文章须要素材啊,为了避免自砸招牌,最近可能会少更。。
关于本文有什么问题能够在下面留言交流,若是您以为本文对您有帮助,能够点击下面的 推荐
支持一下我,博客一直在更新,欢迎 关注
。
参考: