使用webSocket协议实现类论坛帖子列表的点赞实时展现功能

在开发的过程中,经常会碰到产品的这样一个要求,界面上的数据要实时展示。像一些全局性的数据,或者业务交集较少的数据可以实时去数据库查询,但是像一些列表类型的、用户访问量大的数据,不适合实时去查询。之前碰到的一个情况是这样的,如下图

论坛帖子列表

帖子列也要展示封面图、帖子标题、帖子标签、用户头像、用户昵称、点赞数、用户属性等等字段。其中,点赞功能的操作发生概率很大,而运营团队做活动,对实时性的要求很高,需要让用户快速定位到热帖是哪个,进而参与话题活动等。

初次做的时候,是拒绝这个功能的(不知道怎么去好的实现),因为访问量大,而且要实时查询数据库的数据,会给社区业务带来一些性能上的影响,所以拒绝了产品的功能要求。按照我们的现有想法来做。

mysql查询 + redis缓存 + 代码优化实现列表的展示。查数据库时精确字段,排除冗余业务字段;用redis做了二级的数据缓存,一级是社区首页的全部数据(入口也),二级是板块、帖子、广告等数据的缓存。针对帖子的发表时间、热度、精选、点赞等不同维度都做了缓存。


现在呢,想想这个功能,可能会有几种方式去做。

  1. ES处理。整个项目后期增加了搜索引擎es,方便快速地查询帖子。在针对帖子的创建、修改等操作都会对es索引进行同步修改。由于ES的特性本身就适合查询(特性是全文检索、存储、分析),速度很快,所以将数据库查询改为 nosql查询是可以达到这个目的的(实时查询ES,我这里的测试,帖子索引数量是百万级,更高的没试过)。可能碰到的问题就是处理好,并发修改时的version版本号问题。(这种需要,客户端不断请求服务端数据),ELK相关请看《ELK学习

再后来,了解到WebSocket协议,那感觉,这个更合适去处理实时性高的场景。

  1. WebSocket处理
    WebSocketWebSocket 是 HTML5 开始提供的一种在单个 TCP连接上进行全双工通讯的协议。能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
    webSocket AWT界面实现
    这种方式不需要客户端轮询请求服务器端。只要首次建立链接之后,服务器端可以推送消息。Websocket 通过HTTP/1.1 协议的101状态码进行握手。

websocket的pom引用

<dependencies>
	  <!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
	<dependency>
	    <groupId>org.java-websocket</groupId>
	    <artifactId>Java-WebSocket</artifactId>
	    <version>1.4.0</version>
	</dependency>
  
  </dependencies>

webSocket的服务端、AWT客户端、H5客户端实现

在官方的git地址中,有很多的例子,请自行查阅
https://github.com/TooTallNate/Java-WebSocket/tree/master/src/main/example

1 .webSocket服务端代码

创建连接并监听事件

package com.chl.websocket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;

import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

/**
 * webSocket.jar  使用来自以下地址
 * https://repo1.maven.org/maven2/org/java-websocket/Java-WebSocket/1.4.0/ 
 * @author chenhailong
 */
public class WebSocketTest extends WebSocketServer {

	/**
	 * 构造函数
	 * @param port
	 * @throws UnknownHostException
	 */
	public WebSocketTest( int port ) throws UnknownHostException {
		super( new InetSocketAddress( port ) );
	}

	/**
	 * 构造函数
	 * @param address
	 */
	public WebSocketTest( InetSocketAddress address ) {
		super( address );
	}

	/**
	 * 打开连接时的监听事件
	 * conn.send(msg);   //发送信息给最新的客户端
	 * broadcast();      //广播模式,发送给所有连接此端口的客户端
	 */
	@Override
	public void onOpen( WebSocket conn, ClientHandshake handshake ) {
		conn.send("欢迎访问webSocket服务!");  
		broadcast( "new connection: " + handshake.getResourceDescriptor() );  
		System.out.println( conn.getRemoteSocketAddress().getAddress().getHostAddress() + " 来到了这个聊天室!" );
	}

