高峰期慢业务降级方案

在高峰期,为避免低频慢业务影响主业务,本文提出了三层防御的降级方案。首先在数据库层通过脚本批量kill MySQL线程,接着在dubbo层利用dubbo admin禁止服务,最后在web层通过nginx拦截特定URL并返回500错误,以保护主业务正常运行。重点介绍了web层的实现,包括使用redis存储黑名单URL,nginx结合lua脚本进行拦截。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

面对高峰期间低频慢业务对主业务的影响时,一般的做法有两种,一种是大公司采用的底层优化, 代价大收效高,但性价比低,一种是小微企业采用的降级方案,在服务器资源消耗较高时,此做法性价比相对高一些,既不需要太大的研发工作量,也较好的保护了主业务不受影响。

方案

三层防御,首先是数据库层,一旦发现CPU较高,甚至达到100%,可使用脚本批量kill mysql线程,第二层是dubbo层,可利用dubbo admin一键禁止某服务,杀伤力较大,请谨慎使用,第三层也就是web层的防御,通过ngnix对请求进行拦截,只要url在我的黑名单里面,nginx统一返回500,提示用户当前业务已降级,请稍后再试,从而释放了底层数据库服务器资源,保护主业务正常访问。这里主要介绍第三层方案。

实施

  1. 使用redis destop创建键值集用于存放需要拦截的url地址,后续可以持续加入
SADD url_block /webapp/path/get
  1. 确保已安装了nginx并启用了lua,在nginx.conf文件中http模块加入以下配置:
lua_shared_dict limit 50m;
lua_shared_dict block_url 50m;
access_by_lua_file /usr/local/nginx/conf/url_block.lua;
  1. 上述url_block.lua主要内容是每隔10秒从redis中取出最新需要拦截的url,如果客户端url在黑名单中,则返回提示语,中断请求。脚本如下:
function get_client_url()
    local CLIENT_URL = ngx.var.uri
    if CLIENT_URL == nil then
        CLIENT_URL  = "unknown"
    end
    return CLIENT_URL
end

local redis_host = "172.27.66.1"
local redis_port = 6379
local redis_auth = "xxxxxxxx"
local limit = ngx.shared.limit

local random = ngx.var.cookie_seed
local CC_TOKEN = ngx.var.remote_addr .. "_token"

local redis_conn_timeout = 1000
local redis_key = "url_block"
local cache_ttl = 10

local url = get_client_url()
local url_list = ngx.shared.block_url
local last_update_time = url_list:get("last_update_time");


if last_update_time == nil or last_update_time < (ngx.now() - cache_ttl) then

        local redis = require "resty.redis";
        local r = redis:new();
        r:set_timeout(redis_conn_timeout);

        local ok,err = r:connect(redis_host, redis_port);
        r:auth(redis_auth)
        if not ok then
            ngx.say(ngx.DEBUG, "failed" .. err)
            ngx.log(ngx.DEBUG, "connect redis failed " .. err);
        else
            	local new_url_list, err = r:smembers(redis_key);
                if err then
                    ngx.say(err)
                        ngx.log(ngx.DEBUG, "get block url failed " .. err)
                return ngx.exit(401)
            else
                url_list:flush_all();
                for index, banned_url in ipairs(new_url_list) do
                    url_list:set(banned_url, true)
                end

                url_list:set("last_update_time", ngx.now());
            end
        end
end
if url_list:get(url) then
	json = require "cjson"
	local ret = {};
	ret["errorCode"] = "-1";
	ret["message"] = "😭亲爱的用户,当前使用此业务的用户较多,请稍后再试";
	ngx.header['Content-Type'] = 'application/json; charset=utf-8';
	ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
	ngx.say(json.encode(ret));
	ngx.exit(405);
end

附批量杀mysql线程的脚本:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lrapi.lr;
import lrapi.lr;

public class Actions
{
    Connection connect=null;
    Statement stmt=null;
	public int init() throws Throwable {
            Class.forName("com.mysql.jdbc.Driver");
	    System.out.println("Success loading Mysql Driver!");
	    lr.start_transaction("conncetion");
	    connect = DriverManager.getConnection("jdbc:mysql://mysql001/information_schema", "user01", "123456");

	    lr.end_transaction("conncetion", lr.AUTO);
	    stmt = connect.createStatement();
           
		return 0;
	}//end of init


	public int action() throws Throwable {
	    String sql="select ID,DB,COMMAND,TIME,STATE,left(INFO,100) as INFO from information_schema.`PROCESSLIST` "+
		       "where DB is not null AND COMMAND<>'Sleep' AND TIME>=1 and DB='ei_eqs'";
	    lr.start_transaction("事务开始");
	    ResultSet rs = stmt.executeQuery(sql); 
	    Map<String,String> map = new HashMap();
            while(rs.next()){
		String id  = rs.getString("ID");
		String time  = rs.getString("TIME");
		String cmd  = rs.getString("COMMAND");
		String state  = rs.getString("STATE");
		String db = rs.getString("DB");
		String info = rs.getString("INFO");
		map.put(id,"耗时"+time+":"+cmd+":"+state+":"+db+":"+info);
	    }
	    // 开始杀线程
	    for (String id : map.keySet()) {
		try {
		    stmt.execute("kill "+ id);
		} catch (Exception e) {
		    lr.error_message("异常Killed:"+id+":"+map.get(id)+":"+e);
		}		
		System.out.println("Killed:"+id+":"+map.get(id));
	    }
	    lr.error_message("Killed线程数:"+map.size());
            rs.close();
	    lr.end_transaction("事务开始", lr.AUTO);    
	    return 0;
	}//end of action


	public int end() throws Throwable {
	    lr.start_transaction("disconncetion");
	    connect.close();
	    lr.end_transaction("disconncetion", lr.AUTO);
	    return 0;
	}//end of end
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值