WebSocket實現app掃描二維碼登入以及ws應用進行負載均衡?

天府雲創發表於2017-12-15
最近在做一個掃碼登入功能,為此我還在網上搜了一下關於微信的掃描登入的實現方式。當這個功能完成了後,我決定將整個實現思路整理出來,方便自己以後檢視也方便其他有類似需求的程式猿些。
要實現掃碼登入我們需要解決兩個問題:
1.  在沒有輸入使用者名稱及密碼的情況下,如何解決許可權安全問題?換句話講,如何讓伺服器知道掃碼二維碼的客戶端是一個合法的使用者?
2.  伺服器根據使用者在客戶端的選擇如何實時在網頁上作出相應的響應?

首先我們先理一下微信的實現思路,來方便我們理解解決這一難題的思路方向。微信登入的二維碼實際上是將一個URL轉換成二維碼的形式,而通過微信客戶端掃碼後,無非就是開啟了這個url, 我捕捉到的微信二維碼的url為https://login.weixin.qq.com/l/YdmTu30I5A== ,這個url裡的YdmTu30I5A==代表的是本次會話的唯一ID, 這個有點兒類似瀏覽器裡的session id,通過這個ID,微信就能定向將確認結果反饋到網頁上。使用微信二維碼登入功能,需要有兩個前提:一是客戶端上需要安裝微信app。 二是使用者需要登入到到微信app。https://wx.qq.com/

 

 WebsocketWeb實時訊息後臺伺服器推送技術

 

為什麼要有這兩個條件呢?那是因為微信在確認是否允許登入到網頁版的時候,微信需要提取當前app的登入資訊並將上面的session ID一併發給伺服器,這樣伺服器收到了登入資訊和sessionID後就可以確認兩件事:一是用來確認登入的客戶端的使用者是驗證過的;二是通過session ID伺服器知道將反饋結果推送到哪個網頁。

   所以針對第一點,我們的關鍵在於,在掃描前要確保使用者是已經被驗證過且合法的使用者(驗證方式可以是使用者名稱+密碼,也可以是一個secure key),在選擇是否登入時將這個結果一併推送到伺服器端,就好了。如果使用者沒有驗證是否合法,可以像微信的處理方式一樣直接告訴使用者二維碼不可識別或提示請先登入到app。

  有了身份驗證,那麼現在就解決第二個問題,如何將反饋結果實時地顯示在網頁上呢?有朋友可能會說,客戶端這邊很簡單發一個請求到後臺就好了,而網頁上用ajax定時傳送到伺服器端看是否有反饋。我不贊成這種做法,因為ajax輪詢方式十分消耗客戶端和伺服器端資源!這裡涉及到另一個技術-web實時推送技術,使用推送技術可以節約伺服器端和客戶端的資源,可以穩定地推送和接收任何訊息。我在實現的過程中我採用了第三方推送服務-GoEasy推送,用它是實現非常簡單,我們專案裡的其他功能也用到了GoEasy web實時推送服務,所以在此我直接就用的GoEasy推送來將登入反饋結果推送到伺服器。我的實現步驟非常簡單,將傳送的session ID作為客戶端與網頁端的通訊channel,網頁端訂閱用session ID作為值得channel,客戶端將驗證結果和session ID傳送到伺服器端,伺服器端可以通過這個channel主動將結果推送給網頁版!如果客戶端也需要做相應的反饋的話,那麼客戶端也只需要訂閱這個channel,然後伺服器端會同時將結果推送給網頁版和客戶端,收到訊息後,就可以根據需求在goeasy的回撥函式裡做你想做的事情了。關於goeasy推送的使用,大家可以參考這篇部落格: http://www.cnblogs.com/jishaochengduo/articles/5552645.html, 另外GoEasy推送官網上也有一個demo:GoEasy二維碼掃碼登入demo,大家可以去看看效果.


話不多說,直接上程式碼,上程式碼,上程式碼。專案整起!!!!!

後臺框架採用SpringMVC,不同的框架可根據邏輯更改即可:

【思路】- PC端生成二維碼,二維碼包含uuid(全域性唯一識別符號),且打通websocket通道,等待伺服器返回登入成功資訊;APP掃描二維碼,獲取uuid及登入資訊,推送給服務端,處理後的登入資訊通過websocket返回給PC端,PC端得到登入資訊後儲存即登入成功。APP掃描確認登入的資訊可以採用ActiveMQ進行推送。

