mysql队列 并发_高并发简单解决方案————redis队列缓存+mysql 批量入库(ThinkPhP)...

本文介绍了如何利用Redis队列缓存处理高并发日志存储问题,通过批量入库到MySQL来降低数据库压力。设计了数据库表和存储方案,使用Redis的list实现消息队列,然后通过PHP脚本读取队列数据并批量插入MySQL。同时,还提供了离线统计和清理脚本以优化存储和保证系统稳定。该解决方案简单有效,适合处理大量日志数据的场景。
摘要由CSDN通过智能技术生成

问题分析

问题一:要求日志最好入库;但是,直接入库mysql确实扛不住,批量入库没有问题,done。【批量入库和直接入库性能差异】

问题二:批量入库就需要有高并发的消息队列,决定采用redis list 仿真实现,而且方便回滚。

问题三:日志量毕竟大,保存最近30条足矣,决定用php写个离线统计和清理脚本。

一、设计数据库表和存储

考虑到log系统对数据库的性能更多一些,稳定性和安全性没有那么高,存储引擎自然是只支持select insert 没有索引的archive。如果确实有update需求,也可以采用myISAM。

考虑到log是实时记录的所有数据,数量可能巨大,主键采用bigint,自增即可。

考虑到log系统以写为主,统计采用离线计算,字段均不要出现索引,因为一方面可能会影响插入数据效率,另外读时候会造成死锁,影响写数据。

二、redis存储数据形成消息队列

/**

* 使用队列生成reids测试数据

* 成功:执行 RPUSH操作后,返回列表的长度:8

*/

public function createRedisList($listKey = 'message01')

{

$redis = RedisInstance::MasterInstance();

$redis->select(1);

$message = [

'type' => 'say',

'userId' => $redis->incr('user_id'),

'userName' => 'Tinywan' . mt_rand(100, 9999), //是否正在录像

'userImage' => '/res/pub/user-default-w.png', //是否正在录像

'openId' => 'openId' . mt_rand(100000, 9999999999999999),

'roomId' => 'openId' . mt_rand(30, 50),

'createTime' => date('Y-m-d H:i:s', time()),

'content' => $redis->incr('content') //当前是否正在打流状态

];

$rPushResul = $redis->rPush($listKey, json_encode($message)); //执行成功后返回当前列表的长度 9

return $rPushResul;

}

三、读取redis消息队列里面的数据,批量入库

第一种思路:

/**

* 消息Redis方法保存到Mysql数据库

* @param string $liveKey

*/

public function RedisSaveToMysql($listKey = 'message01')

{

if (empty($listKey)) {

$result = ["errcode" => 500, "errmsg" => "this parameter is empty!"];

exit(json_encode($result));

}

$redis = RedisInstance::MasterInstance();

$redis->select(1);

$redisInfo = $redis->lRange($listKey, 0, 5);

$dataLength = $redis->lLen($listKey);

$model = M("User");

while ($dataLength > 65970) {

try {

$model->startTrans();

$redis->watch($listKey);

$arrList = [];

foreach ($redisInfo as $key => $val) {

$arrList[] = array(

'username' => json_decode($val, true)['userName'],

'logintime' => json_decode($val, true)['createTime'],

'description' => json_decode($val, true)['content'],

'pido' => json_decode($val, true)['content']

);

}

$insertResult = $model->addAll($arrList);

if (!$insertResult) {

$model->rollback();

$result = array("errcode" => 500, "errmsg" => "Data Insert into Fail!", 'data' => 'dataLength:' . $dataLength);

exit(json_encode($result));

}

$model->commit();

$redis->lTrim($listKey, 6, -1);

$redisInfo = $redis->lRange($listKey, 0, 5);

$dataLength = $redis->lLen($listKey);

} catch (Exception $e) {

$model->rollback();

$result = array("errcode" => 500, "errmsg" => "Data Insert into Fail!");

exit(json_encode($result));

}

}

$result = array("errcode" => 200, "errmsg" => "Data Insert into Success!", 'data' => 'dataLength:' . $dataLength . 'liveKey:' . $listKey);

exit(json_encode($result));

}

第二种思路(供参考,非框架)

$redis_xx = new Redis();

$redis_xx->connect('ip', port);

$redis_xx->auth("password");

// 获取现有消息队列的长度

$count = 0;

$max = $redis_xx->lLen("call_log");

// 获取消息队列的内容,拼接sql

$insert_sql = "insert into fb_call_log (`interface_name`, `createtime`) values ";

// 回滚数组

$roll_back_arr = array();

while ($count < $max) {

$log_info = $redis_cq01->lPop("call_log");

$roll_back_arr = $log_info;

if ($log_info == 'nil' || !isset($log_info)) {

$insert_sql .= ";";

break;

}

// 切割出时间和info

$log_info_arr = explode("%", $log_info);

$insert_sql .= " ('" . $log_info_arr[0] . "','" . $log_info_arr[1] . "'),";

$count++;

}

