0%

一些比较有用的库

awesome-resty

awesome-resty 这个项目,维护了几乎所有 OpenResty 可用的包。

lua-resty-jit-uuid

lua-resty-jit-uuid 用于生成 uuid,注意如果在容器中使用需要对种子特殊处理。

lua-rapidjson

lua-rapidjson,它是对 rapidjson 这个腾讯开源的 JSON 库的封装,以性能见长。支持 JSON Schema,JSON Schema 是一个通用的标准,借助这个标准,可以精确地描述接口中参数的格式,以及如何校验的问题。

1
2
3
4
5
6
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
阅读全文 »

性能优化

避免使用 NYI

pairs() vs ipairs()

如果 table 内部为 array,应该优先使用哪个?为什么?


pairs vs ipairs

NYI(Not Yet Implemented)的概念

LuaJIT 的运行时环境,除了一个汇编实现的 Lua 解释器外,还有一个可以直接生成机器代码的 JIT 编译器。

LuaJIT 中 JIT 编译器的实现还不完善,有一些原语它还无法编译,因为这些原语实现起来比较困难,再加上 LuaJIT 的作者目前处于半退休状态。

这些原语包括常见的 pairs() 函数、unpack() 函数、基于 Lua CFunction 实现的 Lua C 模块等。

这样一来,当 JIT 编译器在当前代码路径上遇到它不支持的操作时,便会退回到解释器模式。

全称为 Not Yet Implemented NYI 完整列表

阅读全文 »

需要注意的点

时间 API

返回当前时间的 API,如果没有非阻塞网络 IO 操作来触发,便会一直返回缓存的值,而不是像我们想的那样,能够返回当前的实时时间。

1
2
3
$ resty -e 'ngx.say(ngx.now())
os.execute("sleep 1")
ngx.say(ngx.now())'

在两次调用 ngx.now 之间,使用 Lua 的阻塞函数 sleep 了 1 秒钟,但从打印的结果来看,这两次返回的时间戳却是一模一样的。
如果换成是非阻塞的 sleep 函数,它就会打印出不同的时间戳了。

1
2
3
$ resty -e 'ngx.say(ngx.now())
ngx.sleep(1)
ngx.say(ngx.now())'
阅读全文 »

OpenResty 高性能的原因

运行在 Nginx 整体架构之上

OpenResty 的 master 和 worker 进程中,都包含一个 LuaJIT VM。在同一个进程内的所有协程,都会共享这个 VM,并在这个 VM 中运行 Lua 代码。

而在同一个时间点上,每个 worker 进程只能处理一个用户的请求,也就是只有一个协程在运行。

NGINX 实际上是通过 epoll 的事件驱动,来减少等待和空转,才尽可能地让 CPU 资源都用于处理用户的请求。
毕竟,只有单个的请求被足够快地处理完,整体才能达到高性能的目的。
如果采用的是多线程模式,让一个请求对应一个线程,那么在 C10K 的情况下,资源很容易就会被耗尽的。


OpenResty 和 LuaJit 架构图
阅读全文 »

OpenResty 的发展

OpenResty 并不像其他的开发语言一样从零开始搭建,而是基于成熟的开源组件——NGINX 和 LuaJIT。

OpenResty 诞生于 2007 年,不过,它的第一个版本并没有选择 Lua,而是用了 Perl,这跟作者章亦春的技术偏好有很大关系。

但 Perl 的性能远远不能达到要求,于是,在第二个版本中,Perl 就被 Lua 给替换了。

不过,在 OpenResty 官方的项目中,Perl 依然占据着重要的角色,OpenResty 工程化方面都是用 Perl 来构建,比如测试框架、Linter、CLI 等。


OpenResty 的主要组成
阅读全文 »

背景

我们公司平时业务开发使用 OpenResty,也一直使用 lua-resty-mysql 库连接 Mysql。

有一个业务场景,MySQL 中表的主键是自增 id,业务需要在 insert 语句之后需要拿到此次插入的自增 id 值。
我们一直使用 lua-resty-mysql 库(Openresty 组件中的一个)中执行 insert 语句返回的 res.insert_id 作为这个值(这样可以一次请求就拿到这个值)。

但是最近开始这个值变成负数,导致业务出现很多错误,于是便开始排查为什么 res.insert_id 会返回负数。

基本猜测

首先确定 res.insert_id 这个值是负数之后,第一反应想到的是会不会 MySQL 的主键超出类型范围,导致自增 id 变成负数,但是想到我们用的类型时 bigint,不太可能会超出范围,

阅读全文 »

Redis 事务机制

参考:
    1. [从面试被问到吐血,Redis事务的问题个个触及知识盲区,脸都绿了](https://zhuanlan.zhihu.com/p/156889090)

1. multi —— 开启事务
2.命令入队列
    之后所有的命令都会放入事务队列中,并不会立刻执行。
    如果客户端发送的命令为 EXEC,DISCARD 的其中一个,服务器会立刻执行这个命令。
    对于其它命令,服务器并不会立刻执行,而是将这个命令放入一个事务队列中,然后向客户端返回 QUEUED 回复
3. exec —— 执行事务
4. DISCARD —— 放弃执行

Redis 的事务机制可以保证一致性和隔离性(watch 机制)。
持久性取决于是否开启持久化以及持久化机制,极端情况下还是无法保证。
原子性的情况比较复杂:
    1. 命令入队时就报错,会放弃事务执行,保证原子性
    2. 命令入队时没报错,实际执行时报错,不保证原子性
    3. EXEC 命令执行时实例故障,如果开启了 AOF 日志,可以保证原子性
      (使用 redis-check-aof 工具检查 AOF 日志文件,这个工具可以把未完成的事务操作从 AOF 文件中去除。
       使用 AOF 恢复实例后,事务操作不会再被执行,从而保证原子性。
    只有当事务中使用的命令语法有误时,原子性得不到保证,在其它情况下,事务都可以原子性执行。
阅读全文 »

热点 key 问题

热点 key 问题就是,突然有几十万甚至更大的请求去访问 Redis 上的某个特定 key。
那么,这样会造成流量过于集中,达到 Redis 单实例瓶颈(一般是 10W OPS 级别),或者物理网卡上限,从而导致这台 Redis 的服务器 Hold 不住。

怎么发现热 key?
1. 凭借业务经验,进行预估哪些是热 key
2. 在客户端进行收集
3. 在 Proxy 层做收集
4. 用 Redis 自带命令
    4.1 monitor 命令,该命令可以实时抓取出 Redis 服务器接收到的命令,然后写代码统计出热 key 是啥。
        当然,也有现成的分析工具可以给你使用,比如 redis-faina。但是该命令在高并发的条件下,有内存增暴增的隐患,还会降低 Redis 的性能。
    4.2 hotkeys 参数(必须配合 LFU),Redis 4.0.3 提供了 redis-cli 的热点 key 发现功能,执行 redis-cli 时加上 –hotkeys 选项即可。
        但是该参数在执行的时候,如果 key 比较多,执行起来比较慢。
5. 自己抓包评估,Redis 客户端使用 TCP 协议与服务端进行交互,通信协议采用的是 RESP。自己写程序监听端口,按照 RESP 协议解析数据,进行分析。
   缺点就是开发成本高,维护困难,有丢包可能性。

如何解决?
1. 设置二级缓存(推荐)
2. 利用分片算法的特性,对 key 进行打散处理
   hot key 之所以是 hot key,是因为它只有一个 key,落地到一个实例上。
   可以给 hot key 加上前缀或者后缀,把一个 hotkey 的数量经过分片分布到不同的实例上,将访问量均摊到所有实例。
阅读全文 »