项目里用到了websocket 但是后台是tomcat集群 有时候就会发现前台没收到推送消息。发现websocket是用一个静态变量存的前后台连接。

百度发现解决方案有好多 选了个简单的 使用redis 发布订阅方式实现  就是后台收到需要推送的消息时就往redis发布一下。然后所有的tomcat都订阅他  就可以实现 集群不漏发的问题了。

一下是一些实现的代码和逻辑。

websocket代码、

package com.asdc.jbp.hisLogin.websocket;

import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ContextLoader;



@Component("OutpatientDocWebSocket")
@ServerEndpoint("/outpatientDoc/{userId}")
public class OutpatientDocWebSocket {

	// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
	//private static CopyOnWriteArraySet<OutpatientDocWebSocket> outpatientDocWebSocket = new CopyOnWriteArraySet<OutpatientDocWebSocket>();
	private static ConcurrentHashMap<String, OutpatientDocWebSocket> outpatientDocWebSocket = new ConcurrentHashMap<String, OutpatientDocWebSocket>();
	// 与某个客户端的连接会话,需要通过它来给客户端发送数据
	private Session session;
	
	//当前发消息的人员id
    private String userId = "";
    
    //保证当前连接是唯一的
    private String uniqueUuid = "";
	
    @Autowired
	private OutpatientRedisDao outpatientRedisDao;
	//=(OutpatientRedisDao) ContextLoader.getCurrentWebApplicationContext().getBean("OutpatientRedisDao")
	private Logger log=LoggerFactory.getLogger(getClass());

	/**
	 * 连接建立成功调用的方法
	 * @param session  可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
	 */
	@OnOpen
	public void onOpen(@PathParam(value = "userId") String paramUserId,Session session) {
		this.session = session;
		userId=paramUserId;
		uniqueUuid=UUID.randomUUID().toString();
		outpatientDocWebSocket.put(paramUserId, this); // 加入set中
		OutpatientRedisDao outpatientRedisDaoOnOpen=(OutpatientRedisDao) ContextLoader.getCurrentWebApplicationContext().getBean("OutpatientRedisDao");
		//广播建立连接 删除其他服务器连接
		outpatientRedisDaoOnOpen.sendMessageByOpen(OutpatientRedisDao.outpatientSocketOpenChannel, userId+"|"+uniqueUuid);
		   
	}

	/**
	 * 连接关闭调用的方法
	 */
	@OnClose
	public void onClose() {
		if(userId!=""){
			outpatientDocWebSocket.remove(userId); // 从map中删除
		}
		
	}

	/**
	 * 收到客户端消息后调用的方法
	 * @param message 客户端发送过来的消息
	 * @param session 可选的参数
	 */
	@OnMessage
	public void onMessage(String message, Session session) {
		//System.out.println("来自客户端的消息:" + message);
		//接受到的只能是websocket heart请求 不需理会
		//System.out.println(message);
		
	}

	/**
	 * 发生错误时调用
	 * @param session
	 * @param error
	 */
	@OnError
	public void onError(Session session, Throwable error) {
		//System.out.println("发生错误");
		log.error("websocket发生错误:",error);
		//error.printStackTrace();
	}

	/**
	 * 
	 * Description : 向前台发送消息 <br>
	 * Create Time: 2018年4月26日 <br>
	 * Create by : xxx<br>
	 *
	 * @param message
	 * @throws IOException
	 */
	public void sendMessage(String message) throws IOException {
		this.session.getBasicRemote().sendText(message);
	}


	/**
	 * 
	 * Description : 后台业务调用方法 <br>
	 * Create Time: 2018年5月7日 <br>
	 * Create by : xxx<br>
	 *
	 * @param regMsg
	 */
	public void SendRegisterMessage(String regMsg) {

		outpatientRedisDao.sendMessage(OutpatientRedisDao.outpatientChannel, regMsg);
		// 群发消息,向所有连接这个websocket服务器的客户端发送消息.
		
	}
	
	/**
	 * 
	 * Description : 接受redis回调的推送方法 <br>
	 * Create Time: 2018年5月7日 <br>
	 * Create by : xxx<br>
	 *
	 * @param callBackMsg
	 */
	public void sendMessageHandle(String callBackMsg){
		for (OutpatientDocWebSocket item : outpatientDocWebSocket.values()) {
			try {
				item.sendMessage(callBackMsg);
			} catch (IOException e) {
				log.error("websocket发送出错:",e);
				e.printStackTrace();
				continue;
			}
		}
	}
	