// 判定存在数据,批量入库

if ($count != 0) {

$link_2004 = mysql_connect('ip:port', 'user', 'password');

if (!$link_2004) {

die("Could not connect:" . mysql_error());

}

$crowd_db = mysql_select_db('fb_log', $link_2004);

$insert_sql = rtrim($insert_sql, ",") . ";";

$res = mysql_query($insert_sql);

// 输出入库log和入库结果;

echo date("Y-m-d H:i:s") . "insert " . $count . " log info result:";

echo json_encode($res);

echo "\n";

// 数据库插入失败回滚

if (!$res) {

foreach ($roll_back_arr as $k) {

$redis_xx->rPush("call_log", $k);

}

}

// 释放连接

mysql_free_result($res);

mysql_close($link_2004);

}

$redis_cq01->close();

?>

四、获取Redis数据缓存数据

/**

* [0]检查当前Redis是否连接成功

* [1]获取数据,首先从Redis中去获取,没有的话再从数据库中去获取

*/

public function findDataRedisOrMysql($listKey = 'message01')

{

//Check the current connection status 查看服务是否运行

if (RedisInstance::MasterInstance() != false) {

$redis = RedisInstance::MasterInstance();

$redis->select(2);

/**

* 首先从Redis中去获取数据

* lRange 获取为空的话,则表示没有数据,否则返回一个非空数组

*/

$redisData = $redis->lRange($listKey, 0, 9);

$resultData = [];

if (!empty($redisData)) {

$resultData['status_code'] = 200;

$resultData['msg'] = 'Data Source from Redis Cache';

foreach ($redisData as $key => $val) {

$resultData['listData'][] = json_decode($val, true);

}

} else {

$resultData['redis_msg'] = 'Redis is Expire';

$conditions = array('status' => ':status');

$mysqlData = M('User')->where($conditions)->bind(':status', 1, \PDO::PARAM_STR)->select();

if ($mysqlData) {

$resultData['status_code'] = 200;

$resultData['mysql_msg'] = 'Data Source from Mysql is Success';

$redis->select(2);

foreach ($mysqlData as $key => $val) {

$resultData['listData'][] = $val;

//写入Redis作为缓存

$redis->rPush($listKey, json_encode($val));

}

//同时设置一个过期时间

$redis->expire($listKey,30);

} else {

$resultData['status_code'] = 500;

$resultData['mysql_msg'] = 'Data Source from Mysql is Fail';

}

}

} else {

$resultData['redis_msg'] = 'Redis server went away';

$resultData['mysql_msg'] = 'Mysql Data2';

$conditions = array('status' => ':status');

$mysqlData = M('User')->where($conditions)->bind(':status', 1, \PDO::PARAM_STR)->select();

foreach ($mysqlData as $key => $val) {

$resultData['listData'][] = $val;

}

}

homePrint($resultData);

}

四、离线天级统计和清理数据脚本

/**

* static log :每天离线统计代码日志和删除五天前的日志

* */

// 离线统计

$link_2004 = mysql_connect('ip:port', 'user', 'pwd');

if (!$link_2004) {

die("Could not connect:" . mysql_error());

}

$crowd_db = mysql_select_db('fb_log', $link_2004);

// 统计昨天的数据

$day_time = date("Y-m-d", time() - 60 * 60 * 24 * 1);

$static_sql = "get sql";

$res = mysql_query($static_sql, $link_2004);

// 获取结果入库略

// 清理15天之前的数据

$before_15_day = date("Y-m-d", time() - 60 * 60 * 24 * 15);

$delete_sql = "delete from xxx where createtime < '" . $before_15_day . "'";

try {

$res = mysql_query($delete_sql);

}catch(Exception $e){

echo json_encode($e)."\n";

echo "delete result:".json_encode($res)."\n";

}

mysql_close($link_2004);

?>

五:代码部署

主要是部署,批量入库脚本的调用和天级统计脚本,crontab例行运行。

# 批量入库脚本

*/2 * * * * /home/cuihuan/xxx/lamp/php5/bin/php /home/cuihuan/xxx/batchLog.php >>/home/cuihuan/xxx/batchlog.log

# 天级统计脚本

0 5 * * * /home/cuihuan/xxx/php5/bin/php /home/cuihuan/xxx/staticLog.php >>/home/cuihuan/xxx/staticLog.log

总结:相对于其他复杂的方式处理高并发,这个解决方案简单有效:通过redis缓存抗压,mysql批量入库解决数据库瓶颈,离线计算解决统计数据,通过定期清理保证库的大小。

转载:【高并发简单解决方案 &vert; 靠谱崔小拽 】redis队列缓存 &plus; mysql 批量入库 &plus; php离线整合

需求背景:有个调用统计日志存储和统计需求,要求存储到mysql中:存储数据高峰能达到日均千万,瓶颈在于直接入库并发太高,可能会把mysql干垮. 问题分析 思考:应用网站架构的衍化过程中,应用最新的框 ...

