In some website applications that require the Real-Time Message function, in addition to the client polling request server to obtain the message, there is also a solution that pushes messages through the comet persistent connection. Obviously, the latter has more advantages, high real-time performance, and low pressure on clients and servers. For the persistent connection scheme, we need to consider managing the persistent connection so that messages can be pushed to the client when there is a message. However, the establishment of a large number of persistent connections on the server may be overwhelmed, so it is not just acceptable to accept connections and hold connections, and connection timeout management is required, if the connection times out, you need to push the reconnection message or close the connection. We can analyze the specific management logic at the beginning.
First, a simple analysis of specific application scenarios: users log on to an e-commerce website (Suning, Gome), exactly after a large number of customers log on, sellers (Haier, Gree, etc) launched a series of promotions (for registered users), which need to be pushed to the browser in a timely manner for users to understand, without refreshing the page.
What should we do with such a common feature? First, when the user opens the page, the client JS needs to request a persistent connection and carry the user information (ID). The server uses list, queue, and other storage resources to record the request time at that time; start another daemon thread to scan for a connection that has exceeded or needs to be disconnected. If the connection times out, re-connect the message and disconnect the message; if a message needs to be pushed, you can obtain the connection push from list and queue. It seems that there is no problem. For timeout processing, it is unacceptable to traverse a large number of elements for a long time. Therefore, to exclude list storage, queue has the FIFO feature, and each check times out, just check the Header element. This is very fast. However, the reconnection problem is ignored. During the interval, you cannot reconnect to a new connection object (a large number of new objects cannot afford to consume memory). You can only reuse previous objects, the time of the previous object will change, and the queue will become an ordered queue not sorted by time. If it is processed in the previous way, it will be blocked in the header and cannot be processed, in addition, the reconnection traversal of queue is also a problem, and it is troublesome to traverse the queue itself. From now on, a single data structure is hard to solve such a problem, and multiple data structures need to be used in combination. We know that the timer implementation in the Linux kernel uses wheel. We can learn from this. For timeout processing, we use hashedwheel, hashed + wheel. Hash is mainly used to process the time and decide to put it in the range of wheel. Wheel is an array, reduces the number of traversal tasks.
For details about the implementation of hashedwheeltimer, see the code. Netty has similar implementations, which are simplified here. Mainly include: 1. the daemon thread is provided by the external server. This thread calls hashedwheel; 2. assume that the timeout value is not greater than the cycle time of the wheel instance (assuming that the cycle is rotated once every 1 s, with a total of 512 intervals, the cycle time is 512 S, our timeout time will not be so long), there will be no number of lap computing problems;
Package COM. yq. algorithmic;/***** Description: Connection Management * Creation Time: 08:42:34 * @ author yq76034150 **/public class timeout {final long time; Final long deadline; private int rounds; // connected user ID // connected channelpublic timeout (long time, long deadline) {super (); this. time = time; this. deadline = deadline;} public int getrounds () {return rounds;} public void setrounds (INT rounds) {This. rounds = rounds;} public long gettime () {return time;} public long getdeadline () {return deadline;} Public String tostring () {return "interval VAL:" + (system. currenttimemillis ()-time );}}
Package COM. yq. algorithmic; import Java. util. queue; import Java. util. concurrent. concurrentlinkedqueue;/***** Description: A Time-hash-Based Wheel timeout device * Creation Time: 11:44:02 * @ author yq76034150 **/public class hashedwheeltimer {volatile int tick = 1; volatile int currentwheel; private queue <timeout> [] wheel; Final int mask; private long delay; // Private int ticksperwheel; private long tickduration; // Private long round Duration;/*** Description: * @ Param ticksperwheel how many tick */Public hashedwheeltimer (INT ticksperwheel, long tickduration, long delay) {mask = ticksperwheel-1; this. delay = delay; // This. ticksperwheel = ticksperwheel; this. tickduration = tickduration; // This. roundduration = tickduration * ticksperwheel; createwheel (ticksperwheel);}/***** Description: fill data in the wheel * @ Param timeout */Public void newtimeout (timeout timeou T) {long shift = delay/tickduration; // long remainrounds = delay/roundduration; int stopindex = currentwheel + (INT) shift & mask; wheel [stopindex]. offer (timeout);}/*** Description: External Thread call. The External Thread call interval must be the same as that of tickduration */Public void run (long start) {// Number of execution intervals of simulated external execution threads try {thread. sleep (tickduration);} catch (interruptedexception e) {// todo auto-generated catch blocke. printstacktrace ();} tick ++; currentwheel = curre Ntwheel + 1 & mask; long deadline = start + tick * tickduration; queue <timeout> queue = wheel [currentwheel]; timeout = queue. Peek (); If (timeout! = NULL) {long firstdeadline = timeout. getdeadline (); Boolean isdead = firstdeadline <= deadline; if (firstdeadline <= deadline) {While (isdead) {// recursively checks timeout queue for queue elements in a tick. remove (); system. out. println ("delete:" + currentwheel + timeout); timeout = queue. peek (); If (timeout! = NULL) {firstdeadline = timeout. getdeadline (); isdead = firstdeadline <= deadline;} else {break ;}}}/ ***** description: initialize wheel * @ Param ticksperwheel */private void createwheel (INT ticksperwheel) {wheel = new queue [ticksperwheel]; for (INT I = 0; I <ticksperwheel; I ++) {wheel [I] = new concurrent1_queue <timeout> () ;}}/*** Description: * @ Param ARGs */public static void main (string [] ARGs) {long tickduration = 1000; long delay = 9000; hashedwheeltimer timer = new hashedwheeltimer (4, tickduration, delay); long start = system. currenttimemillis (); timeout = new timeout (start, start + delay); timer. newtimeout (timeout); timer. run (start); Start = system. currenttimemillis (); timeout = new timeout (start, start + delay); timer. newtimeout (timeout); timer. run (start); For (INT I = 0; I <10; I ++) {timer. run (start );}}}
The preceding example uses queue, which causes blocking. The test code is as follows:
Package COM. yq. hash. timer; import Java. util. queue; import Java. util. concurrent. concurrentlinkedqueue;/***** Description: A Time-hash-Based Wheel timeout device * Creation Time: 11:44:02 * @ author yq76034150 **/public class hashedwheeltimer {volatile int tick = 1; volatile int currentwheel; private queue <timeout> [] wheel; Final int mask; private long delay; // Private int ticksperwheel; private long tickduration; // Private long roundd Uration;/*** Description: * @ Param ticksperwheel how many tick */Public hashedwheeltimer (INT ticksperwheel, long tickduration, long delay) {mask = ticksperwheel-1; this. delay = delay; // This. ticksperwheel = ticksperwheel; this. tickduration = tickduration; // This. roundduration = tickduration * ticksperwheel; createwheel (ticksperwheel);}/***** Description: fill data in the wheel * @ Param timeout */Public void newtimeout (timeout) {Long shift = delay/tickduration; // long remainrounds = delay/roundduration; int stopindex = currentwheel + (INT) shift & mask; wheel [stopindex]. offer (timeout);}/*** Description: External Thread call. The External Thread call interval must be the same as that of tickduration */Public void run (long start) {// Number of execution intervals of simulated external execution threads try {thread. sleep (tickduration);} catch (interruptedexception e) {// todo auto-generated catch blocke. printstacktrace ();} tick ++; currentwheel = Curren Twheel + 1 & mask; long deadline = start + tick * tickduration; queue <timeout> queue = wheel [currentwheel]; timeout = queue. Peek (); If (timeout! = NULL) {long firstdeadline = timeout. getdeadline (); Boolean isdead = firstdeadline <= deadline; // If (firstdeadline <= deadline) {While (isdead) {// recursively checks timeout queue for queue elements in a tick. remove (); system. out. println ("tick:" + tick + "delete:" + currentwheel + timeout); timeout = queue. peek (); If (timeout! = NULL) {firstdeadline = timeout. getdeadline (); isdead = firstdeadline <= deadline;} else {break ;}/// }}/ ***** description: initialize wheel * @ Param ticksperwheel */private void createwheel (INT ticksperwheel) {wheel = new queue [ticksperwheel]; for (INT I = 0; I <ticksperwheel; I ++) {wheel [I] = new concurrent1_queue <timeout> () ;}}/*** Description: * @ Param ARGs */public static void main (string [] ARGs) {long tickduration = 1000; long delay = 9000; hashedwheeltimer timer = new hashedwheeltimer (4, tickduration, delay); long start = system. currenttimemillis (); timeout = new timeout (start, start + delay, 1); timer. newtimeout (timeout); timer. run (start); // After 4s, ensure that it falls in the same interval as before for (INT I = 0; I <3; I ++) {timer. run (start);} timeout timeout2 = new timeout (system. currenttimemillis (), system. currenttimemillis () + delay, 2); timer. newtimeout (timeout2); timer. run (start); // changing the time queue structure of the header will block timeout. settime (start + 19000); timeout. setdeadline (start + 19000 + delay); timer. newtimeout (timeout); timer. run (start); For (INT I = 0; I <30; I ++) {timer. run (start );}}}
It will be found that it will be deleted after 9 s.
tick: 30 delete: 1v:1316356889764 time:1316356879760 deadline: 1316356888760 survival: 10004tick: 30 delete: 1v:1316356889765 time:1316356864763 deadline: 1316356873763 survival: 25002tick: 31 delete: 2v:1316356890765 time:1316356879760 deadline: 1316356888760 survival: 11005
After changing the queue to map, it is normal.
Package COM. yq. hash. timer; import Java. util. map; import Java. util. map. entry; import Java. util. queue; import Java. util. concurrent. concurrenthashmap; import Java. util. concurrent. concurrentlinkedqueue;/***** Description: A Time-hash-Based Wheel timeout device * Creation Time: 11:44:02 * @ author yq76034150 **/public class hashedwheeltimer {volatile int tick = 1; volatile int currentwheel; private Map <timeout, Boolean> [] wheel; Final int mask; private long delay; // Private int ticksperwheel; private long tickduration; // Private long roundduration;/*** description: * @ Param ticksperwheel tick */Public hashedwheeltimer (INT ticksperwheel, long tickduration, long delay) {mask = ticksperwheel-1; this. delay = delay; // This. ticksperwheel = ticksperwheel; this. tickduration = tickduration; // This. roundduration = tickduration * ticksperwheel; createwheel (ticksperwheel);}/***** Description: fill data in the wheel * @ Param timeout */Public void newtimeout (timeout) {long shift = delay/tickduration; // long remainrounds = delay/roundduration; int stopindex = currentwheel + (INT) shift & mask; wheel [stopindex]. put (timeout, true);}/*** Description: External Thread call. The External Thread call interval must be the same as that of tickduration */Public void run (long start) {// Number of execution intervals of simulated external execution threads try {thread. sleep (tickduration);} catch (interruptedexception e) {// todo auto-generated catch blocke. printstacktrace ();} tick ++; currentwheel = currentwheel + 1 & mask; long deadline = start + tick * tickduration; Map <timeout, Boolean> map = wheel [currentwheel]; timeout timeout = NULL; For (Entry <timeout, Boolean> entry: map. entryset () {timeout = entry. getkey (); If (timeout. getdeadline () <= deadline) {map. remove (timeout); system. out. println ("tick:" + tick + "delete:" + currentwheel + timeout) ;}}/***** description: initialize wheel * @ Param ticksperwheel */private void createwheel (INT ticksperwheel) {wheel = new map [ticksperwheel]; for (INT I = 0; I <tsperickwheel; I ++) {wheel [I] = new concurrenthashmap <timeout, Boolean> () ;}}/*** Description: * @ Param ARGs */public static void main (string [] ARGs) {long tickduration = 1000; long delay = 9000; hashedwheeltimer timer = new hashedwheeltimer (4, tickduration, delay); long start = system. currenttimemillis (); timeout = new timeout (start, start + delay, 1); timer. newtimeout (timeout); timer. run (start); // After 4s, ensure that it falls in the same interval as before for (INT I = 0; I <3; I ++) {timer. run (start);} Long start2 = system. currenttimemillis (); timeout timeout2 = new timeout (start2, start2 + delay, 2); timer. newtimeout (timeout2); timer. run (start); // changing the time queue structure of the header will block timeout. settime (start + 30000); timeout. setdeadline (start + 30000 + delay); timer. newtimeout (timeout); timer. run (start); For (INT I = 0; I <50; I ++) {timer. run (start );}}}
In the same range, delete 2 and then 1, instead of waiting for 1 to delete 2.