mnode第三篇 在mysql之上搭建redis缓存

地址:https://github.com/zhengjinwei123/mnode/tree/master/mnode/mnode/plugin/mysqlRedisCache

此项目主要是为了解决mysql操作数据缓慢的问题,同时交互简单,数据落地安全可靠

项目还在完成中, 比较懒,我只贴上代码大笑,有兴趣可以到github上把代码copy下来运行

数据模型封装:

/**
 * Created by zhengjinwei
 * 数据模型模板生成器
 */
var _ = require("lodash");
var Async = require("async");
var Util = require("util");

function Model() {
    this.updateFields = {};
    this.pk = null;//主键
    this.tableName = "";
    this.tablePrefix = "t_";

    this.mysql = null;
    this.redis = null;

    this.mode = 'c';//d || c      查询类型 d:直接操作mysql  c:缓存
}

Model.prototype.setTableName = function (n) {
    this.tableName = n;
};

Model.prototype.getTableName = function () {
    return this.tableName;
};

Model.prototype.initFields = function (fields) {
    this.fields = fields;
};

Model.prototype.getFullTableName = function () {
    return this.tablePrefix + this.tableName;
};


// 获取主键值
Model.prototype.getPKV = function () {
    if (this.pk) {
        return this.getField(this.pk);
    }
    return null;
};

// 设置主键值
Model.prototype.setPKV = function (pkv) {
    if (this.pk) {
        this.setField(this.pk, pkv);
    }
};

// 获取缓存键名
Model.prototype.getCK = function (pkV) {
    if (_.isUndefined(pkV)) {
        pkV = this.getPKV();
    }
    return this.tableName + ":" + pkV;
};


Model.prototype.byD = function () {
    this.mode = 'd';
};
Model.prototype.byC = function () {
    this.mode = 'c';
};

Model.prototype.setField = function (field, value) {
    if (!_.isUndefined(this.fields[field])) {
        this.fields[field] = value;
        this.updateFields[field] = value;
    }
};
Model.prototype.getField = function (field) {
    if (!_.isUndefined(this.fields[field])) {
        return this.fields[field];
    }
    return null;
};

Model.prototype.getFields = function () {
    return this.fields;
};

Model.prototype.getFieldChanged = function () {
    return this.updateFields;
};
Model.prototype.init_field_changed = function () {
    this.updateFields = [];
};

Model.prototype.setMysql = function (cfg) {
    this.mysql = require("../../utils/db-mysql/app")(cfg);
};

Model.prototype.setRedis = function (cfg) {
    var Redis = require("../../utils/db-redis/app");
    if (_.isUndefined(cfg.poolCnt) ||
        _.isUndefined(cfg.namePrefix) ||
        _.isUndefined(cfg.host) ||
        _.isUndefined(cfg.port) ||
        _.isUndefined(cfg.db) ||
        _.isUndefined(cfg.auth)) {
        throw new TypeError("redis cfg error");
    }
    this.redis = new Redis(cfg.poolCnt, cfg.namePrefix, cfg.host, cfg.port, cfg.db, cfg.auth);
};

Model.prototype.check = function () {
    if (!this.mysql || !this.redis) {
        return false;
    }
    return true;
};