【高并发简单解决方案】redis队列缓存 &plus; mysql 批量入库 &plus; php离线整合

需求背景:有个调用统计日志存储和统计需求,要求存储到mysql中:存储数据高峰能达到日均千万,瓶颈在于直接入库并发太高,可能会把mysql干垮. 问题分析 思考:应用网站架构的衍化过程中,应用最新的框 ...

redis 队列缓存 &plus; mysql 批量入库 &plus; php 离线整合

问题分析 思考:应用网站架构的衍化过程中,应用最新的框架和工具技术固然是最优选择:但是,如果能在现有的框架的基础上提出简单可依赖的解决方案,未尝不是一种提升自我的尝试. 解决: 问题一:要求日志最好入 ...

【高并发简单解决方案】redis缓存队列&plus;mysql 批量入库&plus;php离线整合

原文出处: 崔小拽 需求背景:有个调用统计日志存储和统计需求,要求存储到mysql中:存储数据高峰能达到日均千万,瓶颈在于直接入库并发太高,可能会把mysql干垮. 问题分析 思考:应用网站架构的衍化 ...

【高并发架构】Redis缓存高并发之-主从架构

Redis主从架构 到目前为止,Redis Cluster 能实现很好的性能,但如果只是缓存几个G的数据,那么单机Redis就足够了,但缓存主要用来读的,单机的QPS有一定的极限,一两万QPS一台应该 ...

java高级精讲之高并发抢红包~揭开Redis分布式集群与Lua神秘面纱

java高级精讲之高并发抢红包~揭开Redis分布式集群与Lua神秘面纱 redis数据库 Redis企业集群高级应用精品教程[图灵学院] Redis权威指南 利用redis + lua解决抢红包高并 ...

&lbrack;转&rsqb;高并发访问下避免对象缓存失效引发Dogpile效应

避免Redis/Memcached缓存失效引发Dogpile效应 Redis/Memcached高并发访问下的缓存失效时可能产生Dogpile效应(Cache Stampede效应). 推荐阅读:高并 ...

PHP 高并发秒杀解决方案

本文提供 PHP 高并发秒杀解决方案(附加三个案例说明(普通流程,使用文件锁,使用redis消息队列)) 1:(正常流程,不做任何高并发处理),代码如下: <?php $_mysqli = ne ...

高并发大流量专题---10、MySQL数据库层的优化

高并发大流量专题---10.MySQL数据库层的优化 一.总结 一句话总结: mysql先考虑做分布式缓存,过了缓存后就做mysql数据库层面的优化 1.mysql数据库层的优化的前面一层是什么? 数 ...

随机推荐

CSS3的文字阴影—text-shadow

text-shadow还没有出现时,大家在网页设计中阴影一般都是用photoshop做成图片,现在有了css3可以直接使用text-shadow属性来指定阴影. 这个属性可以有两个作用,产生阴影和模糊 ...

C&num;构造函数、操作符重载以及自定义类型转换

构造器 构造器(构造函数)是将类型的实例初始化的特殊方法.构造器可分为实例构造器和类型构造器,本节将详细介绍有关内容. 实例构造器 顾名思义,实例构造器的作用就是对类型的实例进行初始化.如果类没有显示 ...

2016普及组t3海港

好的,说说这道题的思路,爆搜队列嘛: 用一个结构体队列存每个人来的时间和他的国籍,用一个vis数组存每个人来的次数,是第一次来sum便加一. 然后从前面第一个人开始扔(原谅我用这个词,因为我找不到更好 ...

深入理解ES6之—数据解构

一 对象解构 对象解构语法在赋值语句的左侧使用了对象字面量 let node = { type: true, name: false } //既声明又赋值 let { type, name } = n ...

Vue的自定义组件之间的数据传递

一,父级传向子级 1,在子级的属性中添加props:['myname',......],参数可以传多个,看具体而定: 2,在父级data中定义好需要传递的变量数据,例如name:"rose& ...

Jenkins解析日志(log-parser-plugin)

Jenkins打包机打包时产生了大量的日志,当报错时,不方便查看error日志 因为日志量太大,查看全部log的时候整个web页面会卡死,所以引用log-parser-plugin可以增加过滤条件显示 ...

python&plus;selenium滑动式验证码解决办法

from selenium.webdriver import ActionChains action = ActionChains(driver) source=driver.find_element ...

ES6 用Promise对象实现的 Ajax 操作

下面是一个用Promise对象实现的 Ajax 操作的例子. const getJSON = function(url) { const promise = new Promise(function( ...

openxml excel封装类

public class ExcelUntity { #region property /// /// excel文档(相当于excel程序) ///

记录一个nginx的proxy&lowbar;pass

server { listen 80; server_name www.hw801.com; server_name_in_redirect off; access_log /home/logs/ng ...

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值