前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >服务器架设笔记——多模块和全局数据

服务器架设笔记——多模块和全局数据

作者头像
方亮
发布2019-01-16 15:04:14
8490
发布2019-01-16 15:04:14
举报
文章被收录于专栏:方亮方亮

        随着项目工程的发展,多模块设计和性能优化是在所难免的。本文我将基于一些现实中可能遇到的需求,讲解如何在Apache的Httpd插件体系中实现这些功能。(转载请指明出于breaksoftware的csdn博客)

        之前我碰到两个需求:

  1. 需要将从数据库中查询到的某些字段替换成另一个字符,替换的映射关系在另外一张表里面(这张表很小,且几乎不变动)。
  2. 需要返回一个可配置的字符串(基本不变动)。

        对于需求1,我们最简单的办法就是:每次请求过来都去查询一下映射关系数据表,然后替换相关字符。但是这个方法对于一个优秀的实现来说,还是挺low的。我们可以注意到这个需求的特点——几乎不变动、且数据量少,那我们应该可以把他们放到我们内存里。

        对于需求2,可以想到的最简单的办法就是:在代码中硬编码,将可配置的字符串写死在代码里。然后如果一旦有修改,那么我们就需要修改代码文件中的硬编码字段,然后编译后上线。这种方式非常麻烦,且可能会带来不稳定因素——说不定谁谁忘记了给待转义字符增加转义符呢。而且代码中字符串一堆双引号、单引号或者转义符看着实在令人难受。我们还是通过动态加载配置文件的形式,将这段配置加载进来比较靠谱。

        那么我就想,我需要设计一个模块,用于预处理以上的需求——将数据加载到内存中。我给这个模块取名为prepare。至于插件模块的创建可以参见《服务器架设笔记——编译Apache及其插件》,本文我不在赘述。

        prepare中的处理handler需要执行于其他业务handler之前。我们需要对httpd.conf做如下配置

代码语言:javascript
复制
<Location /query_board_list>
    SetHandler prepare
    SetHandler query_board_list
</Location>

<Location /query_project_info>
    SetHandler prepare
    SetHandler query_project_info
</Location>

<Location /query_board_info>
    SetHandler prepare
    SetHandler query_board_info
</Location>

        上例的写法,便将prepare的执行于其他handler之前。但是仅仅如此是不够的,还有个隐藏的配置困扰了我很久,最后我开始“迷性”顺序关系才找到问题的所在。见httpd.conf的模块加载配置段

代码语言:javascript
复制
LoadModule prepare_module     modules/mod_prepare.so
LoadModule query_board_list_module modules/mod_query_board_list.so
LoadModule query_project_info_module modules/mod_query_project_info.so
LoadModule query_board_info_module modules/mod_query_board_info.so

        这一点一定要切记:要把需要起始执行的模块,在之后处理的模块之前加载。如果我们把mod_prepare.so加载于mod_query_board_list.so之后,那么prepare将不会在query_board_list之前执行。

        然后我们来看下prepare内部的书写。

代码语言:javascript
复制
static int prepare_handler(request_rec *r)
{
    apr_status_t rv;
    const char* user_data = NULL;
    const char* front_page_key = "front_page";
    const char* front_page_conf_path = "/usr/local/apache2/conf/front_page.template";
    const char* select_page_key = "select_page";
    const char* select_page_conf_path = "/usr/local/apache2/conf/select_page.template";

    apr_pool_userdata_setn(r, "request_rec_ptr", NULL, r->pool);

    rv = prepare_data(r->server->process->pool, front_page_key, front_page_conf_path);
    rv = prepare_data(r->server->process->pool, select_page_key, select_page_conf_path);

    prepare_map_from_db(r->server->process->pool, "LocationTable", "location");
    prepare_map_from_db(r->server->process->pool, "SourceTable", "source");
    prepare_map_from_db(r->server->process->pool, "ScopeTable", "scope");
    prepare_map_from_db(r->server->process->pool, "StageTable", "stage");
    return DECLINED;
}

        这段代码,需要注意的有四个部分:

  1. 将request_rec指针r保存到r->pool的内存池中,从而实现了在请求级别的“全局数据”——之后的一些模块,可能没有传入request_rec指针。
  2. 通过prepare_data将配置文件内存保存到进程级别的内存池中,这样一个进程只加载一次。之后通过判断key是否存在来知道是否已经加载。
  3. 通过prepare_map_from_db将数据库中不同表的数据保存到内存中。这样的操作也是进程级别的。
  4. 返回DECLINED。返回这个值,告诉httpd还需要继续向后执行其他handler。

        以下是代码的罗列