Model.prototype.find = function (pKV) {
    var argsCnt = _.keys(arguments).length;
    var columns, expire, callback = null;
    if (argsCnt == 2) {
        columns = "*";
        expire = 86400;
        callback = arguments[1];
    } else if (argsCnt == 3) {
        columns = "*";
        expire = 86400;
        callback = arguments[2];
    } else {
        columns = arguments[1];
        expire = arguments[2];
        callback = arguments[3];
    }

    var self = this;


    if (!this.check()) {
        callback("please init connection")
    } else {
        if ((this.mode === 'c') && this.pk) {
            var results = null;
            Async.waterfall([
                function (cb) {
                    self.redis.exists(self.getCK(pKV), function (err, exists) {
                        if (!exists) {
                            cb("key:" + self.getCK(pKV) + " not exists");
                        } else {
                            if (columns === "*") {
                                self.redis.hGetAll(self.getCK(pKV), function (err, resp) {
                                    results = resp;
                                    cb(err);
                                });
                            } else {
                                columns = _.isArray(columns) ? columns : [columns];
                                self.redis.hmGet(self.getCK(pKV), columns, function (err, resp) {
                                    results = resp;
                                    cb(err);
                                });
                            }
                        }
                    });
                },
                function (cb) {
                    self.redis.expire(self.getCK(pKV), expire, function (err, resp) {
                        cb(err);
                    })
                }
            ], function (err, resp) {
                if (err || _.isEmpty(results)) {
                    var selFields = [];
                    if (columns === "*") {
                        selFields = "*";
                    } else {
                        selFields = columns.join(",");
                    }

                    var sql = Util.format("SELECT %s FROM %s WHERE %s=%s LIMIT 1", selFields, self.getFullTableName(), self.pk, pKV);

                    self.query(sql, [], function (err, results, fields) {
                        if (!err && (!_.isEmpty(results))) {
                            self.initFields(results[0]);
                            var temp = [];
                            _.forEach(self.getFields(), function (v, k) {
                                temp.push(k);
                                temp.push(v);
                            });
                            self.redis.hmSet(self.getCK(pKV), temp, function (err, resp) {
                                callback(err, self);
                            });
                        } else {
                            callback(err, null);
                        }
                    });
                } else {
                    self.initFields(results);
                    callback(err, self);
                }
            });
        } else {
            var selFields = [];
            if (columns === "*") {
                selFields = "*";
            } else {
                selFields = columns.join(",");
            }

            var sql = Util.format("SELECT %s FROM %s WHERE %s=%s LIMIT 1", selFields, this.getFullTableName(), this.pk, pKV);
            this.query(sql, [], function (err, results, fields) {
                if (!err && (!_.isEmpty(results))) {
                    self.initFields(results[0]);
                    callback(err, self);
                } else {
                    callback(err, results);
                }
            });
        }
    }
};

Model.prototype.insert = function (callback) {
    var fields = [], temp = [], values = [];
    _.forEach(this.fields, function (v, k) {
        fields.push(k);
        values.push(v);
        temp.push("?");
    });

    var self = this;
    var sql = Util.format("insert into %s (%s) values(%s)", this.getFullTableName(), fields.join(","), temp.join(","));

    this.query(sql, values, function (err, resp) {
        if (!err) {
            if (self.mode === "c") {
                var temp = [];
                _.forEach(self.getFields(), function (v, k) {
                    temp.push(k);
                    temp.push(v);
                });
                self.redis.hmSet(self.getCK(), temp, function (err, resp) {
                    callback(err, self);
                });
            } else {
                callback(err);
            }
        } else {
            callback(err);
        }
    });
};

Model.prototype.update = function (callback) {
    var keys = _.keys(this.updateFields);
    if (keys.length == 0) {
        callback(null);
        return 0;
    }

    if (this.pk == '') {
        callback(null);
        return 0;
    }

    if (this.mode === "c") {
        var self = this;

        self.redis.ttl(self.getCK(), function (err, ttl) {
            if (ttl != -1 && ttl < 3) {
                callback("data not exists ttl:" + ttl + self.getCK());
            } else {
                if (err) {
                    callback(err);
                } else {
                    self.redis.multi(function (multi, release) {
                        var temp = [];
                        _.forEach(self.getFieldChanged(), function (v, k) {
                            temp.push(k);
                            temp.push(v);
                        });
                        multi.hmset(self.getCK(), temp);
                        multi.expire(self.getCK(), 68400);
                        self.redis.exec(multi, release, function (err) {
                            if (!err) {
                                self.init_field_changed();
                            }
                            callback(err);
                        })
                    });
                }
            }
        });
    } else {
        var temp = [];
        _.forEach(this.getFieldChanged(), function (v, k) {
            temp.push(k + "=" + v);
        });

        var sql = Util.format("update %s set %s where %s=`%s`", this.getFullTableName(), temp.join(","), this.pk, this.getPKV());
        this.query(sql, [], function (err, resp) {
            callback(err);
        });
    }
};

Model.prototype.delete = function (callback) {
    if (this.pk == '') {
        callback(null);
        return 0;
    }

    if (this.mode === "c") {
        var self = this;
        this.redis.del(this.getCK(), function (err, resp) {
            if (!err) {
                var sql = Util.format("delete from %s where %s=%s", self.getFullTableName(), self.pk, self.getPKV());
                self.query(sql, [], function (err, resp) {
                    callback(err);
                })
            } else {
                callback(err);
            }
        });
    } else {
        var sql = Util.format("delete from %s where %s=`%s`", this.getFullTableName(), this.pk, this.getPKV());
        this.query(sql, [], function (err, resp) {
            callback(err);
        })
    }
};


Model.prototype.query = function (sql, params, callback) {
    params = (_.isArray(params)) ? params : [];
    if (!this.mysql) {
        throw new Error("please call setMysql");
    }
    this.mysql.query(sql, params, {
        failed: function (err) {
            callback(err);
        },
        success: function (rows, fields) {
            callback(null, rows, fields);
        }
    })
};

module.exports = Model;



通过xml自动生成数据模型脚本,模型与xml,xml与数据库绑定:

/**
 * Created by zhengjinwei
 * mysql-redis 缓存 模型化插件
 * 解决操作mysql的复杂度和速率,利用redis作为缓存,redis定时落地到mysql
 */
var XmlParser = require("../../utils/xml-parser/app");
var FileUtil = require("../../utils/file-utils/app");
var _ = require("lodash");
var Util = require("util");

var MysqlRedisCache = function (xmlPath,ModelPath, callback) {
    if (!FileUtil.isFile(xmlPath)) {
        throw new Error(xmlPath + " file not exits");
    }

    genDBCode(xmlPath, function (err, result, modeList) {
        if (!err && result) {
            var fileName = xmlPath.replace(".xml", ".sql");
            FileUtil.writeSync(fileName, result);

            if (modeList) {
                genModels(ModelPath,modeList,callback);
            }
        }
    });
};

function firstUppercase(str){ // 正则法
    str = str.toLowerCase();
    var reg = /\b(\w)|\s(\w)/g; //  \b判断边界\s判断空格
    return str.replace(reg,function(m){
        return m.toUpperCase()
    });
}

function genModels(ModelPath,dataList,callback) {
    var template = FileUtil.readSync(Path.join(__dirname,"./template/model.js"));

    _.forEach(dataList,function(v,tableName){
        var temp = template.replace(/MODELNAME/g,firstUppercase(tableName));
        temp = temp.replace(/tablename/,tableName);
        temp = temp.replace(/\{\}/,JSON.stringify(v['fields']));
        temp = temp.replace(/t_/,v['tablePrefix']);
        temp = temp.replace(/pkv/,v['pk']);
        temp = temp.replace(/genTime/,new Date().toLocaleString());

        //console.log(temp)
        FileUtil.writeSync(Path.join(ModelPath,"/"+tableName+".js"), temp);
    });
    callback(null);
}

var genDBCode = function (xmlPath, callback) {
    XmlParser(xmlPath, function (err, results) {
        var databaseObj = {};
        if (!err && results && results['database']) {
            var db = results['database']['$']['name'];
            var dbCharacter = results['database']['$']['character'];
            var dbCollate = results['database']['$']['collate'];

            databaseObj.dbName = db;
            databaseObj.dbCharacter = dbCharacter;
            databaseObj.dbCollate = dbCollate;
            databaseObj.tables = [];

            if (!results['database']['tables']) {
                callback("xml format error");
            } else {

                var tables = results['database']['tables'];

                if (!_.isArray(tables)) {
                    tables = [tables];
                }
                parseTables(tables, databaseObj);
                genSql(databaseObj, callback);
            }
        } else {
            callback("xml format error");
        }
    });
};


