"一切单机缓存都是魔鬼,与其被消灭,不如与其共舞"

来由

之前接到我们uAuth的一个bug,具体原因为,当一个用户改密后,原token理应失效,但是线上时常会有原token访问的正常的情况。 可是在测试环境上,确无论如何也复现不出来。

后来仔细分析了源码,是由于token的存储用了openresty的缓存,当token失效后,只在线上的n台服务器中的一台做了失效处理,而其他的n-1台的缓存仍然有效。

思路

缓存不一致 ———— 这确实是好多场景容易碰到的问题,那么怎么办?解决方式有二:

  1. 干掉openresty的缓存,将存储设计由openresty缓存/redis/mysql 改为redis/mysql 两层结构。

  2. 设计openresty的缓存同步机制,从根儿上解决这个问题。

方式1,确实是简单直接有效的方式,but:

使用openresty,不让我用缓存,那和一条咸鱼有什么区别?

于是,我选择了第2种方式,那么问题来了,如何设计这个同步机制 ? 经典的同步肯定是发布/订阅来搞定,第一时间自然想到了kafka,可是查了一圈发现openresty中官方的resty.kafka只支持生产,并且大多数场景都是用来日志记录。到时redis,有一个经典的subscribe例子,那么好,就这么干。

撸码

封装发布/订阅操作

既然同步,那咱就整到位。 第一步,封装一个redis_message.lua

 1 local redis_c = require "resty.redis"
 2 local cjson = require 'cjson.safe'
 3 local M = {}
 4 local mt = {__index = M}
 5 function M:new(cfg)
 6     ocal ins = {
 7         timeout = cfg.timeout or 60000,
 8         pool = cfg.pool or {maxIdleTime = 120000,size = 200},
 9         database = cfg.database or 0,
10         host = cfg .host,
11         port = cfg. port,
12         password = cfg .password or ""
13     }
14     setmetatable(ins,mt)
15     return ins
16 end
17 local function get_con(cfg)
18     local red = redis_c:new()
19     red:set_timeout(cfg.timeout)
20     local ok,err = red:connect(cfg.host,cfg.port)
21     if not ok then
22         return nil
23     end
24     local count ,err = red:get_reused_times()
25     if 0 == count then
26         ok ,err = red:auth(cfg.password)
27     elseif err then
28         return nil
29     end
30 
31     red:select(cfg.database)
32     return red
33 end
34  
35 local function keep_alive(red,cfg)
36     local ok,err = red:set_keepalive(cfg.pool.maxIdleTime,cfg.pool.size)
37     if not ok then
38         red:close()
39     end
40     return true
41 end
42 
43 function M:subscribe(key,func)
44     local co = coroutine.create(function()
45         local red = get_con(self)
46         local ok,err = red:subscribe(key)
47         if not ok then
48             return err
49         end
50         local flag = true
51         while flag do
52             local res,err = red:read_reply()
53             if err then
54                 ;
55             else
56                 if res[1] == "message" then
57                     local obj = cjson.decode(res[3])
58                     flag = func(obj.msg)
59                 end
60             end
61             red:set_keepalive(100,100)
62         end
63     end)
64     coroutine.resume(co)
65 
66 end
67 function M:publish(key,msg)
68     local red = get_con(self)
69     local obj = {}
70     obj.type = type(msg)
71     obj.msg = msg
72     local ok,err = red:publish(key,cjson.encode(obj))
73     if not ok then
74         return false
75     else
76         return true
77     end
78     keep_alive(red,self)
79 end
80 
81 return M
View Code

相关文章: