我有一个表作为ad\u banner\u queue,我使用它来根据广告的权重生成队列。广告被插入到广告表中。如果队列中的所有现有广告都传递给用户,则将生成队列。
现在的问题是,如果请求同时到达并且rand()返回相同的记录,我应该如何防止发送重复的广告?
代码如下:
<?php
/* To Get the random Ad */
public function getBanner($params) {
/* Fetch the Random from table */
$ads_queue = (new \yii\db\Query())
->select('ad_quque_id, banner_image, unique_code')
->from('ad_banner_queue')
->join('inner join', 'advertisement', 'ad_banner_queue.ad_id = advertisement.ad_id')
->where('is_sent=0')
->orderBy('RAND()')
->one();
/* In case of queue is not there generate the new queue */
if ($ads_queue === false) {
$output = $this->generateAdQueue();
//In case of something went wrong while generating the queue
if ($output == false) {
return array();
}
//Now fetch the record again
$ads_queue = (new \yii\db\Query())
->select('ad_quque_id, banner_image, unique_code')
->from('ad_banner_queue')
->join('inner join', 'advertisement', 'ad_banner_queue.ad_id = advertisement.ad_id')
->where('is_sent=0')
->orderBy('RAND()')
->one();
}
/* Now, marked that one as is_sent */
Yii::$app->db->createCommand()->update('ad_banner_queue', ['is_sent' => 1], 'ad_quque_id =:ad_quque_id', array(':ad_quque_id' => $ads_queue['ad_quque_id']))->execute();
return $ads_queue;
}
/**
* Below will Generate the Queue if not exist
*/
public function generateAdQueue() {
/* First check thatt there is existing queue, if so don't generate it */
$data_exist = (new \yii\db\Query())
->select('ad_quque_id')
->from('ad_banner_queue')
->where('is_sent=0')
->scalar();
if ($data_exist === false) {
/* Delete all other entries */
(new \yii\db\Query())
->createCommand()
->delete('ad_banner_queue')
->execute();
/* Fetch all banner */
$ads = (new \yii\db\Query())
->select('ad_id, unique_code, ad_name, banner_image,ad_delivery_weightage')
->from('advertisement')
->where('status_id in (8)') //Means only fetch Approved ads
->all();
if (!empty($ads)) {
foreach ($ads as $ad) {
/* Make entry as per that weightage, example, if weightage is 10 then make entry 10 times */
$ins_fields = array();
for ($i = 1; $i <= $ad['ad_delivery_weightage']; $i++) {
$ins_fields[] = array($ad['ad_id']);
}
Yii::$app->db->createCommand()->batchInsert('ad_banner_queue', ['ad_id'], $ins_fields)->execute();
}
return true;
} else {
return false;
}
} else {
return false;
}
}
7条答案
按热度按时间rta7y2nd1#
您可以使用mutex组件来确保只有一个进程试图从队列中弹出广告。
但是请注意,这可能会大大降低性能,特别是如果您希望同时处理多个请求。对于这样的队列,您可能会考虑使用不同的技术,关系数据库实际上并不适合这样的任务。使用基于redis的队列和
SPOP
可能是更好的选择。kr98yfug2#
您可以使用事务和选择来更新锁数据的构造和查询的一致执行。例如:
csga3l583#
据推测,这些广告是从不同的页面呈现的。html是“无状态的”,所以你不能期望一个页面知道以前显示过什么广告。因此,您必须将此信息从一页传递到另一页,或者将其存储在与单个用户相关联的数据库中的某个位置。
你还想随机化吗?让我们同时做这两件事。
什么是“国家”?有一个“初始状态”,此时您随机选择要显示的第一个广告。然后将这些信息传递到下一页(在url、cookie或数据库中)。
另一个“状态”查看前一个状态并计算下一个要显示的广告(最终,你需要担心广告会用完——你会重新开始吗?你会重新随机分组吗?等等)
但如何避免连续两次出现同样的“随机”广告呢?
你有n个广告-
SELECT COUNT(*) ...
您选择addnumberj作为第一个要显示的广告——简单的RAND()
,在sql或应用程序中。选择一个数字m,使得m和n是“相对素数”。
“下一个”广告是数字(j:=(j+m)mod n)。这将在所有广告中循环播放而不复制,直到所有广告都显示出来同样,这可以在sql或ap中完成。
把j和m从一页传到下一页。
获取第j行:要么将行唯一地连续编号;或使用
ORDER BY ... LIMIT 1 OFFSET J
. (警告:将j填充到sql中可能很棘手。)没有表锁,没有互斥锁,只是将信息从一个页面传递到下一个页面。
lymgl2op4#
尽管这似乎是一个无关紧要的问题,但事实并非如此,有几种方法可以解决这个问题,每种方法都有其缺点,主要是您可以从三个不同的Angular 来面对这个问题:
接受它
在现实生活中,你获得重复播放的机会很低,你需要认真思考,如果你愿意面对额外的工作,只是为了确保一个广告不会连续播放两次,你还需要考虑缓存的存在,你可能会打破你的大脑,让广告原子化,只是为了发现浏览器/代理/缓存正在为重复的广告服务:(
在数据库上处理它
您可以处理这个问题,让数据库负责保持数据的安全性和一致性(实际上,这是数据库的主要任务),有几种方法:
锁和表(如前所述),我个人喜欢在php和mysql中使用锁的方法,您将遭受性能损失并面临死锁的风险,但无论如何,这仍然是一个解决方案,您只需在队列表上选择一个select for update,以确保在您更新之前没有人再读取。在这里的问题是,你会锁定整个表,而这是做,你需要小心你的db驱动程序和自动提交。
游标是基本上为您愿意执行的任务而创建的数据库结构,您可以创建一个游标并用它的函数安全地遍历它。由于事务的原因,在php中使用游标可能非常棘手,您需要非常清楚地知道您在做什么来避免错误。
游标和存储过程将其处理到数据库中的最佳方法是在数据库本身内部管理游标,这就是为什么会有存储过程,只需创建过程从游标中提取一个新项,并在它全部用完后再次填充它。
在php端处理
在这种情况下,您需要在php上实现自己的队列,可能有几种方法可以做到这一点,但主要问题可能是在应用程序上实现多进程安全的原子操作,我个人不喜欢使用任何类型的锁,如果您对应用程序的执行流没有100%的把握,或者您可能最终将其全部锁定。总之这里有三个机会:
使用sems或mutex都包括在php或第三方,超时和锁可以成为地狱,他们不容易检测,所以如上所述,我会避免它。
使用php msg queue我认为这是最安全的方法,只要你在*nix系统上运行你的应用程序,只需将所有可用的广告发送到消息队列,而不是在数据库上创建一个表,一旦所有广告被消耗,你就可以再次重新生成队列,此系统的缺点是无法分发服务器,如果在重新启动前不保存,则可能会丢失当前队列状态。
第三方队列系统取决于您的应用程序工作负载或交互您可能需要使用队列管理系统,这是必须的如果您想要一个分布式系统,使用msg队列系统来处理这个问题听起来可能太严重了,但这种方法可能是救命稻草。
摘要
如果您不能接受它,并且对数据库足够精通,我会选择存储过程和游标,您不需要考虑并发性,只要您使用的是符合acid的数据库(而不是myisam i.e.),数据库就会处理它
如果您想避免进入数据库,并且您的系统是*nix并且不会被分发,您可以尝试使用msg\u队列
如果您认为您的系统有时可能是分布式的,或者不依赖旧的sysv机制,您可以尝试使用rabbitmq这样的消息代理,这些好东西会让人上瘾,一旦您开始使用它们,您就开始每天看到它们的新用途。
tuwxkamq5#
使您的索引唯一或进行检查,检查数据,看看它是否是重复的。
希望这有帮助。祝你好运
3bygqnnd6#
我认为你的意思是,不同的“人”同时进行请求不应该得到相同的随机行?为了避免同一条记录在两个正在运行的请求中被选中两次的可能性,最可靠的方法(不需要测试)可能是锁定表并在事务中执行读取和更新。您必须使用支持此功能的存储引擎,例如innodb。
实现的方法
LOCK TABLES
以及UNLOCK TABLES
对于事务性表,比如innodb表,是以SET autocommit = 0
,不是START TRANSACTION
,后跟LOCK TABLES
. 那你不应该打电话UNLOCK TABLES
直到显式提交事务。例如,如果需要一次性读取和写入表,可以执行以下操作:
我们使用别名锁定的原因是,不能在使用相同名称的单个查询中多次引用锁定的表。所以我们改用别名,并为表和每个别名获得一个单独的锁。
jutyujz07#
您应该创建一个单独的db表,并标记用户在其帮助下收到的广告。在向用户发送广告之前,请检查用户是否已经收到广告。