code[class*="language-"],
pre[class*="language-"] {
background-color: #fdfdfd;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin-bottom: 1em;
}

:not(pre) > code[class*="language-"] {
position: relative;
padding: .2em;
-webkit-border-radius: 0.3em;
-moz-border-radius: 0.3em;
-ms-border-radius: 0.3em;
-o-border-radius: 0.3em;
border-radius: 0.3em;
color: #c92c2c;
border: 1px solid rgba(0, 0, 0, 0.1);
display: inline;
white-space: normal;
}

pre[class*="language-"]:before,
pre[class*="language-"]:after {
content: '';
z-index: -2;
display: block;
position: absolute;
bottom: 0.75em;
left: 0.18em;
width: 40%;
height: 20%;
max-height: 13em;
-webkit-box-shadow: 0px 13px 8px #979797;
-moz-box-shadow: 0px 13px 8px #979797;
box-shadow: 0px 13px 8px #979797;
-webkit-transform: rotate(-2deg);
-moz-transform: rotate(-2deg);
-ms-transform: rotate(-2deg);
-o-transform: rotate(-2deg);
transform: rotate(-2deg);
}

:not(pre) > code[class*="language-"]:after,
pre[class*="language-"]:after {
right: 0.75em;
left: auto;
-webkit-transform: rotate(2deg);
-moz-transform: rotate(2deg);
-ms-transform: rotate(2deg);
-o-transform: rotate(2deg);
transform: rotate(2deg);
}

.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #7D8B99;
}

.token.punctuation {
color: #5F6364;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.function-name,
.token.constant,
.token.symbol,
.token.deleted {
color: #c92c2c;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.function,
.token.builtin,
.token.inserted {
color: #2f9c0a;
}

.token.operator,
.token.entity,
.token.url,
.token.variable {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}

.token.atrule,
.token.attr-value,
.token.keyword,
.token.class-name {
color: #1990b8;
}

.token.regex,
.token.important {
color: #e90;
}

.language-css .token.string,
.style .token.string {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}

.token.important {
font-weight: normal;
}

.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}

.token.entity {
cursor: help;
}

.namespace {
opacity: .7;
}

@media screen and (max-width: 767px) {
pre[class*="language-"]:before,
pre[class*="language-"]:after {
bottom: 14px;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}

}

.token.tab:not(:empty):before,
.token.cr:before,
.token.lf:before {
color: #e0d7d1;
}

pre[class*="language-"].line-numbers {
padding-left: 0;
}

pre[class*="language-"].line-numbers code {
padding-left: 3.8em;
}

pre[class*="language-"].line-numbers .line-numbers-rows {
left: 0;
}

pre[class*="language-"][data-line] {
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
}
pre[data-line] code {
position: relative;
padding-left: 4em;
}
pre .line-highlight {
margin-top: 0;
}

pre.line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}

pre.line-numbers > code {
position: relative;
}

.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;

-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}

.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
-->

前言

Redis作为一个非常成功的数据库,提供了非常丰富的数据类型和命令,使用这些,我们可以轻易而高效地完成很多缓存操作,可是总有一些比较特殊的问题或需求需要解决,这时候可能就需要我们自己定制自己的 Redis 数据结构和命令。

文章欢迎转载,请尊重作者劳动成果,带上原文链接:http://www.cnblogs.com/zhenbianshu/p/8416162.html


Redis命令问题

线程安全问题

我们都知道 Redis 是单线程的,可是它怎么会有 线程安全 问题呢?

我们正常理解的线程安全问题是指单进程多线程模型内部多个线程操作进程内共享内存导致的数据资源充突。而 Redis 的线程安全问题的产生,并不是来自于 Redis 服务器内部。

Redis 作为数据服务器,就相当于多个客户端的共享内存,多个客户端就相当于同一进程下的多个线程,如果多个客户端之间没有良好的数据同步策略,就会产生类似线程安全的问题。

典型场景是:

  • Redis 内存储了一个用户的状态: user5277=idle
  • 客户端连接 A 读取了用户状态,获取到用户的空闲状态 status = get("user5277")
  • 客户端连接 B 也同样读取了用户状态;
  • 客户端连接 A 给用户安排了一个任务,并将 Redis 内用户状态置为忙碌 set("user5277", "busy")
  • 客户端连接 B 同样设置用户为忙碌状态。
  • 可是此时用户却被同时分配了两个任务。

导致这个问题的原因就是虽然 Redis 是单线程的,能保证命令的序列化,但由于其执行效率很高,多个客户端的命令之间不做好请求同步,同样会造成命令的顺序错乱。

当然这个问题也很好解决,给用户状态加锁就行了,使同一时间内只能有一个客户端操作用户状态。不过加锁我们就需要考虑锁粒度、死锁等问题了,无疑添加了程序的复杂性,不利于维护。

