最近花了大力气在做openapi的优化,使其尽量柔性可用,借此也有些想法想和大家分享一下。 柔性服务,google一下,在网上并没有这样一个标准的概念,所以应该是公司自己取的一个名字。但是这种概念,相信大家都应该很容易能明白,即:
最大程度的保证关键服务的可用性
通俗点来说,一个人不能走路了,他起码可以说话,不能说话了,起码可以点头,头都不能点了,起码得能活着,即心脏还在跳动。这就是柔性。 对应互联网服务来说就是要实现两点:
1.要尽可能成功返回关键数据 2.要尽可能正常接收请求,不能堵死
笔者总结了一下,只要CGI满足其中一个或几个特点,就可以考虑使用柔性服务:
1.在整个CGI的执行过程中,存在关键路径和非关键路径 2.CGI中存在循环调用接口,导致执行时间不确定
我们分上面两种特点来看: 对于第一种,我们举一个简单的例子,比如有一个CGI,做了两件事情分别是:
1.验证登录态 2.获取用户信息
很明显可以看出,验证登录态这个接口是关键路径,而获取用户信息这个接口是非关键的。所以按照柔性服务的定义,当获取用户信息接口失败时,起码还应该返回登录成功。 但是这个时候毕竟还是要区分出完全成功和部分成功的,所以我们可以定义返回码如下(目前腾讯社区开放平台的openapi就是如下定义):
ret==0:完全成功 0=1000:完全失败
这样就很明白了,我们在调用获取个人信息接口失败是,返回ret=1即可。 但是这样是否足够了呢? 考虑一下,如果获取个人信息接口此时是超时,那么会导致整个CGI的返回变慢,从而导致接收进程挂死。此时虽然我们做了所谓的柔性服务,但是仍然是无法正常提供服务的。 怎么解决呢?我们需要一个能够动态调整超时时间的算法,当接口的响应时间远大于平常的平均值时,分配给接口的超时时间也要响应的调小。 伪编码如下:
EMA g_CGITimeoutMng;//超时时间管理
class CCGI
{
public:
CCGI ()
{
m_TrueRet=0;
g_CGITimeoutMng.start();
}
virtual ~CCGI ()
{
g_CGITimeoutMng.stop();
}
int HandleInput()
{
int ret = check_login(g_CGITimeoutMng.remainTime());
if (ret!=0)
{
return 1000;
}
ret = get_profile(g_CGITimeoutMng.remainTime());
if (ret!=0)
{
m_TrueRet++;
}
return m_TrueRet;
}
private:
int m_TrueRet;//容错返回码
};
那这里的动态调整算法使用什么呢?目前使用的是EMA来估算出下一时间点应当分配的超时时间,EMA一开始是用于股票的,后来公司的某大牛将其引入进来动态计算超时时间,大家可以google一下EMA的定义。 现在看来问题是解决了,但是这里会有一个比较大的限制,即:
关键路径和非关键路径要有明确的分割,即不可以颠倒顺序,或者互相穿插
但是万一就是有出现不符合这种限制的情况,该怎么办呢?其实也是有解决方法的,之前我们是只定义了一个g_CGITimeoutMng,他负责分配CGI的总处理时间,但是我们还可以再定义一个g_InterfaceTimeoutMng1,来动态调整某个接口的超时时间。 一般情况下我们仅需要对非关键接口加上这种单独的调整,并且取g_CGITimeoutMng.remainTime()和g_InterfaceTimeoutMng1.remainTime()中的较小值。 示例代码如下:
EMA g_CGITimeoutMng;//超时时间管理
EMA g_InterfaceTimeoutMng1;//超时时间管理
class CCGI
{
public:
CCGI ()
{
m_TrueRet=0;
g_CGITimeoutMng.start();
}
virtual ~CCGI ()
{
g_CGITimeoutMng.stop();
}
int HandleInput()
{
int ret = check_login(g_CGITimeoutMng.remainTime());
if (ret!=0)
{
return 1000;
}
g_InterfaceTimeoutMng1.start()
timeout = g_CGITimeoutMng.remainTime() ret = get_profile(timeout);
g_InterfaceTimeoutMng1.stop();
if (ret!=0)
{
m_TrueRet++;
}
ret = check_right(g_CGITimeoutMng.remainTime());
if (ret!=0)
{
return 1001;
}
return m_TrueRet;
}
private:
int m_TrueRet;//容错返回码
};
这样的话,基本就可以完美解决了。 再回来说一下第二种,即有循环调用的问题,其实要做柔性的主要原因也是因为如果不计算使用的时间而直接容错的话,会导致时间很长。 示例代码如下:
EMA g_CGITimeoutMng;//超时时间管理
class CCGI
{
public:
CCGI ()
{
m_TrueRet=0;
g_CGITimeoutMng.start();
}
virtual ~CCGI ()
{
g_CGITimeoutMng.stop();
}
int HandleInput()
{
int ret;
for (i = 0; i < count; i++)
{
ret = get_somedata(g_CGITimeoutMng.remainTime());
if (ret!=0)
{
m_TrueRet++;
continue;
}
//做取到数据之后该做的事情
}
return m_TrueRet;
}
private:
int m_TrueRet;//容错返回码
};
PS:
上面的代码在调用接口接口之前并没有判断g_CGITimeoutMng.remainTime()<=0,这是因为我们假定接口内部对timeout<=0都是直接返回错误的。如果不是这样的话,需要在调用接口前进行判断。
这样,服务才能做到真正的柔性可用。
hex nuts on #
什么叫柔性服务,能不能具体解释一下
Reply
Dante on #
嗯,其实可能不同的公司会有不同的名字,但是意思应该都是一样的,举个简单的例子,你登录QQ,可能这时候拉取会员等级的服务刮掉了,但是这不是关键路径,所以不能影响你正常登录QQ。而且不能因为获取不到会员等级就卡死在登录界面上,对于用户侧来看,就是容许有损的体验~
Reply
hex nuts on #
原来是这样,那有没有其他的叫法
Reply
guojing on #
总结的还是很有趣的,实际上,任何设计的时候都要考虑所为的柔韧性的。:)
Reply