假設咱們如今要實現這樣一個功能:瀏覽器要實時展現服務端計算出來的數據。
一種可能的實現是:瀏覽器頻繁(例如定時1秒)向服務端發起請求以得到服務端數據。但定時請求並不能「實時」反應服務端的數據變化狀況。
若定時週期爲S,則數據延遲週期最大即爲S。若想縮短數據延遲週期,則應使S儘可能小,而S越小,瀏覽器向服務端發起請求的頻率越高,又形成網絡握手次數越多,影響了效率。所以,此場景應使用服務端實時推送技術。javascript
這裏說是推送,其實仍是基於請求-響應機制,只不過發起的請求會在服務端掛起,直到請求超時或服務端有數據推送時纔會作出響應,響應的時機徹底由服務端控制。因此,總體效果看起來就像是服務端真的在「實時推送」同樣。java
能夠利用SpringMVC的DeferredResult
來實現異步長鏈接的服務端實時推送。web
@RequestMapping("/call") @ResponseBody public DeferredResult<Object> call() { // 泛型Object表示返回結果的類型 DeferredResult<Object> response = new DeferredResult<Object>( 10000, // 請求的超時時間 null); // 超時後響應的結果 response.onCompletion(new Runnable() { @Override public void run() { // 請求處理完成後所作的一些工做 } }); // 設置響應結果 // 調用此方法時當即向瀏覽器發出響應;未調用時請求被掛起 response.setResult(new Object()); return response; }
<servlet> <servlet-name>springServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> <!-- 添加異步支持 --> </servlet> <servlet-mapping> <servlet-name>springServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
確保你使用的是servlet 3+spring
var loopCall = function() { $.get("${yourContext}/call", function(r) { loopCall(); console.log("call: "); console.log(r); }); }; loopCall(); // 循環發起異步請求
DeferredResult.setResult()
,請求被喚醒,返回結果接下來,看一個更接近真實場景的示例:瀏覽器向A系統發起異步長鏈接請求,等到B系統給A推送數據時,A會馬上向瀏覽器響應結果。瀏覽器
public interface DeferredData { String getId(); // 惟一標識 }
public interface DeferredResultHolder<DeferredData> { DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult); void add(String key, DeferredResult<DeferredData> deferredResult); DeferredResult<DeferredData> get(String key); void remove(String key); void handleDeferredData(DeferredData deferredData); }
public class SimpleDeferredResultHolder implements DeferredResultHolder<DeferredData> { private Map<String, DeferredResult<DeferredData>> deferredResults = new ConcurrentHashMap<String, DeferredResult<DeferredData>>(); public DeferredResult<DeferredData> newDeferredResult(String key) { return newDeferredResult(key, 30 * 1000L, null); } public DeferredResult<DeferredData> newDeferredResult(String key, long timeout) { return newDeferredResult(key, timeout, null); } public DeferredResult<DeferredData> newDeferredResult(String key, Object timeoutResult) { return newDeferredResult(key, 30 * 1000L, timeoutResult); } @Override public DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult) { DeferredResult<DeferredData> deferredResult = new DeferredResult<DeferredData>(timeout, timeoutResult); add(key, deferredResult); deferredResult.onCompletion(new Runnable() { @Override public void run() { remove(key); } }); return deferredResult; } @Override public void add(String key, DeferredResult<DeferredData> deferredResult) { deferredResults.put(key, deferredResult); } @Override public DeferredResult<DeferredData> get(String key) { return deferredResults.get(key); } @Override public void remove(String key) { deferredResults.remove(key); } @Override public void handleDeferredData(DeferredData deferredData) { String key = deferredData.getId(); DeferredResult<DeferredData> deferredResult = get(key); if (deferredResult != null) { deferredResult.setResult(deferredData); } } }
用mq或dubbo等技術發送均可以,這裏用rabbitmq作示例。
若是消費的消費者作了集羣部署,則只能使用mq的topic機制分發,推送消息到A的全部部署節點,若使用dubbo則只能調用其中一個節點。所以,這裏最好仍是使用mq。spring-mvc
public interface DeferredDataProducer { void sendDeferredData(DeferredData deferredData); }
public interface DeferredDataConsumer { void consume(DeferredData deferredData) throws Exception; }
public class DeferredDataMqProducer implements DeferredDataProducer { @Autowired private AmqpTemplate amqpTemplate; private String exchange; private String routingKey = ""; public void setExchange(String exchange) { this.exchange = exchange; } public void setRoutingKey(String routingKey) { this.routingKey = routingKey; } @Override public void sendDeferredData(DeferredData deferredData) { amqpTemplate.convertAndSend(exchange, routingKey, deferredData); } }
public class DeferredDataMqConsumer implements DeferredDataConsumer { private DeferredResultHolder<DeferredNotification> deferredResultHolder; public void setDeferredResultHolder(DeferredResultHolder<DeferredNotification> deferredResultHolder) { this.deferredResultHolder = deferredResultHolder; } @Override public void consume(DeferredData deferredData) throws Exception; deferredResultHolder.handleDeferredData(deferredData); } }
public class Notification { private String to; // 接收者 private String content; // 內容 // 省略getter和setter方法 }
public DeferredNotification extends Notification implements DeferredResponse { @Override public String getId() { return getTo(); } }
public class NotificationProducer implements DeferredResponseProducer<DeferredNotification> { @Autowired private AmqpTemplate amqpTemplate; @Override public void sendMessage(DeferredNotification notification, String exchange) { amqpTemplate.convertAndSend(exchange, "", notification); } }
@Autowired private DeferredDataProducer<DeferredNotification> deferredDataProducer; public void test() { DeferredNotification n = new DeferredNotification(); n.setTo("abc"); // 會員 n.setContent("哈哈,我是從admin推送過來的"); deferredDataProducer.sendDeferredData(n); }
var loopCall = function() { $.get("${yourContext}/call", function(r) { loopCall(); console.log("call: "); console.log(r); }); }; loopCall(); // 循環發起異步請求
@RequestMapping @Controller public class CallController { @Autowired private DeferredResultHolder deferredResultHolder; @RequestMapping("/call") @ResponseBody public DeferredResult<DeferredData> call() { String id = "abc"; return deferredResultHolder.newDeferredResult(id, 10 * 1000L, null); } }
代碼就是這些了,好好理一下思路,實現你本身的功能吧!網絡