效率问题

Redis 作为一个极其高效的内存数据服务器,其命令执行速度极快,之前看过阿里云 Redis 的一个压测结果,执行效率可以达到 10W写QPS, 60W读QPS,那么,它的效率问题又来自何处呢?

答案是网络,做 Web 的都知道,效率优化要从网络做起,服务端又是优化代码,又是优化数据库,不如网络连接的一次优化,而网络优化最有效的就是减少请求数。我们要知道执行一次内存访问的耗时约是 100ns,而不同机房之间来回一次约需要 500000ns,其中的差距可想而知。

Redis在单机内效率超高,但工业化部署总不会把服务器和 Redis 放在同一台机器上,如果触碰到效率瓶颈的话,那就是网络。

典型场景就是我们从 Redis 里读出一条数据,再使用这条数据做键,读取另外一条数据。这样来来回回,便有两次网络往返。

导致这种问题的原因就是 Redis 的普通命令没有服务端计算的能力,无法在服务器进行复合命令操作,虽然有 Redis 也提供了 pipeline 的特性,但它需要多个命令的请求和响应之间没有依赖关系。想简化多个相互依赖的命令就只能将数据拉回客户端,由客户端处理后再请求 Redis。

综上,我们要更高效更方便的使用 Redis 就需要自己“定制”一些命令了。


内嵌Lua的执行

万幸 Redis 内嵌了 Lua 执行环境,支持 Lua 脚本的执行,通过执行 Lua 脚本,我们可以把多个命令复合为一个 Lua 脚本,通过 Lua 脚本来实现上文中提到的 Redis 命令的次序性和 Redis 服务端计算。

Lua

Lua 是一个简洁、轻量、可扩展的脚本语言,它的特性有:

  • 轻量:源码包只有核心库,编译后体积很小。
  • 高效:由 ANSI C 写的,启动快、运行快。
  • 内嵌:可内嵌到各种编程语言或系统中运行,提升静态语言的灵活性。如 OpenResty 就是将 Lua 嵌入到 nginx 中执行。

而且完全不需要担心语法问题,Lua 的语法很简单,分分钟使用不成问题。

执行步骤

Redis 在 2.6 版本后,启动时会创建 Lua 环境、载入 Lua 库、定义 Redis 全局表格、存储 redis.pcall 等 Redis 命令,以准备 Lua 脚本的执行。

一个典型的 Lua 脚本执行步骤如下:

  1. 检查脚本是否执行过,没执行过使用脚本的 sha1 校验和生成一个 Lua 函数;
  2. 为函数绑定超时、错误处理勾子;
  3. 创建一个伪客户端,通过这个伪客户端执行 Lua 中的 Redis 命令;
  4. 处理伪客户端的返回值,最终返回给客户端;

交互时序如图

虽然 Lua 脚本使用的是伪客户端,但 Redis 处理它会跟普通客户端一样,也会将执行的 Redis 命令进行 rdb aof 主从复制等操作。

使用

Lua 脚本的使用可以通过 Redis 的 EVALEVALSHA 命令。

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 脚本实例

下面写几个 Lua 脚本的实例,用来介绍语法的,仅供参考。

  • Redis 里 hashSet A 的 字段 B 的值是 C,取出 Redis 里键为 C 的值。
// 使用: EVAL script 2 A B

local tmpKey = redis.call('HGET', KEYS[1], KEYS[2]);
return redis.call('GET', tmpKey);
  • 一次 lpop 出多个值,直到值为 n,或 list 为空(pipeline 也可轻易实现);
// 使用: 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;
  • 获取 zset 内 score 最多的 n 个元素 对应 hashset 中的详细信息;
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 脚本实现原子性操作,避免不同客户端访问 Redis 服务器造成的数据冲突。
  • 在前后多次请求的结果有依赖时,可以使用 Lua 脚本把多个请求整合为一个请求。

注意点

使用 Lua 脚本,我们还需要注意:

  • 要保证安全性,在 Lua 脚本中不要使用全局变量,以免污染 Lua 环境,虽然使用全局变量全报错,Lua 脚本停止执行,但还是在定义变量时添加 local 关键字。
  • 要注意 Lua 脚本的时间复杂度,Redis 的单线程同样会阻塞在 Lua 脚本的执行中。
  • 使用 Lua 脚本实现原子操作时,要注意如果 Lua 脚本报错,之前的命令同样无法回滚。
  • 一次发出多个 Redis 请求,但请求前后无依赖时,使用 pipeline,比 Lua 脚本方便。

小结

最近工作有了较大的变动,从业务到技术栈都跟原来完全不同了,所有代码和业务都脱离了自己掌控的感觉真的很不爽,工作中全是“开局一个搜索引擎,语法全靠查”,每天还要熬到很晚熟悉新的东西,有点小累,果然换工作就是找罪受啊。不过走出舒适区后的充实感也在提醒自己正在不停进步,倒也挺有成就感的。

