|
1 | 1 | # php-job-server
|
2 | 2 |
|
3 |
| -## Errors |
| 3 | +Spawns multiple PHP processes that act as independent worker processes. This |
| 4 | +allows you to parallelize work easily. |
4 | 5 |
|
5 |
| -Workers' PHP errors are logged to syslog (i.e. /var/log/syslog). |
| 6 | +Inter-process communication between the workers and the server is done via |
| 7 | +Unix domain sockets. |
| 8 | + |
| 9 | + |
| 10 | +## Installation |
| 11 | + |
| 12 | +Put this in your project's composer.json file: |
6 | 13 |
|
7 |
| -## Brainstorming: |
| 14 | +``` |
| 15 | +"repositories": [ |
| 16 | + { |
| 17 | + "url": "https://github.com/Crusse/php-job-server.git", |
| 18 | + "type": "vcs" |
| 19 | + } |
| 20 | +], |
| 21 | +"require": { |
| 22 | + "crusse/job-server": "dev-master" |
| 23 | +} |
| 24 | +``` |
8 | 25 |
|
9 |
| - Blocking i/o (perfectly synchronized): |
10 | 26 |
|
11 |
| - worker1: 2ms to finish |
12 |
| - worker2: 2ms to finish |
13 |
| - worker3: 2ms to finish |
| 27 | +## Usage |
14 | 28 |
|
15 |
| - server reads result: 0.5ms (r) |
16 |
| - server writes new job: 0.5ms (w) |
| 29 | +Create the jobs: |
17 | 30 |
|
18 |
| - time: 0 1 2 3 4 5 6 7 |
19 |
| - worker1: rrrwww rrrwww |
20 |
| - worker2: rrrwww rrrwww |
21 |
| - worker3: rrrwww rrrwww |
| 31 | +``` |
| 32 | +$server = new Crusse\JobServer\Server( 4 ); |
| 33 | +$server->setWorkerTimeout( 2 ); |
22 | 34 |
|
23 |
| - ----------------------------------------------------------- |
| 35 | +for ( $i = 0; $i < 20; $i++ ) |
| 36 | + $server->addJob( 'strtoupper', 'foo'. $i ); |
| 37 | +``` |
24 | 38 |
|
25 |
| - Blocking i/o (not synchronized): |
| 39 | +Get results: |
26 | 40 |
|
27 |
| - worker1: 3ms to finish |
28 |
| - worker2: 2ms to finish |
29 |
| - worker3: 1ms to finish |
| 41 | +``` |
| 42 | +print_r( $server->getOrderedResults() ); |
| 43 | +``` |
30 | 44 |
|
31 |
| - server reads result: 0.5ms (r) |
32 |
| - server writes new job: 0.5ms (w) |
33 |
| - cpu time available for extra non-i/o work: (+) |
34 |
| - waiting for i/o: (.) |
35 |
| - |
36 |
| - time: 0 1 2 3 4 5 6 7 |
37 |
| - worker1: rrrwww rrrwww |
38 |
| - worker2: ......rrrwww ......rrrwww |
39 |
| - worker3: ............rrrwww ............rrrwww |
40 |
| - server: ...... |
41 |
| - |
42 |
| - ----------------------------------------------------------- |
43 |
| - |
44 |
| - Non-blocking i/o (perfectly synchronized): |
45 |
| - |
46 |
| - worker1: 2ms to finish |
47 |
| - worker2: 2ms to finish |
48 |
| - worker3: 2ms to finish |
49 |
| - |
50 |
| - server reads result: 0.5ms (r) |
51 |
| - server writes new job: 0.5ms (w) |
52 |
| - cpu time available for extra non-i/o work: (+) |
53 |
| - waiting for i/o: (.) |
54 |
| - |
55 |
| - time: 0 1 2 3 4 5 6 7 |
56 |
| - worker1: r++r++r++w++w++w r++r++r++w++w++w |
57 |
| - worker2: r++r++r++w++w++w r++r++r++w++w++w |
58 |
| - worker3: r++r++r++w++w++w r++r++r++w++w++w |
59 |
| - server: ++++++++++ |
60 |
| - |
61 |
| - ----------------------------------------------------------- |
62 |
| - |
63 |
| - Non-blocking i/o (not synchronized): |
64 |
| - |
65 |
| - worker1: 3ms to finish |
66 |
| - worker2: 2ms to finish |
67 |
| - worker3: 1ms to finish |
68 |
| - |
69 |
| - server reads result: 0.5ms (r) |
70 |
| - server writes new job: 0.5ms (w) |
71 |
| - cpu time available for extra non-i/o work: (+) |
72 |
| - waiting for i/o: (.) |
73 |
| - |
74 |
| - time: 0 1 2 3 4 5 6 7 |
75 |
| - worker1: r++r++r++w++w++w r+r+rwww |
76 |
| - worker2: r++r++r++w++w++w r+rrw+w+w |
77 |
| - worker3: r++r++r++w++w++w rrrww+w |
78 |
| - server: ++++++ |
79 |
| - |
80 |
| - ----------------------------------------------------------- |
81 |
| - |
82 |
| - |
83 |
| - Blocking i/o: |
84 |
| - ------------- |
85 |
| - |
86 |
| - Server: |
87 |
| - |
88 |
| - results = [] |
89 |
| - stream_socket_server |
90 |
| - while ( unfinished jobs ) |
91 |
| - stream_socket_accept |
92 |
| - while ( got data ) |
93 |
| - stream_socket_recvfrom |
94 |
| - results[] = data |
95 |
| - stream_socket_sendto |
96 |
| - fclose |
97 |
| - |
98 |
| - Worker: |
99 |
| - |
100 |
| - stream_socket_client |
101 |
| - stream_socket_sendto [register worker with server] |
102 |
| - stream_socket_recvfrom [get first job from server] |
103 |
| - while ( got job ) |
104 |
| - stream_socket_sendto [send result] |
105 |
| - stream_socket_recvfrom [get another job] |
106 |
| - |
107 |
| - |
108 |
| - Non-blocking i/o: |
109 |
| - ----------------- |
110 |
| - |
111 |
| - Server: |
112 |
| - |
113 |
| - clients = [] |
114 |
| - writeBufferPerClient = [] |
115 |
| - readBufferPerClient = [] |
116 |
| - stream_socket_server |
117 |
| - stream_set_blocking( server socket not blocking ) |
118 |
| - while ( unfinished jobs ) |
119 |
| - stream_select( readable: clients + server, writable: clients ) |
120 |
| - if ( server is readable ) |
121 |
| - clients[] = stream_socket_accept |
122 |
| - stream_set_blocking( client socket not blocking ) |
123 |
| - foreach ( clients[] ) |
124 |
| - if ( is readable ) |
125 |
| - while ( got data, which means client socket is open ) |
126 |
| - stream_socket_recvfrom |
127 |
| - readBufferPerClient[] .= data |
128 |
| - if ( no received data ) |
129 |
| - results[] = Request(readBuffer)->getBody/Headers() |
130 |
| - if ( is writable ) |
131 |
| - while ( didn't write all bytes of writeBufferPerClient[current] yet ) |
132 |
| - stream_socket_sendto |
133 |
| - if ( writeBufferPerClient[current] is empty ) |
134 |
| - fclose |
135 |
| - unset( clients[current] ) |
136 |
| - |
137 |
| - Worker: |
138 |
| - |
139 |
| - initialized = false |
140 |
| - jobQueue = [] |
141 |
| - resultsQueue = [] |
142 |
| - readBuffer = '' |
143 |
| - writeBuffer = '' |
144 |
| - stream_socket_client |
145 |
| - stream_set_blocking( client socket not blocking ) |
146 |
| - while ( true ) |
147 |
| - stream_select( readable: server, writable: server ) |
148 |
| - if ( server is writable ) |
149 |
| - if ( !initialized ) |
150 |
| - writeBuffer = I'm a new worker, register me |
151 |
| - else |
152 |
| - writeBuffer = here's a job result resultsQueue.pop(), gimme another job |
153 |
| - while ( didn't write all bytes of writeBuffer yet ) |
154 |
| - stream_socket_sendto |
155 |
| - if ( writeBuffer is empty ) |
156 |
| - fclose |
157 |
| - if ( server is readable ) |
158 |
| - while ( got data, which means client socket is open ) |
159 |
| - stream_socket_recvfrom |
160 |
| - readBuffer .= data |
161 |
| - if ( no received data ) |
162 |
| - jobQueue[] = Request(readBuffer)->getBody/Headers() |
| 45 | +Alternative way to get each result immediately after it's been computed: |
163 | 46 |
|
| 47 | +``` |
| 48 | +$server->getResults( function( $result, $jobNumber, $total ) { |
| 49 | + echo 'Job '. $jobNumber .'/'. $total .': '. $result . PHP_EOL; |
| 50 | +} ); |
| 51 | +``` |
164 | 52 |
|
| 53 | + |
| 54 | +## Errors |
| 55 | + |
| 56 | +Workers' PHP errors are logged to syslog (i.e. /var/log/syslog). |
0 commit comments