一、什么是降级
降级的最终目的是保证核心服务的高可用。过程就是丢卒保帅,有些服务是无法降级的,比如支付。
当我们的服务器压力剧增为了保证核心功能的可用性 ,而选择性的降低一些功能的可用性,或者直接关闭该功能。
这就是典型的丢车保帅了。 就比如贴吧类型的网站,当服务器吃不消的时候,可以选择把发帖功能关闭,注册功能关闭,改密码,改头像这些都关了,为了确保登录和浏览帖子这种核心的功能。
降级的原理就是降低次要功能的可用性实用性,增加核心功能的高可用性。
二、 降级的种类
1、根据降级的开关位置
根据降级的开关位置,分为服务代码降级和开关前置降级。
前置降级就是把降级开关放到http请求链路层的上游,降低链路层消耗。比如提升到Nginx,甚至可以提升到前端,当提升到前端,后端访问压力接近于0。
2、根据读写
(1)读降级
比如读取动态数据,降级为读取静态数据。
(2)写降级
比如写入MySQL,降级为写入消息队列或Redis, 等高峰期过后,在从MQ或Redis写入MySQL。需要注意的是,写降级仅针对不重要、实时性不高且不涉及钱的业务。
3 根据降级的性质
根据降级的性质:分为返回内容降级,限流降级,限速降级。
返回内容降级,比如,返回实时数据,降级为返回兜底数据
限流降级,比如1000个请求,我只接受500个。 这么做也是无奈之下选择,要不然系统崩了谁都访问不了。
限速降级,比如,对于那些访问过于频繁的ip进行限速。
nginx 自带限流限速,比如ngx_http_limit_req_module、ngx_http_limit_conn_module 、limit_rate。但是这类模块只是提供了在Nginx配置文件中进行简单的参数配置。如果想更加灵活和功能更多,可以编写自己的lua代码进行控制。
4、根据降级的维护特点
根据降级的维护特点,分为手动降级和自动降级。
手动降级,是人为看到系统负载异常后,手动调整降级。
自动降级,是系统监测到异常后,自动降级,自动降级虽然更加智能,但有时候自动脚本可能会干一些超乎预料的事情。
三、业务分析
大家知道,广告推荐模块的特点:
是要经过对数据模型进行大量分析,并结合用户刚刚的浏览记录,计算出用户喜欢喜欢什么商品,然后给他打什么广告,运算量相当大。
但是广告推荐模块不是商城的核心模块,没有了这个模块, 买家照样可以完成商品的购买。
总结上面两点, 我们可以在商城负载过高时,对广告推荐模块进行降级,让它只从缓存或静态文件中读取数据,或者干脆nginx不返回任何数据给它。
四、设计分析
功能重要性越低,越有可能成为我们降级的对象,降级好比丢卒保帅,保证重要服务基本的可用性。
可以降级的接口有很多,一般是和产品以及市场一起确定的,依据功能的重要程度, 不重要的功能就可以为它配置降级,我们这里挑选广告推荐进行实战。
五、实现降级原理图
六、配置中心
配置中心应该是一个后台管理系统,在后台管理系统中操作Redis。这里为了方便演示我们直接手工操作Redis。
1、nginx.conf
user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
lua_package_path "/usr/share/lua/5.1/lua-resty-redis/lib/?.lua;;/usr/share/lua/5.1/lua-resty-redis-cluster/lib/resty‘7/?.lua;;";
lua_package_cpath "/usr/share/lua/5.1/lua-resty-redis-cluster/lib/libredis_slot.so;;";
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name 127.0.0.1;
server_name 192.168.232.100;
#获取广告推荐数据
location /goods_list_advert {
default_type 'application/x-javascript;charset=utf-8';
content_by_lua_file /etc/nginx/lua/goods_list_advert.lua;
}
#从服务层+mysql获取数据
location /goods_list_advert_from_data {
#allow 127.0.0.1;
#deny all;
default_type 'application/x-javascript;charset=utf-8';
#rewrite https//www.baidu.com/ break;
proxy_pass http://192.168.232.101:18306/goods/getList;
#content_by_lua '
# ngx.say("从服务层+mysql获取数据")
#';
}
}
}
2、goods_list_advert.lua
--获取get或post参数--------------------
local request_method = ngx.var.request_method
local args
--获取参数的值
if "GET" == request_method then
args = ngx.req.get_uri_args()
elseif "POST" == request_method then
ngx.req.read_body()
args = ngx.req.get_post_args()
end
sku_id = args["sku_id"]
--关闭redis的函数--------------------
local function close_redis(redis_instance)
if not redis_instance then
return
end
local ok,err = redis_instance:close();
if not ok then
ngx.say("close redis error : ",err);
end
end
--连接redis--------------------
local redis = require("resty.redis");
--local redis = require "redis"
-- 创建一个redis对象实例。在失败,返回nil和描述错误的字符串的情况下
local redis_instance = redis:new();
--设置后续操作的超时(以毫秒为单位)保护,包括connect方法
redis_instance:set_timeout(1000)
--建立连接
local ip = '127.0.0.1'
local port = 6379
--尝试连接到redis服务器正在侦听的远程主机和端口
local ok,err = redis_instance:connect(ip,port)
if not ok then
ngx.say("connect redis error : ",err)
return close_redis(redis_instance);
end
--从redis里面读取开关--------------------
local key = "level_goods_list_advert"
local switch, err = redis_instance:get(key)
if not switch then
ngx.say("get msg error : ", err)
return close_redis(redis_instance)
end
--得到的开关为空处理--------------------
if switch == ngx.null then
switch = "FROM_DATA" --比如默认值
end
--当开关是要从服务中获取数据时--------------------
if "FROM_DATA" == switch then
ngx.exec('/goods_list_advert_from_data');
--当开关是要从缓存中获取数据时--------------------
elseif "FROM_CACHE" == switch then
local resp = redis_instance:get("hello")
ngx.say(resp)
--当开关是要从静态资源中获取数据时--------------------
elseif "FROM_STATIC" == switch then
ngx.header.content_type="application/x-javascript;charset=utf-8"
local file = "/etc/nginx/html/goods_list_advert.json"
local f = io.open(file, "rb")
local content = f:read("*all")
f:close()
ngx.print(content)
--当开关是要停掉数据获取时--------------------
elseif "SHUT_DOWN" == switch then
ngx.say('no data')
end
--close_redis(redis_instance)
--ngx.exit(ngx.OK)
--判断错误的响应,并进行计数, 后续便可以参考这个数值进行降级
if tonumber(ngx.var.status) == 200 then
ngx.say(ngx.var.status)
ngx.log(ngx.ERR,"upstream reponse status is " .. ngx.var.status .. ",please notice it")
local error_count, err = redis_instance:get("error_count_goods_list_advert")
--得到的数据为空处理
if error_count == ngx.null then
error_count = 0
end
error_count = error_count + 1
--统计错误次数到error_count_goods_list_advert
local resp, err = redis_instance:set("error_count_goods_list_advert",error_count)
if not resp then
ngx.say("set msg error : ",err)
return close_redis(redis_instance)
end
end
close_redis(redis_instance)