刚接触新的东西没什么沉淀,又不想写一些《带你三天精通 Java》这种水文,工作之余的时间都被拿去补充工作需要的技术栈了,也没时间研究些自己觉得有意思的东西,写文章需要素材啊,为了不自砸招牌,最近可能会少更。。

关于本文有什么问题可以在下面留言交流,如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我,博客一直在更新,欢迎 关注

参考:

Redis 设计与实现 » Lua 脚本

Redis 与 Lua 脚本

Redis的Lua脚本编程的实现和应用

用Lua定制Redis命令的更多相关文章

  1. Redis进阶之使用Lua脚本自定义Redis命令

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 1.在Redis ...

  2. Redis中的原子操作(2)-redis中使用Lua脚本保证命令原子性

    Redis 如何应对并发访问 使用 Lua 脚本 Redis 中如何使用 Lua 脚本 EVAL EVALSHA SCRIPT 命令 SCRIPT LOAD SCRIPT EXISTS SCRIPT ...

  3. 玩转 lua in Redis

    一.引言 Redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何平台上,也可以嵌入到大多数语言当中,来扩展其功 ...

  4. Lua 与 Redis

    Lua 与 Redis 标签: Java与NoSQL 从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis - 案例-实现访问频率限制: 实现访问者 $ip 在一定的 ...

  5. nginx lua mysql redis设置

    最近公司网站改版,程序和数据库全部用新版,旧版的数据要导入,旧网站的30万条数据url要全部重定向到新版网站,正好前段时间在学习nginx+lua+mysql+memcache(redis),找资料真 ...

  6. Lua: 给 Redis 用户的入门指导

    转自:http://www.oschina.net/translate/intro-to-lua-for-redis-programmers 可能你已经听说过Redis 中嵌入了脚本语言,但是你还没有 ...

  7. 高性能伪事务之Lua in Redis

    EVAL简介 Redis2.6加入了对Lua脚本的支持.Lua脚本可以被用来扩展Redis的功能,并提供更好的性能. 在<Redis拾遗>中曾经引用了<Redis in Action ...

  8. Netty开发redis客户端,Netty发送redis命令,netty解析redis消息

    关键字:Netty开发redis客户端,Netty发送redis命令,netty解析redis消息, netty redis ,redis RESP协议.redis客户端,netty redis协议 ...

  9. lua 操作redis

    Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行.使用脚本的好处如下: 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在red ...

随机推荐

  1. 聊聊C#与冲顶大会

    一.由跳一跳开始 2018年初,跳一跳小程序着实火了一把.一时间,各种攻略,甚至辅助工具也应运而生.作为.net阵营的一员,园友的这篇http://www.cnblogs.com/bqh10086/p ...

  2. 简单的ajax遮罩层(加载进度圈)cvi_busy_lib.js

    cvi_busy_lib.js cvi_busy_lib.js 是一个基于ajax的遮罩js,遮罩区域为body区域.使用比较简单. 效果: 在下面的Js代码,标注为红色标记为需要设置的参数. 1.g ...

  3. Activiti 5.22.0 之自由驳回任务实现(亲测)

    ​ 上篇博文,我们完成一个任务SKIP的实现,说好要给各位看官带来驳回实现的现在,就奉上具体实现和讲解.(其实我感觉我的注释写的已经非常清楚了,哈哈) ​ 依旧是,先说我们的需求和思路. PS: ​ ...

  4. Jfinal拦截器源码解读

    本文对Jfinal拦截器源码做以下分析说明

  5. Mvc 模板化的Razor引擎委托

    最近在研究NopCommerce,它后台用的富文本编辑器可根据语言库加载不同语言的编辑器,其中用到了模板化Razor引擎委托,参考这儿 废话不多说,直接上代码. public static class ...

  6. HashMap 源码详细分析(JDK1.8)

    一.概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap.HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值, ...

  7. Codeforces 834E The Bakery【枚举+数位dp】

    E. Ever-Hungry Krakozyabra time limit per test:1 second memory limit per test:256 megabytes input:st ...

  8. [51nod1440]迈克打电话

    有n只熊,从1到n进行编号. 第i只熊的电话号码是si.每只熊会给那些电话号码是他的子串的熊打电话(可能会给自己打). call(i, j) 表示第i只熊给第j只熊打电话的次数,也就是第j个串在第i个 ...

  9. BZOJ 2219: 数论之神

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2219 N次剩余+CRT... 就是各种奇怪的分类讨论.. #include<cstrin ...

  10. vue vue-style-loader !css-loader错误

    最近在学习vue框架,使用webpack打包vue项目,在执行npm run start的时候 出现如下错误: This dependency was not found: * !!vue-style ...