生成二維碼部分引入依賴檔案

[html] view plain copy
  1.     <dependency>  
  2.     <groupId>com.google.zxing</groupId>  
  3.     <artifactId>core</artifactId>  
  4.     <version>3.1.0</version>  
  5. </dependency>  
  6. <dependency>    
  7.         <groupId>com.google.zxing</groupId>    
  8.         <artifactId>javase</artifactId>    
  9.         <version>3.1.0</version>    
  10.     </dependency>   

二維碼登入後臺控制層Controller

[java] view plain copy
  1. /** 
  2.  * 專案名稱:dream_user 
  3.  * 專案包名:org.fore.user.controller 
  4.  * 建立時間:2017年8月8日下午5:29:41 
  5.  * 建立者:Administrator-宋發元 
  6.  * 建立地點:杭州 
  7.  */  
  8. package org.fore.user.controller;  
  9.   
  10. import java.io.IOException;  
  11. import java.io.OutputStream;  
  12. import java.util.HashMap;  
  13. import java.util.Map;  
  14. import java.util.UUID;  
  15.   
  16. import javax.servlet.ServletException;  
  17. import javax.servlet.http.HttpServletRequest;  
  18. import javax.servlet.http.HttpServletResponse;  
  19.   
  20. import org.slf4j.Logger;  
  21. import org.slf4j.LoggerFactory;  
  22. import org.fore.model.user.UserAccount;  
  23. import org.fore.model.user.UserModel;  
  24. import org.fore.user.qrcode.websocket.WebSocketHandler;  
  25. import org.fore.user.service.UserAccountService;  
  26. import org.fore.user.service.UserService;  
  27. import org.fore.utils.jms.JmsSender;  
  28. import org.fore.utils.mvc.TokenUtil;  
  29. import org.fore.utils.mvc.annotation.LimitLess;  
  30. import org.springframework.beans.factory.annotation.Autowired;  
  31. import org.springframework.beans.factory.annotation.Qualifier;  
  32. import org.springframework.stereotype.Controller;  
  33. import org.springframework.web.bind.annotation.RequestMapping;  
  34. import org.springframework.web.bind.annotation.ResponseBody;  
  35.   
  36. import com.alibaba.fastjson.JSONObject;  
  37. import com.google.zxing.BarcodeFormat;  
  38. import com.google.zxing.EncodeHintType;  
  39. import com.google.zxing.MultiFormatWriter;  
  40. import com.google.zxing.WriterException;  
  41. import com.google.zxing.client.j2se.MatrixToImageWriter;  
  42. import com.google.zxing.common.BitMatrix;  
  43. import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;  
  44.   
  45.   
  46. /** 
  47.  * 描述:控制層 
  48.  * @author songfayuan 
  49.  * 2017年8月8日下午5:29:41 
  50.  */  
  51. @Controller  
  52. @RequestMapping("/qrcodelogin")  
  53. public class QrCodeLoginController {  
  54.   
  55.     private Logger logger = LoggerFactory.getLogger(QrCodeLoginController.class);  
  56.       
  57.     public static int defaultWidthAndHeight=260;  
  58.       
  59.     @Autowired  
  60.     private WebSocketHandler webSocketHandler;  
  61.     @Autowired  
  62.     private UserService userService;  
  63.     @Autowired  
  64.     private UserAccountService userAccountService;  
  65.     @Autowired  
  66.     @Qualifier(value = "qrCodeLoginSender")  
  67.     private JmsSender jmsSender;  
  68.       
  69.     /** 
  70.      * 描述:PC獲取二維碼 
  71.      * @param uuid 
  72.      * @param request 
  73.      * @param response 
  74.      * @throws ServletException 
  75.      * @throws IOException 
  76.      * @author songfayuan 
  77.      * 2017年8月11日上午9:04:43 
  78.      */  
  79.     @RequestMapping("/getLoginQrCode")  
  80.     @ResponseBody  
  81.     @LimitLess  
  82.     public void getLoginQrCode(String uuid, HttpServletRequest request,  
  83.             HttpServletResponse response) throws ServletException, IOException {  
  84.         //生成引數  
  85.         //String uuid = generateUUID();  
  86.         String host = request.getHeader("Host");  
  87.         JSONObject data = new JSONObject();  
  88.         data.put("code"200);  
  89.         data.put("msg""獲取二維碼成功");  
  90.         data.put("uuid", uuid);  
  91.         data.put("host", host);  
  92.         logger.info("【二維碼內容】:{}",data);  
  93.           
  94.         //生成二維碼  
  95.         Map<EncodeHintType, Object>  hints=new HashMap<EncodeHintType, Object>();  
  96.         // 指定糾錯等級    
  97.         hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);    
  98.         // 指定編碼格式    
  99.         hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");    
  100.         hints.put(EncodeHintType.MARGIN, 1);  
  101.         try {  
  102.             BitMatrix bitMatrix = new MultiFormatWriter().encode(data.toString(),BarcodeFormat.QR_CODE, defaultWidthAndHeight, defaultWidthAndHeight, hints);  
  103.             OutputStream out = response.getOutputStream();  
  104.             MatrixToImageWriter.writeToStream(bitMatrix, "png", out);//輸出二維碼  
  105.             out.flush();  
  106.             out.close();  
  107.               
  108.         } catch (WriterException e) {  
  109.             // TODO Auto-generated catch block  
  110.             e.printStackTrace();  
  111.         }  
  112.     }  
  113.       
  114.     /** 
  115.      * 描述:app確認請求處理 
  116.      * @param uuid 
  117.      * @param host 
  118.      * @param userid 
  119.      * @author songfayuan 
  120.      * 2017年8月11日上午9:05:56 
  121.      */  
  122.     @RequestMapping("/sendCodeLoginInfo")  
  123.     @ResponseBody  
  124.     @LimitLess  
  125.     public void sendCodeLoginInfo(String uuid, String host, Integer userid) {  
  126.         // 註冊成功後 或 登入,需要同步賬戶資訊,獲取使用者基本資訊  
  127.         UserAccount account = userAccountService.findCurrentUserAccount(userid);  
  128.         userAccountService.syncAccount(account);  
  129.   
  130.         UserModel userModel = userService.findUserById(userid);  
  131.         userModel = changeUserForShow(userModel);  
  132.         JSONObject token = TokenUtil.generateTokenByQrCodeLogin(userid, host);  
  133.         JSONObject object = new JSONObject();  
  134.         object.put("code"10086);  
  135.         object.put("uuid", uuid);  
  136.         object.put("userinfo", userModel);  
  137.         object.put("token", token);  
  138.         object.put("msg""登入成功");  
  139.         //this.webSocketHandler.forwardQrCode(object.toString());  
  140.         jmsSender.sendMessage(object.toString()); //採用ActiveMQ進行推送,也可以直接注入websocket進行傳送  
  141.     }  
  142.     //處理使用者登入資訊  
  143.     private UserModel changeUserForShow(UserModel userModel) {  
  144.         UserModel user = new UserModel();  
  145.         user.setId(userModel.getId());  
  146.         user.setUserName(userModel.getUserName());  
  147.         user.setUserSex(userModel.getUserSex());  
  148.         user.setUserPortrait(userModel.getUserPortrait());  
  149.         return user;  
  150.     }  
  151.       
  152.     /** 
  153.      * 描述:唯一識別符號 
  154.      * @return 
  155.      * @author songfayuan 
  156.      * 2017年8月11日上午9:06:12 
  157.      */  
  158.     public static String generateUUID() {  
  159.         String uuid = UUID.randomUUID().toString();  
  160.         uuid = uuid.replace("-""");  
  161.         Long currentTime = System.currentTimeMillis();  
  162.         String currentDate = String.valueOf(currentTime);  
  163.         return uuid + currentDate;  
  164.     }  
  165.       
  166. }  

