RedisQueue.php 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <?php
  2. namespace Illuminate\Queue;
  3. use Illuminate\Support\Str;
  4. use Illuminate\Queue\Jobs\RedisJob;
  5. use Illuminate\Contracts\Redis\Factory as Redis;
  6. use Illuminate\Contracts\Queue\Queue as QueueContract;
  7. class RedisQueue extends Queue implements QueueContract
  8. {
  9. /**
  10. * The Redis factory implementation.
  11. *
  12. * @var \Illuminate\Contracts\Redis\Factory
  13. */
  14. protected $redis;
  15. /**
  16. * The connection name.
  17. *
  18. * @var string
  19. */
  20. protected $connection;
  21. /**
  22. * The name of the default queue.
  23. *
  24. * @var string
  25. */
  26. protected $default;
  27. /**
  28. * The expiration time of a job.
  29. *
  30. * @var int|null
  31. */
  32. protected $retryAfter = 60;
  33. /**
  34. * The maximum number of seconds to block for a job.
  35. *
  36. * @var int|null
  37. */
  38. protected $blockFor = null;
  39. /**
  40. * Create a new Redis queue instance.
  41. *
  42. * @param \Illuminate\Contracts\Redis\Factory $redis
  43. * @param string $default
  44. * @param string $connection
  45. * @param int $retryAfter
  46. * @param int|null $blockFor
  47. * @return void
  48. */
  49. public function __construct(Redis $redis, $default = 'default', $connection = null, $retryAfter = 60, $blockFor = null)
  50. {
  51. $this->redis = $redis;
  52. $this->default = $default;
  53. $this->blockFor = $blockFor;
  54. $this->connection = $connection;
  55. $this->retryAfter = $retryAfter;
  56. }
  57. /**
  58. * Get the size of the queue.
  59. *
  60. * @param string $queue
  61. * @return int
  62. */
  63. public function size($queue = null)
  64. {
  65. $queue = $this->getQueue($queue);
  66. return $this->getConnection()->eval(
  67. LuaScripts::size(), 3, $queue, $queue.':delayed', $queue.':reserved'
  68. );
  69. }
  70. /**
  71. * Push a new job onto the queue.
  72. *
  73. * @param object|string $job
  74. * @param mixed $data
  75. * @param string $queue
  76. * @return mixed
  77. */
  78. public function push($job, $data = '', $queue = null)
  79. {
  80. return $this->pushRaw($this->createPayload($job, $data), $queue);
  81. }
  82. /**
  83. * Push a raw payload onto the queue.
  84. *
  85. * @param string $payload
  86. * @param string $queue
  87. * @param array $options
  88. * @return mixed
  89. */
  90. public function pushRaw($payload, $queue = null, array $options = [])
  91. {
  92. $this->getConnection()->rpush($this->getQueue($queue), $payload);
  93. return json_decode($payload, true)['id'] ?? null;
  94. }
  95. /**
  96. * Push a new job onto the queue after a delay.
  97. *
  98. * @param \DateTimeInterface|\DateInterval|int $delay
  99. * @param object|string $job
  100. * @param mixed $data
  101. * @param string $queue
  102. * @return mixed
  103. */
  104. public function later($delay, $job, $data = '', $queue = null)
  105. {
  106. return $this->laterRaw($delay, $this->createPayload($job, $data), $queue);
  107. }
  108. /**
  109. * Push a raw job onto the queue after a delay.
  110. *
  111. * @param \DateTimeInterface|\DateInterval|int $delay
  112. * @param string $payload
  113. * @param string $queue
  114. * @return mixed
  115. */
  116. protected function laterRaw($delay, $payload, $queue = null)
  117. {
  118. $this->getConnection()->zadd(
  119. $this->getQueue($queue).':delayed', $this->availableAt($delay), $payload
  120. );
  121. return json_decode($payload, true)['id'] ?? null;
  122. }
  123. /**
  124. * Create a payload string from the given job and data.
  125. *
  126. * @param string $job
  127. * @param mixed $data
  128. * @return string
  129. */
  130. protected function createPayloadArray($job, $data = '')
  131. {
  132. return array_merge(parent::createPayloadArray($job, $data), [
  133. 'id' => $this->getRandomId(),
  134. 'attempts' => 0,
  135. ]);
  136. }
  137. /**
  138. * Pop the next job off of the queue.
  139. *
  140. * @param string $queue
  141. * @return \Illuminate\Contracts\Queue\Job|null
  142. */
  143. public function pop($queue = null)
  144. {
  145. $this->migrate($prefixed = $this->getQueue($queue));
  146. list($job, $reserved) = $this->retrieveNextJob($prefixed);
  147. if ($reserved) {
  148. return new RedisJob(
  149. $this->container, $this, $job,
  150. $reserved, $this->connectionName, $queue ?: $this->default
  151. );
  152. }
  153. }
  154. /**
  155. * Migrate any delayed or expired jobs onto the primary queue.
  156. *
  157. * @param string $queue
  158. * @return void
  159. */
  160. protected function migrate($queue)
  161. {
  162. $this->migrateExpiredJobs($queue.':delayed', $queue);
  163. if (! is_null($this->retryAfter)) {
  164. $this->migrateExpiredJobs($queue.':reserved', $queue);
  165. }
  166. }
  167. /**
  168. * Migrate the delayed jobs that are ready to the regular queue.
  169. *
  170. * @param string $from
  171. * @param string $to
  172. * @return array
  173. */
  174. public function migrateExpiredJobs($from, $to)
  175. {
  176. return $this->getConnection()->eval(
  177. LuaScripts::migrateExpiredJobs(), 2, $from, $to, $this->currentTime()
  178. );
  179. }
  180. /**
  181. * Retrieve the next job from the queue.
  182. *
  183. * @param string $queue
  184. * @return array
  185. */
  186. protected function retrieveNextJob($queue)
  187. {
  188. if (! is_null($this->blockFor)) {
  189. return $this->blockingPop($queue);
  190. }
  191. return $this->getConnection()->eval(
  192. LuaScripts::pop(), 2, $queue, $queue.':reserved',
  193. $this->availableAt($this->retryAfter)
  194. );
  195. }
  196. /**
  197. * Retrieve the next job by blocking-pop.
  198. *
  199. * @param string $queue
  200. * @return array
  201. */
  202. protected function blockingPop($queue)
  203. {
  204. $rawBody = $this->getConnection()->blpop($queue, $this->blockFor);
  205. if (! empty($rawBody)) {
  206. $payload = json_decode($rawBody[1], true);
  207. $payload['attempts']++;
  208. $reserved = json_encode($payload);
  209. $this->getConnection()->zadd($queue.':reserved', [
  210. $reserved => $this->availableAt($this->retryAfter),
  211. ]);
  212. return [$rawBody[1], $reserved];
  213. }
  214. return [null, null];
  215. }
  216. /**
  217. * Delete a reserved job from the queue.
  218. *
  219. * @param string $queue
  220. * @param \Illuminate\Queue\Jobs\RedisJob $job
  221. * @return void
  222. */
  223. public function deleteReserved($queue, $job)
  224. {
  225. $this->getConnection()->zrem($this->getQueue($queue).':reserved', $job->getReservedJob());
  226. }
  227. /**
  228. * Delete a reserved job from the reserved queue and release it.
  229. *
  230. * @param string $queue
  231. * @param \Illuminate\Queue\Jobs\RedisJob $job
  232. * @param int $delay
  233. * @return void
  234. */
  235. public function deleteAndRelease($queue, $job, $delay)
  236. {
  237. $queue = $this->getQueue($queue);
  238. $this->getConnection()->eval(
  239. LuaScripts::release(), 2, $queue.':delayed', $queue.':reserved',
  240. $job->getReservedJob(), $this->availableAt($delay)
  241. );
  242. }
  243. /**
  244. * Get a random ID string.
  245. *
  246. * @return string
  247. */
  248. protected function getRandomId()
  249. {
  250. return Str::random(32);
  251. }
  252. /**
  253. * Get the queue or return the default.
  254. *
  255. * @param string|null $queue
  256. * @return string
  257. */
  258. public function getQueue($queue)
  259. {
  260. return 'queues:'.($queue ?: $this->default);
  261. }
  262. /**
  263. * Get the connection for the queue.
  264. *
  265. * @return \Illuminate\Redis\Connections\Connection
  266. */
  267. protected function getConnection()
  268. {
  269. return $this->redis->connection($this->connection);
  270. }
  271. /**
  272. * Get the underlying Redis instance.
  273. *
  274. * @return \Illuminate\Contracts\Redis\Factory
  275. */
  276. public function getRedis()
  277. {
  278. return $this->redis;
  279. }
  280. }