function genSql(databaseObj, callback) {
    if (databaseObj['dbName']) {
        var modelList = {};//用于记录数据模型,之后用这份数据自动生成数据库模型脚本文件

        var sql = Util.format("CREATE DATABASE IF NOT EXISTS `%s` character set %s collate %s;", databaseObj['dbName'], databaseObj['dbCharacter'] || 'utf8', databaseObj['dbCollate'] || 'utf8_general_ci');
        sql += Util.format('\r\nUSE `%s`\r\n', databaseObj['dbName']);

        if (databaseObj['tables']) {
            _.forEach(databaseObj['tables'], function (t, k) {
                sql += Util.format('%s \r\n%s\r\n%s\r\n', '-- ------------------', '-- ' + t['table']['desc'], '-- ------------------');
                sql += Util.format('CREATE TABLE IF NOT EXISTS `%s` (\r\n', t['tablePrefix'] + t['table']['name']);
                if (!t['table']['fields']) {
                    return 0;
                }

                modelList[t['table']['name']] = modelList[t['table']['name']] || {};
                modelList[t['table']['name']].tablePrefix = t['tablePrefix'];

                var auto = null;
                var fieldsSql = [];

                modelList[t['table']['name']].fields = {};

                _.forEach(t['table']['fields'], function (f, k) {
                    var tempSql = "";
                    if (f['autoincr']) {
                        auto = f['start'] || 1;
                    }
                    if (f['name']) {
                        if (!f['length']) {
                            tempSql += Util.format("\t`%s` %s", f['name'], f['type']);
                        } else {
                            tempSql += Util.format("\t`%s` %s(%s)", f['name'], f['type'], f['length']);
                        }

                        if (f['default']) {
                            tempSql += " NOT NULL";
                            tempSql += " DEFAULT " + f['default'];
                        }
                        if (f['autoincr']) {
                            tempSql += ' AUTO_INCREMENT';
                        }
                        if (f['comment']) {
                            tempSql += ' COMMENT ' + "'" + f['comment'] + "'";
                        }
                        fieldsSql.push(tempSql);

                        if (f['default']) {
                            modelList[t['table']['name']].fields[f['name']] = f['default'];
                        } else {
                            if (f['type'].toLowerCase() == 'varchar' || f['type'].toLowerCase() == 'char') {
                                modelList[t['table']['name']].fields[f['name']] = '';
                            } else {
                                modelList[t['table']['name']].fields[f['name']] = 0;
                            }
                        }
                    }
                });

                if (fieldsSql.length) {
                    sql += fieldsSql.join(",\r\n");
                }

                var indexSQL = [];
                if (t['table']['indexs']) {
                    _.forEach(t['table']['indexs'], function (i, k) {
                        var tempSql = "";
                        var type = i['type'].toUpperCase();
                        if (type == 'PRIMARY KEY') {

                            modelList[t['table']['name']].pk = i['field'];//记录唯一索引

                            tempSql += '\tPRIMARY KEY (' + i['field'] + ')'
                        } else if (type == 'KEY' || type == 'UNIQUE KEY') {
                            var keyFields = i['field'].split(",");

                            tempSql += Util.format("\t%s `%s`(", type, i['name']);

                            var tempKeys = [];
                            _.forEach(keyFields, function (v, k) {
                                tempKeys.push(Util.format("`%s`", v));
                            });
                            tempSql += tempKeys.join(",");
                            tempSql += ") USING " + (i['using'] || 'BTREE');
                        }
                        indexSQL.push(tempSql);
                    });
                }
                if (indexSQL.length) {
                    sql += ",\r\n";
                    sql += indexSQL.join(",\r\n");
                }

                sql += "\r\n)";
                if (t['table']['engine']) {
                    sql += 'ENGINE=' + (t['engine'] || 'InnoDB');
                }
                if (auto) {
                    sql += ' AUTO_INCREMENT=' + auto;
                }
                if (t['table']['row_format']) {
                    sql += ' ROW_FORMAT=' + t['table']['row_format'];
                }
                if (t['table']['key_block_size']) {
                    sql += ' KEY_BLOCK_SIZE=' + t['table']['key_block_size'];
                }
                if (t['table']['charset']) {
                    sql += ' DEFAULT CHARSET=' + t['table']['charset'];
                }

                sql += ";\r\n\r\n";
            });
        }

        callback(null, sql, modelList);
    } else {
        callback("xml format error");
    }
}


