• openresty lua-resty-lock数据库锁



    openresty lua-resty-lock数据库锁

               

    官网:https://github.com/openresty/lua-resty-lock

               

                    

                                              

    lua-resty-lock 说明

               

    1. This library implements a simple mutex lock in a similar way to
    2. ngx_proxy module's proxy_cache_lock directive.
    3. * lua-resty-lock锁为互斥锁
    4. Under the hood, this library uses ngx_lua module's shared memory
    5. dictionaries. The lock waiting is nonblocking because we use
    6. stepwise ngx.sleep to poll the lock periodically
    7. * 使用ngx_lua的共享模块实现锁
    8. * 锁非阻塞,使用ngx.sleep等待,周期性地获取锁状态

            

    new:创建锁

    1. 语法格式:obj, err = lock:new(dict_name, opts?)
    2. Creates a new lock object instance by specifying the shared dictionary
    3. name (created by lua_shared_dict) and an optional options table opts.
    4. * 使用共享空间实现锁,opts参数可选
    5. In case of failure, returns nil and a string describing the error.
    6. * 如果失败,返回nil、错误描述信息
    7. # opts可选参数
    8. * exptime:锁过期时间,默认30s,可精确到0.001s
    9. * timeout:等待获取锁的超时时间,默认5s,设置为0表示如果获取不到锁,不等待直接离开
    10. * step:锁初始等待时间,单位为秒,可精确到0.001s
    11. * ratio:锁等待时间的放大比例,默认2,每次等待获取锁sleep时长变为上次2倍
    12. * max_step:最大sleep时长,默认0.5s

                      

    lock:加锁

    1. 语法格式:elapsed, err = obj:lock(key)
    2. Tries to lock a key across all the Nginx worker processes in the
    3. current Nginx server instance. Different keys are different locks.
    4. The length of the key string must not be larger than 65535 bytes.
    5. * 所有的worker进程获取同一把锁
    6. * 不同的key表示不同的锁
    7. * key的长度不能大于65535字节
    8. Returns the waiting time (in seconds) if the lock is successfully acquired. Otherwise returns nil and a string describing the error.
    9. * 如果加锁成功,返回等待时间
    10. * 加锁失败,返回nil、错误描述信息
    11. The waiting time is not from the wallclock, but rather is from simply
    12. adding up all the waiting "steps". A nonzero elapsed return value indicates
    13. that someone else has just hold this lock. But a zero return value cannot
    14. gurantee that no one else has just acquired and released the lock.
    15. * 等待时间是所有steps的和
    16. * 非0值表示有其他worker持有锁
    17. * 0不一定表示又其他worker持有锁,并刚刚释放锁
    18. When this method is waiting on fetching the lock, no operating system
    19. threads will be blocked and the current Lua "light thread" will be
    20. automatically yielded behind the scene.
    21. * 等待获取锁期间,操作系统线程不会阻塞
    22. * 当前的lua ligth thread会自动让出cpu
    23. It is strongly recommended to always call the unlock() method to
    24. actively release the lock as soon as possible.
    25. * 建议释放锁的时候手动调用unlock
    26. If the unlock() method is never called after this method call, the
    27. lock will get released when
    28. the current resty.lock object instance is collected automatically by the Lua GC.
    29. the exptime for the lock entry is reached.
    30. * 如果没有手动调用unlock,锁会在以下情况自动释放
    31. * lua回收resty.lock对象
    32. * lock锁过期
    33. Common errors for this method call is
    34. "timeout" : The timeout threshold specified by the timeout option of the new method is exceeded.
    35. "locked" : The current resty.lock object instance is already holding a lock (not necessarily of the same key).
    36. * 常见错误:锁等待超时、当前对象已经持有锁
    37. Other possible errors are from ngx_lua's shared dictionary API.
    38. * 其他可能的错误是ngx_lua的错误
    39. It is required to create different resty.lock instances for multiple
    40. simultaneous locks (i.e., those around different keys)
    41. * 需要为并发锁(不同的线程)创建不同的锁对象(new创建新的锁对象)

            

    unlock:释放锁

    1. 语法格式:ok, err = obj:unlock()
    2. Releases the lock held by the current resty.lock object instance.
    3. Returns 1 on success. Returns nil and a string describing the error otherwise.
    4. * 释放当前锁对象持有的锁
    5. * 如果成功,返回1
    6. * 如果失败,返回nil、错误描述信息
    7. If you call unlock when no lock is currently held, the error
    8. "unlocked" will be returned
    9. * 如果当前对象不持有锁,调用unlock会返回unlocked错误信息

               

    expire:设置当前锁的到期时间

    1. 语法格式:ok, err = obj:expire(timeout)
    2. Sets the TTL of the lock held by the current resty.lock object instance.
    3. This will reset the timeout of the lock to timeout seconds if it is given,
    4. otherwise the timeout provided while calling new will be used.
    5. * 设置当前锁的到期时间
    6. Note that the timeout supplied inside this function is independent from
    7. the timeout provided while calling new. Calling expire() will not change
    8. the timeout value specified inside new and subsequent expire(nil) call
    9. will still use the timeout number from new.
    10. * 如果设置为expire(nil),锁过期时间仍为new创建的过期时间
    11. Returns true on success. Returns nil and a string describing the error otherwise.
    12. * 设置成功,返回true
    13. * 设置失败,返回nil、错误描述信息
    14. If you call expire when no lock is currently held, the error
    15. "unlocked" will be returned
    16. * 如果当前没有持有锁,返回unlocked错误描述信息

              

                       

                                              

    注意事项

               

    为不同的线程创建不同的锁对象

    1. It is always a bad idea to share a single resty.lock object instance
    2. across multiple ngx_lua "light threads" because the object itself is
    3. stateful and is vulnerable to race conditions. It is highly recommended
    4. to always allocate a separate resty.lock object instance for each
    5. "light thread" that needs one
    6. * 推荐为不同的线程创建不同的锁对象

               

    缓存后端数据,加锁流程

    1. One common use case for this library is avoid the so-called "dog-pile effect",
    2. that is, to limit concurrent backend queries for the same key when a cache miss
    3. happens. This usage is similar to the standard ngx_proxy module's proxy_cache_lock
    4. directive.
    5. * lock锁通常用在缓存时,给后端数据库加锁
    6. The basic workflow for a cache lock is as follows:
    7. * Check the cache for a hit with the key. If a cache miss happens,
    8. proceed to step 2.
    9. * Instantiate a resty.lock object, call the lock method on the key,
    10. and check the 1st return value, i.e., the lock waiting time. If it
    11. is nil, handle the error; otherwise proceed to step 3.
    12. * Check the cache again for a hit. If it is still a miss, proceed to step 4;
    13. otherwise release the lock by calling unlock and then return the cached value.
    14. * Query the backend (the data source) for the value, put the result into the
    15. cache, and then release the lock currently held by calling unlock
    16. * 一般流程如下
    17. * 检查缓存是否存在,如果缓存不存在,执行步骤2
    18. * 创建锁,尝试加锁,获取锁后,执行步骤3
    19. * 再次检查缓存,如果有数据,直接返回,如果没有,去后端查询数据
    20. * 后端返回数据,将数据缓存,并返回给客户端

               

    缓存加锁流程

    1. local resty_lock = require "resty.lock"
    2. local cache = ngx.shared.my_cache
    3. -- step 1:
    4. local val, err = cache:get(key)
    5. if val then
    6. ngx.say("result: ", val)
    7. return
    8. end
    9. if err then
    10. return fail("failed to get key from shm: ", err)
    11. end
    12. -- cache miss!
    13. -- step 2:
    14. local lock, err = resty_lock:new("my_locks")
    15. if not lock then
    16. return fail("failed to create lock: ", err)
    17. end
    18. local elapsed, err = lock:lock(key)
    19. if not elapsed then
    20. return fail("failed to acquire the lock: ", err)
    21. end
    22. -- lock successfully acquired!
    23. -- step 3:
    24. -- someone might have already put the value into the cache
    25. -- so we check it here again:
    26. val, err = cache:get(key)
    27. if val then
    28. local ok, err = lock:unlock()
    29. if not ok then
    30. return fail("failed to unlock: ", err)
    31. end
    32. ngx.say("result: ", val)
    33. return
    34. end
    35. --- step 4:
    36. local val = fetch_redis(key)
    37. if not val then
    38. local ok, err = lock:unlock()
    39. if not ok then
    40. return fail("failed to unlock: ", err)
    41. end
    42. -- FIXME: we should handle the backend miss more carefully
    43. -- here, like inserting a stub value into the cache.
    44. ngx.say("no value found")
    45. return
    46. end
    47. -- update the shm cache with the newly fetched value
    48. local ok, err = cache:set(key, val, 1)
    49. if not ok then
    50. local ok, err = lock:unlock()
    51. if not ok then
    52. return fail("failed to unlock: ", err)
    53. end
    54. return fail("failed to update shm cache: ", err)
    55. end
    56. local ok, err = lock:unlock()
    57. if not ok then
    58. return fail("failed to unlock: ", err)
    59. end
    60. ngx.say("result: ", val)

               

                          

                                              

    使用示例

               

    创建mysql容器

    1. docker run -it -d --net fixed --ip 172.18.0.61 -p 3306:3306 \
    2. -e MYSQL_ROOT_PASSWORD=123456 --name mysql4 mysql

               

    修改数据库:权限、创建表、添加数据

    1. mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
    2. Query OK, 0 rows affected (0.01 sec)
    3. mysql> flush privileges;
    4. Query OK, 0 rows affected (0.00 sec)
    5. mysql> create database lihu;
    6. ERROR 1007 (HY000): Can't create database 'lihu'; database exists
    7. mysql> drop database lihu;
    8. Query OK, 1 row affected (0.04 sec)
    9. mysql> create database lihu;
    10. Query OK, 1 row affected (0.01 sec)
    11. mysql> use lihu;
    12. Database changed
    13. mysql> create table test(id int not null primary key auto_increment, name varchar(20));
    14. Query OK, 0 rows affected (0.04 sec)
    15. mysql> insert into test(id, name) values(1, 'gtlx'), (2, 'hzw');
    16. Query OK, 2 rows affected (0.01 sec)
    17. Records: 2 Duplicates: 0 Warnings: 0
    18. mysql> select * from test;
    19. +----+------+
    20. | id | name |
    21. +----+------+
    22. | 1 | gtlx |
    23. | 2 | hzw |
    24. +----+------+
    25. 2 rows in set (0.00 sec)

                

    nginx.conf

    1. pcre_jit on;
    2. #error_log logs/error.log;
    3. #error_log logs/error.log notice;
    4. #error_log logs/error.log info;
    5. events {
    6. worker_connections 1024;
    7. }
    8. http {
    9. include mime.types;
    10. default_type application/octet-stream;
    11. client_body_temp_path /var/run/openresty/nginx-client-body;
    12. proxy_temp_path /var/run/openresty/nginx-proxy;
    13. fastcgi_temp_path /var/run/openresty/nginx-fastcgi;
    14. uwsgi_temp_path /var/run/openresty/nginx-uwsgi;
    15. scgi_temp_path /var/run/openresty/nginx-scgi;
    16. sendfile on;
    17. keepalive_timeout 65;
    18. include /etc/nginx/conf.d/*.conf;
    19. #设置共享缓存
    20. lua_shared_dict test 10m;
    21. }

              

    default.conf

    1. server {
    2. listen 80;
    3. server_name localhost;
    4. location / {
    5. root /usr/local/openresty/nginx/html;
    6. index index.html index.htm;
    7. }
    8. location /test {
    9. content_by_lua_block {
    10. local cache = ngx.shared.test;
    11. local cjson = require 'cjson';
    12. local resty_lock = require 'resty.lock';
    13. local mysql = require 'resty.mysql';
    14. local db, err = mysql:new();
    15. if not db then
    16. ngx.say("mysql创建失败", err);
    17. end
    18. db:set_timeout(1000);
    19. local res, err, errcode, sqlstate = db:connect({
    20. host = "172.18.0.61", port = 3306, database = "lihu",
    21. user = "root", password = "123456"
    22. });
    23. if not res then
    24. ngx.say("连接出错", err, errcode, sqlstate);
    25. end
    26. local function fetch_data(id)
    27. local res, err, errcode, sqlstate = db:query(
    28. "select * from test where id ="..id
    29. );
    30. if not res then
    31. ngx.say("数据查询失败", err);
    32. return nil;
    33. end
    34. if #res == 0 then
    35. ngx.say("后端没有查询到数据");
    36. return nil;
    37. end
    38. ngx.say("后端查询结果 ==> ");
    39. ngx.say(type(res));
    40. ngx.say(cjson.encode(res));
    41. for key,value in pairs(res) do
    42. ngx.say(key, " ==> ", cjson.encode(value))
    43. end
    44. ngx.say("\n后端返回数据 ==> ",res[1].name);
    45. return res[1].name;
    46. end
    47. local id = ngx.var.arg_id;
    48. local value, err = cache:get(id);
    49. if value then
    50. ngx.say("缓存中查询到结果: ", id, " ==> ", value);
    51. return
    52. end
    53. local lock, err = resty_lock:new("test");
    54. if not lock then
    55. ngx.say("创建锁失败 ==> ", err);
    56. return
    57. end
    58. local elapsed, err = lock:lock("lock");
    59. if not elapsed then
    60. ngx.say("加锁失败 ==> ", err);
    61. return
    62. end
    63. value, err = cache:get(id);
    64. if value then
    65. local ok, err = lock:unlock();
    66. if not ok then
    67. ngx.say("释放锁失败 ==> ", err);
    68. end
    69. ngx.say("缓存中二次查询获取结果: ", id, " ==> ", value);
    70. return
    71. end
    72. value = fetch_data(id);
    73. if not value then
    74. ok, err = lock:unlock();
    75. if not ok then
    76. ngx.say("释放锁失败 ==> ", err);
    77. end
    78. ngx.say("数据库没有查询到结果");
    79. return
    80. end
    81. cache:set(id, value);
    82. ok, err = lock:unlock();
    83. if not ok then
    84. ngx.say("释放锁失败 ==> ", err);
    85. end
    86. ngx.say("数据库查询后返回数据: ", id, " ==> ", value);
    87. }
    88. }
    89. error_page 500 502 503 504 /50x.html;
    90. location = /50x.html {
    91. root /usr/local/openresty/nginx/html;
    92. }
    93. }

            

    创建openresty容器

    1. docker run -it -d --net fixed --ip 172.18.0.101 -p 8001:80 \
    2. -v /Users/huli/lua/openresty/cache/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf \
    3. -v /Users/huli/lua/openresty/cache/default.conf:/etc/nginx/conf.d/default.conf \
    4. --name open-cache lihu12344/openresty

             

    使用测试

    1. # 缓存中没有,需要从后端数据库读取数据
    2. huli@hudeMacBook-Pro cache % curl --location --request GET 'localhost:8001/test?id=2'
    3. 后端查询结果 ==>
    4. table
    5. [{"id":2,"name":"hzw"}]
    6. 1 ==> {"id":2,"name":"hzw"}
    7. 后端返回数据 ==> hzw
    8. 数据库查询后返回数据: 2 ==> hzw
    9. # 再次读取数据时,直接从缓存中获取
    10. huli@hudeMacBook-Pro cache % curl --location --request GET 'localhost:8001/test?id=2'
    11. 缓存中查询到结果: 2 ==> hzw

               

                       

  • 相关阅读:
    如何自动获取短信验证码?
    了解一下RabbitMQ
    Java -- this关键字
    ZooKeeper~ZooKeeper集群搭建
    AtCoder Beginner Contest 276「A」「B」「C」「D 思维」「E 联通块」「F 树状数组维护期望」
    918. 环形子数组的最大和
    使用JS简单实现一下apply、call和bind方法
    自己最好的canny实现(c#)
    设计与人工智能的关系,人工智能和建筑设计
    【图像处理与机器视觉】图像处理概述与像素
  • 原文地址:https://blog.csdn.net/weixin_43931625/article/details/125918046