websocket實現(本案例採用Spring自帶的websocket)

[java] view plain copy
  1. package org.fore.sms.qrcode.websocket;  
  2.   
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.context.annotation.Bean;  
  5. import org.springframework.context.annotation.Configuration;  
  6. import org.springframework.web.servlet.config.annotation.EnableWebMvc;  
  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;  
  8. import org.springframework.web.socket.config.annotation.EnableWebSocket;  
  9. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;  
  10. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;  
  11. import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;  
  12.   
  13. @Configuration  
  14. @EnableWebMvc  
  15. @EnableWebSocket  
  16. public class QrCodeLoginWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {  
  17.   
  18.     @Autowired  
  19.     private QrCodeLoginWebSocketEndPoint endPoint;  
  20.     @Autowired  
  21.     private QrCodeLoginHandshakeInterceptor interceptor;  
  22.   
  23.     @Override  
  24.     public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {  
  25.         registry.addHandler(endPoint, "/qrcodelogin.do").addInterceptors(interceptor).setAllowedOrigins("*");  
  26. //       registry.addHandler(endPoint,  
  27. //       "/sockjs.do").addInterceptors(interceptor).setAllowedOrigins("*")  
  28. //       .withSockJS();  
  29.     }  
  30.   
  31.     /** 
  32.      * Each underlying WebSocket engine exposes configuration properties that 
  33.      * control runtime characteristics such as the size of message buffer sizes, 
  34.      * idle timeout, and others. 
  35.      */  
  36.   
  37.     /** 
  38.      * For Tomcat, WildFly, and GlassFish add a 
  39.      * ServletServerContainerFactoryBean to your WebSocket Java config: 
  40.      */  
  41.     @Bean  
  42.     public ServletServerContainerFactoryBean createWebSocketContainer() {  
  43.         ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();  
  44.         container.setMaxTextMessageBufferSize(8192);  
  45.         container.setMaxBinaryMessageBufferSize(8192);  
  46.         return container;  
  47.     }  
  48.   
  49.     /** 
  50.      * For Jetty, you’ll need to supply a pre-configured Jetty 
  51.      * WebSocketServerFactory and plug that into Spring’s 
  52.      * DefaultHandshakeHandler through your WebSocket Java config: 
  53.      */  
  54.     // @Bean  
  55.     // public DefaultHandshakeHandler handshakeHandler() {  
  56.     //  
  57.     // WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);  
  58.     // policy.setInputBufferSize(8192); /* 設定訊息緩衝大小 */  
  59.     // policy.setIdleTimeout(600000); /* 10分鐘read不到資料的話,則斷開該客戶端 */  
  60.     //  
  61.     // return new DefaultHandshakeHandler(new JettyRequestUpgradeStrategy(new  
  62.     // WebSocketServerFactory(policy)));  
  63.     // }  
  64.   
  65. }  

