Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
時(shí)候,我們會希望網(wǎng)頁自動(dòng)跳轉(zhuǎn),應(yīng)用場景包括:
下面總結(jié)下如何在前端頁面中控制跳轉(zhuǎn)的方法:
利用html的refresh
<meta http-equiv="refresh" content="0;url=index.html">
其中0表示0秒以后跳轉(zhuǎn),可以自行設(shè)定時(shí)間。
利用js的href屬性
window.location.href='index.html';
如果要設(shè)定延遲時(shí)間,則加上setTimeout
setTimeout("javascript:location.href='index.html'", 5000);
利用js的navigate方式
window.navigate("index.html");
自動(dòng)刷新頁面
在上述方式中,如果跳轉(zhuǎn)的頁面就是本頁面,那么就是自動(dòng)刷新頁面的功能。
或者使用reload
location.reload()
跳轉(zhuǎn)到上一頁,下一頁的方式
window.history.go(-1);
其中 -1 表示上一頁,如果沒有負(fù)號的就是表示下一頁
如果不是1而是 2,3,4......n 則表示前進(jìn)或者后退 n 頁
后退還可以用
window.history.back();
兩者的區(qū)別是:
go(-1):返回上一頁,原頁面表單中的內(nèi)容會丟失;
back():返回上一頁,原頁表表單中的內(nèi)容會保留。
前進(jìn)則對應(yīng)的是:
history.forward():
此外,還有一個(gè)參數(shù) history.length 記錄了頁面前進(jìn)的序號,如果等于0表示第一頁
怎么選擇
至此,自動(dòng)跳轉(zhuǎn)頁面、刷新頁面、前后切換的方法都齊了!方法多了就有了選擇恐懼癥?
基本原則:
單純的頁面跳轉(zhuǎn)建議就用html的refresh方法,無需js代碼,很簡潔。
如果比較復(fù)雜,涉及js代碼的業(yè)務(wù)功能,再加上跳轉(zhuǎn)功能的,就用js的各種方法。
此外還要考慮頁面是否刷新的問題,希望刷新就用go,否則用back/forward
延時(shí)消息(定時(shí)消息)指的在 分布式異步消息場景 下,生產(chǎn)端發(fā)送一條消息,希望在指定延時(shí)或者指定時(shí)間點(diǎn)被消費(fèi)端消費(fèi)到,而不是立刻被消費(fèi)。
延時(shí)消息適用的業(yè)務(wù)場景非常的廣泛,在分布式系統(tǒng)環(huán)境下,延時(shí)消息的功能一般會在下沉到中間件層,通常是 MQ 中內(nèi)置這個(gè)功能或者內(nèi)聚成一個(gè)公共基礎(chǔ)服務(wù)。
本文旨在探討常見延時(shí)消息的實(shí)現(xiàn)方案以及方案設(shè)計(jì)的優(yōu)缺點(diǎn)。
這里討論的外部存儲指的是在 MQ 本身自帶的存儲以外又引入的其他的存儲系統(tǒng)。
基于外部存儲的方案本質(zhì)上都是一個(gè)套路,將 MQ 和 延時(shí)模塊 區(qū)分開來,延時(shí)消息模塊是一個(gè)獨(dú)立的服務(wù)/進(jìn)程。延時(shí)消息先保留到其他存儲介質(zhì)中,然后在消息到期時(shí)再投遞到 MQ。當(dāng)然還有一些細(xì)節(jié)性的設(shè)計(jì),比如消息進(jìn)入的延時(shí)消息模塊時(shí)已經(jīng)到期則直接投遞這類的邏輯,這里不展開討論。
下述方案不同的是,采用了不同的存儲系統(tǒng)。
基于關(guān)系型數(shù)據(jù)庫(如MySQL)延時(shí)消息表的方式來實(shí)現(xiàn)。
CREATE TABLE `delay_msg` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`delivery_time` DATETIME NOT NULL COMMENT '投遞時(shí)間',
`payloads` blob COMMENT '消息內(nèi)容',
PRIMARY KEY (`id`),
KEY `time_index` (`delivery_time`)
)
通過定時(shí)線程定時(shí)掃描到期的消息,然后進(jìn)行投遞。定時(shí)線程的掃描間隔理論上就是你延時(shí)消息的最小時(shí)間精度。
優(yōu)點(diǎn):
缺點(diǎn):
RocksDB 的方案其實(shí)就是在上述方案上選擇了比較合適的存儲介質(zhì)。
RocksDB 在筆者之前的文章中有聊過,LSM 樹根更適合大量寫入的場景。滴滴開源的DDMQ中的延時(shí)消息模塊 Chronos 就是采用了這個(gè)方案。
DDMQ 這個(gè)項(xiàng)目簡單來說就是在 RocketMQ 外面加了一層統(tǒng)一的代理層,在這個(gè)代理層就可以做一些功能維度的擴(kuò)展。延時(shí)消息的邏輯就是代理層實(shí)現(xiàn)了對延時(shí)消息的轉(zhuǎn)發(fā),如果是延時(shí)消息,會先投遞到 RocketMQ 中 Chronos 專用的 topic 中。延時(shí)消息模塊 Chronos 消費(fèi)得到延時(shí)消息轉(zhuǎn)出到 RocksDB,后面就是類似的邏輯了,定時(shí)掃描到期的消息,然后往 RocketMQ 中投遞。
這個(gè)方案老實(shí)說是一個(gè)比較重要的方案。因?yàn)榛?RocksDB 來實(shí)現(xiàn)的話,從數(shù)據(jù)可用性的角度考慮,你還需要自己去處理多副本的數(shù)據(jù)同步等邏輯。
優(yōu)點(diǎn):
缺點(diǎn):
再來聊聊 Redis 的方案。下面放一個(gè)比較完善的方案。
本方案來源于: https://www.cnblogs.com/lylife/p/7881950.html
這個(gè)方案選用 Redis 存儲在我看來有以下幾點(diǎn)考慮,
但是這個(gè)方案其實(shí)也有需要斟酌的地方,上述方案通過創(chuàng)建多個(gè) Delayed Queue 來滿足對于并發(fā)性能的要求,但這也帶來了多個(gè) Delayed Queue 如何在多個(gè)節(jié)點(diǎn)情況下均勻分配,并且很可能出現(xiàn)到期消息并發(fā)重復(fù)處理的情況,是否要引入分布式鎖之類的并發(fā)控制設(shè)計(jì)?
在量不大的場景下,上述方案的架構(gòu)其實(shí)可以蛻化成主從架構(gòu),只允許主節(jié)點(diǎn)來處理任務(wù),從節(jié)點(diǎn)只做容災(zāi)備份。實(shí)現(xiàn)難度更低更可控。
上述幾個(gè)方案中,都通過線程定時(shí)掃描的方案來獲取到期的消息。
定時(shí)線程的方案在消息量較少的時(shí)候,會浪費(fèi)資源,在消息量非常多的時(shí)候,又會出現(xiàn)因?yàn)閽呙栝g隔設(shè)置不合理導(dǎo)致延時(shí)時(shí)間不準(zhǔn)確的問題。可以借助 JDK Timer 類中的思想,通過 wait-notify 來節(jié)省 CPU 資源。
獲取中最近的延時(shí)消息,然后wait(執(zhí)行時(shí)間-當(dāng)前時(shí)間),這樣就不需要浪費(fèi)資源到達(dá)時(shí)間時(shí)會自動(dòng)響應(yīng),如果有新的消息進(jìn)入,并且比我們等待的消息還要小,那么直接notify喚醒,重新獲取這個(gè)更小的消息,然后又wait,如此循環(huán)。
再來講講目前自帶延時(shí)消息功能的開源MQ,它們是如何實(shí)現(xiàn)的
RocketMQ 開源版本支持延時(shí)消息,但是只支持 18 個(gè) Level 的延時(shí),并不支持任意時(shí)間。只不過這個(gè) Level 在 RocketMQ 中可以自定義的,所幸來說對普通業(yè)務(wù)算是夠用的。默認(rèn)值為“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”,18個(gè)level。
通俗地講,設(shè)定了延時(shí) Level 的消息會被暫存在名為 SCHEDULE_TOPIC_XXXX 的topic中,并根據(jù) level 存入特定的queue,queueId=delayTimeLevel – 1,**即一個(gè)queue只存相同延時(shí)的消息,保證具有相同發(fā)送延時(shí)的消息能夠順序消費(fèi)。**broker會調(diào)度地消費(fèi)SCHEDULE_TOPIC_XXXX,將消息寫入真實(shí)的topic。
下面是整個(gè)實(shí)現(xiàn)方案的示意圖,紅色代表投遞延時(shí)消息,紫色代表定時(shí)調(diào)度到期的延時(shí)消息:
優(yōu)點(diǎn):
缺點(diǎn):
Pulsar 支持“任意時(shí)間”的延時(shí)消息,但實(shí)現(xiàn)方式和 RocketMQ 不同。
通俗的講,Pulsar 的延時(shí)消息會直接進(jìn)入到客戶端發(fā)送指定的 Topic 中,然后在堆外內(nèi)存中創(chuàng)建一個(gè)基于時(shí)間的優(yōu)先級隊(duì)列,來維護(hù)延時(shí)消息的索引信息。延時(shí)時(shí)間最短的會放在頭上,時(shí)間越長越靠后。在進(jìn)行消費(fèi)邏輯時(shí)候,再判斷是否有到期需要投遞的消息,如果有就從隊(duì)列里面拿出,根據(jù)延時(shí)消息的索引查詢到對應(yīng)的消息進(jìn)行消費(fèi)。
如果節(jié)點(diǎn)崩潰,在這個(gè) broker 節(jié)點(diǎn)上的 Topics 會轉(zhuǎn)移到其他可用的 broker 上,上面提到的這個(gè)優(yōu)先級隊(duì)列也會被重建。
下面是 Pulsar 公眾號中對于 Pulsar 延時(shí)消息的示意圖。
乍一看會覺得這個(gè)方案其實(shí)非常簡單,還能支持任意時(shí)間的消息。但是這個(gè)方案有幾個(gè)比較大的問題
對于前面第一點(diǎn)和第二點(diǎn)的問題,社區(qū)也設(shè)計(jì)了解決方案,在隊(duì)列中加入時(shí)間分區(qū),Broker 只加載當(dāng)前較近的時(shí)間片的隊(duì)列到內(nèi)存,其余時(shí)間片分區(qū)持久化磁盤,示例圖如下圖所示:
但是目前,這個(gè)方案并沒有對應(yīng)的版本。可以在實(shí)際使用時(shí),規(guī)定只能使用較小時(shí)間跨度的延時(shí)消息,來減少前兩點(diǎn)缺陷的影響。
至于第三個(gè)方案,估計(jì)是比較難解決的,需要在數(shù)據(jù)存儲層將延時(shí)消息和正常消息區(qū)分開來,單獨(dú)存儲延時(shí)消息。
QMQ提供任意時(shí)間的延時(shí)/定時(shí)消息,你可以指定消息在未來兩年內(nèi)(可配置)任意時(shí)間內(nèi)投遞。
把 QMQ 放到最后,是因?yàn)槲矣X得 QMQ 是目前開源 MQ 中延時(shí)消息設(shè)計(jì)最合理的。里面設(shè)計(jì)的核心簡單來說就是 多級時(shí)間輪 + 延時(shí)加載 + 延時(shí)消息單獨(dú)磁盤存儲 。
如果對時(shí)間輪不熟悉的可以閱讀筆者的這篇文章 從 Kafka 看時(shí)間輪算法設(shè)計(jì)
QMQ的延時(shí)/定時(shí)消息使用的是兩層 hash wheel 來實(shí)現(xiàn)的。第一層位于磁盤上,每個(gè)小時(shí)為一個(gè)刻度(默認(rèn)為一個(gè)小時(shí)一個(gè)刻度,可以根據(jù)實(shí)際情況在配置里進(jìn)行調(diào)整),每個(gè)刻度會生成一個(gè)日志文件(schedule log),因?yàn)镼MQ支持兩年內(nèi)的延時(shí)消息(默認(rèn)支持兩年內(nèi),可以進(jìn)行配置修改),則最多會生成 2 * 366 * 24=17568 個(gè)文件(如果需要支持的最大延時(shí)時(shí)間更短,則生成的文件更少)。 第二層在內(nèi)存中,當(dāng)消息的投遞時(shí)間即將到來的時(shí)候,會將這個(gè)小時(shí)的消息索引(索引包括消息在schedule log中的offset和size)從磁盤文件加載到內(nèi)存中的hash wheel上,內(nèi)存中的hash wheel則是以500ms為一個(gè)刻度 。
總結(jié)一下設(shè)計(jì)上的亮點(diǎn):
本文匯總了目前業(yè)界常見的延時(shí)消息方案,并且討論了各個(gè)方案的優(yōu)缺點(diǎn)。希望對讀者有所啟發(fā)。
原文 https://ricstudio.top/archives/delay-msg-designs
目前常見的應(yīng)用軟件都有消息的延遲推送的影子,應(yīng)用也極為廣泛,例如:
在上面兩種場景中,如果我們使用下面兩種傳統(tǒng)解決方案無疑大大降低了系統(tǒng)的整體性能和吞吐量:
在 RabbitMQ 3.6.x 之前我們一般采用死信隊(duì)列+TTL過期時(shí)間來實(shí)現(xiàn)延遲隊(duì)列,我們這里不做過多介紹,可以參考之前文章來了解:TTL、死信隊(duì)列
在 RabbitMQ 3.6.x 開始,RabbitMQ 官方提供了延遲隊(duì)列的插件,可以下載放置到 RabbitMQ 根目錄下的 plugins 下。延遲隊(duì)列插件下載
首先我們創(chuàng)建交換機(jī)和消息隊(duì)列,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<>(); //設(shè)置交換機(jī)支持延遲消息推送 //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 的聲明中可以設(shè)置exchange.setDelayed(true)來開啟延遲隊(duì)列,也可以設(shè)置為以下內(nèi)容傳入交換機(jī)聲明的方法中,因?yàn)榈谝环N方式的底層就是通過這種方式來實(shí)現(xiàn)的。
//Map<String, Object> pros=new HashMap<>(); //設(shè)置交換機(jī)支持延遲消息推送 //pros.put("x-delayed-message", "topic"); TopicExchange exchange=new TopicExchange(LAZY_EXCHANGE, true, false, pros);
發(fā)送消息時(shí)我們需要指定延遲推送的時(shí)間,我們這里在發(fā)送消息的方法中傳入?yún)?shù) new MessagePostProcessor() 是為了獲得 Message對象,因?yàn)樾枰柚?Message對象的api 來設(shè)置延遲時(shí)間。
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 + 時(shí)間戳 全局唯一 CorrelationData correlationData=new CorrelationData("12345678909"+new Date()); //發(fā)送消息時(shí)指定 header 延遲時(shí)間 rabbitTemplate.convertAndSend(MQConfig.LAZY_EXCHANGE, "lazy.boot", message, new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { //設(shè)置消息持久化 message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); //message.getMessageProperties().setHeader("x-delay", "6000"); message.getMessageProperties().setDelay(6000); return message; } }, correlationData); } }
我們可以觀察 setDelay(Integer i)底層代碼,也是在 header 中設(shè)置 s-delay。等同于我們手動(dòng)設(shè)置 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); } } 消費(fèi)端進(jìn)行消費(fèi) 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())); }
測試結(jié)果
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:
轉(zhuǎn)載請注明出處,謝謝。https://www.cnblogs.com/haixiang/p/10966985.html
*請認(rèn)真填寫需求信息,我們會在24小時(shí)內(nèi)與您取得聯(lián)系。