chctl.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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. $trace = new scope_trace(__METHOD__ . "speed_calc");
  129. $usable_items = [];
  130. foreach ($ctl_items as $item)
  131. {
  132. if($item->opened()) {
  133. $usable_items[] = $item;
  134. $item->calc_speed(); //此处必须先计算速率,因为usort函数里面不能使用协程。
  135. } else {
  136. $key = $item->prefix();
  137. Log::record("key={$key} has not opened",Log::DEBUG);
  138. }
  139. }
  140. return $usable_items;
  141. };
  142. //去掉已经关闭通道
  143. $usable_items = $chooser($ctl_items);
  144. //不过载的排在前面
  145. $ascending = function ($l, $r)
  146. {
  147. $lproity = $l->priority();
  148. $rproity = $r->priority();
  149. //usort 函数内部,不可使用协程等待之类的代码.
  150. $lover = $l->speed_overload() ? 1 : 0;
  151. $rover = $r->speed_overload() ? 1 : 0;
  152. if($lover == $rover)
  153. {
  154. if($lover) {
  155. return $lproity > $rproity ? -1 : 1; //如果都过载保优先级高的
  156. }
  157. else {
  158. return $lproity < $rproity ? -1 : 1;
  159. }
  160. }
  161. else {
  162. return $lover < $rover ? -1 : 1;
  163. }
  164. };
  165. usort($usable_items, $ascending);
  166. $result = [];
  167. $over_loads = [];
  168. foreach ($usable_items as $item)
  169. {
  170. $name = $item->name();
  171. $over_load = $item->speed_overload();
  172. if($over_load) {
  173. $over_loads[] = $item;
  174. } else {
  175. $result[] = $name;
  176. }
  177. }
  178. if(!empty($over_loads))
  179. {
  180. $tracex = new scope_trace(__METHOD__ . " overload_assigner");
  181. $assigner = new overload_assigner();
  182. $assigner->add($over_loads);
  183. $over_loads = $assigner->assign();
  184. foreach ($over_loads as $item) {
  185. $result[] = $item->name();
  186. }
  187. }
  188. return $result;
  189. }
  190. private function prefix($name,$spec,$card_type,$quality)
  191. {
  192. return "{$name}-{$spec}-{$card_type}-{$quality}";
  193. }
  194. const feed_minorder = 1;
  195. const sleep_ratio = 0.005;
  196. const max_sleep_time = 120;
  197. const sleep_count = 5;
  198. const wakeup_commit_count = 10;
  199. const sleep_commit_count = 15;
  200. const sleep_notify_count = 15;
  201. const profit_count = 90;
  202. const avg_order_time = 180;
  203. private function knockout($ctls,$amount)
  204. {
  205. $waker = function ($items,$max_sleep_time)
  206. {
  207. $workers = [];
  208. $wakeups = [];
  209. $sleeps = [];
  210. foreach ($items as $item)
  211. {
  212. [$fSleep,$time] = $item->sleeping();
  213. if($fSleep)
  214. {
  215. if(time() > $time + $max_sleep_time) {
  216. $item->wakeup();
  217. $wakeups[] = $item;
  218. }
  219. else {
  220. $sleeps[] = $item;
  221. }
  222. }
  223. else {
  224. $workers[] = $item;
  225. }
  226. }
  227. return [$sleeps,$wakeups,$workers];
  228. };
  229. $sleeper = function($sleeps,$wakeups,$workers)
  230. {
  231. foreach ($workers as $item)
  232. {
  233. [$notify_count, $ratio] = $item->notify_ratio();
  234. [$commit_count, $commit_succ] = $item->commit_statics();
  235. if ($commit_succ >= self::sleep_commit_count && $notify_count >= self::sleep_notify_count && $ratio < self::sleep_ratio) {
  236. $item->sleep();
  237. $sleeps[] = $item;
  238. } else {
  239. $wakeups[] = $item;
  240. }
  241. }
  242. return [$sleeps,$wakeups];
  243. };
  244. $desc_profit = function ($l, $r) use($amount)
  245. {
  246. [$lCount,$lRatio] = $l->notify_ratio();
  247. [$rCount,$rRatio] = $r->notify_ratio();
  248. $lProfit = $amount - $l->price();
  249. $rRrofit = $amount - $r->price();
  250. $lProfitRatio = $lProfit * $lRatio;
  251. $rRrofitRatio = $rRrofit * $rRatio;
  252. if($lProfitRatio > $rRrofitRatio) return -1;
  253. elseif($lProfitRatio < $rRrofitRatio) return 1;
  254. else return 0;
  255. };
  256. [$sleeps,$wakeups,$workers] = $waker($ctls,self::max_sleep_time);
  257. usort($workers, $desc_profit);
  258. return $sleeper($sleeps,$wakeups,$workers);
  259. }
  260. private function feed_as_lazy_speed($ctls)
  261. {
  262. //当前提交量计算喂订单
  263. $asc_speed = function ($l, $r) {
  264. $lspeed = $l->lazy_speed();
  265. $rspeed = $r->lazy_speed();
  266. return $lspeed < $rspeed ? -1 : 1;
  267. };
  268. usort($ctls, $asc_speed);
  269. $feeds = [];
  270. $profits = [];
  271. foreach ($ctls as $item)
  272. {
  273. $name = $item->name();
  274. [$count,$ratio] = $item->notify_ratio();
  275. $lazy_spped = $item->lazy_speed();
  276. Log::record("auto_match channel {$name} count = {$count} ratio={$ratio} speed={$lazy_spped}",Log::DEBUG);
  277. if($lazy_spped < self::feed_minorder) {
  278. $feeds[] = $item;
  279. } else {
  280. $profits[] = $item;
  281. }
  282. }
  283. return [$feeds,$profits];
  284. }
  285. public function feed_as_commit($ctls)
  286. {
  287. //当前提交量计算喂订单
  288. $ascer = function ($l, $r) {
  289. [$lCount,$lSucc] = $l->commit_statics();
  290. [$rCount,$rSucc] = $r->commit_statics();
  291. return $lSucc < $rSucc ? -1 : 1;
  292. };
  293. usort($ctls, $ascer);
  294. $feeds = [];
  295. $profits = [];
  296. foreach ($ctls as $item)
  297. {
  298. $name = $item->name();
  299. [$comit_count,$commit_succ] = $item->commit_statics();
  300. [$notify_count,$notify_succ] = $item->notify_statics();
  301. $speed = $item->lazy_speed();
  302. if($comit_count < self::wakeup_commit_count && $speed < self::feed_minorder) {
  303. $feeds[] = $item;
  304. $state = 'feed';
  305. } else {
  306. $profits[] = $item;
  307. $state = 'profit';
  308. }
  309. 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);
  310. }
  311. return [$feeds,$profits];
  312. }
  313. public function auto_match($names, int $spec, int $card_type, int $quality, $out_price, $max_inprice, $left_time): array
  314. {
  315. $formater = function ($val) {
  316. return number_format($val,10,'.','');
  317. };
  318. $desctor = function ($item) use($out_price,$formater,$card_type,$spec) {
  319. $name = $item->name();
  320. [$notify_count,$ratio] = $item->notify_ratio();
  321. [$commit_count,$commit_succ] = $item->commit_statics();
  322. $prifit = $out_price - $item->price();
  323. $profit_ratio = $formater($prifit * $ratio);
  324. [$fSleep,$time] = $item->sleeping();
  325. if($fSleep) {
  326. $state = "sleeping";
  327. $time = $time + 120 - time();
  328. } else {
  329. $state = "waking";
  330. }
  331. return "{$card_type}-{$spec} {$name} {$state} time={$time} commit_count={$commit_count} notify_count={$notify_count} ratio={$ratio} profit_ratio={$profit_ratio}";
  332. };
  333. $left_times = intval($left_time / self::avg_order_time);
  334. $left_times = $left_times <= 0 ? 1 : $left_times;
  335. Log::record("left_times = {$left_times}",Log::DEBUG);
  336. $desc_profit = function ($l, $r) use($out_price,$formater,$left_times)
  337. {
  338. [$lCount,$lRatio] = $l->notify_ratio();
  339. [$rCount,$rRatio] = $r->notify_ratio();
  340. $lProfit = $out_price - $l->price();
  341. $rRrofit = $out_price - $r->price();
  342. $lProfitRatio = $lProfit * $lRatio;
  343. $rRrofitRatio = $rRrofit * $rRatio;
  344. $lProfit = $formater($lProfit);
  345. $rRrofit = $formater($rRrofit);
  346. $lProfitRatio = $formater($lProfitRatio);
  347. $rRrofitRatio = $formater($rRrofitRatio);
  348. $lRatio = $formater($lRatio);
  349. $rRatio = $formater($rRatio);
  350. $lRatios = intval($lRatio * $left_times + 0.05);
  351. $rRatios = intval($rRatio * $left_times + 0.05);
  352. $lRatios = $lRatios > 1 ? 1 : $lRatios;
  353. $rRatios = $rRatios > 1 ? 1 : $rRatios;
  354. if($lRatios == $rRatios && $lRatios == 1) { //次数大概率成功,按利润优先
  355. if($lProfit > $rRrofit) return -1;
  356. elseif($lProfit < $rRrofit) return 1;
  357. else return 0;
  358. }
  359. elseif($lProfitRatio > $rRrofitRatio) return -1;
  360. elseif($lProfitRatio < $rRrofitRatio) return 1;
  361. elseif($lProfitRatio == 0) //利润获得概率为0,说明成功率或者利润可能性为0
  362. {
  363. if($lCount > $rCount) //此时优先喂单
  364. {
  365. if($rCount >= self::profit_count) //单量都大于阈值,优先利润
  366. {
  367. if($lProfit > $rRrofit) return -1;
  368. elseif($lProfit < $rRrofit) return 1;
  369. else return 0;
  370. }
  371. else {
  372. return 1;
  373. }
  374. }
  375. elseif($lCount < $rCount)
  376. {
  377. if($lCount >= self::profit_count) //单量都大于阈值,优先利润
  378. {
  379. if($lProfit > $rRrofit) return -1;
  380. elseif($lProfit < $rRrofit) return 1;
  381. else return 0;
  382. }
  383. else {
  384. return -1;
  385. }
  386. }
  387. else
  388. {
  389. $count = $lCount;
  390. if($count >= self::profit_count) //单量都大于阈值,优先利润
  391. {
  392. if($lProfit > $rRrofit) return -1;
  393. elseif($lProfit < $rRrofit) return 1;
  394. else return 0;
  395. }
  396. else {
  397. return 0;
  398. }
  399. }
  400. }
  401. elseif ($lRatio > $rRatio) return -1; //以下是利润概率相等,优先成功率保障
  402. elseif ($lRatio < $rRatio) return 1;
  403. else return 0;
  404. };
  405. $pThis = $this;
  406. $price_filter = function ($names,$spec,$card_type,$quality) use($pThis,$max_inprice)
  407. {
  408. $ctls = [];
  409. foreach ($names as $name)
  410. {
  411. $key = $pThis->prefix($name,$spec,$card_type,$quality);
  412. if(array_key_exists($key,$pThis->mSpeedtable))
  413. {
  414. $item = $pThis->mSpeedtable[$key];
  415. $item->calc_speed();
  416. $inPrice = $item->price();
  417. if ($max_inprice !== false && $inPrice > $max_inprice) {
  418. continue;
  419. } else {
  420. $ctls[] = $item;
  421. }
  422. }
  423. else {
  424. Log::record("auto_match speed table key={$key} is empty.",Log::DEBUG);
  425. }
  426. }
  427. return $ctls;
  428. };
  429. $logger = function ($items,$label) use ($desctor)
  430. {
  431. foreach ($items as $item) {
  432. $msg = $desctor($item);
  433. Log::record("auto_match {$label} = {$msg}",Log::DEBUG);
  434. }
  435. };
  436. $names = array_unique($names);
  437. Log::record("auto_match outprice= {$out_price} names=" . implode(',', $names), Log::DEBUG);
  438. $can_feed = true;
  439. $ctls = $price_filter($names,$spec,$card_type,$quality);
  440. [$sleeps,$wakeups] = $this->knockout($ctls,$out_price);
  441. $logger($sleeps,'sleeps');
  442. $logger($wakeups,'wakeups');
  443. if(empty($wakeups)) {
  444. $ctls = $sleeps;
  445. $sleeps = [];
  446. } else {
  447. $ctls = $wakeups;
  448. }
  449. //找出需要喂单
  450. [$feeds,$profits] = $this->feed_as_commit($ctls);
  451. usort($feeds, $desc_profit);
  452. $profits = array_merge($profits, $sleeps);
  453. usort($profits, $desc_profit);
  454. $normals = [];
  455. $overloads = [];
  456. foreach ($profits as $item)
  457. {
  458. if($item->speed_overload()) {
  459. Log::record("name={$item->name()} speed_overload",Log::DEBUG);
  460. $overloads[] = $item;
  461. }
  462. else {
  463. $normals[] = $item;
  464. }
  465. }
  466. if(!empty($overloads)) {
  467. $assigner = new overload_assigner();
  468. $assigner->add($overloads);
  469. $overloads = $assigner->assign();
  470. }
  471. $profits = array_merge($normals, $overloads);
  472. $feed_names = [];
  473. foreach ($feeds as $item) {
  474. $feed_names[] = $item->name();
  475. }
  476. $profit_names = [];
  477. foreach ($profits as $item) {
  478. $profit_names[] = $item->name();
  479. }
  480. if ($can_feed) {
  481. $result = array_merge($feed_names, $profit_names);
  482. } else {
  483. $result = array_merge($profit_names,$feed_names);
  484. }
  485. $scan_feed = $can_feed ? 'true' : 'false';
  486. $logger($feeds,'feeds');
  487. $logger($profits,'profits');
  488. Log::record("auto_match {$card_type}-{$spec} can_feed = {$scan_feed} result =" . implode(',',$result),Log::DEBUG);
  489. return $result;
  490. }
  491. }