	/**
	 * 关闭连接时的监听事件
	 * 
	 */
	@Override
	public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
		broadcast("连接为:"+ conn + "的端已经离开了这个聊天室 !" );
		System.out.println("连接为:"+ conn + "的端已经离开了这个聊天室 !");
	}

	/**
	 * 监听字符串消息,并广播
	 */
	@Override
	public void onMessage( WebSocket conn, String message ) {
		broadcast( message );
		System.out.println("连接为:"+ conn + "的端发送消息: " + message );
	}
	
	/**
	 * 监听字节消息,并广播
	 */
	@Override
	public void onMessage( WebSocket conn, ByteBuffer message ) {
		broadcast( message.array() );
		System.out.println("连接为:"+ conn + "的端发送消息: " + message );
	}

	/**
	 * main方法,创建并开启webSocket服务端, 轮询消息并输出
	 * @param args
	 * @throws InterruptedException
	 * @throws IOException
	 */
	public static void main( String[] args ) throws InterruptedException , IOException {
		int port = 9999; 
		try {
			port = Integer.parseInt( args[ 0 ] );
		} catch ( Exception ex ) {
		}
		WebSocketTest s = new WebSocketTest( port );
		s.start();
		System.out.println( "WebSocket 服务端已经运行,端口为: " + s.getPort() );

		//这里是控制台输入,可以更改为数据库读取或者nosql查询等
		BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) );
		while ( true ) { //可以设置查询时间
			String in = sysin.readLine();
			s.broadcast( in );
			if( in.equals( "exit" ) ) {
				s.stop(1000);
				break;
			}
		}
	}
	
	/**
	 * 监听错误事件
	 */
	@Override
	public void onError( WebSocket conn, Exception ex ) {
		ex.printStackTrace();
		if( conn != null ) {
			// some errors like port binding failed may not be assignable to a specific websocket
		}
	}

	/**
	 * 监听开启事件
	 */
	@Override
	public void onStart() {
		System.out.println("WebSocket 服务端已经正常开启!");
		setConnectionLostTimeout(0);
		setConnectionLostTimeout(100);
	}

}

2 .webSocket客户端AWT实现代码

简单类似聊天室功能,做连接、关闭、输入等范例

package com.chl.websocket;

import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.net.URI;
import java.net.URISyntaxException;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;

/**
 * 这里是官网的客户端例子,
 * 利用awt组件做了一个聊天界面,可以开启、关闭webSocket连接。也可以接收其他端或服务端的消息并展示
 * 为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking
 * @author chenhailong
 *
 */
public class WebSocketClientAWT extends JFrame implements ActionListener {
	private static final long serialVersionUID = -6056260699202978657L;

	private final JTextField uriField;
	private final JButton connect;
	private final JButton close;
	private final JTextArea ta;
	private final JTextField chatField;
	@SuppressWarnings("rawtypes")
	private final JComboBox draft;
	private WebSocketClient cc;