function parseTables(tables, databaseObj) {
    _.forEach(tables, function (t, k) {
        var tablePrefix = t['$']['tablePrefix'];
        var table = t['table'];

        if (!table) {
            return 0;
        }
        if (!_.isArray(table)) {
            table = [table];
        }
        parseFields(tablePrefix, table, databaseObj);
    });
}

function parseFields(tablePrefix, table, databaseObj) {
    _.forEach(table, function (t, k) {
        var tt = {
            tablePrefix: tablePrefix,
            table: {}
        };

        if (!t['$']['name']) {
            return 0;
        }

        tt.table.name = t['$']['name'];

        if (t['$']['engine']) {
            tt.table.engine = t['$']['engine'];
        }
        if (t['$']['charset']) {
            tt.table.charset = t['$']['charset'];
        }

        if (t['$']['desc']) {
            tt.table.desc = t['$']['desc'];
        }

        if (t['$']['row_format']) {
            tt.table.row_format = t['$']['row_format'];
        }
        if (t['$']['key_block_size']) {
            tt.table.key_block_size = t['$']['key_block_size'];
        }

        var fields = t['field'];
        if (!fields) {
            return 0;
        }
        if (!_.isArray(fields)) {
            fields = [fields];
        }

        tt.table.fields = [];
        parseField(tt, fields);

        if (t['index']) {
            var indexs = t['index'];
            if (!_.isArray(t['index'])) {
                indexs = [t['index']];
            }
            tt.table.indexs = [];
            parseIndex(tt, indexs);
        }

        databaseObj.tables.push(tt);
    });
}

function parseIndex(tt, indexs) {
    _.forEach(indexs, function (i, k) {
        var $ = i['$'];
        tt.table.indexs.push($);
    });
}

function parseField(tt, fields) {
    _.forEach(fields, function (f, k) {
        var $ = f['$'];
        tt.table.fields.push($);
    });
}

module.exports = MysqlRedisCache;


//var Path = require("path");
//var m = new MysqlRedisCache(Path.join(__dirname, "/template/db.xml"), Path.join(__dirname, "/template"),function () {
//
//});


数据落地:

/**
 * Created by zhengjinwei
 * 数据落地 从redis到mysqldb
 */
var _ = require("lodash");
var Async = require("async");
var Util = require("util");

function Cache2DB(redisCfg, mysqlCfg, cacheInterval) {
    this.mysql = require("../../utils/db-mysql/app")(mysqlCfg);

    var Redis = require("../../utils/db-redis/app");
    if (_.isUndefined(redisCfg.poolCnt) ||
        _.isUndefined(redisCfg.namePrefix) ||
        _.isUndefined(redisCfg.host) ||
        _.isUndefined(redisCfg.port) ||
        _.isUndefined(redisCfg.db) ||
        _.isUndefined(redisCfg.auth)) {
        throw new TypeError("redis cfg error");
    }
    this.stat = false;
    this.redis = new Redis(parseInt(redisCfg.poolCnt), redisCfg.namePrefix, redisCfg.host, parseInt(redisCfg.port), redisCfg.db, redisCfg.auth);
    this.startCache(cacheInterval || (3 * 1000 * 60));
}

//获取所有的缓存key
Cache2DB.prototype.getCKS = function (callback) {
    this.redis.keys("updatecachelist:*", callback);
};

Cache2DB.prototype.startCache = function (cacheInterval) {
    if (cacheInterval) {
        var self = this;
        setInterval(function () {
            if (self.stat == false) {
                self.cacheRun(function (err) {
                    self.stat = false;
                });
            }
        }, cacheInterval);
    } else {
        throw new Error("cacheInterval undefined");
    }
};

