最后更新于 .

相信对于这个标题,用过lisp的朋友一定不陌生,本来也是准备了一大堆理论要讲,想了想还是直接举例子比较好。 就举最近产品提的一个产品需求吧,简单描述一下:

  1. 对于不同的第三方应用,有不同的频率限制。没有配置则使用默认值
  2. 对于不同的第三方应用,在不同的时间段,有不同的频率限制。没有配置则使用默认值

公司内部都是用C++,当时第一点想到的肯定是配置一个xml文件,里面配置上这些参数,在进程启动的时候,用tinnyxml或者其他xml解析器把xml解析成C++可以辨识的数据结构。

我们来看一下这个xml配置有多复杂:

<freq_config interval="60" pt_relevant="1">
    <app_list>
        <app appid="0" max_day_load="360000">
            <time_range_list>
                <time_range begin_time="1" end_time="5" load_perc="0.1" />
                <time_range begin_time="17" end_time="20" load_perc="0.5" />
            </time_range_list>
            <pt_config xy="0.5" qz="0.5" wb="0.5"></pt_config>
        </app>
        <app appid="10881" max_day_load="100000">
            <time_range_list>
                <time_range begin_time="1" end_time="5" load_perc="0.1" />
                <time_range begin_time="17" end_time="20" load_perc="0.5" />
            </time_range_list>
            <pt_config xy="0.5" qz="0.5" wb="0.5"></pt_config>
        </app>
        <app appid="10883" max_day_load="360000">
            <time_range_list>
                <time_range begin_time="1" end_time="5" load_perc="0.5" />
                <time_range begin_time="17" end_time="20" load_perc="0.5" />
            </time_range_list>
            <pt_config xy="0.5" qz="0.5" wb="0.5"></pt_config>
        </app>
    </app_list>
</freq_config>

这个配置可以说已经非常复杂了,而最重要的是,这种产品上的需求,说变就变,如果真的现有的配置格式满足不了新需求,那么只能变更了。做哪些变更呢?

  1. 配置文件格式
  2. 配置文件解析代码
  3. 数据结构代码
  4. 计算逻辑
  5. 重新编译发布

一个小小的产品需求变更居然会带来这么多修改量,这是很不合理的,况且需求变更从来都是最频繁的事情。 我们对这五个操作步骤考虑一下: 如果把计算逻辑从C++中剥离出来,放在脚本里,由脚本解析器作为一个通用的配置文件解析工具,那么1,2,3,4,5步就都不需要修改任何C++框架代码

好吧,我们已经得到答案了,其实C++框架只是想要一个频率限制的值而已,那么这个值究竟是怎么算出来的,就交给脚本去做吧。 而对脚本来说,xml什么的配置文件完全没有必要,因为在python,lua,lisp这样的脚本语言这里,数据和代码的界限已经越来越小(lisp的思想的数据=代码,实在是很有预见性的理论)。

OK,那我们来看一下实现,由于lua本身的小巧,这次的实现是使用了lua作为脚本(其实我更想用python,但是考虑到python的确有点太大了,不过如果想看C++中调用python的话,可以看这里:C,C++代码中调用python脚本C,C++中调用python脚本(2)-高级应用) lua的代码(没有写太复杂,只是示例而已):

tb_app2limit = {
    {100, 100},
    {23, 1000}
}
 
function get_freq_limit(appid)
 
    limit = -1
 
    for k,v in pairs(tb_app2limit) do
        if v[1] == appid then
            limit = v[2]
            break
        end
    end
 
    return limit
end

C++封装类代码:

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <iostream>
#include <memory>
#include <sstream>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
#include "lua_pub.h"
using namespace std;

