chctl.php 17 KB

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