[java] view plain copy
  1. package org.fore.sms.qrcode.websocket;  
  2.   
  3. import java.util.Map;  
  4.   
  5. import javax.servlet.http.HttpServletRequest;  
  6.   
  7. import org.slf4j.Logger;  
  8. import org.slf4j.LoggerFactory;  
  9. import org.springframework.http.server.ServerHttpRequest;  
  10. import org.springframework.http.server.ServerHttpResponse;  
  11. import org.springframework.stereotype.Component;  
  12. import org.springframework.web.socket.WebSocketHandler;  
  13. import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;  
  14.   
  15. @Component  
  16. public class QrCodeLoginHandshakeInterceptor extends HttpSessionHandshakeInterceptor {  
  17.     private Logger logger = LoggerFactory.getLogger(QrCodeLoginHandshakeInterceptor.class);  
  18.   
  19.     @Override  
  20.     public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,  
  21.             Map<String, Object> attributes) throws Exception {  
  22.         return super.beforeHandshake(request, response, wsHandler, attributes);  
  23.     }  
  24.   
  25.     @Override  
  26.     public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,  
  27.             Exception ex) {  
  28.         super.afterHandshake(request, response, wsHandler, ex);  
  29.     }  
  30. }  

[java] view plain copy
  1. package org.fore.sms.qrcode.websocket;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.Map;  
  5. import java.util.UUID;  
  6. import java.util.concurrent.ConcurrentHashMap;  
  7.   
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10. import org.fore.model.quota.tcp.ReqCode;  
  11. import org.springframework.stereotype.Component;  
  12. import org.springframework.web.socket.CloseStatus;  
  13. import org.springframework.web.socket.TextMessage;  
  14. import org.springframework.web.socket.WebSocketSession;  
  15. import org.springframework.web.socket.handler.TextWebSocketHandler;  
  16.   
  17. import com.alibaba.fastjson.JSON;  
  18. import com.alibaba.fastjson.JSONObject;  
  19.   
  20. @Component  
  21. public class QrCodeLoginWebSocketEndPoint extends TextWebSocketHandler {  
  22.     private Logger logger = LoggerFactory.getLogger(QrCodeLoginWebSocketEndPoint.class);  
  23.   
  24.     private static Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();  
  25.     private static Map<WebSocketSession,String > sessionMap2 = new ConcurrentHashMap<>();  
  26.   
  27.     @Override  
  28.     public void afterConnectionEstablished(WebSocketSession session) throws Exception {  
  29.         logger.info("WebSocketHandler:客戶端{}上線", session.getRemoteAddress());  
  30.         String uuid = generateUUID();  
  31.         sessionMap.put(uuid,session);  
  32.         sessionMap2.put(session,uuid);  
  33.     }  
  34.   
  35.     @Override  
  36.     protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {  
  37.         String msg = message.getPayload();  
  38.         String ipAddress = session.getRemoteAddress().toString();  
  39.         JSONObject requestData = JSON.parseObject(msg);  
  40.         Integer code = requestData.getInteger("code");  
  41.         JSONObject result = new JSONObject();  
  42.         String uuid = sessionMap2.get(session);  
  43.         result.put("code"200);  
  44.         result.put("uuid", uuid);  
  45.         switch (code) {  
  46.         case ReqCode.REQ_QR_CODE:  
  47.             logger.info("WebSocketHandler:客戶端{}傳送訊息{}...", ipAddress, msg);  
  48.             if(session.isOpen())  
  49.             session.sendMessage(new TextMessage(result.toString()));  
  50.             logger.info("WebSocketHandler:客戶端{}傳送訊息{}完成", ipAddress, msg);  
  51.             break;  
  52.         default:  
  53.             break;  
  54.         }  
  55.     }  
  56.   
  57.     @Override  
  58.     public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {  
  59.         String ipAddress = session.getRemoteAddress().toString();  
  60.         logger.info("WebSocketHandler:客戶端{}下線", ipAddress);  
  61.         logger.info("WebSocketHandler:刪除客戶端{}的session...", ipAddress);  
  62.         logger.info("WebSocketHandler:刪除sessionMap的客戶端{}連線...", ipAddress);  
  63.         String uuid = sessionMap2.get(session);  
  64.         sessionMap.remove(uuid);  
  65.         sessionMap2.remove(session);  
  66.         logger.info("WebSocketHandler:刪除sessionMap的客戶端{}連線完成", ipAddress);  
  67.         logger.info("WebSocketHandler:刪除WebSocket客戶端{}連線...", ipAddress);  
  68. //      logger.info("{}", sessionMap);  
  69.         sessionMap.remove(session);  
  70. //      logger.info("{}", sessionMap);  
  71.         logger.info("WebSocketHandler:刪除WebSocket客戶端{}連線完成", ipAddress);  
  72.         logger.info("WebSocketHandler:刪除客戶端{}的session完成", ipAddress);  
  73.         if(session.isOpen())  
  74.         session.close();  
  75.     }  
  76.   
  77.     @Override  
  78.     public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {  
  79.         logger.info("WebSocketHandler:客戶端{}異常", session.getRemoteAddress(), exception);  
  80.     }  
  81.       
  82.     //傳送訊息  
  83.     public void sendMessage(String userInfo) throws Exception {  
  84.         JSONObject json = JSONObject.parseObject(userInfo);  
  85.         String uuid = json.getString("uuid");  
  86.         WebSocketSession session = sessionMap.get(uuid);  
  87.         if (session == null) {  
  88.             logger.info("app傳送給PC的登入資訊:{}引數不正確!",userInfo);  
  89.         }else {  
  90.             logger.info("app傳送給PC的登入資訊:{}",userInfo);  
  91.             session.sendMessage(new TextMessage(userInfo));  
  92.         }  
  93.     }  
  94.       
  95.     //唯一識別符號  
  96.     public static String generateUUID() {  
  97.         String uuid = UUID.randomUUID().toString();  
  98.         uuid = uuid.replace("-""");  
  99.         Long currentTime = System.currentTimeMillis();  
  100.         String currentDate = String.valueOf(currentTime);  
  101.         return uuid + currentDate;  
  102.     }  
  103. }  