	/**
	 * 利用AWT组件构建一个聊天界面
	 * @param defaultlocation
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public WebSocketClientAWT( String defaultlocation ) {
		super( "WebSocket 聊天客户端" );
		Container c = getContentPane();
		GridLayout layout = new GridLayout();
		layout.setColumns( 1 );
		layout.setRows( 6 );
		c.setLayout( layout );

		Draft[] drafts = { new Draft_6455() };
		draft = new JComboBox( drafts );
		c.add( draft );

		uriField = new JTextField();
		uriField.setText( defaultlocation );
		c.add( uriField );

		connect = new JButton( "建立连接!" );
		connect.addActionListener( this );
		c.add( connect );

		close = new JButton( "关闭连接!" );
		close.addActionListener( this );
		close.setEnabled( false );
		c.add( close );

		JScrollPane scroll = new JScrollPane();
		ta = new JTextArea();
		scroll.setViewportView( ta );
		c.add( scroll );

		chatField = new JTextField();
		chatField.setText( "" );
		chatField.addActionListener( this );
		c.add( chatField );

		java.awt.Dimension d = new java.awt.Dimension( 300, 400 );
		setPreferredSize( d );
		setSize( d );

		addWindowListener( new java.awt.event.WindowAdapter() {
			@Override
			public void windowClosing( WindowEvent e ) {
				if( cc != null ) {
					cc.close();
				}
				dispose();
			}
		} );

		setLocationRelativeTo( null );
		setVisible( true );
	}

	/**
	 * 事件监听
	 */
	public void actionPerformed( ActionEvent e ) {

		if( e.getSource() == chatField ) {
			if( cc != null ) {
				cc.send( chatField.getText() );
				chatField.setText( "" );
				chatField.requestFocus();
			}

		} else if( e.getSource() == connect ) {
			try {
				// cc = new ChatClient(new URI(uriField.getText()), area, ( Draft ) draft.getSelectedItem() );
				cc = new WebSocketClient( new URI( uriField.getText() ), (Draft) draft.getSelectedItem() ) {

					@Override
					public void onMessage( String message ) {
						ta.append( "got: " + message + "\n" );
						ta.setCaretPosition( ta.getDocument().getLength() );
					}

					@Override
					public void onOpen( ServerHandshake handshake ) {
						ta.append( "You are connected to ChatServer: " + getURI() + "\n" );
						ta.setCaretPosition( ta.getDocument().getLength() );
					}

					@Override
					public void onClose( int code, String reason, boolean remote ) {
						ta.append( "You have been disconnected from: " + getURI() + "; Code: " + code + " " + reason + "\n" );
						ta.setCaretPosition( ta.getDocument().getLength() );
						connect.setEnabled( true );
						uriField.setEditable( true );
						draft.setEditable( true );
						close.setEnabled( false );
					}

					@Override
					public void onError( Exception ex ) {
						ta.append( "Exception occured ...\n" + ex + "\n" );
						ta.setCaretPosition( ta.getDocument().getLength() );
						ex.printStackTrace();
						connect.setEnabled( true );
						uriField.setEditable( true );
						draft.setEditable( true );
						close.setEnabled( false );
					}
				};

				close.setEnabled( true );
				connect.setEnabled( false );
				uriField.setEditable( false );
				draft.setEditable( false );
				cc.connect();
			} catch ( URISyntaxException ex ) {
				ta.append( uriField.getText() + " is not a valid WebSocket URI\n" );
			}
		} else if( e.getSource() == close ) {
			cc.close();
		}
	}

	public static void main( String[] args ) {
		String location;
		if( args.length != 0 ) { //有参是取这里
			location = args[ 0 ];
			System.out.println( "默认的服务端url为: \'" + location + "\'" );
		} else {
			location = "ws://localhost:9999";
			System.out.println( "默认的服务端url为:  \'" + location + "\'" );
		}
		new WebSocketClientAWT( location );
	}

}
3.html5的js实现websocket

服务器推送数据,前端客户端可以正常监听处理

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket的html样例</title>

<script type="text/javascript">
      	 //客户端的WebSocket连接
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的浏览器支持 WebSocket!");
               // 打开一个 web socket
               var ws = new WebSocket("ws://localhost:9999/");
               //这是连接事件
               ws.onopen = function()
               {
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("发送数据");
                  alert("数据发送中...");
               };
               //这是消息监听事件,在一个html标签中显示
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  var context = document.getElementById("show").innerText;
                  document.getElementById("show").innerText = context +"\n"+ received_msg;
               };
                
               //这是关闭事件(服务器关闭)
               ws.onclose = function()
               { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
            }
            else
            {
               // 浏览器不支持 WebSocket
               alert("您的浏览器不支持 WebSocket!");
            }
         }
      </script>
</head>
<body onload="WebSocketTest()">
	<br><hr> <div id="show"></div>
</body>
</html>

webSocket AWT界面实现

服务器端输出如下:

## 客户端连接时输出
127.0.0.1 来到了这个聊天室!

## 客户端发送消息时输出
连接为:org.java_websocket.WebSocketImpl@46775c78的端发送消息: 你好
连接为:org.java_websocket.WebSocketImpl@46775c78的端发送消息: 这是一个AWT界面

## 服务端输出内容,客户端也会显示
收到了
你是第一个来的客户

这只是websocket的简单实现,如果要实现帖子点赞数的实时展示,对于数据的更新还要做对应的处理。比如持久化及即时展现,还是要做些设计的。用户点赞的数据是经过内存,再持久化到数据库。还是直接到数据库,根据自身的情况做处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值