关于java:Redis-是并发安全的吗你确定

39次阅读

共计 3666 个字符,预计需要花费 10 分钟才能阅读完成。

起源:https://zhenbianshu.github.io…

Redis 作为一个十分胜利的数据库,提供了十分丰盛的数据类型和命令,应用这些,咱们能够轻易而高效地实现很多缓存操作,可是总有一些比拟非凡问题或需要须要解决,这时候可能就须要咱们本人定制本人的 Redis 数据结构和命令。

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 脚本不便。

小结

最近工作有了较大的变动,从业务到技术栈都跟原来齐全不同了,所有代码和业务都脱离了本人掌控的感觉真的很不爽,工作中全是“开局一个搜索引擎,语法全靠查”,每天还要熬到很晚相熟新的货色,有点小累,果然换工作就是找罪受啊。

不过走出舒服区后的充实感也在揭示本人正在不停提高,倒也挺有成就感的。

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2021 最新版)

2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!

3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0