時候,我們會希望網頁自動跳轉,應用場景包括:
下面總結下如何在前端頁面中控制跳轉的方法:
利用html的refresh
<meta http-equiv="refresh" content="0;url=index.html">
其中0表示0秒以后跳轉,可以自行設定時間。
利用js的href屬性
window.location.href='index.html';
如果要設定延遲時間,則加上setTimeout
setTimeout("javascript:location.href='index.html'", 5000);
利用js的navigate方式
window.navigate("index.html");
自動刷新頁面
在上述方式中,如果跳轉的頁面就是本頁面,那么就是自動刷新頁面的功能。
或者使用reload
location.reload()
跳轉到上一頁,下一頁的方式
window.history.go(-1);
其中 -1 表示上一頁,如果沒有負號的就是表示下一頁
如果不是1而是 2,3,4......n 則表示前進或者后退 n 頁
后退還可以用
window.history.back();
兩者的區別是:
go(-1):返回上一頁,原頁面表單中的內容會丟失;
back():返回上一頁,原頁表表單中的內容會保留。
前進則對應的是:
history.forward():
此外,還有一個參數 history.length 記錄了頁面前進的序號,如果等于0表示第一頁
怎么選擇
至此,自動跳轉頁面、刷新頁面、前后切換的方法都齊了!方法多了就有了選擇恐懼癥?
基本原則:
單純的頁面跳轉建議就用html的refresh方法,無需js代碼,很簡潔。
如果比較復雜,涉及js代碼的業務功能,再加上跳轉功能的,就用js的各種方法。
此外還要考慮頁面是否刷新的問題,希望刷新就用go,否則用back/forward
延時消息(定時消息)指的在 分布式異步消息場景 下,生產端發送一條消息,希望在指定延時或者指定時間點被消費端消費到,而不是立刻被消費。
延時消息適用的業務場景非常的廣泛,在分布式系統環境下,延時消息的功能一般會在下沉到中間件層,通常是 MQ 中內置這個功能或者內聚成一個公共基礎服務。
本文旨在探討常見延時消息的實現方案以及方案設計的優缺點。
這里討論的外部存儲指的是在 MQ 本身自帶的存儲以外又引入的其他的存儲系統。
基于外部存儲的方案本質上都是一個套路,將 MQ 和 延時模塊 區分開來,延時消息模塊是一個獨立的服務/進程。延時消息先保留到其他存儲介質中,然后在消息到期時再投遞到 MQ。當然還有一些細節性的設計,比如消息進入的延時消息模塊時已經到期則直接投遞這類的邏輯,這里不展開討論。
下述方案不同的是,采用了不同的存儲系統。
基于關系型數據庫(如MySQL)延時消息表的方式來實現。
CREATE TABLE `delay_msg` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`delivery_time` DATETIME NOT NULL COMMENT '投遞時間',
`payloads` blob COMMENT '消息內容',
PRIMARY KEY (`id`),
KEY `time_index` (`delivery_time`)
)
通過定時線程定時掃描到期的消息,然后進行投遞。定時線程的掃描間隔理論上就是你延時消息的最小時間精度。
優點:
缺點:
RocksDB 的方案其實就是在上述方案上選擇了比較合適的存儲介質。
RocksDB 在筆者之前的文章中有聊過,LSM 樹根更適合大量寫入的場景。滴滴開源的DDMQ中的延時消息模塊 Chronos 就是采用了這個方案。
DDMQ 這個項目簡單來說就是在 RocketMQ 外面加了一層統一的代理層,在這個代理層就可以做一些功能維度的擴展。延時消息的邏輯就是代理層實現了對延時消息的轉發,如果是延時消息,會先投遞到 RocketMQ 中 Chronos 專用的 topic 中。延時消息模塊 Chronos 消費得到延時消息轉出到 RocksDB,后面就是類似的邏輯了,定時掃描到期的消息,然后往 RocketMQ 中投遞。
這個方案老實說是一個比較重要的方案。因為基于 RocksDB 來實現的話,從數據可用性的角度考慮,你還需要自己去處理多副本的數據同步等邏輯。
優點:
缺點:
再來聊聊 Redis 的方案。下面放一個比較完善的方案。
本方案來源于: https://www.cnblogs.com/lylife/p/7881950.html
這個方案選用 Redis 存儲在我看來有以下幾點考慮,
但是這個方案其實也有需要斟酌的地方,上述方案通過創建多個 Delayed Queue 來滿足對于并發性能的要求,但這也帶來了多個 Delayed Queue 如何在多個節點情況下均勻分配,并且很可能出現到期消息并發重復處理的情況,是否要引入分布式鎖之類的并發控制設計?
在量不大的場景下,上述方案的架構其實可以蛻化成主從架構,只允許主節點來處理任務,從節點只做容災備份。實現難度更低更可控。
上述幾個方案中,都通過線程定時掃描的方案來獲取到期的消息。
定時線程的方案在消息量較少的時候,會浪費資源,在消息量非常多的時候,又會出現因為掃描間隔設置不合理導致延時時間不準確的問題。可以借助 JDK Timer 類中的思想,通過 wait-notify 來節省 CPU 資源。
獲取中最近的延時消息,然后wait(執行時間-當前時間),這樣就不需要浪費資源到達時間時會自動響應,如果有新的消息進入,并且比我們等待的消息還要小,那么直接notify喚醒,重新獲取這個更小的消息,然后又wait,如此循環。
再來講講目前自帶延時消息功能的開源MQ,它們是如何實現的
RocketMQ 開源版本支持延時消息,但是只支持 18 個 Level 的延時,并不支持任意時間。只不過這個 Level 在 RocketMQ 中可以自定義的,所幸來說對普通業務算是夠用的。默認值為“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”,18個level。
通俗地講,設定了延時 Level 的消息會被暫存在名為 SCHEDULE_TOPIC_XXXX 的topic中,并根據 level 存入特定的queue,queueId=delayTimeLevel – 1,**即一個queue只存相同延時的消息,保證具有相同發送延時的消息能夠順序消費。**broker會調度地消費SCHEDULE_TOPIC_XXXX,將消息寫入真實的topic。
下面是整個實現方案的示意圖,紅色代表投遞延時消息,紫色代表定時調度到期的延時消息:
優點:
缺點:
Pulsar 支持“任意時間”的延時消息,但實現方式和 RocketMQ 不同。
通俗的講,Pulsar 的延時消息會直接進入到客戶端發送指定的 Topic 中,然后在堆外內存中創建一個基于時間的優先級隊列,來維護延時消息的索引信息。延時時間最短的會放在頭上,時間越長越靠后。在進行消費邏輯時候,再判斷是否有到期需要投遞的消息,如果有就從隊列里面拿出,根據延時消息的索引查詢到對應的消息進行消費。
如果節點崩潰,在這個 broker 節點上的 Topics 會轉移到其他可用的 broker 上,上面提到的這個優先級隊列也會被重建。
下面是 Pulsar 公眾號中對于 Pulsar 延時消息的示意圖。
乍一看會覺得這個方案其實非常簡單,還能支持任意時間的消息。但是這個方案有幾個比較大的問題
對于前面第一點和第二點的問題,社區也設計了解決方案,在隊列中加入時間分區,Broker 只加載當前較近的時間片的隊列到內存,其余時間片分區持久化磁盤,示例圖如下圖所示:
但是目前,這個方案并沒有對應的版本。可以在實際使用時,規定只能使用較小時間跨度的延時消息,來減少前兩點缺陷的影響。
至于第三個方案,估計是比較難解決的,需要在數據存儲層將延時消息和正常消息區分開來,單獨存儲延時消息。
QMQ提供任意時間的延時/定時消息,你可以指定消息在未來兩年內(可配置)任意時間內投遞。
把 QMQ 放到最后,是因為我覺得 QMQ 是目前開源 MQ 中延時消息設計最合理的。里面設計的核心簡單來說就是 多級時間輪 + 延時加載 + 延時消息單獨磁盤存儲 。
如果對時間輪不熟悉的可以閱讀筆者的這篇文章 從 Kafka 看時間輪算法設計
QMQ的延時/定時消息使用的是兩層 hash wheel 來實現的。第一層位于磁盤上,每個小時為一個刻度(默認為一個小時一個刻度,可以根據實際情況在配置里進行調整),每個刻度會生成一個日志文件(schedule log),因為QMQ支持兩年內的延時消息(默認支持兩年內,可以進行配置修改),則最多會生成 2 * 366 * 24=17568 個文件(如果需要支持的最大延時時間更短,則生成的文件更少)。 第二層在內存中,當消息的投遞時間即將到來的時候,會將這個小時的消息索引(索引包括消息在schedule log中的offset和size)從磁盤文件加載到內存中的hash wheel上,內存中的hash wheel則是以500ms為一個刻度 。
總結一下設計上的亮點:
本文匯總了目前業界常見的延時消息方案,并且討論了各個方案的優缺點。希望對讀者有所啟發。
原文 https://ricstudio.top/archives/delay-msg-designs
目前常見的應用軟件都有消息的延遲推送的影子,應用也極為廣泛,例如:
在上面兩種場景中,如果我們使用下面兩種傳統解決方案無疑大大降低了系統的整體性能和吞吐量:
在 RabbitMQ 3.6.x 之前我們一般采用死信隊列+TTL過期時間來實現延遲隊列,我們這里不做過多介紹,可以參考之前文章來了解:TTL、死信隊列
在 RabbitMQ 3.6.x 開始,RabbitMQ 官方提供了延遲隊列的插件,可以下載放置到 RabbitMQ 根目錄下的 plugins 下。延遲隊列插件下載
首先我們創建交換機和消息隊列,application.properties 中配置與上一篇文章相同。
import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class MQConfig { public static final String LAZY_EXCHANGE="Ex.LazyExchange"; public static final String LAZY_QUEUE="MQ.LazyQueue"; public static final String LAZY_KEY="lazy.#"; @Bean public TopicExchange lazyExchange(){ //Map<String, Object> pros=new HashMap<>(); //設置交換機支持延遲消息推送 //pros.put("x-delayed-message", "topic"); TopicExchange exchange=new TopicExchange(LAZY_EXCHANGE, true, false, pros); exchange.setDelayed(true); return exchange; } @Bean public Queue lazyQueue(){ return new Queue(LAZY_QUEUE, true); } @Bean public Binding lazyBinding(){ return BindingBuilder.bind(lazyQueue()).to(lazyExchange()).with(LAZY_KEY); } }
我們在 Exchange 的聲明中可以設置exchange.setDelayed(true)來開啟延遲隊列,也可以設置為以下內容傳入交換機聲明的方法中,因為第一種方式的底層就是通過這種方式來實現的。
//Map<String, Object> pros=new HashMap<>(); //設置交換機支持延遲消息推送 //pros.put("x-delayed-message", "topic"); TopicExchange exchange=new TopicExchange(LAZY_EXCHANGE, true, false, pros);
發送消息時我們需要指定延遲推送的時間,我們這里在發送消息的方法中傳入參數 new MessagePostProcessor() 是為了獲得 Message對象,因為需要借助 Message對象的api 來設置延遲時間。
import com.anqi.mq.config.MQConfig; import org.springframework.amqp.AmqpException; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Date; @Component public class MQSender { @Autowired private RabbitTemplate rabbitTemplate; //confirmCallback returnCallback 代碼省略,請參照上一篇 public void sendLazy(Object message){ rabbitTemplate.setMandatory(true); rabbitTemplate.setConfirmCallback(confirmCallback); rabbitTemplate.setReturnCallback(returnCallback); //id + 時間戳 全局唯一 CorrelationData correlationData=new CorrelationData("12345678909"+new Date()); //發送消息時指定 header 延遲時間 rabbitTemplate.convertAndSend(MQConfig.LAZY_EXCHANGE, "lazy.boot", message, new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { //設置消息持久化 message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); //message.getMessageProperties().setHeader("x-delay", "6000"); message.getMessageProperties().setDelay(6000); return message; } }, correlationData); } }
我們可以觀察 setDelay(Integer i)底層代碼,也是在 header 中設置 s-delay。等同于我們手動設置 header
message.getMessageProperties().setHeader("x-delay", "6000"); /** * Set the x-delay header. * @param delay the delay. * @since 1.6 */ public void setDelay(Integer delay) { if (delay==null || delay < 0) { this.headers.remove(X_DELAY); } else { this.headers.put(X_DELAY, delay); } } 消費端進行消費 import com.rabbitmq.client.Channel; import org.springframework.amqp.rabbit.annotation.*; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Map; @Component public class MQReceiver { @RabbitListener(queues="MQ.LazyQueue") @RabbitHandler public void onLazyMessage(Message msg, Channel channel) throws IOException{ long deliveryTag=msg.getMessageProperties().getDeliveryTag(); channel.basicAck(deliveryTag, true); System.out.println("lazy receive " + new String(msg.getBody())); }
測試結果
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @SpringBootTest @RunWith(SpringRunner.class) public class MQSenderTest { @Autowired private MQSender mqSender; @Test public void sendLazy() throws Exception { String msg="hello spring boot"; mqSender.sendLazy(msg + ":"); } }
果然在 6 秒后收到了消息 lazy receive hello spring boot:
轉載請注明出處,謝謝。https://www.cnblogs.com/haixiang/p/10966985.html
*請認真填寫需求信息,我們會在24小時內與您取得聯系。