JMS實現

[java] view plain copy
  1. package org.fore.sms.qrcode.jms;  
  2.   
  3. import org.fore.utils.jms.Listener;  
  4. import org.fore.sms.qrcode.websocket.QrCodeLoginWebSocketEndPoint;  
  5. import org.slf4j.Logger;  
  6. import org.slf4j.LoggerFactory;  
  7. import org.springframework.beans.factory.annotation.Autowired;  
  8. import org.springframework.stereotype.Component;  
  9.   
  10. import com.alibaba.fastjson.JSONObject;  
  11.   
  12. @Component  
  13. public class QrCodeLoginListener implements Listener {  
  14.     private Logger logger = LoggerFactory.getLogger(QrCodeLoginListener.class);  
  15.     @Autowired  
  16.     private QrCodeLoginWebSocketEndPoint qrCodeLoginWebSocketEndPoint;  
  17.   
  18.     @Override  
  19.     public void onMessage(String message) {  
  20.         logger.info("app確認登入資訊:接收app推送的確定PC登入訊息{}", message);  
  21.         JSONObject object = JSONObject.parseObject(message);  
  22.         try {  
  23.             qrCodeLoginWebSocketEndPoint.sendMessage(object.toJSONString());  
  24.         } catch (Exception e) {  
  25.             logger.info("app確認登入資訊:接收app推送的確定PC登入訊息異常", e);  
  26.         }  
  27.     }  
  28.   
  29. }  

