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 。
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);
}
} } }
我们甚至可以通过如下短短的几行代码写一个简易的聊天程序,这样,一个人发送的消息,组内所有成员都可以收到,并且可以同步聊天记录,同时组内节点可以感知道其他节点的加入,关闭,甚至意外退出。
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 servers 间是如何实现 session 同步的的更多相关文章
- Java Web学习总结(10)——Session详解
摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术.本文将详细讨论session的工作机制并且对在Java ...
- Java Web学习总结(11)——Session使用示例教程
一.Session简单介绍 在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下).因此,在需要保存用户数据时,服务 ...
- Java Web之会话管理二:Session
一.Session 在web开发中,服务器可以为每个yoghurt浏览器创建一个会话对象(Session)对象.注意:一个浏览器独占一个Session对象.因此,在需要保存用户数据时,服务器程序可以把 ...
- Java web课程学习之会话(Session)
Session会话 l web应用中的会话是指一个客户端浏览器与web服务器之间连续发生一系列请求和响应过程 l web应用的会话状态是指web服务器与浏览器在会话过程中产生的状态信息,借助会话状 ...
- 《深入分析Java Web技术内幕》读后感(Session、cookie)
第10章 P263 理解Cookie 理解Session Session如何工作的
- Java Web学习总结(20)——基于ZooKeeper的分布式session实现
1. 认识ZooKeeper ZooKeeper-- "动物园管理员".动物园里当然有好多的动物,游客可以根据动物园提供的向导图到不同的场馆观赏各种类型的动物,而不是像走在原始 ...
- java web Session会话技术(原理图解+功能+与Cookie的区别+基本使用)
java web Session会话技术(原理图解+功能+与Cookie的区别+基本使用) 这是我关于会话技术的第二篇文章,对 Cookie有不了解的兄弟可以点击下方的Cookie跳转 Cookie链 ...
- Java Web Session设置
一.前言 在做 java web项目时,我们很多时候都要用到 Session,那么我就简单的写一下 Session 的写法. 二.代码实现 Servlet Session 的设置 package co ...
- Java Web开发Session超时设置
在Java Web开发中,Session为我们提供了很多方便,Session是由浏览器和服务器之间维护的.Session超时理解为:浏览器和服务器之间创建了一个Session,由于客户端长时间(休眠时 ...
随机推荐
- 【mock.js】后端不来过夜半,闲敲mock落灯花 ——南宋·赵师秀
mock的由来[假] 赵师秀:南宋时期的一位前端工程师 诗词背景:在一个梅雨纷纷的夜晚,正值产品上线的紧张时期,书童却带来消息:写后端的李秀才在几个时辰前就赶往临安度假去了, 赵师秀非常生气 ...
- Python爬虫从入门到放弃(二十)之 Scrapy分布式原理
关于Scrapy工作流程回顾 Scrapy单机架构 上图的架构其实就是一种单机架构,只在本机维护一个爬取队列,Scheduler进行调度,而要实现多态服务器共同爬取数据关键就是共享爬取队列. 分布式架 ...
- 深度学习:Keras入门(二)之卷积神经网络(CNN)
说明:这篇文章需要有一些相关的基础知识,否则看起来可能比较吃力. 1.卷积与神经元 1.1 什么是卷积? 简单来说,卷积(或内积)就是一种先把对应位置相乘然后再把结果相加的运算.(具体含义或者数学公式 ...
- FPGA功能仿真,门级仿真,后仿真的区别
前言 分清楚各种仿真间的关系,工具采用quartus prime16.0,仿真工具采用modelsim10 ae版:项目:led_display; 流程 1.RTL行为级仿真:也叫功能仿真,这个阶段的 ...
- ReactiveSwift源码解析(十一) Atomic的代码实现以及其中的Defer延迟、Posix互斥锁、递归锁
本篇博客我们来聊一下ReactiveSwift中的原子性操作,在此内容上我们简单的聊一下Posix互斥锁以及递归锁的概念以及使用场景.然后再聊一下Atomic的代码实现.Atomic主要负责多线程下的 ...
- python学习之元组与文件
元组 元组是最后一个python集合类型.元组由简单的对象构成,元组与列表非常相似,只不过元组不能在原处修改,并且通常写成圆括号,虽然元组部支持任何方法调用,但元组具有列表的大多数属性. 实际应用中的 ...
- 一些常用的集合工具的代码块(缓慢更新XD)
鱼的记忆 我发现在项目中常常要用到一些集合的处理,不同的项目我经常会编写自己的集合工具代码块,后来我发现我总是在写一样的代码块(可能是我记性不好吧:),毕竟鱼的记忆只有7秒),所以我意识到了是时候 ...
- 【技术干货】听阿里云CDN安防技术专家金九讲SystemTap使用技巧
1.简介 SystemTap是一个Linux非常有用的调试(跟踪/探测)工具,常用于Linux 内核或者应用程序的信息采集,比如:获取一个函数里面运行时的变 量.调用堆栈,甚至可以直接修改变量的值, ...
- 【转】深入探讨 Java 类加载器
转自:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html 类加载器是 Java 语言的一个创新,也是 Java ...
- R的数据图形
R支持4种图形类型: base graphics, grid graphics, lattice graphics, ggplot2. Base graphics是R的默认图形系统. 一. 基本图 ...