chctl.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. <?php
  2. namespace refill;
  3. use Log;
  4. use mtopcard;
  5. use algorithm;
  6. use scope_trace;
  7. class chctl
  8. {
  9. protected $mSpeedtable;
  10. static $cache_names = [
  11. ['quality'=> 1,'name' => 'channel-ctl-oil-common-limit'],
  12. ['quality'=> 2,'name' => 'channel-ctl-oil-fast-limit'],
  13. ['quality'=> 3,'name' => 'channel-ctl-oil-card-limit'],
  14. ['quality'=> 5,'name' => 'channel-ctl-oil-slow-limit'],
  15. ['quality'=> 1,'name' => 'channel-ctl-phone-common-limit'],
  16. ['quality'=> 2,'name' => 'channel-ctl-phone-fast-limit'],
  17. ['quality'=> 3,'name' => 'channel-ctl-phone-card-limit'],
  18. ['quality'=> 4,'name' => 'channel-ctl-phone-third-limit'],
  19. ['quality'=> 5,'name' => 'channel-ctl-phone-slow-limit'], //24 hour
  20. ['quality'=> 6,'name' => 'channel-ctl-phone-slow6-limit'], //6 hour
  21. ['quality'=> 7,'name' => 'channel-ctl-phone-slow2-limit'], //2 hour
  22. ['quality'=> 8,'name' => 'channel-ctl-phone-slow48-limit'],//48 hour
  23. ['quality'=> 9,'name' => 'channel-ctl-phone-slow72-limit'] //72 hour
  24. ];
  25. public function __construct()
  26. {
  27. $this->mSpeedtable = [];
  28. }
  29. public function update_price($policy)
  30. {
  31. $QPTA = $policy->getQPTA();
  32. foreach ($this->mSpeedtable as $key => $item)
  33. {
  34. $quality = $item->quality();
  35. $name = $item->name();
  36. $spec = $item->spec();
  37. $card_type = $item->card_type();
  38. $prefix = "{$name}-{$card_type}-{$spec}";
  39. if(array_key_exists($quality,$QPTA) && array_key_exists($prefix,$QPTA[$quality])) {
  40. $price = $QPTA[$quality][$prefix]['price'];
  41. $item->set_price($price);
  42. }
  43. }
  44. }
  45. public function update_ratios($ratios)
  46. {
  47. foreach ($ratios as $key => $ratio)
  48. {
  49. if(array_key_exists($key,$this->mSpeedtable)) {
  50. $item = $this->mSpeedtable[$key];
  51. $item->set_ratio($ratio);
  52. }
  53. }
  54. }
  55. public function update_speeds($speeds)
  56. {
  57. foreach ($speeds as $key => $speed)
  58. {
  59. if(array_key_exists($key,$this->mSpeedtable)) {
  60. $item = $this->mSpeedtable[$key];
  61. $item->set_speed($speed);
  62. }
  63. }
  64. }
  65. public function load($opened_names)
  66. {
  67. $this->mSpeedtable = [];
  68. foreach (self::$cache_names as $cache)
  69. {
  70. $quality = $cache['quality'];
  71. $cache_name = $cache['name'];
  72. $data = rcache($cache_name,"provider-");
  73. $data = unserialize($data['data']);
  74. $cfgs = empty($data) ? [] : $data;
  75. foreach ($cfgs as $items)
  76. {
  77. foreach ($items as $item)
  78. {
  79. $name = $item['name'];
  80. if(!algorithm::binary_search($opened_names,$name)) {
  81. continue;
  82. }
  83. $amount = $item['amount'];
  84. $card_type = $item['type'];
  85. $opened = $item['opened'] == 1;
  86. $sort = $item['sort'];
  87. $speed = $item['speed'];
  88. if($opened == false) continue;
  89. $key = $this->prefix($name,$amount,$card_type,$quality);
  90. $this->mSpeedtable[$key] = new ctl_item($name,$card_type,$amount,$speed,$sort,0,$opened,$quality);
  91. }
  92. }
  93. }
  94. }
  95. public function match($names, int $spec, int $card_type, int $quality, $max_inprice)
  96. {
  97. $trace = new scope_trace(__METHOD__);
  98. if ($card_type == mtopcard\ThirdRefillCard) {
  99. return $names;
  100. }
  101. $pThis = $this;
  102. $price_filter = function ($names,$spec,$card_type,$quality) use($pThis,$max_inprice)
  103. {
  104. $ctls = [];
  105. foreach ($names as $name)
  106. {
  107. $key = $pThis->prefix($name,$spec,$card_type,$quality);
  108. if(array_key_exists($key,$pThis->mSpeedtable))
  109. {
  110. $item = $pThis->mSpeedtable[$key];
  111. $inPrice = $item->price();
  112. // Log::record("max_price = {$max_inprice},in_price={$inPrice}",Log::DEBUG);
  113. if ($max_inprice !== false && $inPrice > $max_inprice) {
  114. continue;
  115. } else {
  116. $ctls[] = $item;
  117. }
  118. }
  119. else {
  120. // Log::record("auto_match speed table key={$key} is empty.",Log::DEBUG);
  121. }
  122. }
  123. return $ctls;
  124. };
  125. $ctl_items = $price_filter($names,$spec,$card_type,$quality);
  126. $chooser = function ($ctl_items)
  127. {
  128. $usable_items = [];
  129. foreach ($ctl_items as $item)
  130. {
  131. if($item->opened()) {
  132. $usable_items[] = $item;
  133. $item->calc_speed(); //此处必须先计算速率,因为usort函数里面不能使用协程。
  134. } else {
  135. // $key = $item->prefix();
  136. // Log::record("key={$key} has not opened",Log::DEBUG);
  137. }
  138. }
  139. return $usable_items;
  140. };
  141. //去掉已经关闭通道
  142. $usable_items = $chooser($ctl_items);
  143. //不过载的排在前面
  144. $ascending = function ($l, $r)
  145. {
  146. $lproity = $l->priority();
  147. $rproity = $r->priority();
  148. //usort 函数内部,不可使用协程等待之类的代码.
  149. $lover = $l->speed_overload() ? 1 : 0;
  150. $rover = $r->speed_overload() ? 1 : 0;
  151. if($lover == $rover)
  152. {
  153. if($lover) {
  154. return $lproity > $rproity ? -1 : 1; //如果都过载保优先级高的
  155. }
  156. else {
  157. return $lproity < $rproity ? -1 : 1;
  158. }
  159. }
  160. else {
  161. return $lover < $rover ? -1 : 1;
  162. }
  163. };
  164. usort($usable_items, $ascending);
  165. $result = [];
  166. $over_loads = [];
  167. foreach ($usable_items as $item)
  168. {
  169. $name = $item->name();
  170. $over_load = $item->speed_overload();
  171. if($over_load) {
  172. $over_loads[] = $item;
  173. } else {
  174. $result[] = $name;
  175. }
  176. }
  177. if(!empty($over_loads))
  178. {
  179. $assigner = new overload_assigner();
  180. $assigner->add($over_loads);
  181. $over_loads = $assigner->assign();
  182. foreach ($over_loads as $item) {
  183. $result[] = $item->name();
  184. }
  185. }
  186. return $result;
  187. }
  188. private function prefix($name,$spec,$card_type,$quality)
  189. {
  190. return "{$name}-{$spec}-{$card_type}-{$quality}";
  191. }
  192. const feed_minorder = 1;
  193. const sleep_ratio = 0.005;
  194. const max_sleep_time = 120;
  195. const sleep_count = 5;
  196. const wakeup_commit_count = 10;
  197. const sleep_commit_count = 15;
  198. const sleep_notify_count = 15;
  199. const profit_count = 90;
  200. const avg_order_time = 120;
  201. private function knockout($ctls,$amount)
  202. {
  203. $waker = function ($items,$max_sleep_time)
  204. {
  205. $workers = [];
  206. $wakeups = [];
  207. $sleeps = [];
  208. foreach ($items as $item)
  209. {
  210. [$fSleep,$time] = $item->sleeping();
  211. if($fSleep)
  212. {
  213. if(time() > $time + $max_sleep_time) {
  214. $item->wakeup();
  215. $wakeups[] = $item;
  216. }
  217. else {
  218. $sleeps[] = $item;
  219. }
  220. }
  221. else {
  222. $workers[] = $item;
  223. }
  224. }
  225. return [$sleeps,$wakeups,$workers];
  226. };
  227. $sleeper = function($sleeps,$wakeups,$workers)
  228. {
  229. foreach ($workers as $item)
  230. {
  231. [$notify_count, $ratio] = $item->notify_ratio();
  232. [$commit_count, $commit_succ] = $item->commit_statics();
  233. if ($commit_succ >= self::sleep_commit_count && $notify_count >= self::sleep_notify_count && $ratio < self::sleep_ratio) {
  234. $item->sleep();
  235. $sleeps[] = $item;
  236. } else {
  237. $wakeups[] = $item;
  238. }
  239. }
  240. return [$sleeps,$wakeups];
  241. };
  242. $desc_profit = function ($l, $r) use($amount)
  243. {
  244. [$lCount,$lRatio] = $l->notify_ratio();
  245. [$rCount,$rRatio] = $r->notify_ratio();
  246. $lProfit = $amount - $l->price();
  247. $rRrofit = $amount - $r->price();
  248. $lProfitRatio = $lProfit * $lRatio;
  249. $rRrofitRatio = $rRrofit * $rRatio;
  250. if($lProfitRatio > $rRrofitRatio) return -1;
  251. elseif($lProfitRatio < $rRrofitRatio) return 1;
  252. else return 0;
  253. };
  254. [$sleeps,$wakeups,$workers] = $waker($ctls,self::max_sleep_time);
  255. usort($workers, $desc_profit);
  256. return $sleeper($sleeps,$wakeups,$workers);
  257. }
  258. private function feed_as_lazy_speed($ctls)
  259. {
  260. //当前提交量计算喂订单
  261. $asc_speed = function ($l, $r) {
  262. $lspeed = $l->lazy_speed();
  263. $rspeed = $r->lazy_speed();
  264. return $lspeed < $rspeed ? -1 : 1;
  265. };
  266. usort($ctls, $asc_speed);
  267. $feeds = [];
  268. $profits = [];
  269. foreach ($ctls as $item)
  270. {
  271. $name = $item->name();
  272. [$count,$ratio] = $item->notify_ratio();
  273. $lazy_spped = $item->lazy_speed();
  274. // Log::record("auto_match channel {$name} count = {$count} ratio={$ratio} speed={$lazy_spped}",Log::DEBUG);
  275. if($lazy_spped < self::feed_minorder) {
  276. $feeds[] = $item;
  277. } else {
  278. $profits[] = $item;
  279. }
  280. }
  281. return [$feeds,$profits];
  282. }
  283. public function feed_as_commit($ctls)
  284. {
  285. //当前提交量计算喂订单
  286. $ascer = function ($l, $r) {
  287. [$lCount,$lSucc] = $l->commit_statics();
  288. [$rCount,$rSucc] = $r->commit_statics();
  289. return $lSucc < $rSucc ? -1 : 1;
  290. };
  291. usort($ctls, $ascer);
  292. $feeds = [];
  293. $profits = [];
  294. foreach ($ctls as $item)
  295. {
  296. $name = $item->name();
  297. [$comit_count,$commit_succ] = $item->commit_statics();
  298. [$notify_count,$notify_succ] = $item->notify_statics();
  299. $speed = $item->lazy_speed();
  300. if($comit_count < self::wakeup_commit_count && $speed < self::feed_minorder) {
  301. $feeds[] = $item;
  302. $state = 'feed';
  303. } else {
  304. $profits[] = $item;
  305. $state = 'profit';
  306. }
  307. // Log::record("auto_match channel {$name} state={$state} comit_succ={$commit_succ} count={$comit_count} notify_count={$notify_count} notify_succ={$notify_succ} speed={$speed}", Log::DEBUG);
  308. }
  309. return [$feeds,$profits];
  310. }
  311. public function auto_match($names, int $spec, int $card_type, int $quality, $out_price, $max_inprice, $left_time): array
  312. {
  313. $formater = function ($val) {
  314. return number_format($val,10,'.','');
  315. };
  316. $desctor = function ($item) use($out_price,$formater,$card_type,$spec) {
  317. $name = $item->name();
  318. [$notify_count,$ratio] = $item->notify_ratio();
  319. [$commit_count,$commit_succ] = $item->commit_statics();
  320. $prifit = $out_price - $item->price();
  321. $profit_ratio = $formater($prifit * $ratio);
  322. [$fSleep,$time] = $item->sleeping();
  323. if($fSleep) {
  324. $state = "sleeping";
  325. $time = $time + 120 - time();
  326. } else {
  327. $state = "waking";
  328. }
  329. return "{$card_type}-{$spec} {$name} {$state} time={$time} commit_count={$commit_count} notify_count={$notify_count} ratio={$ratio} profit_ratio={$profit_ratio}";
  330. };
  331. $left_times = intval($left_time / self::avg_order_time);
  332. $left_times = $left_times <= 0 ? 1 : $left_times;
  333. Log::record("left_times = {$left_times}",Log::DEBUG);
  334. $desc_profit = function ($l, $r) use($out_price,$formater,$left_times)
  335. {
  336. [$lCount,$lRatio] = $l->notify_ratio();
  337. [$rCount,$rRatio] = $r->notify_ratio();
  338. $lProfit = $out_price - $l->price();
  339. $rRrofit = $out_price - $r->price();
  340. $lProfitRatio = $lProfit * $lRatio;
  341. $rRrofitRatio = $rRrofit * $rRatio;
  342. $lProfit = $formater($lProfit);
  343. $rRrofit = $formater($rRrofit);
  344. $lProfitRatio = $formater($lProfitRatio);
  345. $rRrofitRatio = $formater($rRrofitRatio);
  346. $lRatio = $formater($lRatio);
  347. $rRatio = $formater($rRatio);
  348. $lRatios = intval($lRatio * $left_times + 0.05);
  349. $rRatios = intval($rRatio * $left_times + 0.05);
  350. $lRatios = $lRatios > 1 ? 1 : $lRatios;
  351. $rRatios = $rRatios > 1 ? 1 : $rRatios;
  352. if($lRatios == $rRatios && $lRatios == 1) { //次数大概率成功,按利润优先
  353. if($lProfit > $rRrofit) return -1;
  354. elseif($lProfit < $rRrofit) return 1;
  355. else return 0;
  356. }
  357. elseif($lProfitRatio > $rRrofitRatio) return -1;
  358. elseif($lProfitRatio < $rRrofitRatio) return 1;
  359. elseif($lProfitRatio == 0) //利润获得概率为0,说明成功率或者利润可能性为0
  360. {
  361. if($lCount > $rCount) //此时优先喂单
  362. {
  363. if($rCount >= self::profit_count) //单量都大于阈值,优先利润
  364. {
  365. if($lProfit > $rRrofit) return -1;
  366. elseif($lProfit < $rRrofit) return 1;
  367. else return 0;
  368. }
  369. else {
  370. return 1;
  371. }
  372. }
  373. elseif($lCount < $rCount)
  374. {
  375. if($lCount >= self::profit_count) //单量都大于阈值,优先利润
  376. {
  377. if($lProfit > $rRrofit) return -1;
  378. elseif($lProfit < $rRrofit) return 1;
  379. else return 0;
  380. }
  381. else {
  382. return -1;
  383. }
  384. }
  385. else
  386. {
  387. $count = $lCount;
  388. if($count >= self::profit_count) //单量都大于阈值,优先利润
  389. {
  390. if($lProfit > $rRrofit) return -1;
  391. elseif($lProfit < $rRrofit) return 1;
  392. else return 0;
  393. }
  394. else {
  395. return 0;
  396. }
  397. }
  398. }
  399. elseif ($lRatio > $rRatio) return -1; //以下是利润概率相等,优先成功率保障
  400. elseif ($lRatio < $rRatio) return 1;
  401. else return 0;
  402. };
  403. $pThis = $this;
  404. $price_filter = function ($names,$spec,$card_type,$quality) use($pThis,$max_inprice)
  405. {
  406. $ctls = [];
  407. foreach ($names as $name)
  408. {
  409. $key = $pThis->prefix($name,$spec,$card_type,$quality);
  410. if(array_key_exists($key,$pThis->mSpeedtable))
  411. {
  412. $item = $pThis->mSpeedtable[$key];
  413. $item->calc_speed();
  414. $inPrice = $item->price();
  415. if ($max_inprice !== false && $inPrice > $max_inprice) {
  416. continue;
  417. } else {
  418. $ctls[] = $item;
  419. }
  420. }
  421. else {
  422. Log::record("auto_match speed table key={$key} is empty.",Log::DEBUG);
  423. }
  424. }
  425. return $ctls;
  426. };
  427. $logger = function ($items,$label) use ($desctor)
  428. {
  429. foreach ($items as $item) {
  430. $msg = $desctor($item);
  431. Log::record("auto_match {$label} = {$msg}",Log::DEBUG);
  432. }
  433. };
  434. $names = array_unique($names);
  435. Log::record("auto_match outprice= {$out_price} names=" . implode(',', $names), Log::DEBUG);
  436. $can_feed = true;
  437. $ctls = $price_filter($names,$spec,$card_type,$quality);
  438. [$sleeps,$wakeups] = $this->knockout($ctls,$out_price);
  439. $logger($sleeps,'sleeps');
  440. $logger($wakeups,'wakeups');
  441. if(empty($wakeups)) {
  442. $ctls = $sleeps;
  443. $sleeps = [];
  444. } else {
  445. $ctls = $wakeups;
  446. }
  447. //找出需要喂单
  448. [$feeds,$profits] = $this->feed_as_commit($ctls);
  449. usort($feeds, $desc_profit);
  450. $profits = array_merge($profits, $sleeps);
  451. usort($profits, $desc_profit);
  452. $normals = [];
  453. $overloads = [];
  454. foreach ($profits as $item)
  455. {
  456. if($item->speed_overload()) {
  457. $overloads[] = $item;
  458. }
  459. else {
  460. $normals[] = $item;
  461. }
  462. }
  463. if(!empty($overloads)) {
  464. $assigner = new overload_assigner();
  465. $assigner->add($overloads);
  466. $overloads = $assigner->assign();
  467. }
  468. $profits = array_merge($normals, $overloads);
  469. $feed_names = [];
  470. foreach ($feeds as $item) {
  471. $feed_names[] = $item->name();
  472. }
  473. $profit_names = [];
  474. foreach ($profits as $item) {
  475. $profit_names[] = $item->name();
  476. }
  477. if ($can_feed) {
  478. $result = array_merge($feed_names, $profit_names);
  479. } else {
  480. $result = array_merge($profit_names,$feed_names);
  481. }
  482. $scan_feed = $can_feed ? 'true' : 'false';
  483. $logger($feeds,'feeds');
  484. $logger($profits,'profits');
  485. Log::record("auto_match {$card_type}-{$spec} can_feed = {$scan_feed} result =" . implode(',',$result),Log::DEBUG);
  486. return $result;
  487. }
  488. }