核心程式碼就醬......簡短專案就是這些了,好了到了關鍵釋出和部署到伺服器環節####

如何對 Websocket 應用進行負載均衡?

#nginx websocket 負載均衡配置(用1.3以後版本的nginx,原生支援websocket反向代理;壓力測試可以用jmeter+第三方websocket外掛,具體可以到github上搜一下。

#回傳訊息 需要 uid+serverip+fd 繫結關係 來實現

#壓測 可以用jmeter 或者 swoole作者寫的 swoole-src/run.php at master · swoole/swoole-src · GitHub

#add 2017 1126

upstream websocket{

server serverip01;

server serverip02

}

map $http_upgrade $connection_upgrade {

default upgrade;

'' close;

}

server {

listen 8020;

location / {

proxy_pass websocket;

proxy_http_version 1.1;

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection "Upgrade";

}

}

客戶端連結時負載…就是公佈出無數個WS連結點,客戶端獲取“通過一個計算策略分配的連結點”地址,客戶端連結…

nginx負載沒用,代理連結數在那兒放著的…

我的簡單方案:我後臺用PHP跑了6個程式監聽六個埠(12322〜12327),然後Nginx部署安裝了yaoweibin的ngx_tcp_proxy_module實現了tcp upstream,目前執行良好。

之前專案有過類似實踐,一百萬個併發連線。

第一層:DNS輪訓 ,域名可指向多個同等配置的NGINX

第二層:NGINX,後端配置多個無狀態應用程式

DNS輪訓主要解決單機NGINX併發連線數瓶頸,不高的話直接NGINX亦可。


NGINX WEBSOCKET和負載均衡可參考:

Note: 基本的 WebSocket 的 Nginx 配置

NGINX負載均衡配置


覺得很容易用到.. Nginx 從 1.3 開始支援 WebSocket, 現在已經是 1.4.4 了
相對 HTTP, 看過例子發現配置其實比較簡單,

先用 ws 模組寫一個簡單的 WebSocket 伺服器:

Server = require('ws').Server

wss = new Server port: 3000

wss.on 'connection', (ws) ->
  console.log 'a connection'
  ws.send 'started'

console.log 'server started'

然後修改 Hosts, 新增, 比如 ws.repo, 指向 127.0.0.1
然後是 Nginx 配置:

server {
  listen 80;
  server_name ws.repo;

  location / {
    proxy_pass http://127.0.0.1:3000/;
    proxy_redirect off;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

Reload Nginx 然後從瀏覽器控制檯嘗試連結, OK

new WebSocket('ws://ws.repo/')

或者通過 Upstream 的寫法:

upstream ws_server {
  server 127.0.0.1:3000;
}

server {
  listen 80;
  server_name ws.repo;

  location / {
    proxy_pass http://ws_server/;
    proxy_redirect off;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

WebSocket 先是通過 HTTP 建立連線,
然後通過 101 狀態碼, 表示切換協議,, 在配置裡是 Upgrade

【博主推薦兩個比較常用的WS負載元件】

1、Swoole - 面向生產環境的 PHP 非同步網路通訊引擎 https://www.swoole.com/

2、Java丨PHP丨C#丨Websocket丨Asp.net Web實時訊息伺服器推送 - GoEasy http://goeasy.io/cn/

 希望對大家有幫助,如有理解錯誤的地方,還請大家斧正。




相關文章