防刷的概念:

防刷的目的是为了防止有些IP来爬去我们的网页,获取我们的价格等信息。不像普通的搜索引擎,这种爬去行为我们经过统计最高每秒300次访问,平均每秒266次访问。

由于我们的网站的页面都在CDN上,导致我们的CDN流量会定时冒尖。为了防止这种情况,打算将网页页面的访问从CDN切回主站。同时开启防刷功能,目前设置一秒200次访问即视为非法,会阻止10分钟的访问。

限流的概念:

限流的目的是在大促或者流量突增期间,我们的后端服务假设某个接口能够扛住的的QPS为10000,这时候同时有20000个请求进来,经过限流模块,会先放10000个请求,其余的请求会阻塞一段时间。不简单粗暴的返回404,让客户端重试,同时又能起到流量销峰的作用。

目前防刷模块已经经过ab的压测。

限流模块经过测试后发现,请求几乎很平均的按照限流的模式进行分布,不过会有接近1%的请求超时。

因为极端情况下,一个请求总是被阻塞。(目前想到的解决方案:一个请求被阻塞多次后就放行,不再需要判断当前总请求数。)

redis部署方式:

单docker实例,由marathon负责调度,无需开启rdb和aof

风险:

redis挂了。 处理方式:直接放行。 同时,我们的mesos能够保证redis在秒级内重启。

在限流模块的时候采用了redis的eval命令来进行原子的执行,而防刷模块没有。

下面放出代码,请各位大拿指正。close_redis的代码抄自开涛的博客中相关内容。

防刷代码– access_by_lua_file ‘/opt/ops/lua/access_limit.lua’

  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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297


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_log(ngx_ERR, "set redis keepalive error : ", err)


    end


end


 


local redis = require "resty.redis"


local red = redis:new()


red:set_timeout(1000)


local ip = "redis-ip"


local port = redis-port


local ok, err = red:connect(ip,port)


if not ok then


    return close_redis(red)


end


 


local clientIP = ngx.req.get_headers()["X-Real-IP"]


if clientIP == nil then


   clientIP = ngx.req.get_headers()["x_forwarded_for"]


end


if clientIP == nil then


   clientIP = ngx.var.remote_addr


end


 


local incrKey = "user:"..clientIP..":freq"


local blockKey = "user:"..clientIP..":block"


 


local is_block,err = red:get(blockKey) -- check if ip is blocked


if tonumber(is_block) == 1 then


   ngx.exit(ngx.HTTP_FORBIDDEN)


   return close_redis(red)


end


 


res, err = red:incr(incrKey)


 


if res == 1 then


   res, err = red:expire(incrKey,1)


end


 


if res > 200 then


    res, err = red:set(blockKey,1)


    res, err = red:expire(blockKey,600)


end


 


close_redis(red)





 





-- access_by_lua_file '/opt/ops/lua/access_flow_control.lua'


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_log(ngx_ERR, "set redis keepalive error : ", err)


    end


end


 


local function wait()


   ngx.sleep(1)


end


 


local redis = require "resty.redis"


local red = redis:new()


red:set_timeout(1000)


local ip = "redis-ip"


local port = redis-port


local ok, err = red:connect(ip,port)


if not ok then


    return close_redis(red)


end


 


local uri = ngx.var.uri -- 获取当前请求的uri


local uriKey = "req:uri:"..uri


res, err = red:eval("local res, err = redis.call('incr',KEYS[1]) if res == 1 then local resexpire, err = redis.call('expire',KEYS[1],KEYS[2]) end return (res)",2,uriKey,1)


while (res > 10)


do 


   local twait, err = ngx.thread.spawn(wait)


   ok, threadres = ngx.thread.wait(twait)


   if not ok then


      ngx_log(ngx_ERR, "wait sleep error: ", err)


      break;


   end


   res, err = red:eval("local res, err = redis.call('incr',KEYS[1]) if res == 1 then local resexpire, err = redis.call('expire',KEYS[1],KEYS[2]) end return (res)",2,uriKey,1)


end


close_redis(red)