# 178. 商品详情页动态渲染系统:高可用架构优化之读链路多级降级思路介绍

本章节也只是思路介绍,脚本也会有示例。

读链路:nginx local cache -> 本机房 redis 从集群 -> 数据直连服务的 jvm 堆缓存(之前讲解,这次没做) -> 其他机房 redis 主集群 -> 依赖服务

这里最有可能出现问题的就是:redis 从集群、 数据直连服务、redis 主集群。 如果这三个都挂了,在读链路上来看,也是灾难性的了,依赖服务很有可能被干死嘛。

那么针对这三个出问题的地方进行降级处理,思路如下脚本,当访问失败时做标记,失败一定次数就标记为挂掉, 在一定时间内就不再提供服务或访问降级策略(降级策略都失败就不再提供服务)。 其实这里做的功能就是之前 hystrix 中的熔断器类似的功能。

这个思路是没有问题,唯一的问题就是对于计数处理等场景,如:下面先从缓存获取失败次数,然后加 1,再更新到缓存中, 这种操作方式不会有数据竞争问题导致脏数据吗?还是说在这种高并发的场景下,脏一点无关紧要?

local cjson = require("cjson")
local http = require("resty.http")
local redis = require("resty.redis")  

local function close_redis(red)  
	if not red then  
			return  
	end  
	local pool_max_idle_time = 10000
	local pool_size = 100
	local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  
	if not ok then  
			ngx.say("set keepalive error : ", err)  
	end  
end

local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]

local cache_ngx = ngx.shared.my_cache
local productCacheKey = "product_"..productId
local productCache = cache_ngx:get(productCacheKey)

if productCache == "" or productCache == nil then
  local slaveRedisDegrade = cache_ngx:get("slaveRedisDegrade")
  -- redis 从集群是否挂掉  
  if slaveRedisDegrade == "true" then
        local dataLinkDegrade = cache_ngx:get("dataLinkDegrade")
		-- 数据直连服务是否挂掉
		if dataLinkDegrade == true then
			  local red = redis:new()  
			  red:set_timeout(1000)  
			  local ip = "192.168.31.223"  
			  local port = 1111  
			  local ok, err = red:connect(ip, port)
			  local redisResp, redisErr = red:get("dim_product_"..productId)
			  productCache = redisResp

			    local curTime = os.time()
				local diffTime = os.difftime(curTime, cache_ngx:get("startdataLinkDegradeTime"))
				// 当挂掉时间超过 60 秒的时候,再次尝试从数据直连服务获取数据
				if diffTime > 60 then
				    local httpc = http.new()
					local resp, err = httpc:request_uri("http://192.168.31.179:8767",{
					  method = "GET",
					  path = "/product?productId="..productId
					})

				    if resp then
				      cache_ngx:set("dataLinkDegrade", "false")
				    end
				end
		else
			local httpc = http.new()
			local resp, err = httpc:request_uri("http://192.168.31.179:8767",{
			  method = "GET",
			  path = "/product?productId="..productId
			})
			-- 当从数据直连服务访问不到数据时,就计数
			if not resp then  
				ngx.say("request error :", err)  

				local dataLinkFailureCnt = cache_ngx:get("dataLinkFailureCnt")
				cache_ngx:set("dataLinkFailureCnt", dataLinkFailureCnt + 1)
				-- 当数据直连服务访问失败到达 10 次时,就标记为数据直连服务已经挂掉了
				if dataLinkFailureCnt > 10 then
				  cache_ngx:set("dataLinkDegrade", "true")
				  cache_ngx:set("startDataLinkDegradeTime", os.time())
				end
			end

			productCache = resp.body

			local curTime = os.time()
			local diffTime = os.difftime(curTime, cache_ngx:get("startSlaveRedisDegradeTime"))

			if diffTime > 60 then
			  local red = redis:new()  
			  red:set_timeout(1000)  
			  local ip = "192.168.31.223"  
			  local port = 1112  
			  local ok, err = red:connect(ip, port)  

			  if ok then
				cache_ngx:set("slaveRedisDegrade", "false")
			  end
			end
		end
  else
	  local red = redis:new()  
	  red:set_timeout(1000)  
	  local ip = "192.168.31.223"  
	  local port = 1112  
	  local ok, err = red:connect(ip, port)  

	  if not ok then  
		ngx.say("connect to redis error : ", err)  

		local slaveRedisFailureCnt = cache_ngx:get("slaveRedisFailureCnt")
		cache_ngx:set("slaveRedisFailureCnt", slaveRedisFailureCnt + 1)

		if slaveRedisFailureCnt > 10 then
		  cache_ngx:set("slaveRedisDegrade", "true")
		  cache_ngx:set("startSlaveRedisDegradeTime", os.time())
		end

		return close_redis(red)  
	  end

	  local redisResp, redisErr = red:get("dim_product_"..productId)

	  if redisResp == ngx.null or redisResp == "" or redisResp == nil then  
	    local dataLinkDegrade = cache_ngx:get("dataLinkDegrade")

		if dataLinkDegrade == "true" then
			  local red = redis:new()  
			  red:set_timeout(1000)  
			  local ip = "192.168.31.223"  
			  local port = 1111  
			  local ok, err = red:connect(ip, port)
			  local redisResp, redisErr = red:get("dim_product_"..productId)
			  productCache = redisResp

			  local curTime = os.time()
				local diffTime = os.difftime(curTime, cache_ngx:get("startdataLinkDegradeTime"))

				if diffTime > 60 then
				    local httpc = http.new()
					local resp, err = httpc:request_uri("http://192.168.31.179:8767",{
					  method = "GET",
					  path = "/product?productId="..productId
					})

				    if resp then
				      cache_ngx:set("dataLinkDegrade", "false")
				    end
				end
		else
			local httpc = http.new()
			local resp, err = httpc:request_uri("http://192.168.31.179:8767",{
			  method = "GET",
			  path = "/product?productId="..productId
			})

			if not resp then  
				ngx.say("request error :", err)  

				local dataLinkFailureCnt = cache_ngx:get("dataLinkFailureCnt")
				cache_ngx:set("dataLinkFailureCnt", dataLinkFailureCnt + 1)

				if dataLinkFailureCnt > 10 then
				  cache_ngx:set("dataLinkDegrade", "true")
				  cache_ngx:set("startDataLinkDegradeTime", os.time())
				end
			end

			productCache = resp.body
		end
	  else
		productCache = redisResp
	  end
  end

  math.randomseed(tostring(os.time()):reverse():sub(1, 7))
  local expireTime = math.random(600, 1200)  

  cache_ngx:set(productCacheKey, productCache, expireTime)
end

local context = {
  productInfo = productCache,
}

local template = require("resty.template")
template.render("product.html", context)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175