視圖是指對(duì)數(shù)據(jù)的引用,通過該引用亦便可訪問、操作原有數(shù)據(jù),但原有數(shù)據(jù)不會(huì)產(chǎn)生拷貝。如果我們對(duì)視圖進(jìn)行修改,它會(huì)影響到原始數(shù)據(jù),物理內(nèi)存在同一位置。副本是一個(gè)數(shù)據(jù)的完整的拷貝,如果我們對(duì)副本進(jìn)行修改,它不會(huì)影響到原始數(shù)據(jù),物理內(nèi)存不在同一位置。視圖一般發(fā)生在:Numpy 的切片操作返回原數(shù)據(jù)的視圖;調(diào)用 ndarray 的 view() 函數(shù)產(chǎn)生一個(gè)視圖。副本一般發(fā)生在:在對(duì) Python 序列進(jìn)行切片操作時(shí),同時(shí)調(diào)用 deepcopy() 函數(shù);調(diào)用 ndarray (或其切片)的時(shí)候,同時(shí)調(diào)用 copy() 函數(shù)產(chǎn)生一個(gè)副本。
所謂純函數(shù),就是沒有副作用的函數(shù)一個(gè)函數(shù)從執(zhí)行開始到結(jié)束,沒有對(duì)外部環(huán)境做任何操作,即對(duì)外部環(huán)境沒有任何影響(沒有副作用),這樣的函數(shù)就是純函數(shù)。純函數(shù)只負(fù)責(zé)輸入輸出,對(duì)于一種輸入只有一種函數(shù)返回值。如果函數(shù)中存在 Math.random 這種影響返回值的函數(shù),也不能算是純函數(shù)。// 純函數(shù)function add(a, b) { return a + b;}// 非純函數(shù)var person = { name: '小明' };function changeName { person.name = '小紅'; // 影響了函數(shù)外的內(nèi)容,產(chǎn)生了副作用}
要使用其他配置的副本開始定義運(yùn)行/調(diào)試配置,請(qǐng)按以下步驟操作:打開 Run/Debug Configurations 對(duì)話框;在左側(cè)窗格中選擇現(xiàn)有的運(yùn)行/調(diào)試配置;點(diǎn)擊 Copy Configuration 圖標(biāo) ;在 Name 字段中輸入一個(gè)名稱;根據(jù)需要修改配置;請(qǐng)務(wù)必更正在此對(duì)話框底部顯示的所有錯(cuò)誤;點(diǎn)擊 OK。
功能描述: 提供查詢出所有學(xué)生的業(yè)務(wù)邏輯。public class StudentService implements IStudentService { @Autowired private StudentMapper StudentMapper; @Override public List<Student> getAllStudents() { return this.StudentMapper.getStudents();; }}Tips: 業(yè)務(wù)對(duì)象依賴于映射器組件。使用 @Autowired 注解讓 Spring 自動(dòng)注入進(jìn)來。
Http 定義了 5大類別的錯(cuò)誤碼,這些錯(cuò)誤碼是通用的,其中只有 5XX 是表示后臺(tái)服務(wù)的錯(cuò)誤。各個(gè)系統(tǒng)的后端服務(wù)的用途/業(yè)務(wù)相差甚遠(yuǎn),為數(shù)不多 5XX 遠(yuǎn)遠(yuǎn)不夠用來表示可能出現(xiàn)的各種情況。于是,后端系統(tǒng)需要根據(jù)自己的業(yè)務(wù)制定業(yè)務(wù)級(jí)別的錯(cuò)誤碼,而 Http 的錯(cuò)誤碼,我們稱其為協(xié)議級(jí)別的錯(cuò)誤碼。
上面的登錄認(rèn)證案例只是比較簡(jiǎn)單的一個(gè)業(yè)務(wù),真實(shí)項(xiàng)目當(dāng)中,肯定是很多的業(yè)務(wù)組合而成的,那么如何基于 Netty 的 Handler 去實(shí)現(xiàn)呢?
功能描述: 定義查詢所有學(xué)生業(yè)務(wù)。public interface IStudentService { public List<Student> getAllStudents;}
在上述發(fā)布訂閱消息發(fā)送模式中,我們介紹了發(fā)布訂閱模式的基礎(chǔ)概念,同時(shí),引入了兩種發(fā)布訂閱模式的業(yè)務(wù)場(chǎng)景,并且對(duì)不同的業(yè)務(wù)場(chǎng)景做了基本的介紹,下面讓我們來看一下如何使用代碼來實(shí)現(xiàn)這兩種不同的業(yè)務(wù)場(chǎng)景。實(shí)現(xiàn)代碼:// 發(fā)布訂閱模式-第一種業(yè)務(wù)場(chǎng)景ConnectionFactory connectionFactory = new ConnectionFactory();connectionFactory.setHost("192.168.0.1");connectionFactory.setPort(5672);connectionFactory.setUsername("guest");connectionFactory.setPassword("guest");Connection connection = connectionFactory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, "fanout");channel.close();connection.close();代碼解釋:第 1-5 行,我們使用了 RabbitMQ 的 ConnectionFactory 連接工廠,來初始化了一些獲取 RabbitMQ 連接的必要參數(shù)。第 6 行,我們從 RabbitMQ 的連接工廠中,獲取到了一個(gè) RabbitMQ Conneciton 連接實(shí)例。第 7 行,我們通過 RabbitMQ 連接實(shí)例,創(chuàng)建了一個(gè) channel 通道,為之后的消息通信提供媒介。第 8 行,我們使用了 channel 通道中的 exchangeDeclare 方法,來為我們的 channel 通道綁定了一個(gè) exchange 交換機(jī)。由于在第一種業(yè)務(wù)場(chǎng)景中,我們不會(huì)在 exchange 交換機(jī)上綁定任何的消息隊(duì)列,所以在上述代碼中,我們看不到 channel 通道與 queue 消息隊(duì)列進(jìn)行綁定的方法。在此種業(yè)務(wù)場(chǎng)景下,當(dāng)生產(chǎn)者生產(chǎn)出來消息之后,在將消息發(fā)布到我們的 exchange 交換機(jī)上,整個(gè)流程就結(jié)束了,沒有消費(fèi)者可以拿到這一消息,所以,這種業(yè)務(wù)場(chǎng)景毫無意義。最后,讓我們來看第二種業(yè)務(wù)場(chǎng)景。
上一小節(jié)我們完成了數(shù)據(jù)庫(kù)的設(shè)計(jì)和創(chuàng)建,也向數(shù)據(jù)表中插入了一些初始數(shù)據(jù),本小節(jié)我們將開始具體業(yè)務(wù)代碼的實(shí)現(xiàn),如果大家還沒有完成上一小節(jié)的任務(wù),請(qǐng)務(wù)必先完成再來學(xué)習(xí)本節(jié)內(nèi)容。
業(yè)務(wù)場(chǎng)景描述有這樣一個(gè)真實(shí)的業(yè)務(wù)場(chǎng)景:在某大廠某銷售業(yè)務(wù)項(xiàng)目中,由于某大廠銷售業(yè)務(wù)板塊業(yè)務(wù)的持續(xù)增加,導(dǎo)致之前原本設(shè)計(jì)好的項(xiàng)目架構(gòu)出現(xiàn)了問題,不足以支撐持續(xù)增長(zhǎng)的業(yè)務(wù)需要,于是,某大廠程序員對(duì)項(xiàng)目架構(gòu)做了拆分,并最終形成了以 Spring Cloud 為基礎(chǔ)架構(gòu)的微服務(wù)分布式項(xiàng)目架構(gòu)。在拆分了項(xiàng)目架構(gòu)之后,雖然可以支撐持續(xù)增長(zhǎng)的業(yè)務(wù)需要,但是,在拆分后的項(xiàng)目架構(gòu)中,Hystrix 無法對(duì)所有項(xiàng)目進(jìn)行監(jiān)控,即 Hystrix 服務(wù)監(jiān)控平臺(tái)只能監(jiān)控一個(gè)分散的項(xiàng)目,無法對(duì)項(xiàng)目整體進(jìn)行監(jiān)控。問題原因分析在解決問題之前,我們首先來分析一下這種問題產(chǎn)生的原因。上述場(chǎng)景場(chǎng)景中,項(xiàng)目的架構(gòu)方式是微服務(wù)的分布式架構(gòu),而一般來說的 Hystrix 微服務(wù)監(jiān)控平臺(tái)默認(rèn)只對(duì)一個(gè)項(xiàng)目實(shí)例起作用,所以,也就導(dǎo)致了一個(gè)微服務(wù)平臺(tái)只對(duì)一個(gè)微服務(wù)實(shí)例起作用。
ndarray.copy() 函數(shù)創(chuàng)建一個(gè)副本。 對(duì)副本數(shù)據(jù)進(jìn)行修改,不會(huì)影響到原始數(shù)據(jù),它們物理內(nèi)存不在同一位置。案例創(chuàng)建數(shù)組 a,并產(chǎn)生 a 的副本,記為 b:a = np.array([[0,1], [2,3], [4,5]])b = a.copy()判斷 a 和 b 是否具有同一性:b is aout: False可以看到,a 和 b 互相獨(dú)立,這和賦值顯然不同。對(duì)副本進(jìn)行修改,觀察原始數(shù)組:b[0,0]=100print("修改后的數(shù)組b:", b)print("原始數(shù)組a:", a)打印結(jié)果為:修改后的數(shù)組b: [[100 1] [ 2 3] [ 4 5]]原始數(shù)組a: [[0 1] [2 3] [4 5]]可以發(fā)現(xiàn),副本產(chǎn)生的變化,并不會(huì)對(duì)原始數(shù)組產(chǎn)生影響。
值傳入是在函數(shù)調(diào)用過程中,將原函數(shù)的數(shù)值復(fù)制一份后,將副本再傳入調(diào)用的函數(shù)中。在函數(shù)中操作的都是副本的數(shù)值。對(duì)原有函數(shù)中的原變量是沒有影響的。
業(yè)務(wù)碼不屬于 Http 協(xié)議的成員,是實(shí)踐中的產(chǎn)物。它是定義在返回的消息實(shí)體中的,并沒有固定的格式,但無非就是下面3種模塊?!惧e(cuò)誤級(jí)別(可選)】-【功能模塊(必要)】-【具體錯(cuò)誤編號(hào)(必要)】錯(cuò)誤碼一般由 5~6 位整數(shù)組成,例子如下:模塊模塊編碼錯(cuò)誤編碼描述庫(kù)存10001庫(kù)存不足庫(kù)存10002盤盈庫(kù)存10002盤虧資金20001參數(shù)不正確
我們來看主體模式的代碼實(shí)現(xiàn):實(shí)現(xiàn)代碼:ConnectionFactory connectionFactory = new ConnectionFactory();connectionFactory.setHost("192.168.0.1");connectionFactory.setPort(5672);connectionFactory.setUsername("guest");connectionFactory.setPassword("guest");Connection connection = connectionFactory.newConnection();Channel channel = connection.createChannel();channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");channel.basicConsume(QUEUE_NAME, false, consumer);channel.close();connection.close();代碼解釋:第 1-7 行,代碼和第一種業(yè)務(wù)場(chǎng)景相似,這里不再贅述。第 8 行,我們使用 channel 的 queueBind 方法來將消息隊(duì)列和通道進(jìn)行綁定,這里注意,我們并沒有為我們的消息隊(duì)列指定一個(gè) Routing Key 值,如果這里指定了,就不是發(fā)布訂閱模式了。第 9 行,我們使用 channel 的 basicConsume 方法,來獲取我們的消息,并對(duì)消息進(jìn)行一個(gè)消費(fèi)。第 10-11 行,代碼和第一種業(yè)務(wù)場(chǎng)景相似,這里不再贅述。通過上述代碼段,我們可以看到,我們?cè)?channel 通道上,綁定了一個(gè)消息隊(duì)列,這樣我們的消息就可以被消費(fèi)者進(jìn)行消費(fèi)了。Tips: 1. 我們?cè)趯?shí)現(xiàn)發(fā)布訂閱模式的時(shí)候,在生產(chǎn)者端,我們需要將 exchange 交換機(jī)的類型,聲明為 fanout 類型,這種類型才是我們說的發(fā)布訂閱模式; 2. 發(fā)布訂閱模式更多的應(yīng)用場(chǎng)景,是用在消息通知群發(fā)、批量傳送數(shù)據(jù)等業(yè)務(wù)場(chǎng)景,消息傳送效率還是很高的。
本部分內(nèi)容,老師會(huì)詳細(xì)介紹本套課程最終需要實(shí)現(xiàn)的一種業(yè)務(wù)場(chǎng)景,此種業(yè)務(wù)場(chǎng)景是老師自己在實(shí)際工作中遇到的真實(shí)的業(yè)務(wù)場(chǎng)景,同學(xué)們一定要先對(duì)這種業(yè)務(wù)場(chǎng)景有個(gè)清晰地了解之后,在繼續(xù)學(xué)習(xí)本小節(jié)后續(xù)地內(nèi)容,如果你對(duì)這種業(yè)務(wù)場(chǎng)景沒有充分地了解,那么后面的實(shí)現(xiàn)思路你將不會(huì)看懂,這點(diǎn)同學(xué)們注意。本業(yè)務(wù)場(chǎng)景實(shí)際上并不算復(fù)雜,我們每個(gè)同學(xué)在真實(shí)的日常生活中,或多或少都會(huì)接觸到,只不過平時(shí)同學(xué)們可能不會(huì)注意觀察或者思考。這種業(yè)務(wù)場(chǎng)景有一個(gè)專有的代名詞,相信大家都已經(jīng)聽說過了,那就是’秒殺’業(yè)務(wù)場(chǎng)景。那么,什么是秒殺業(yè)務(wù)場(chǎng)景呢?這個(gè)秒殺的業(yè)務(wù)場(chǎng)景,出現(xiàn)在銷售行業(yè)的居多,比如日常生活中,我們?cè)诔兄腥ベ?gòu)買一種商品,這種商品的價(jià)格要比往常的價(jià)格要低很多,但是,這種商品的庫(kù)存數(shù)量是有限的,當(dāng)我們購(gòu)買這種商品時(shí),必須要在一瞬間完成搶購(gòu)這一動(dòng)作。隨著互聯(lián)網(wǎng)時(shí)代的快速發(fā)展,越來越多的線上電子商城已經(jīng)出現(xiàn)在人們的日常生活中,以淘寶、京東為代表性的互聯(lián)網(wǎng)電子商城巨頭率先將這些線下的商品購(gòu)買行為,轉(zhuǎn)換為線上的商品購(gòu)買功能。針對(duì)與上述這種秒殺搶購(gòu)的業(yè)務(wù)場(chǎng)景,目前在各互聯(lián)網(wǎng)電子商城巨頭中都是有所體現(xiàn)的,比如我們熟知的雙十一活動(dòng),以及 618 商品大促活動(dòng),這些都是秒殺搶購(gòu)業(yè)務(wù)場(chǎng)景的典型代表,那么,這種業(yè)務(wù)場(chǎng)景在線上又是如何實(shí)現(xiàn)的呢?在分析一秒殺搶購(gòu)業(yè)務(wù)場(chǎng)景的一個(gè)完整的線上業(yè)務(wù)流程是什么樣的之前,我們先來看一下,一般地線上商品購(gòu)買的一個(gè)完整的業(yè)務(wù)流程是什么樣的,如下圖所示:首先,用戶在有這種秒殺搶購(gòu)的實(shí)際需求之后,用戶首先會(huì)登錄我們的線上商城系統(tǒng),在用戶成功登錄本系統(tǒng)之后,用戶需要到我們線上商城系統(tǒng)的秒殺搶購(gòu)專區(qū),用戶可以在這個(gè)秒殺搶購(gòu)專區(qū)中看到本商城系統(tǒng)中參與秒殺搶購(gòu)活動(dòng)的商品,這一過程我們稱為用戶挑選商品階段。用戶在挑選完自己所需要的商品之后,可以將所需的商品放入購(gòu)物車中,也可以直接點(diǎn)擊下單按鈕,來迅速完成對(duì)某一具體商品的下單操作。如果用戶是將商品放入購(gòu)物車中,那么用戶只能進(jìn)行一個(gè)批量下單的動(dòng)作,即用戶前往自己的購(gòu)物車中,選中商品之后,點(diǎn)擊下單按鈕,進(jìn)行一個(gè)批量下單操作,這一過程我們稱為用戶預(yù)下單階段。在用戶將訂單創(chuàng)建完畢之后,就需要用戶選擇對(duì)應(yīng)的支付方式,來完成商品價(jià)格的支付動(dòng)作,對(duì)于線上電子商城而言,用戶可以選擇不同廠家的掃碼支付功能來完成支付,這一過程我們稱為用戶支付階段。在用戶對(duì)所購(gòu)商品支付完成之后,我們需要將用戶的商品支付結(jié)果返回給用戶,告知用戶商品支付的狀態(tài),是支付成功了,還是支付過程中遇到問題,導(dǎo)致支付失敗了,這一過程往往我們會(huì)采取輪詢的方式實(shí)現(xiàn),這一過程我們稱為用戶支付狀態(tài)回調(diào)階段。在用戶的支付狀態(tài)成功回調(diào)給用戶之后,一個(gè)完整的線上商品購(gòu)買流程就結(jié)束了,至于后續(xù)地商品物流信息等其他信息就不屬于我們商品購(gòu)買的流程了。在清楚了一般地商品購(gòu)買全流程之后,我們就不難理解秒殺搶購(gòu)的業(yè)務(wù)場(chǎng)景流程了。其實(shí),秒殺搶購(gòu)的業(yè)務(wù)場(chǎng)景流程和一般地商品購(gòu)買流程是一模一樣地,只不過在用戶預(yù)下單階段,以及用戶支付階段,在同一時(shí)刻會(huì)有大量的用戶請(qǐng)求需要我們處理,這就是秒殺業(yè)務(wù)場(chǎng)景和一般地商品購(gòu)買流程中最大的區(qū)別點(diǎn),其他地方并沒有什么區(qū)別。在本小節(jié)中,我們需要實(shí)現(xiàn)上述業(yè)務(wù)場(chǎng)景,并對(duì)核心的秒殺搶購(gòu)業(yè)務(wù)場(chǎng)景中的用戶預(yù)下單階段,以及用戶支付階段,做好高并發(fā)場(chǎng)景下的處理。Tips: 同學(xué)們一定要清楚地理解上述所介紹的業(yè)務(wù)流程,如果看一遍不理解,那就反復(fù)多看幾遍,直到自己理解了即可。
在了解了秒殺搶購(gòu)的業(yè)務(wù)場(chǎng)景流程之后,接下來我們就需要實(shí)現(xiàn)這一業(yè)務(wù)場(chǎng)景了,那么,這種業(yè)務(wù)場(chǎng)景我們應(yīng)該怎么用 RabbitMQ 和 Redis 去實(shí)現(xiàn)呢?在使用 RabbitMQ 打造扛得住的高并發(fā)環(huán)境系列小節(jié)內(nèi)容的第二小節(jié)中,我們使用 RabbitMQ 消息通信中間件和 Redis 緩存中間件,對(duì) RabbitMQ 自身的消息隊(duì)列進(jìn)行了改造,改造成了一種 Redis 承載的高可用的消息隊(duì)列,在本節(jié),我們就會(huì)用到這一高可用的消息隊(duì)列。在實(shí)現(xiàn)上述實(shí)際高并發(fā)業(yè)務(wù)場(chǎng)景時(shí),由于篇幅原因,我們并不會(huì)從用戶登錄開始,逐步地去實(shí)現(xiàn)每一個(gè)過程,我們只實(shí)現(xiàn)在秒殺搶購(gòu)業(yè)務(wù)場(chǎng)景中,最核心的部分,也就是,當(dāng)我們?cè)诿霘屬?gòu)商品區(qū)域,點(diǎn)擊立即購(gòu)買這個(gè)秒殺按鈕時(shí),我們后臺(tái)所需要應(yīng)對(duì)高并發(fā)處理的內(nèi)容。讓我們來看看具體應(yīng)該怎么設(shè)計(jì)實(shí)現(xiàn)吧。
public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{ private SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //1.獲取Channel通道 final Channel channel=ctx.channel(); //2.創(chuàng)建一個(gè)定時(shí)線程池 ScheduledExecutorService ses=Executors.newScheduledThreadPool(1); //3.一秒鐘之后只需,并且每隔5秒往瀏覽器發(fā)送數(shù)據(jù) ses.scheduleWithFixedDelay(new Runnable() { public void run() { String sendTime=format.format(new Date()); channel.writeAndFlush(new TextWebSocketFrame("推送時(shí)間=" + sendTime)); } },1,5, TimeUnit.SECONDS); } //接受瀏覽器消息 @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { System.out.println("收到消息 " + msg.text()); } //當(dāng)web客戶端連接后,觸發(fā)方法 @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { } //當(dāng)web客戶端斷開后,觸發(fā)方法 @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { }}代碼說明:其實(shí) WebSocket 對(duì)于的 Handler 跟我們普通業(yè)務(wù)的 Handler 沒有什么區(qū)別,這里主要使用定時(shí)線程池定時(shí)往瀏覽器推送消息,這個(gè)是傳統(tǒng)的 Http+Ajax 請(qǐng)求無法實(shí)現(xiàn)的逆向推送效果。
在 Markdown 文件中,用「大于號(hào)」開頭的行會(huì)被轉(zhuǎn)換為塊引用。實(shí)例 1:這是一段普通的文本內(nèi)容?!耙恢Т┰萍?,千軍萬馬來相見;兩副忠義膽,刀山火海提命現(xiàn)?!? —— 星仔其默認(rèn)渲染效果如下實(shí)例 2:在行首加入「大于號(hào) >」,將其改變?yōu)閴K引用。> “一支穿云箭,千軍萬馬來相見;兩副忠義膽,刀山火海提命現(xiàn)?!? —— 星仔其修改后渲染效果如下:實(shí)例 3:如果需要在塊引用內(nèi)的換行,可以在行尾增加兩個(gè)連續(xù)的空格。> 一支穿云箭,千軍萬馬來相見; > 兩副忠義膽,刀山火海提命現(xiàn)。 > —— 星仔其渲染效果如下:實(shí)例 4:這一寫法還可以簡(jiǎn)化成只在第一行加入引用符號(hào)。> 一支穿云箭,千軍萬馬來相見; 兩副忠義膽,刀山火海提命現(xiàn)。 —— 星仔其渲染效果與以前一樣:實(shí)例 5:如果需要在塊引用里分段,只要在塊引用內(nèi)增加由塊引用符號(hào)開啟的空行即可。> 一支穿云箭,千軍萬馬來相見;>> 兩副忠義膽,刀山火海提命現(xiàn)。>> —— 星仔其渲染結(jié)果如下:塊引用會(huì)被渲染成 html 的 blockquote 標(biāo)簽:<blockquote><p>"一支穿云箭,千軍萬馬來相見;兩副忠義膽,刀山火海提命現(xiàn)。" —— 星仔</p></blockquote>
本小節(jié)講解了視圖和副本的概念和區(qū)別。副本是對(duì)原始數(shù)組的完整拷貝,二者互相獨(dú)立,并不互相影響,但是物理內(nèi)存的開銷會(huì)加倍。而視圖(切片)是對(duì)原始數(shù)據(jù)的一種映射,物理內(nèi)存的開銷相對(duì)小一些;對(duì)視圖(切片)的元素更改,會(huì)相應(yīng)地反映到原始數(shù)組中,這是二者最大的區(qū)別。
官方解釋:title 即標(biāo)題組件,包含主標(biāo)題與副標(biāo)題,ECharts 3.0 版本后支持多個(gè)標(biāo)題組件。慕課解釋:標(biāo)題組件對(duì)應(yīng)下圖紅色框部分:title 組件使用頻率比較高,功能也比較完備,除了可以自定義樣式、位置等基礎(chǔ)功能外,還支持超鏈接模式,主標(biāo)題、副標(biāo)題功能等,下面展開討論。
業(yè)務(wù)場(chǎng)景描述有這樣一個(gè)真實(shí)的業(yè)務(wù)場(chǎng)景:在某大廠的訂單與支付模塊,當(dāng)有用戶下了訂單之后,需要在支付模塊進(jìn)行支付,支付動(dòng)作完成之后,支付模塊會(huì)將支付完成的結(jié)果返回給訂單模塊來通知用戶,該訂單是否支付成功,即商品是否已經(jīng)成功購(gòu)買了。在微服務(wù)分布式架構(gòu)模式下,上述業(yè)務(wù)場(chǎng)景中出現(xiàn)了一種異?,F(xiàn)象:當(dāng)用戶下了訂單之后,在支付模塊進(jìn)行支付時(shí),系統(tǒng)一直沒有響應(yīng),無論是否成功支付,用戶都收不到任何通知信息。程序員在排查對(duì)應(yīng)的業(yè)務(wù)實(shí)現(xiàn)代碼時(shí),證實(shí)了業(yè)務(wù)實(shí)現(xiàn)代碼沒有問題,這就導(dǎo)致無法定位問題所在。最終經(jīng)過幾名同事一起排查,發(fā)現(xiàn)是訂單模塊與支付模塊之間進(jìn)行數(shù)據(jù)傳輸時(shí),支付模塊收到了訂單模塊傳遞過來的數(shù)據(jù),但是由于服務(wù)器高壓工作,導(dǎo)致支付模塊始終無法處理該支付請(qǐng)求,這就導(dǎo)致系統(tǒng)一直沒有響應(yīng)。問題原因分析在解決問題之前,我們首先來分析一下這種問題產(chǎn)生的原因。在前面我們介紹什么是 Hystrix 資源隔離小節(jié)中,我為大家闡述了在我們的 Web 項(xiàng)目中,進(jìn)程與線程之間的關(guān)系。我們知道,在一般情況下,一個(gè) Web 項(xiàng)目中只有一個(gè)工作線程來負(fù)責(zé)處理用戶調(diào)用的請(qǐng)求和服務(wù),當(dāng)該工作線程所負(fù)責(zé)的請(qǐng)求處理緩慢時(shí),該線程就會(huì)一直處理當(dāng)前的請(qǐng)求,導(dǎo)致后續(xù)請(qǐng)求只能等待處理,這就是我們說的雪崩現(xiàn)象。雪崩效應(yīng)產(chǎn)生原理在微服務(wù)分布式架構(gòu)模式下,由于我們沒有對(duì)線程進(jìn)行處理,至此在處理所有業(yè)務(wù)請(qǐng)求時(shí),扔是只有一個(gè)工作線程,這就導(dǎo)致上述業(yè)務(wù)場(chǎng)出現(xiàn)了我們所說的雪崩現(xiàn)象,不過還好,這種雪崩現(xiàn)象比較輕微,只影響到了一個(gè)業(yè)務(wù)模塊。很多時(shí)候,當(dāng)我們的項(xiàng)目架構(gòu)演變?yōu)榛谖⒎?wù)的分布式架構(gòu)時(shí),服務(wù)器也需要同步進(jìn)行更新,有很多企業(yè)為了節(jié)約成本,則只更新很少數(shù)量的服務(wù)器,或者壓根就不更新服務(wù)器,這就導(dǎo)致經(jīng)常會(huì)出現(xiàn)由于服務(wù)器高壓工作而出現(xiàn)的請(qǐng)求處理緩慢,或請(qǐng)求無法繼續(xù)處理的情況。
title 組件用于渲染圖表的標(biāo)題,含主標(biāo)題、副標(biāo)題兩部分。 title 組件支持配置位置、文本樣式、鏈接模式等,詳情可參考 Echarts 標(biāo)題 title 一節(jié)。
var user = { parents: { father: { age: 66, }, },};console.log(user);user.parents.father.age = 44;這段代碼邏輯非常簡(jiǎn)單,就是定義了一個(gè)對(duì)象,然后輸出,但是觀察控制臺(tái),會(huì)發(fā)現(xiàn)數(shù)據(jù)并不是剛定義好時(shí)候的數(shù)據(jù),而是修改后的數(shù)據(jù)。根據(jù)現(xiàn)象推測(cè) chrome 在展開對(duì)象時(shí),對(duì)應(yīng)的是當(dāng)前該對(duì)象的狀態(tài),而不是一個(gè)副本。很多時(shí)候會(huì)因?yàn)檫@個(gè)問題排錯(cuò)很久,業(yè)務(wù)邏輯中可能查看對(duì)象的是在代碼 20 行處,但在 200 行的地方被別人改過這個(gè)對(duì)象,這時(shí)候就可能要定位很久問題。通常有兩個(gè)方式來避免這個(gè)問題:只輸出想看的數(shù)據(jù)var user = { parents: { father: { age: 66, }, },};console.log(user.parents.father.age);user.parents.father.age = 44;這樣就輸出了預(yù)期的結(jié)果。要注意的是,如果觀察的是一個(gè)對(duì)象下的子對(duì)象,這個(gè)方法就不靈了,原因是同理的:var user = { parents: { father: { age: 66, }, },};console.log(user.parents);user.parents.father = { name: '爸爸',};在輸出時(shí)候深拷貝一份對(duì)象function clone(obj) { return JSON.parse(JSON.stringify(obj));}var user = { parents: { father: { age: 66, }, },};console.log(clone(user));user.parents.father.age = 44;因?yàn)樵谳敵鰰r(shí)創(chuàng)建了一個(gè)副本,而不是對(duì)對(duì)象的引用了,所以數(shù)據(jù)就保留在了輸出時(shí)候的狀態(tài)。需要注意的是這個(gè)方案適合觀察沒有方法的對(duì)象,因?yàn)榉椒ㄔ诒恍蛄谢髸?huì)被剔除。
誕生:早在 JDK 1.2 的版本中就提供 java.lang.ThreadLocal,ThreadLocal 為解決多線程程序的并發(fā)問題提供了一種新的思路。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫出優(yōu)美的多線程程序。概述:ThreadLocal 很容易讓人望文生義,想當(dāng)然地認(rèn)為是一個(gè) “本地線程”。其實(shí),ThreadLocal 并不是一個(gè) Thread,而是 Thread 的局部變量,也許把它命名為 ThreadLocalVariable 更容易讓人理解一些。當(dāng)使用 ThreadLocal 維護(hù)變量時(shí),ThreadLocal 為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本??傮w概括:從線程的角度看,目標(biāo)變量就象是線程的本地變量,這也是類名中 “Local” 所要表達(dá)的意思。了解完 ThreadLocal 的總體介紹后,對(duì)其有了一個(gè)總體的了解,那我們接下來繼續(xù)探究 ThreadLocal 的真實(shí)面貌以及使用。
實(shí)現(xiàn)思路分析鑒于上述業(yè)務(wù)場(chǎng)景中所描述的問題,我們只要為每個(gè)業(yè)務(wù)模塊分配不同的工作線程,使各模塊間不再一同共用一個(gè)工作線程,就可以有效解決上述問題。當(dāng)我們通過技術(shù)手段為每一個(gè)業(yè)務(wù)模塊都分配了不同的工作線程之后,各模塊的業(yè)務(wù)處理操作都會(huì)有專門的工作線程來完成,不會(huì)再出現(xiàn)各模塊共用一個(gè)工作線程的情況。如果一個(gè)模塊中的請(qǐng)求處理出現(xiàn)問題而等待,由于我們分配了不同的工作線程,所以這種情況不會(huì)影響到其他模塊,這就解決了上述業(yè)務(wù)場(chǎng)景中出現(xiàn)的問題。Hystrix 提供了通過線程池或信號(hào)量隔離的方式來對(duì)服務(wù)資源進(jìn)行隔離,以解決雪崩現(xiàn)象的發(fā)生,接下來讓我們分別來看一下代碼實(shí)現(xiàn)。實(shí)操以線程池隔離為例,我們先給訂單服務(wù)配置資源隔離:@RequestMapping("make_order.do")@ResponseBody@HystrixCommand(threadPoolKey = "userMakeOrderThread")public CommonResponse<String> makeOrder(@RequestBody("order")Order order, @RequestBody("user")User user){ return orderService.makeOrder(order, user);}代碼解釋第 3 行,我們用戶下訂單服務(wù)中通過添加 HystrixCommand 注解的 threadPoolKey 屬性來為用戶下訂單服務(wù)單獨(dú)分配了一個(gè)名為 userMakeOrderThread 的線程池,當(dāng)我們?cè)儆杏脩粝掠唵蔚恼?qǐng)求需要處理時(shí),就會(huì)使用這個(gè)線程池中的線程。同樣的方法,我們?yōu)橛脩糁Ц斗?wù)配置資源隔離:@RequestMapping("aliPay.do")@ResponseBody@HystrixCommand(threadPoolKey = "userAliPayThread")public CommonResponse<String> aliPay(@RequestBody("shipping")Shipping shipping, @RequestBody("user")User user){ return payService.aliPay(shipping, user);} 可以看到,我們?yōu)橹Ц赌K分配了一個(gè)名為 userAliPayThread 的線程池?;谛盘?hào)量隔離的服務(wù)隔離的配置,和上述基于線程池隔離的配置大同小異,下面我將關(guān)鍵代碼放到下方,各位同學(xué)只需要替換掉上述的線程池配置即可。以用戶下訂單服務(wù)為例:@HystrixCommand(fallbackMethod="makeOrderFailed", commandProperties = {@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "50")})代碼解釋第 3- 8 行,使用信號(hào)量隔離需要顯式聲明服務(wù)資源隔離策略,SEMAPHORE 表示使用信號(hào)量隔離策略。Tips: 1. 在實(shí)際項(xiàng)目開發(fā)中,像這種訂單服務(wù)和支付服務(wù)互相依賴的業(yè)務(wù)場(chǎng)景不在少數(shù),各位同學(xué)在工作時(shí),一定要在這種業(yè)務(wù)場(chǎng)景中配置好服務(wù)資源隔離,不要等在線上出現(xiàn)問題之后再解決,那就晚了; 2. 線程池隔離和信號(hào)量隔離之間的區(qū)別有很多,這里就不做介紹了,希望各位同學(xué)可以自行查閱,他們之間的區(qū)別可以幫助你確定不同業(yè)務(wù)場(chǎng)景下采用哪種隔離方式比較合適。
方法原型: public Object merge(Object object); merge() 方法和 persist() 方法類似, 區(qū)別在于:merge() 方法接收一個(gè) PO 作為參數(shù),創(chuàng)建并返回此 PO 的副本對(duì)象;此副本對(duì)象具有對(duì)象持久化能力。這一點(diǎn)是 merge() 方法與其他方法最大的不同。上一段實(shí)例:try{ transaction = session.beginTransaction(); //查詢出來的stu具有持久化能力 Student stu = (Student) session.get(Student.class, new Integer(2)); //轉(zhuǎn)stu對(duì)象持久化狀態(tài)轉(zhuǎn)變成游離狀態(tài) session.clear(); //stu_對(duì)象具有持久化能力 Student stu_ = (Student) session.merge(stu); //這個(gè)操作不能同步到數(shù)據(jù)庫(kù) stu.setStuName("我已經(jīng)不具有持久化能力"); //這個(gè)操作能同步到數(shù)據(jù)庫(kù) stu_.setStuName("我具有持久化能力"); transaction.commit();} catch(Exception e) { transaction.rollback();} finally { session.close();} merge() 方法返回的 stu 對(duì)象的副本 stu_,此對(duì)象具有持久化能力。執(zhí)行下面代碼,數(shù)據(jù)能同步到數(shù)據(jù)庫(kù)中。stu_.setStuName("我具有持久化能力"); Session 中提供的每一個(gè)方法都有其實(shí)際意義。特別是 merge() 方法,既可以保護(hù)原對(duì)象中的數(shù)據(jù)不被污染,又能行使數(shù)據(jù)庫(kù)同步操作。在很多場(chǎng)景里都會(huì)有這個(gè)需求。
業(yè)務(wù)場(chǎng)景描述有這樣一個(gè)真實(shí)的業(yè)務(wù)場(chǎng)景:在某大廠的用戶業(yè)務(wù)模塊下,存在一個(gè)用戶注冊(cè)服務(wù)接口,在正常流量下,請(qǐng)求該用戶注冊(cè)服務(wù)接口,不會(huì)出現(xiàn)任何問題,業(yè)務(wù)可以正常開展。但是,在遇到某活動(dòng)舉辦時(shí),當(dāng)再次請(qǐng)求該用戶注冊(cè)服務(wù)接口時(shí),該服務(wù)接口就會(huì)報(bào)服務(wù)處理異常,我們需要做的就是用服務(wù)容錯(cuò)與降級(jí)的概念來解決這種異?,F(xiàn)象。問題原因分析在解決問題之前,我們首先來分析一下這種問題產(chǎn)生的原因。在遇到某活動(dòng)舉辦時(shí),當(dāng)再次請(qǐng)求該用戶注冊(cè)服務(wù)接口時(shí),由于此時(shí)的請(qǐng)求流量較正常情況下的多,即此時(shí)的請(qǐng)求流量可能是正常情況下請(qǐng)求流量的幾倍,甚至更多,我們的服務(wù)在處理請(qǐng)求時(shí),一方面出現(xiàn)了服務(wù)處理堆積的現(xiàn)象;另一方面,當(dāng)我們的服務(wù)器或數(shù)據(jù)庫(kù)不能繼續(xù)處理更多的請(qǐng)求時(shí),沒有給用戶一個(gè)合理地提示,直接讓程序報(bào)出了異常。以上兩方面就是產(chǎn)生該異常的原因,第一方面我們使用服務(wù)容錯(cuò)與降級(jí)無法解決,只能使用高并發(fā)相關(guān)的知識(shí),通過限流來解決,但是第二方面我們可以使用服務(wù)容錯(cuò)與降級(jí)的概念來解決,接下來就讓我們看一下如何解決吧。
對(duì)于 C 語言來說,由于考慮到效率的問題的。數(shù)組的傳遞一般都是不復(fù)制的。因此函數(shù)中如果傳入的是數(shù)組,那么在被調(diào)用的函數(shù)中其實(shí)就是傳入了原始函數(shù)中的數(shù)組,而不是一個(gè)副本。
使用指針是為了告訴 C 語言,我們這時(shí)希望將原有的變量直接傳入被調(diào)用的函數(shù),而不是一個(gè)副本。這種傳入方式在其它的編譯類型的編程語言中也有類似的傳遞方式。
業(yè)務(wù)場(chǎng)景描述我們繼續(xù)以某大廠的用戶注冊(cè)服務(wù)為例,只不過所在的業(yè)務(wù)場(chǎng)景發(fā)生了變化。有這樣一個(gè)真實(shí)的業(yè)務(wù)場(chǎng)景,在用戶請(qǐng)求用戶注冊(cè)服務(wù)時(shí),該服務(wù)要求用戶上傳自己的注冊(cè)頭像,在單體項(xiàng)目架構(gòu)下,我們上傳多次頭像都沒有任何問題,服務(wù)可以正常的進(jìn)行。但是,將我們把項(xiàng)目架構(gòu)進(jìn)行演進(jìn),項(xiàng)目由單體架構(gòu)演變?yōu)榱宋⒎?wù)的分布式架構(gòu)之后,時(shí)不時(shí)地會(huì)出現(xiàn)因?yàn)橛脩糇?cè)頭像上傳失敗而導(dǎo)致整個(gè)用戶注冊(cè)服務(wù)無法繼續(xù)進(jìn)行的情況。問題原因分析在解決問題之前,我們首先來分析一下這種問題產(chǎn)生的原因。當(dāng)項(xiàng)目架構(gòu)由傳統(tǒng)的單體架構(gòu)演變?yōu)榛谖⒎?wù)的分布式架構(gòu)之后,我們需要處理很多由于微服務(wù)架構(gòu)所帶來的已知問題,在將這些已知問題處理好后,我們的項(xiàng)目才能正常運(yùn)行,而上述業(yè)務(wù)場(chǎng)景中的問題就包括在這些已知問題中,下面我們來介紹一下問題產(chǎn)生的原因(至于其他已知問題,這里不做介紹)。無論是傳統(tǒng)的用戶注冊(cè)服務(wù),還是分布式的用戶注冊(cè)服務(wù),其用戶注冊(cè)處理邏輯都大同小異。在處理用戶注冊(cè)邏輯時(shí),一般會(huì)將用戶注冊(cè)頭像上傳的業(yè)務(wù)邏輯與用戶注冊(cè)的處理邏輯相分離,即將用戶注冊(cè)頭像上傳作為一個(gè)單獨(dú)的服務(wù)存在,同時(shí)用戶注冊(cè)的處理邏輯也作為一個(gè)單獨(dú)的服務(wù)接口。當(dāng)正式調(diào)用用戶注冊(cè)服務(wù)時(shí),會(huì)對(duì)用戶注冊(cè)頭像上傳服務(wù)做一個(gè)校驗(yàn),如果用戶成功上傳了所注冊(cè)的頭像,那么用戶注冊(cè)頭像上傳服務(wù)會(huì)返回一個(gè)頭像地址,并且在最后的用戶注冊(cè)邏輯中,將該地址與當(dāng)前注冊(cè)的用戶相關(guān)聯(lián),這就完成了整個(gè)用戶注冊(cè)的流程。上述業(yè)務(wù)場(chǎng)景的問題就出現(xiàn)在用戶注冊(cè)頭像上傳服務(wù)中,當(dāng)外界因素或者由于我們處理用戶注冊(cè)頭像格式時(shí)的處理邏輯不嚴(yán)謹(jǐn)時(shí),就會(huì)出現(xiàn)用戶注冊(cè)頭像上傳失敗的情況, 其中,由處理邏輯不嚴(yán)謹(jǐn)所引起的錯(cuò)誤可以直接通過修改源碼邏輯來規(guī)避這種錯(cuò)誤,但是由外界因素所引起的錯(cuò)誤就不好規(guī)避了。由外界因素引起的錯(cuò)誤,可能是項(xiàng)目所在服務(wù)器網(wǎng)絡(luò)波動(dòng)的影響,也可能是各微服務(wù)所在節(jié)點(diǎn)間數(shù)據(jù)通信丟失或堵塞的影響,也可能是沒有對(duì)分布式事務(wù)做處理的影響。我們不需要知道這種錯(cuò)誤具體是由于什么外界因素所引起的,我們只需要關(guān)心,在外界因素對(duì)服務(wù)造成影響時(shí),我們有兜底的解決方案即可。那么接下來就讓我們來看一下如何設(shè)計(jì)這個(gè)兜底的方案。