Java web servers 间是如何实现 session 同步的


 Java web servers 间是如何实现 session 同步的

有一个多月的时间没有更新博客了,今天终于忙里偷闲,可以把近期的收获总结一下。

本文是关于Java web servers 之间是如何实现 session 同步的,其实其他技术栈也面临同样的问题需要解决,而且大部分场景下已经有了成熟的解决方案,其实就应用开发本身,大部分人不太会关注这个问题,因为我们大部分人写代码的时候只需要考虑单节点场景,其他同步部分由服务器端负责实现,但是作为一个刨根问底的人,可能这个问题本身已经能够吸引人的了。

 

那么,为了解决这个问题,有哪些关键点呢,下面的几点可能是我们绕不开的,

 

1. 如何保证可靠传输呢,也就是说发送端确认接收节点收到了session数据

2. 一个节点如何知道他自己有哪些伙伴,他需要把session数据发给谁呢

3. 长消息如何发送呢,如何保证数据安全传输

 

写到这里,大家可能脑海中已经出现了可靠传输,IP多播,数据分包,加密解密,数据一致性保证,对的,就是这些技术,但是应用这些底层技术完成应用,确实需要不是一般程序员可以负担起的时间和经历。笔者也不打算展开来讲所有的技术细节,经过简单的研究,笔者发现了一个写的比较好的开源框架,可以完成所有相关的功能,下面就基于这个开源框架谈谈session同步是如何做到的。示例代码和效果如下,当我在第一张面板上写下tea的时候,在其他所用同一个组的面板上也会显示出同样的字样,同样的效果,JBoss cluster 和JBoss Cache都是基于此开源框架进行的实现,此开源框架的名字是 JGroups 。

 

  1 public class Draw extends ReceiverAdapter implements ActionListener, ChannelListener {
  2     protected String               cluster_name="draw";
  3     private JChannel               channel=null;
  4     private int                    member_size=1;
  5     private JFrame                 mainFrame=null;
  6     private JPanel                 sub_panel=null;
  7     private DrawPanel              panel=null;
  8     private JButton                clear_button, leave_button;
  9     private final Random           random=new Random(System.currentTimeMillis());
 10     private final Font             default_font=new Font("Helvetica",Font.PLAIN,12);
 11     private final Color            draw_color=selectColor();
 12     private static final Color     background_color=Color.white;
 13     boolean                        no_channel=false;
 14     boolean                        jmx;
 15     private boolean                use_state=false;
 16     private long                   state_timeout=5000;
 17     private boolean                use_unicasts=false;
 18     protected boolean              send_own_state_on_merge=true;
 19     private final                  List<Address> members=new ArrayList<>();
 20 
 21 
 22     public Draw(String props, boolean no_channel, boolean jmx, boolean use_state, long state_timeout,
 23                 boolean use_unicasts, String name, boolean send_own_state_on_merge, AddressGenerator gen) throws Exception {
 24         this.no_channel=no_channel;
 25         this.jmx=jmx;
 26         this.use_state=use_state;
 27         this.state_timeout=state_timeout;
 28         this.use_unicasts=use_unicasts;
 29         if(no_channel)
 30             return;
 31 
 32         channel=new JChannel(props).addAddressGenerator(gen).setName(name);
 33         channel.setReceiver(this).addChannelListener(this);
 34         this.send_own_state_on_merge=send_own_state_on_merge;
 35     }
 36 
 37     public Draw(JChannel channel) throws Exception {
 38         this.channel=channel;
 39         channel.setReceiver(this);
 40         channel.addChannelListener(this);
 41     }
 42 
 43 
 44     public Draw(JChannel channel, boolean use_state, long state_timeout) throws Exception {
 45         this.channel=channel;
 46         channel.setReceiver(this);
 47         channel.addChannelListener(this);
 48         this.use_state=use_state;
 49         this.state_timeout=state_timeout;
 50     }
 51 
 52 
 53     public String getClusterName() {
 54         return cluster_name;
 55     }
 56 
 57     public void setClusterName(String clustername) {
 58         if(clustername != null)
 59             this.cluster_name=clustername;
 60     }
 61 
 62 
 63     public static void main(String[] args) {
 64         Draw             draw=null;
 65         String           props=null;
 66         boolean          no_channel=false;
 67         boolean          jmx=true;
 68         boolean          use_state=false;
 69         String           group_name=null;
 70         long             state_timeout=5000;
 71         boolean          use_unicasts=false;
 72         String           name=null;
 73         boolean          send_own_state_on_merge=true;
 74         AddressGenerator generator=null;
 75 
 76         for(int i=0; i < args.length; i++) {
 77             if("-help".equals(args[i])) {
 78                 help();
 79                 return;
 80             }
 81             if("-props".equals(args[i])) {
 82                 props=args[++i];
 83                 continue;
 84             }
 85             if("-no_channel".equals(args[i])) {
 86                 no_channel=true;
 87                 continue;
 88             }
 89             if("-jmx".equals(args[i])) {
 90                 jmx=Boolean.parseBoolean(args[++i]);
 91                 continue;
 92             }
 93             if("-clustername".equals(args[i])) {
 94                 group_name=args[++i];
 95                 continue;
 96             }
 97             if("-state".equals(args[i])) {
 98                 use_state=true;
 99                 continue;
100             }
101             if("-timeout".equals(args[i])) {
102                 state_timeout=Long.parseLong(args[++i]);
103                 continue;
104             }
105             if("-bind_addr".equals(args[i])) {
106                 System.setProperty("jgroups.bind_addr", args[++i]);
107                 continue;
108             }
109             if("-use_unicasts".equals(args[i])) {
110                 use_unicasts=true;
111                 continue;
112             }
113             if("-name".equals(args[i])) {
114                 name=args[++i];
115                 continue;
116             }
117             if("-send_own_state_on_merge".equals(args[i])) {
118                 send_own_state_on_merge=Boolean.getBoolean(args[++i]);
119                 continue;
120             }
121             if("-uuid".equals(args[i])) {
122                 generator=new OneTimeAddressGenerator(Long.valueOf(args[++i]));
123                 continue;
124             }
125 
126             help();
127             return;
128         }
129 
130         try {
131             draw=new Draw(props, no_channel, jmx, use_state, state_timeout, use_unicasts, name,
132                           send_own_state_on_merge, generator);
133             if(group_name != null)
134                 draw.setClusterName(group_name);
135             draw.go();
136         }
137         catch(Throwable e) {
138             e.printStackTrace(System.err);
139             System.exit(0);
140         }
141     }
142 
143 
144     static void help() {
145         System.out.println("\nDraw [-help] [-no_channel] [-props <protocol stack definition>]" +
146                 " [-clustername <name>] [-state] [-timeout <state timeout>] [-use_unicasts] " +
147                 "[-bind_addr <addr>] [-jmx <true | false>] [-name <logical name>] [-send_own_state_on_merge true|false] " +
148                              "[-uuid <UUID>]");
149         System.out.println("-no_channel: doesn't use JGroups at all, any drawing will be relected on the " +
150                 "whiteboard directly");
151         System.out.println("-props: argument can be an old-style protocol stack specification, or it can be " +
152                 "a URL. In the latter case, the protocol specification will be read from the URL\n");
153     }
154 
155 
156     private Color selectColor() {
157         int red=Math.abs(random.nextInt() % 255);
158         int green=Math.abs(random.nextInt() % 255);
159         int blue=Math.abs(random.nextInt() % 255);
160         return new Color(red, green, blue);
161     }
162 
163 
164     private void sendToAll(byte[] buf) throws Exception {
165         for(Address mbr: members)
166             channel.send(new Message(mbr, buf));
167     }
168 
169 
170     public void go() throws Exception {
171         if(!no_channel && !use_state)
172             channel.connect(cluster_name);
173         mainFrame=new JFrame();
174         mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
175         panel=new DrawPanel(use_state);
176         panel.setBackground(background_color);
177         sub_panel=new JPanel();
178         mainFrame.getContentPane().add("Center", panel);
179         clear_button=new JButton("Clear");
180         clear_button.setFont(default_font);
181         clear_button.addActionListener(this);
182         leave_button=new JButton("Leave");
183         leave_button.setFont(default_font);
184         leave_button.addActionListener(this);
185         sub_panel.add("South", clear_button);
186         sub_panel.add("South", leave_button);
187         mainFrame.getContentPane().add("South", sub_panel);
188         mainFrame.setBackground(background_color);
189         clear_button.setForeground(Color.blue);
190         leave_button.setForeground(Color.blue);
191         mainFrame.pack();
192         mainFrame.setLocation(15, 25);
193         mainFrame.setBounds(new Rectangle(250, 250));
194 
195         if(!no_channel && use_state) {
196             channel.connect(cluster_name, null, state_timeout);
197         }
198         mainFrame.setVisible(true);
199         setTitle();
200     }
201 
202 
203 
204 
205     void setTitle(String title) {
206         String tmp="";
207         if(no_channel) {
208             mainFrame.setTitle(" Draw Demo ");
209             return;
210         }
211         if(title != null) {
212             mainFrame.setTitle(title);
213         }
214         else {
215             if(channel.getAddress() != null)
216                 tmp+=channel.getAddress();
217             tmp+=" (" + member_size + ")";
218             mainFrame.setTitle(tmp);
219         }
220     }
221 
222     void setTitle() {
223         setTitle(null);
224     }
225 
226     public void receive(Message msg) {
227         byte[] buf=msg.getRawBuffer();
228         if(buf == null) {
229             System.err.printf("%s: received null buffer from %s, headers: %s\n", channel.getAddress(), msg.src(), msg.printHeaders());
230             return;
231         }
232 
233         try {
234             DrawCommand comm=Util.streamableFromByteBuffer(DrawCommand.class, buf, msg.getOffset(), msg.getLength());
235             switch(comm.mode) {
236                 case DrawCommand.DRAW:
237                     if(panel != null)
238                         panel.drawPoint(comm);
239                     break;
240                 case DrawCommand.CLEAR:
241                     clearPanel();
242                     break;
243                 default:
244                     System.err.println("***** received invalid draw command " + comm.mode);
245                     break;
246             }
247         }
248         catch(Exception e) {
249             e.printStackTrace();
250         }
251     }
252 
253     public void viewAccepted(View v) {
254         member_size=v.size();
255         if(mainFrame != null)
256             setTitle();
257         members.clear();
258         members.addAll(v.getMembers());
259 
260         if(v instanceof MergeView) {
261             System.out.println("** " + v);
262 
263             // This is an example of a simple merge function, which fetches the state from the coordinator
264             // on a merge and overwrites all of its own state
265             if(use_state && !members.isEmpty()) {
266                 Address coord=members.get(0);
267                 Address local_addr=channel.getAddress();
268                 if(local_addr != null && !local_addr.equals(coord)) {
269                     try {
270 
271                         // make a copy of our state first
272                         Map<Point,Color> copy=null;
273                         if(send_own_state_on_merge) {
274                             synchronized(panel.state) {
275                                 copy=new LinkedHashMap<>(panel.state);
276                             }
277                         }
278                         System.out.println("fetching state from " + coord);
279                         channel.getState(coord, 5000);
280                         if(copy != null)
281                             sendOwnState(copy); // multicast my own state so everybody else has it too
282                     }
283                     catch(Exception e) {
284                         e.printStackTrace();
285                     }
286                 }
287             }
288         }
289         else
290             System.out.println("** View=" + v);
291     }
292 
293 
294     public void getState(OutputStream ostream) throws Exception {
295         panel.writeState(ostream);
296     }
297 
298     public void setState(InputStream istream) throws Exception {
299         panel.readState(istream);
300     }
301 
302     /* --------------- Callbacks --------------- */
303 
304 
305 
306     public void clearPanel() {
307         if(panel != null)
308             panel.clear();
309     }
310 
311     public void sendClearPanelMsg() {
312         DrawCommand comm=new DrawCommand(DrawCommand.CLEAR);
313         try {
314             byte[] buf=Util.streamableToByteBuffer(comm);
315             if(use_unicasts)
316                 sendToAll(buf);
317             else
318                 channel.send(new Message(null, buf));
319         }
320         catch(Exception ex) {
321             System.err.println(ex);
322         }
323     }
324 
325 
326     public void actionPerformed(ActionEvent e) {
327         String     command=e.getActionCommand();
328         switch(command) {
329             case "Clear":
330                 if(no_channel) {
331                     clearPanel();
332                     return;
333                 }
334                 sendClearPanelMsg();
335                 break;
336             case "Leave":
337                 stop();
338                 break;
339             default:
340                 System.out.println("Unknown action");
341                 break;
342         }
343     }
344 
345 
346     public void stop() {
347         if(!no_channel) {
348             try {
349                 channel.close();
350             }
351             catch(Exception ex) {
352                 System.err.println(ex);
353             }
354         }
355         mainFrame.setVisible(false);
356         mainFrame.dispose();
357     }
358 
359     protected void sendOwnState(final Map<Point,Color> copy) {
360         if(copy == null)
361             return;
362         for(Point point: copy.keySet()) {
363             // we don't need the color: it is our draw_color anyway
364             DrawCommand comm=new DrawCommand(DrawCommand.DRAW, point.x, point.y, draw_color.getRGB());
365             try {
366                 byte[] buf=Util.streamableToByteBuffer(comm);
367                 if(use_unicasts)
368                     sendToAll(buf);
369                 else
370                     channel.send(new Message(null, buf));
371             }
372             catch(Exception ex) {
373                 System.err.println(ex);
374             }
375         }
376     }
377 
378 
379     /* ------------------------------ ChannelListener interface -------------------------- */
380 
381     public void channelConnected(JChannel channel) {
382         if(jmx) {
383             Util.registerChannel(channel, "jgroups");
384         }
385     }
386 
387     public void channelDisconnected(JChannel channel) {
388         if(jmx) {
389             MBeanServer server=Util.getMBeanServer();
390             if(server != null) {
391                 try {
392                     JmxConfigurator.unregisterChannel(channel, server, cluster_name);
393                 }
394                 catch(Exception e) {
395                     e.printStackTrace();
396                 }
397             }
398         }
399     }
400 
401     public void channelClosed(JChannel channel) {
402 
403     }
404 
405 
406     /* --------------------------- End of ChannelListener interface ---------------------- */
407 
408 
409 
410     protected class DrawPanel extends JPanel implements MouseMotionListener {
411         protected final Dimension         preferred_size=new Dimension(235, 170);
412         protected Image                   img; // for drawing pixels
413         protected Dimension               d, imgsize;
414         protected Graphics                gr;
415         protected final Map<Point,Color>  state;
416 
417 
418         public DrawPanel(boolean use_state) {
419             if(use_state)
420                 state=new LinkedHashMap<>();
421             else
422                 state=null;
423             createOffscreenImage(false);
424             addMouseMotionListener(this);
425             addComponentListener(new ComponentAdapter() {
426                 public void componentResized(ComponentEvent e) {
427                     if(getWidth() <= 0 || getHeight() <= 0) return;
428                     createOffscreenImage(false);
429                 }
430             });
431         }
432 
433 
434         public void writeState(OutputStream outstream) throws IOException {
435             if(state == null)
436                 return;
437             synchronized(state) {
438                 DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(outstream));
439                 // DataOutputStream dos=new DataOutputStream(outstream);
440                 dos.writeInt(state.size());
441                 for(Map.Entry<Point,Color> entry: state.entrySet()) {
442                     Point point=entry.getKey();
443                     Color col=entry.getValue();
444                     dos.writeInt(point.x);
445                     dos.writeInt(point.y);
446                     dos.writeInt(col.getRGB());
447                 }
448                 dos.flush();
449                 System.out.println("wrote " + state.size() + " elements");
450             }
451         }
452 
453 
454         public void readState(InputStream instream) throws IOException {
455             DataInputStream in=new DataInputStream(new BufferedInputStream(instream));
456             Map<Point,Color> new_state=new LinkedHashMap<>();
457             int num=in.readInt();
458             for(int i=0; i < num; i++) {
459                 Point point=new Point(in.readInt(), in.readInt());
460                 Color col=new Color(in.readInt());
461                 new_state.put(point, col);
462             }
463 
464             synchronized(state) {
465                 state.clear();
466                 state.putAll(new_state);
467                 System.out.println("read " + state.size() + " elements");
468                 createOffscreenImage(true);
469             }
470         }
471 
472 
473         void createOffscreenImage(boolean discard_image) {
474             d=getSize();
475             if(discard_image) {
476                 img=null;
477                 imgsize=null;
478             }
479             if(img == null || imgsize == null || imgsize.width != d.width || imgsize.height != d.height) {
480                 img=createImage(d.width, d.height);
481                 if(img != null) {
482                     gr=img.getGraphics();
483                     if(gr != null && state != null) {
484                         drawState();
485                     }
486                 }
487                 imgsize=d;
488             }
489             repaint();
490         }
491 
492 
493         /* ---------------------- MouseMotionListener interface------------------------- */
494 
495         public void mouseMoved(MouseEvent e) {}
496 
497         public void mouseDragged(MouseEvent e) {
498             int                 x=e.getX(), y=e.getY();
499             DrawCommand         comm=new DrawCommand(DrawCommand.DRAW, x, y, draw_color.getRGB());
500 
501             if(no_channel) {
502                 drawPoint(comm);
503                 return;
504             }
505 
506             try {
507                 byte[] buf=Util.streamableToByteBuffer(comm);
508                 if(use_unicasts)
509                     sendToAll(buf);
510                 else
511                     channel.send(new Message(null, buf));
512             }
513             catch(Exception ex) {
514                 System.err.println(ex);
515             }
516         }
517 
518         /* ------------------- End of MouseMotionListener interface --------------------- */
519 
520 
521         /**
522          * Adds pixel to queue and calls repaint() whenever we have MAX_ITEMS pixels in the queue
523          * or when MAX_TIME msecs have elapsed (whichever comes first). The advantage compared to just calling
524          * repaint() after adding a pixel to the queue is that repaint() can most often draw multiple points
525          * at the same time.
526          */
527         public void drawPoint(DrawCommand c) {
528             if(c == null || gr == null) return;
529             Color col=new Color(c.rgb);
530             gr.setColor(col);
531             gr.fillOval(c.x, c.y, 10, 10);
532             repaint();
533             if(state != null) {
534                 synchronized(state) {
535                     state.put(new Point(c.x, c.y), col);
536                 }
537             }
538         }
539 
540 
541 
542         public void clear() {
543             if(gr == null) return;
544             gr.clearRect(0, 0, getSize().width, getSize().height);
545             repaint();
546             if(state != null) {
547                 synchronized(state) {
548                     state.clear();
549                 }
550             }
551         }
552 
553 
554         /** Draw the entire panel from the state */
555         public void drawState() {
556             // clear();
557             Map.Entry entry;
558             Point pt;
559             Color col;
560             synchronized(state) {
561                 for(Iterator it=state.entrySet().iterator(); it.hasNext();) {
562                     entry=(Map.Entry)it.next();
563                     pt=(Point)entry.getKey();
564                     col=(Color)entry.getValue();
565                     gr.setColor(col);
566                     gr.fillOval(pt.x, pt.y, 10, 10);
567 
568                 }
569             }
570             repaint();
571         }
572 
573 
574         public Dimension getPreferredSize() {
575             return preferred_size;
576         }
577 
578 
579         public void paintComponent(Graphics g) {
580             super.paintComponent(g);
581             if(img != null) {
582                 g.drawImage(img, 0, 0, null);
583             }
584         }
585 
586     }
587 
588 }
View Code

 

 

 

 

