zoukankan      html  css  js  c++  java
  • 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("
    Draw [-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
    ");
    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
    ", 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 同步的基本原理,大家如果对更多的细节感兴趣,可以和笔者进行沟通,笔者可以在下次的文章中加入更多的细节。

  • 相关阅读:
    drupal中安装CKEditor文本编辑器,并配置图片上传功能 之 方法二
    drupal 开发笔记
    CSS选择器:子选择符号
    转:Redis和Memcache的区别分析
    转:php 获取memcache所有key
    Fatal error: Namespace declaration statement has to be the very first statement or after any declare call in the script in
    drupal7 boost模块为登录用户提供缓存
    转:drupal使用superfish建立下拉菜单
    RAID详解[RAID0/RAID1/RAID10/RAID5]
    DataV纪录
  • 原文地址:https://www.cnblogs.com/pugang/p/7530756.html
Copyright © 2011-2022 走看看