#ifndef FREQCONF_ERROR
#define FREQCONF_ERROR(fmt, args...) \
    snprintf(m_szErrMsg, sizeof(m_szErrMsg), "[%s][%d][%s]"fmt, \
             __FILE__, __LINE__,__FUNCTION__, ##args)
#endif

struct StLuaFree
{
    StLuaFree(lua_State* L)
    {
        m_L = L;
    }

    ~StLuaFree()
    {
        if (m_L)
        {
            lua_close(m_L);
        }
    }

    lua_State* m_L;
};


class CFreqConfig
{
public:
    CFreqConfig(const string& filename = "freq_conf.lua") {
        m_filename = filename;
    }
    virtual ~CFreqConfig() {}
    
    int GetFreqLimit(uint32_t appid, int& limit)
    {
        const char lua_funcname[] = "get_freq_limit";

        lua_State *L = lua_open();

        //自动释放
        StLuaFree st_lua_free(L);

        luaL_openlibs(L);

        if (luaL_loadfile(L, m_filename.c_str()) || lua_pcall(L, 0, 0, 0))
        {
            FREQCONF_ERROR("cannot run configuration file: %s", lua_tostring(L, -1));
            return -1;
        }

        lua_getglobal(L, lua_funcname); /* function to be called */
        lua_pushnumber(L, appid); /* push 1st argument */

        /* do the call (1 arguments, 1 result) */
        if (lua_pcall(L, 1, 1, 0) != 0)
        {
            FREQCONF_ERROR("error running function %s: %s", lua_funcname, lua_tostring(L, -1));
            return -2;
        }

        /* retrieve result */
        if (!lua_isnumber(L, -1))
        {
            FREQCONF_ERROR("function %s must return a number", lua_funcname);
            return -3;
        }

        limit = (int)lua_tonumber(L, -1);

        lua_pop(L, 1); /* pop returned value */

        return 0;
    }

    const char * GetErrMsg()
    {
        return m_szErrMsg;
    }
private:
    string m_filename;

    char m_szErrMsg[1024];
};

main调用代码:

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <iostream>
#include <memory>
#include <sstream>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
 
#include "freq_config.h"
using namespace std;
 
int main(int argc, char **argv)
{
    CFreqConfig conf("freq_conf.lua");
 
    uint32_t appid = 23;
    int limit;
    int ret = conf.GetFreqLimit(appid, limit);
 
    if (ret)
    {
        printf("GetFreqLimit fail.ret:%d,msg:%s\n", ret, conf.GetErrMsg());
        return -1;
    }
 
    printf("limit:%d\n", limit);
    return 0;
}

输出结果:

limit:1000

OK,整个逻辑就是这样。其实实现方式是很简单的,无非就是在C++中调用了lua脚本,但是大家在辛苦的用C/C++写着ini解析类,xml解析类的时候,又在为产品需求变更而抱怨不已的时候,有没有想过能不能把这一切简化呢?

Pingbacks

  1. nginx自定义模块编写-根据post参数路由到不同服务器 | 热资讯 on #

    [...] 二.lua解析器的加入 老博友应该都看过我之前写的一篇博客: 代码即数据,数据即代码(1)-把难以变更的代码变成易于变更的数据,而这一次的需求也非常符合使用脚本的原则: [...]

  2. nginx自定义模块编写-根据post参数路由到不同服务器热资讯 | 热资讯 on #

    [...] 二.lua解析器的加入 老博友应该都看过我之前写的一篇博客: 代码即数据,数据即代码(1)-把难以变更的代码变成易于变更的数据,而这一次的需求也非常符合使用脚本的原则: [...]

Pingbacks已打开。

Trackbacks

引用地址

评论

  1. 依云

    依云 on #

    哈,我之前用 C 写过一个 Linux 下的文件访问重定向的库,配置部分就是用的 Lua~

    Reply

    1. Dante

      Dante on #

      嗯嗯,可惜公司对新语言的接受太过保守,否则真想废掉ini,直接用lua不就得了,还不用再写代码解析。。

      Reply

      1. Ace

        Ace on #

        从这一点来说 还是创业公司适合hacker们啊 哈哈

        Reply

  2. fy

    fy on #

    好文,我顺便做一个小小的补充

    Mac OS X下

    首先,安装lua
    &gt; sudo port install lua
    其次,建立lua_pub.h头文件到当前目录,内容:
    1 extern "C" {
    2 #include "lua.h"
    3 #include "lauxlib.h"
    4 #include "lualib.h"
    5 }
    6
    最后,编译运行
    &gt; g++ -I/opt/local/include/ -L/opt/local/lib/ -llua main.cpp -o main

    &gt; ./main
    limit:1024

    另外,lua可以这样写:
    1 tb_app2limit = {
    2 {100, 100},
    3 {23, 1000},
    4 {25668, 2^10}
    5 }
    有点意思;不过lua没有字典(dict)类型吗?

    我第一次成功运行这样的组合,初学者,见笑了;
    感谢博主。

    Reply

  3. ryanking

    ryanking on #

    楼主贴的是最终代码吗?貌似没有lua_close()

    Reply

    1. Dante

      Dante on #

      感谢提醒!貌似忘记写了。。

      Reply

发表评论