DeferredResult的使用場景及用法

假設咱們如今要實現這樣一個功能:瀏覽器要實時展現服務端計算出來的數據。
一種可能的實現是:瀏覽器頻繁(例如定時1秒)向服務端發起請求以得到服務端數據。但定時請求並不能「實時」反應服務端的數據變化狀況。
若定時週期爲S,則數據延遲週期最大即爲S。若想縮短數據延遲週期,則應使S儘可能小,而S越小,瀏覽器向服務端發起請求的頻率越高,又形成網絡握手次數越多,影響了效率。所以,此場景應使用服務端實時推送技術。javascript

這裏說是推送,其實仍是基於請求-響應機制,只不過發起的請求會在服務端掛起,直到請求超時或服務端有數據推送時纔會作出響應,響應的時機徹底由服務端控制。因此,總體效果看起來就像是服務端真的在「實時推送」同樣。java

能夠利用SpringMVC的DeferredResult來實現異步長鏈接的服務端實時推送。web

核心代碼

Controller

@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;
}

web.xml

<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

js請求

var loopCall = function() {
    $.get("${yourContext}/call", function(r) {
        loopCall();
        console.log("call: ");
        console.log(r);
    });
};
loopCall(); // 循環發起異步請求

執行邏輯

  1. 瀏覽器發起異步請求
  2. 請求到達服務端被掛起(使用瀏覽器查看請求狀態,此時爲pending)
  3. 向瀏覽器進行響應,分爲兩種狀況:
    3.1 調用DeferredResult.setResult(),請求被喚醒,返回結果
    3.2 超時,返回一個你設定的結果
  4. 瀏覽獲得響應,再次重複1,處理這次響應結果

一個示例

接下來,看一個更接近真實場景的示例:瀏覽器向A系統發起異步長鏈接請求,等到B系統給A推送數據時,A會馬上向瀏覽器響應結果。瀏覽器

數據格式

定義AB之間傳輸數據的格式
public interface DeferredData {
    String getId(); // 惟一標識
}
DeferredResult的持有者
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);
}
DeferredResult的持有者實現
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方法
}
Notification的DeferredData適配器
public DeferredNotification extends Notification implements DeferredResponse {
    
    @Override
    public String getId() {
        return getTo();
    }
    
}

B的邏輯

消息的發送者的實現
public class NotificationProducer implements DeferredResponseProducer<DeferredNotification> {

    @Autowired
    private AmqpTemplate amqpTemplate;
    
    @Override
    public void sendMessage(DeferredNotification notification, String exchange) {
        amqpTemplate.convertAndSend(exchange, "", notification);
    }
}

B的Service

@Autowired
private DeferredDataProducer<DeferredNotification> deferredDataProducer;

public void test() {
    DeferredNotification n = new DeferredNotification();
    n.setTo("abc"); // 會員
    n.setContent("哈哈,我是從admin推送過來的");
    deferredDataProducer.sendDeferredData(n);
}

A的邏輯

js請求
var loopCall = function() {
    $.get("${yourContext}/call", function(r) {
        loopCall();
        console.log("call: ");
        console.log(r);
    });
};
loopCall(); // 循環發起異步請求
Controller
@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);
    }

}

代碼就是這些了,好好理一下思路,實現你本身的功能吧!網絡

相關文章
相關標籤/搜索