代码语言:javascript
复制
int prepare_data_from_db(apr_pool_t* pool, const char* database_table, pchar_ptr_map ptr_char_ptr_map) {
	const apr_dbd_driver_t* driver = NULL;
	apr_dbd_t* handle = NULL;
	apr_dbd_results_t* res = NULL;
	char* sql_cmd = NULL;
	apr_dbd_row_t* row = NULL;
	apr_status_t status = APR_SUCCESS;
    const char* value = NULL;
    apr_pool_t* pool_db = NULL;
    int rows_count = 0;
    pchar_item ptr_char_item = NULL;
    int index = 0;

    do {
        if (!pool || !database_table) {
            status = 41;
            break;
        }

        apr_pool_create(&pool_db, pool);
        if (!pool_db) {
            status = 42;
            break;
        }

        apr_dbd_init(pool_db);

        status = apr_dbd_get_driver(pool_db, "mysql", &driver);
        if (APR_SUCCESS != status) {
            status = 43;
            break;
        }

        status = apr_dbd_open(driver, pool_db, "host=localhost;user=root;pass=password;dbname=database_name", &handle);
        if (APR_SUCCESS != status) {
            status = 44;
            break;
        }

        sql_cmd = apr_psprintf(pool, "select * from %s", database_table);

        status = apr_dbd_select(driver, pool_db, handle, &res, sql_cmd, 0);
        if (APR_SUCCESS != status || !res) {
            status = 45;
            break;
        }

        rows_count = 64;
        ptr_char_ptr_map->array = apr_palloc(pool, rows_count *  sizeof(pchar_item));

        while (0 == apr_dbd_get_row(driver, pool_db, res, &row, -1) && row) {
            ptr_char_item = apr_palloc(pool, sizeof(char_item));
            status = 0;
            value = apr_dbd_get_entry(driver, row, 0);
            if (value) {
                ptr_char_item->key = apr_psprintf(pool, "%s", value);
            }
            else {
                ptr_char_item->key = apr_psprintf(pool, "%s", "");
            }

            value = apr_dbd_get_entry(driver, row, 1);
            if (value) {
                ptr_char_item->value = apr_psprintf(pool, "%s", value);
            }
            else {
                ptr_char_item->value = apr_psprintf(pool, "%s", "");
            }
            ptr_char_ptr_map->array[index] = ptr_char_item;
            index++;
        }
        ptr_char_ptr_map->count = index;
    } while(0);

    if (driver && handle) {
        apr_dbd_close(driver, handle);
    }
    if (pool_db) {
        apr_pool_destroy(pool_db);
    }

	return status;
}

int prepare_map_from_db(apr_pool_t* pool, const char* table_name, const char* key) {
    pchar_ptr_map ptr_char_ptr_map = NULL;  
    if (APR_SUCCESS != apr_pool_userdata_get((void**)&ptr_char_ptr_map, key, pool) || !ptr_char_ptr_map) {
        ptr_char_ptr_map = apr_palloc(pool, sizeof(char_ptr_map));  
        prepare_data_from_db(pool, table_name, ptr_char_ptr_map);
        apr_pool_userdata_setn(ptr_char_ptr_map, key, NULL, pool);
    }
    
    return APR_SUCCESS;
}

static apr_status_t save_file_to_mem(apr_pool_t* pool, const char* key, const char* file_path) {
    apr_status_t rv;
    apr_size_t buf_size = 0;
    apr_file_t* file_in = NULL;
    const char* file_buf = NULL;
    apr_size_t real_size = 0;
    apr_off_t offset = 0;
    
    if (!pool || !key || !file_path) {
        return 10;
    }

    rv = apr_file_open(&file_in, file_path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, pool);
    if (APR_SUCCESS != rv) {
        return rv;
    }

    do {
        buf_size = apr_file_buffer_size_get(file_in);
        if (0 == buf_size) {
            rv = 11;
            break;
        }

        real_size = buf_size;
        file_buf = apr_palloc(pool, buf_size);
        if (!file_buf) {
            rv = 12;
            break;
        }

        rv = apr_file_read(file_in, (void*)file_buf, &real_size);
        if (APR_SUCCESS != rv) {
            break;
        }
        apr_pool_userdata_setn(file_buf, key, NULL, pool);

    } while(0);

    apr_file_close(file_in);

    return rv;
}

static apr_status_t prepare_data(apr_pool_t* pool, const char* key, const char* file_path) {
    const char* user_data = NULL;
    if (APR_SUCCESS != apr_pool_userdata_get((void**)&user_data, key, pool) || !user_data) {
        return save_file_to_mem(pool, key, file_path);
    }
    
    return APR_SUCCESS;
}

        不可否认的一点是,在插件中写数据库访问的逻辑还是挺麻烦的。因为总是会遇到一些意想不到的问题,比如在上例中:

  1. 直接使用传入的pool操作数据库——虽然已经apr_dbd_init了,可能会导致进程意外退出。
  2. 调用apr_dbd_select最后一个参数传1,可能会导致进程意外退出。
  3. 调用apr_dbd_select最后一个参数传0,计算结果个数的apr_dbd_num_tuples函数将错误。这个问题与2结合导致我只能硬编码结果上线——low了一下。

        当然可能是我哪儿不得要领,但是从快速开发的角度来说,或许“下雪天,PHP和httpd更配哦”。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015年11月23日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档