	/**
	 * 
	 * Description : 建立连接时删除当前已存在的连接---保持连接唯一 <br>
	 * Create Time: 2018年5月7日 <br>
	 * Create by : xxx<br>
	 *
	 * @param callBackMsg
	 */
	public void onOpenRemoveHandle(String callBackMsg){
		String[] msgs=callBackMsg.split("\\|");
		//0用户id  1 唯一标识uuid
		for (OutpatientDocWebSocket item : outpatientDocWebSocket.values()) {
			if(msgs[0].equals(item.userId)&&!msgs[1].equals(item.uniqueUuid)){
				outpatientDocWebSocket.remove(item.userId);
			}
		}
	}
	
}

监听redis订阅

package com.asdc.jbp.hisLogin.redisUtil;

import org.springframework.beans.factory.annotation.Autowired;



/**
 * 
 * ClassName : OutpatientDocMessageDelegateListenerImpl <br>
 * Description : 接受订阅默认代理-----由 MessageListenerAdapter  delegate  <br>
 * Create Time : 2018年4月26日 <br>
 * Create by : xxx<br>
 *
 */
public class OutpatientDocMessageDelegateListenerImpl{
	
	
	@Autowired
	private OutpatientDocWebSocket outpatientDocWebSocket;
	/**
	 * 
	 * Description : 此方法里实现订阅后调用方法 <br>
	 * Create Time: 2018年4月26日 <br>
	 * Create by : xxx<br>
	 *
	 * @param message
	 */
	public void handleMessage(String message){
		outpatientDocWebSocket.sendMessageHandle(message);
	}
	
	/**
	 * 
	 * Description : 连接时回调方法 <br>
	 * Create Time: 2018年5月9日 <br>
	 * Create by : xxx<br>
	 *
	 * @param message
	 */
	public void handleMessageByOpen(String message){
		outpatientDocWebSocket.onOpenRemoveHandle(message);
	}
}

 

往redis发布消息

public interface OutpatientRedisDao {
	public static final String outpatientChannel ="outpatientDoc_Channel";
	
	public static final String outpatientSocketOpenChannel="outpatientSocketOpen_Channel";
	
	/**
	 * 
	 * Description : 挂号发送医嘱列表信息 <br>
	 * Create Time: 2018年4月26日 <br>
	 * Create by : xxx <br>
	 *
	 * @param channel
	 * @param message
	 */
	public void sendMessage(String channel,String message);
	
	public void sendMessageByOpen(String channel,String message);
}


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component("OutpatientRedisDao")
public class OutpatientRedisDaoImpl implements OutpatientRedisDao {

	@Autowired
	private StringRedisTemplate redisTemplate;
	
	@Override
	public void sendMessage(String channel, String message) {
		redisTemplate.convertAndSend(channel, message);
	}

	@Override
    public void sendMessageByOpen(String channel, String message) {
		redisTemplate.convertAndSend(channel, message);	    
    }

}

spring-redis配置

<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory" />
		<property name="keySerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="valueSerializer">
			<bean
				class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
		</property>
		<property name="hashKeySerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="hashValueSerializer">
			<bean
				class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
		</property>
	</bean>

<!-- 挂号医嘱列表 订阅发布  -->
    <bean id="outpatientMessageDelegateListener" class="com.OutpatientDocMessageDelegateListenerImpl" />
	
    <bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
        <property name="delegate" ref="outpatientMessageDelegateListener" />
        <property name="serializer" ref="serializer" />
    </bean>
    <!-- org.springframework.data.redis.serializer.StringRedisSerializer 会有乱码 -->
    <bean id="serializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
    <redis:listener-container connection-factory="jedisConnectionFactory">
        <!-- the method attribute can be skipped as the default method name is "handleMessage" -->
        <redis:listener ref="outpatientMessageDelegateListener" serializer="serializer" method="handleMessage" topic="outpatientDoc_Channel" />
        <redis:listener ref="outpatientMessageDelegateListener" serializer="serializer" method="handleMessageByOpen" topic="outpatientSocketOpen_Channel" />
    </redis:listener-container>

代码还不是很完善 基本思路就这样。水平有限啊。。。。。