chctl.php 16 KB

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