我们甚至可以通过如下短短的几行代码写一个简易的聊天程序,这样,一个人发送的消息,组内所有成员都可以收到,并且可以同步聊天记录,同时组内节点可以感知道其他节点的加入,关闭,甚至意外退出。

 

 1 public class SimpleChat extends ReceiverAdapter {
 2     JChannel channel;
 3     String user_name=System.getProperty("user.name", "n/a");
 4     final List<String> state=new LinkedList<>();
 5 
 6     public void viewAccepted(View new_view) {
 7         System.out.println("** view: " + new_view);
 8     }
 9 
10     public void receive(Message msg) {
11         String line=msg.getSrc() + ": " + msg.getObject();
12         System.out.println(line);
13         synchronized(state) {
14             state.add(line);
15         }
16     }
17 
18     public void getState(OutputStream output) throws Exception {
19         synchronized(state) {
20             Util.objectToStream(state, new DataOutputStream(output));
21         }
22     }
23 
24     @SuppressWarnings("unchecked")
25     public void setState(InputStream input) throws Exception {
26         List<String> list=Util.objectFromStream(new DataInputStream(input));
27         synchronized(state) {
28             state.clear();
29             state.addAll(list);
30         }
31         System.out.println("received state (" + list.size() + " messages in chat history):");
32         list.forEach(System.out::println);
33     }
34 
35 
36     private void start() throws Exception {
37         channel=new JChannel().setReceiver(this);
38         channel.connect("ChatCluster");
39         channel.getState(null, 10000);
40         eventLoop();
41         channel.close();
42     }
43 
44     private void eventLoop() {
45         BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
46         while(true) {
47             try {
48                 System.out.print("> "); System.out.flush();
49                 String line=in.readLine().toLowerCase();
50                 if(line.startsWith("quit") || line.startsWith("exit")) {
51                     break;
52                 }
53                 line="[" + user_name + "] " + line;
54                 Message msg=new Message(null, line);
55                 channel.send(msg);
56             }
57             catch(Exception e) {
58             }
59         }
60     }
61 
62 
63     public static void main(String[] args) throws Exception {
64         new SimpleChat().start();
65     }
66 }

 

总结

本文通过两个简单的示例展示了JGroups的用法,说明了 Java web servers 间是实现 session 同步的基本原理,大家如果对更多的细节感兴趣,可以和笔者进行沟通,笔者可以在下次的文章中加入更多的细节。

posted @ 2017-09-16 11:49  SolidMango  阅读(1023)  评论(0编辑  收藏  举报