Java web servers 间是如何实现 session 同步的
2017-09-16 11:49
501 查看
Java web servers 间是如何实现 session 同步的
有一个多月的时间没有更新博客了,今天终于忙里偷闲,可以把近期的收获总结一下。
本文是关于Java web servers 之间是如何实现 session 同步的,其实其他技术栈也面临同样的问题需要解决,而且大部分场景下已经有了成熟的解决方案,其实就应用开发本身,大部分人不太会关注这个问题,因为我们大部分人写代码的时候只需要考虑单节点场景,其他同步部分由服务器端负责实现,但是作为一个刨根问底的人,可能这个问题本身已经能够吸引人的了。
那么,为了解决这个问题,有哪些关键点呢,下面的几点可能是我们绕不开的,
1. 如何保证可靠传输呢,也就是说发送端确认接收节点收到了session数据
2. 一个节点如何知道他自己有哪些伙伴,他需要把session数据发给谁呢
3. 长消息如何发送呢,如何保证数据安全传输
写到这里,大家可能脑海中已经出现了可靠传输,IP多播,数据分包,加密解密,数据一致性保证,对的,就是这些技术,但是应用这些底层技术完成应用,确实需要不是一般程序员可以负担起的时间和经历。笔者也不打算展开来讲所有的技术细节,经过简单的研究,笔者发现了一个写的比较好的开源框架,可以完成所有相关的功能,下面就基于这个开源框架谈谈session同步是如何做到的。示例代码和效果如下,当我在第一张面板上写下tea的时候,在其他所用同一个组的面板上也会显示出同样的字样,同样的效果,JBoss cluster 和JBoss Cache都是基于此开源框架进行的实现,此开源框架的名字是 JGroups 。
View Code
![](https://images2017.cnblogs.com/blog/60039/201709/60039-20170916114104266-444013063.png)
我们甚至可以通过如下短短的几行代码写一个简易的聊天程序,这样,一个人发送的消息,组内所有成员都可以收到,并且可以同步聊天记录,同时组内节点可以感知道其他节点的加入,关闭,甚至意外退出。
总结
本文通过两个简单的示例展示了JGroups的用法,说明了 Java web servers 间是实现 session 同步的基本原理,大家如果对更多的细节感兴趣,可以和笔者进行沟通,笔者可以在下次的文章中加入更多的细节。
有一个多月的时间没有更新博客了,今天终于忙里偷闲,可以把近期的收获总结一下。
本文是关于Java web servers 之间是如何实现 session 同步的,其实其他技术栈也面临同样的问题需要解决,而且大部分场景下已经有了成熟的解决方案,其实就应用开发本身,大部分人不太会关注这个问题,因为我们大部分人写代码的时候只需要考虑单节点场景,其他同步部分由服务器端负责实现,但是作为一个刨根问底的人,可能这个问题本身已经能够吸引人的了。
那么,为了解决这个问题,有哪些关键点呢,下面的几点可能是我们绕不开的,
1. 如何保证可靠传输呢,也就是说发送端确认接收节点收到了session数据
2. 一个节点如何知道他自己有哪些伙伴,他需要把session数据发给谁呢
3. 长消息如何发送呢,如何保证数据安全传输
写到这里,大家可能脑海中已经出现了可靠传输,IP多播,数据分包,加密解密,数据一致性保证,对的,就是这些技术,但是应用这些底层技术完成应用,确实需要不是一般程序员可以负担起的时间和经历。笔者也不打算展开来讲所有的技术细节,经过简单的研究,笔者发现了一个写的比较好的开源框架,可以完成所有相关的功能,下面就基于这个开源框架谈谈session同步是如何做到的。示例代码和效果如下,当我在第一张面板上写下tea的时候,在其他所用同一个组的面板上也会显示出同样的字样,同样的效果,JBoss cluster 和JBoss Cache都是基于此开源框架进行的实现,此开源框架的名字是 JGroups 。
public class Draw extends ReceiverAdapter implements ActionListener, ChannelListener { protected String cluster_name="draw"; private JChannel channel=null; private int member_size=1; private JFrame mainFrame=null; private JPanel sub_panel=null; private DrawPanel panel=null; private JButton clear_button, leave_button; private final Random random=new Random(System.currentTimeMillis()); private final Font default_font=new Font("Helvetica",Font.PLAIN,12); private final Color draw_color=selectColor(); private static final Color background_color=Color.white; boolean no_channel=false; boolean jmx; private boolean use_state=false; private long state_timeout=5000; private boolean use_unicasts=false; protected boolean send_own_state_on_merge=true; private final List<Address> members=new ArrayList<>(); public Draw(String props, boolean no_channel, boolean jmx, boolean use_state, long state_timeout, boolean use_unicasts, String name, boolean send_own_state_on_merge, AddressGenerator gen) throws Exception { this.no_channel=no_channel; this.jmx=jmx; this.use_state=use_state; this.state_timeout=state_timeout; this.use_unicasts=use_unicasts; if(no_channel) return; channel=new JChannel(props).addAddressGenerator(gen).setName(name); channel.setReceiver(this).addChannelListener(this); this.send_own_state_on_merge=send_own_state_on_merge; } public Draw(JChannel channel) throws Exception { this.channel=channel; channel.setReceiver(this); channel.addChannelListener(this); } public Draw(JChannel channel, boolean use_state, long state_timeout) throws Exception { this.channel=channel; channel.setReceiver(this); channel.addChannelListener(this); this.use_state=use_state; this.state_timeout=state_timeout; } public String getClusterName() { return cluster_name; } public void setClusterName(String clustername) { if(clustername != null) this.cluster_name=clustername; } public static void main(String[] args) { Draw draw=null; String props=null; boolean no_channel=false; boolean jmx=true; boolean use_state=false; String group_name=null; long state_timeout=5000; boolean use_unicasts=false; String name=null; boolean send_own_state_on_merge=true; AddressGenerator generator=null; for(int i=0; i < args.length; i++) { if("-help".equals(args[i])) { help(); return; } if("-props".equals(args[i])) { props=args[++i]; continue; } if("-no_channel".equals(args[i])) { no_channel=true; continue; } if("-jmx".equals(args[i])) { jmx=Boolean.parseBoolean(args[++i]); continue; } if("-clustername".equals(args[i])) { group_name=args[++i]; continue; } if("-state".equals(args[i])) { use_state=true; continue; } if("-timeout".equals(args[i])) { state_timeout=Long.parseLong(args[++i]); continue; } if("-bind_addr".equals(args[i])) { System.setProperty("jgroups.bind_addr", args[++i]); continue; } if("-use_unicasts".equals(args[i])) { use_unicasts=true; continue; } if("-name".equals(args[i])) { name=args[++i]; continue; } if("-send_own_state_on_merge".equals(args[i])) { send_own_state_on_merge=Boolean.getBoolean(args[++i]); continue; } if("-uuid".equals(args[i])) { generator=new OneTimeAddressGenerator(Long.valueOf(args[++i])); continue; } help(); return; } try { draw=new Draw(props, no_channel, jmx, use_state, state_timeout, use_unicasts, name, send_own_state_on_merge, generator); if(group_name != null) draw.setClusterName(group_name); draw.go(); } catch(Throwable e) { e.printStackTrace(System.err); System.exit(0); } } static void help() { System.out.println("\nDraw [-help] [-no_channel] [-props <protocol stack definition>]" + " [-clustername <name>] [-state] [-timeout <state timeout>] [-use_unicasts] " + "[-bind_addr <addr>] [-jmx <true | false>] [-name <logical name>] [-send_own_state_on_merge true|false] " + "[-uuid <UUID>]"); System.out.println("-no_channel: doesn't use JGroups at all, any drawing will be relected on the " + "whiteboard directly"); System.out.println("-props: argument can be an old-style protocol stack specification, or it can be " + "a URL. In the latter case, the protocol specification will be read from the URL\n"); } private Color selectColor() { int red=Math.abs(random.nextInt() % 255); int green=Math.abs(random.nextInt() % 255); int blue=Math.abs(random.nextInt() % 255); return new Color(red, green, blue); } private void sendToAll(byte[] buf) throws Exception { for(Address mbr: members) channel.send(new Message(mbr, buf)); } public void go() throws Exception { if(!no_channel && !use_state) channel.connect(cluster_name); mainFrame=new JFrame(); mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); panel=new DrawPanel(use_state); panel.setBackground(background_color); sub_panel=new JPanel(); mainFrame.getContentPane().add("Center", panel); clear_button=new JButton("Clear"); clear_button.setFont(default_font); clear_button.addActionListener(this); leave_button=new JButton("Leave"); leave_button.setFont(default_font); leave_button.addActionListener(this); sub_panel.add("South", clear_button); sub_panel.add("South", leave_button); mainFrame.getContentPane().add("South", sub_panel); mainFrame.setBackground(background_color); clear_button.setForeground(Color.blue); leave_button.setForeground(Color.blue); mainFrame.pack(); mainFrame.setLocation(15, 25); mainFrame.setBounds(new Rectangle(250, 250)); if(!no_channel && use_state) { channel.connect(cluster_name, null, state_timeout); } mainFrame.setVisible(true); setTitle(); } void setTitle(String title) { String tmp=""; if(no_channel) { mainFrame.setTitle(" Draw Demo "); return; } if(title != null) { mainFrame.setTitle(title); } else { if(channel.getAddress() != null) tmp+=channel.getAddress(); tmp+=" (" + member_size + ")"; mainFrame.setTitle(tmp); } } void setTitle() { setTitle(null); } public void receive(Message msg) { byte[] buf=msg.getRawBuffer(); if(buf == null) { System.err.printf("%s: received null buffer from %s, headers: %s\n", channel.getAddress(), msg.src(), msg.printHeaders()); return; } try { DrawCommand comm=Util.streamableFromByteBuffer(DrawCommand.class, buf, msg.getOffset(), msg.getLength()); switch(comm.mode) { case DrawCommand.DRAW: if(panel != null) panel.drawPoint(comm); break; case DrawCommand.CLEAR: clearPanel(); break; default: System.err.println("***** received invalid draw command " + comm.mode); break; } } catch(Exception e) { e.printStackTrace(); } } public void viewAccepted(View v) { member_size=v.size(); if(mainFrame != null) setTitle(); members.clear(); members.addAll(v.getMembers()); if(v instanceof MergeView) { System.out.println("** " + v); // This is an example of a simple merge function, which fetches the state from the coordinator // on a merge and overwrites all of its own state if(use_state && !members.isEmpty()) { Address coord=members.get(0); Address local_addr=channel.getAddress(); if(local_addr != null && !local_addr.equals(coord)) { try { // make a copy of our state first Map<Point,Color> copy=null; if(send_own_state_on_merge) { synchronized(panel.state) { copy=new LinkedHashMap<>(panel.state); } } System.out.println("fetching state from " + coord); channel.getState(coord, 5000); if(copy != null) sendOwnState(copy); // multicast my own state so everybody else has it too } catch(Exception e) { e.printStackTrace(); } } } } else System.out.println("** View=" + v); } public void getState(OutputStream ostream) throws Exception { panel.writeState(ostream); } public void setState(InputStream istream) throws Exception { panel.readState(istream); } /* --------------- Callbacks --------------- */ public void clearPanel() { if(panel != null) panel.clear(); } public void sendClearPanelMsg() { DrawCommand comm=new DrawCommand(DrawCommand.CLEAR); try { byte[] buf=Util.streamableToByteBuffer(comm); if(use_unicasts) sendToAll(buf); else channel.send(new Message(null, buf)); } catch(Exception ex) { System.err.println(ex); } } public void actionPerformed(ActionEvent e) { String command=e.getActionCommand(); switch(command) { case "Clear": if(no_channel) { clearPanel(); return; } sendClearPanelMsg(); break; case "Leave": stop(); break; default: System.out.println("Unknown action"); break; } } public void stop() { if(!no_channel) { try { channel.close(); } catch(Exception ex) { System.err.println(ex); } } mainFrame.setVisible(false); mainFrame.dispose(); } protected void sendOwnState(final Map<Point,Color> copy) { if(copy == null) return; for(Point point: copy.keySet()) { // we don't need the color: it is our draw_color anyway DrawCommand comm=new DrawCommand(DrawCommand.DRAW, point.x, point.y, draw_color.getRGB()); try { byte[] buf=Util.streamableToByteBuffer(comm); if(use_unicasts) sendToAll(buf); else channel.send(new Message(null, buf)); } catch(Exception ex) { System.err.println(ex); } } } /* ------------------------------ ChannelListener interface -------------------------- */ public void channelConnected(JChannel channel) { if(jmx) { Util.registerChannel(channel, "jgroups"); } } public void channelDisconnected(JChannel channel) { if(jmx) { MBeanServer server=Util.getMBeanServer(); if(server != null) { try { JmxConfigurator.unregisterChannel(channel, server, cluster_name); } catch(Exception e) { e.printStackTrace(); } } } } public void channelClosed(JChannel channel) { } /* --------------------------- End of ChannelListener interface ---------------------- */ protected class DrawPanel extends JPanel implements MouseMotionListener { protected final Dimension preferred_size=new Dimension(235, 170); protected Image img; // for drawing pixels protected Dimension d, imgsize; protected Graphics gr; protected final Map<Point,Color> state; public DrawPanel(boolean use_state) { if(use_state) state=new LinkedHashMap<>(); else state=null; createOffscreenImage(false); addMouseMotionListener(this); addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { if(getWidth() <= 0 || getHeight() <= 0) return; createOffscreenImage(false); } }); } public void writeState(OutputStream outstream) throws IOException { if(state == null) return; synchronized(state) { DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(outstream)); // DataOutputStream dos=new DataOutputStream(outstream); dos.writeInt(state.size()); for(Map.Entry<Point,Color> entry: state.entrySet()) { Point point=entry.getKey(); Color col=entry.getValue(); dos.writeInt(point.x); dos.writeInt(point.y); dos.writeInt(col.getRGB()); } dos.flush(); System.out.println("wrote " + state.size() + " elements"); } } public void readState(InputStream instream) throws IOException { DataInputStream in=new DataInputStream(new BufferedInputStream(instream)); Map<Point,Color> new_state=new LinkedHashMap<>(); int num=in.readInt(); for(int i=0; i < num; i++) { Point point=new Point(in.readInt(), in.readInt()); Color col=new Color(in.readInt()); new_state.put(point, col); } synchronized(state) { state.clear(); state.putAll(new_state); System.out.println("read " + state.size() + " elements"); createOffscreenImage(true); } } void createOffscreenImage(boolean discard_image) { d=getSize(); if(discard_image) { img=null; imgsize=null; } if(img == null || imgsize == null || imgsize.width != d.width || imgsize.height != d.height) { img=createImage(d.width, d.height); if(img != null) { gr=img.getGraphics(); if(gr != null && state != null) { drawState(); } } imgsize=d; } repaint(); } /* ---------------------- MouseMotionListener interface------------------------- */ public void mouseMoved(MouseEvent e) {} public void mouseDragged(MouseEvent e) { int x=e.getX(), y=e.getY(); DrawCommand comm=new DrawCommand(DrawCommand.DRAW, x, y, draw_color.getRGB()); if(no_channel) { drawPoint(comm); return; } try { byte[] buf=Util.streamableToByteBuffer(comm); if(use_unicasts) sendToAll(buf); else channel.send(new Message(null, buf)); } catch(Exception ex) { System.err.println(ex); } } /* ------------------- End of MouseMotionListener interface --------------------- */ /** * Adds pixel to queue and calls repaint() whenever we have MAX_ITEMS pixels in the queue * or when MAX_TIME msecs have elapsed (whichever comes first). The advantage compared to just calling * repaint() after adding a pixel to the queue is that repaint() can most often draw multiple points * at the same time. */ public void drawPoint(DrawCommand c) { if(c == null || gr == null) return; Color col=new Color(c.rgb); gr.setColor(col); gr.fillOval(c.x, c.y, 10, 10); repaint(); if(state != null) { synchronized(state) { state.put(new Point(c.x, c.y), col); } } } public void clear() { if(gr == null) return; gr.clearRect(0, 0, getSize().width, getSize().height); repaint(); if(state != null) { synchronized(state) { state.clear(); } } } /** Draw the entire panel from the state */ public void drawState() { // clear(); Map.Entry entry; Point pt; Color col; synchronized(state) { for(Iterator it=state.entrySet().iterator(); it.hasNext();) { entry=(Map.Entry)it.next(); pt=(Point)entry.getKey(); col=(Color)entry.getValue(); gr.setColor(col); gr.fillOval(pt.x, pt.y, 10, 10); } } repaint(); } public Dimension getPreferredSize() { return preferred_size; } public void paintComponent(Graphics g) { super.paintComponent(g); if(img != null) { g.drawImage(img, 0, 0, null); } } } }
View Code
![](https://images2017.cnblogs.com/blog/60039/201709/60039-20170916114104266-444013063.png)
我们甚至可以通过如下短短的几行代码写一个简易的聊天程序,这样,一个人发送的消息,组内所有成员都可以收到,并且可以同步聊天记录,同时组内节点可以感知道其他节点的加入,关闭,甚至意外退出。
public class SimpleChat extends ReceiverAdapter { JChannel channel; String user_name=System.getProperty("user.name", "n/a"); final List<String> state=new LinkedList<>(); public void viewAccepted(View new_view) { System.out.println("** view: " + new_view); } public void receive(Message msg) { String line=msg.getSrc() + ": " + msg.getObject(); System.out.println(line); synchronized(state) { state.add(line); } } public void getState(OutputStream output) throws Exception { synchronized(state) { Util.objectToStream(state, new DataOutputStream(output)); } } @SuppressWarnings("unchecked") public void setState(InputStream input) throws Exception { List<String> list=Util.objectFromStream(new DataInputStream(input)); synchronized(state) { state.clear(); state.addAll(list); } System.out.println("received state (" + list.size() + " messages in chat history):"); list.forEach(System.out::println); } private void start() throws Exception { channel=new JChannel().setReceiver(this); channel.connect("ChatCluster"); channel.getState(null, 10000); eventLoop(); channel.close(); } private void eventLoop() { BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); while(true) { try { System.out.print("> "); System.out.flush(); String line=in.readLine().toLowerCase(); if(line.startsWith("quit") || line.startsWith("exit")) { break; } line="[" + user_name + "] " + line; Message msg=new Message(null, line); channel.send(msg); } catch(Exception e) { } } } public static void main(String[] args) throws Exception { new SimpleChat().start(); } }
总结
本文通过两个简单的示例展示了JGroups的用法,说明了 Java web servers 间是实现 session 同步的基本原理,大家如果对更多的细节感兴趣,可以和笔者进行沟通,笔者可以在下次的文章中加入更多的细节。
相关文章推荐
- Java Web 中application(应用级) session(会话级) request(请求级)如何实现数据共享
- java web-如何实现IE禁用cookie后继续使用session
- JAVA 如何实现WEB上曲线走势图
- 采用memcache在web集群中实现session的同步会话
- Java Session 是如何实现的
- JAVAWEB项目如何实现验证码
- javaWeb_08-用session实现简单的购物
- 如何用Java实现Web服务器
- Java Session 是如何实现的
- JAVAWEB项目如何实现验证码
- 关于JavaWeb如何实现用户注册 后台即时提醒(类似站内信功能)
- 【javaweb】Session原理以及浏览器禁止Cookie之后服务器如何获取Session
- javaweb登录页面验证码验证以及session中验证码值获取不同步的问题
- Apache shiro集群实现 (八) web集群时session同步的3种方法
- Java Web应用中如何实现任务有效调度-Java基础-Java-编程开发
- 如何用Java实现Web服务器(转-->)
- JAVAWEB项目如何实现验证码
- 我的异常网 » Java Web开发 » 验证码跟session不同步
- 如何用Java实现Web服务器
- Java Session 是如何实现的