Cache2DB.prototype.cacheRun = function (callback) {
    this.stat = true;
    var self = this;
    this.getCKS(function (err, cacheKeys) {
        if (!err && cacheKeys) {
            if (!_.isArray(cacheKeys)) {
                cacheKeys = [cacheKeys];
            }
            console.time("cache");
            Async.each(cacheKeys, function (cacheKey, cb) {
                self.cache(cacheKey, cb);
            }, function (err, resp) {
                callback(err);
                console.timeEnd("cache");
            });
        } else {
            callback(err);
        }
    });
};

Cache2DB.prototype.cache = function (key, callback) {
    var self = this;
    this.redis.lRange(key, 0, 999, function (err, list) {
        if (!err && list) {
            if (!_.isArray(list)) {
                list = [list];
            }
            if (list.length <= 0) {
                callback(null);
            } else {
                var updateList = {};
                var cnt = 0;
                _.forEach(list, function (v, k) {
                    var data = JSON.parse(v);
                    if (_.isUndefined(updateList[data['ck']])) {
                        updateList[data['ck']] = data;
                    } else {
                        updateList[data['ck']]['fields'] = _.merge(updateList[data['ck']]['fields'], data['fields']);
                    }
                    cnt++;
                });

                //sql合并
                if (cnt) {
                    var updateKeys = _.keys(updateList);
                    var sqlArray = [];
                    Async.each(updateKeys, function (key, cb) {
                        self.redis.exists(key, function (err, exists) {
                            if (!exists) {
                                console.error("cache miss key:" + key);
                                cb(null);
                            } else {
                                var fields = updateList[key]['fields'];
                                self.redis.hmGet(key, _.keys(fields), function (err, result) {
                                    if (!err && result) {
                                        var sql = parseSql(updateList[key], _.keys(fields), result);
                                        sqlArray.push(sql);
                                    }
                                    cb(null);
                                });
                            }
                        });
                    }, function (err, resp) {
                        if (!err) {
                            self.execSql(sqlArray, function (err) {
                                self.redis.lTrim(key, list.length, -1, function (err, resp) {
                                    callback(null);
                                })
                            });
                        } else {
                            callback(null);
                        }
                    });
                }
            }
        } else {
            if (err) {
                console.error("cache key:" + key + " error:" + err);
            }
            callback(null);
        }
    });
};

Cache2DB.prototype.execSql = function (sqlArray, callback) {
    if (sqlArray.length) {
        var start = 0;
        var len = 20;

        var sqlGroup = [];
        while (sqlArray.length) {
            var sqls = sqlArray.splice(start, len);
            sqls = sqls.join(";");
            sqlGroup.push(sqls);
        }

        var self = this;
        Async.each(sqlGroup, function (sqls, cb) {
            console.log(sqls);
            self.query(sqls, [], function (err, resp) {
                if (err) {
                    console.error("execSql error:", err);
                }
                cb(null);
            });
        }, function (err, resp) {
            callback(null);
        });
    } else {
        callback(null);
    }
};

Cache2DB.prototype.query = function (sql, params, callback) {
    params = (_.isArray(params)) ? params : [];
    this.mysql.query(sql, params, {
        failed: function (err) {
            callback(err);
        },
        success: function (rows, fields) {
            callback(null, rows, fields);
        }
    })
};

function parseSql(stru, fields, data) {
    if (data) {
        var updateFields = [];
        _.forEach(data, function (v, k) {
            if (_.isNaN(parseFloat(v))) {
                updateFields.push(fields[k] + "=" + "'" + v + "'");
            } else {
                updateFields.push(fields[k] + "=" + parseFloat(v));
            }
        });
        if (updateFields) {
            return Util.format("update %s set %s where %s=%s", stru['tbn'], updateFields.join(","), stru['pk'], stru['pkv']);
        }
    }
    return null;
}

module.exports = Cache2DB;



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值