主頁 > 後端開發 > golang 服務詭異499、504網路故障排查

golang 服務詭異499、504網路故障排查

2020-09-17 06:37:59 後端開發

  • 事故經過
  • 排查
  • 總結

事故經過

11-01 12:00 中午午飯期間,手機突然收到業務網關非200例外報警,平時也會有一些少量499或者網路抖動問題觸發報警,但是很快就會恢復(目前配置的報警閾值是5%,閾值跟當時的采樣視窗qps有直接關系),

報警當時非200占比已經過10%并且在持續升高,根據歷史規律應該很快就會恢復,我們稍微觀察了幾分鐘(一邊吃著很香的餃子一邊看著手機),但是過了幾分鐘故障沒有恢復而且占比升高了突破50%,故障逐漸升級(故障如果不在固定時間內解決會逐漸升級,故障群每次升級都會逐層拉更高level的boss進來)手機持續報警震動已經發燙了,故障占比已經快100%,影響面突然變大,

此時提現系統也開始報警,大量打款訂單擠壓(打款訂單擠壓突破一定閾值才會報警,所以不是實時),工位同事也反應支付系統也有少量連接錯誤,突然感覺情況復雜了,迅速停止吃飯,趕緊回公司排查,

回到工位時間差不多12:40左右,快速查看監控大盤,基本都是499、504錯誤,此類錯誤都是因為網路超時導致,集群中的兩臺機器均有錯,而且qps也比較平均,可以排除某臺機器問題,
vim

RT99線基本5s,而且連續橫盤,這5s是觸發了上游sidecar proxy呼叫超時主動斷開了,真正的RT時間可能更長,
vim

故障還未見恢復,業務運維協助一起排查,此時故障群已經升級到技術中心老大,壓力瞬間大的一筆,

查看網關系統日志,大量呼叫我們內部的兩個系統報出“下游服務器超時”錯誤,根據日志資訊可以判斷網路問題導致超時,但是我們呼叫的是內網服務,如果是網路問題為什么只有我們的系統受到影響,

在12:51到13:02之間錯誤占比情況有所好轉,但是之后錯誤占比繼續升高,

此時業務運維同步其他部門有大量302報警,時間線有點吻合,此時時間差不多13:30,但是別的部門的系統和我們的系統沒有任何關系,太多的疑問大家開始集中坐到一起排查問題,

他們嘗試做了版本回滾未見好轉,然后嘗試將訪問回傳302域名切到內網故障立馬恢復,此時正好14:00,根據他們的反饋在做實驗放量,導致在12:00的時候有一波流量高峰,但是這一波流量高峰對我的系統鏈路沖擊在哪里,一臉懵逼,疑點重重,
vim

vim

本次故障持續時間太長,報警整整報了兩個小時,故障群每三種報警一次并且電話通知,報警電話幾十個,微信報警群“災難”級別的資訊更多,嚴重程度可想而知,

排查

雖然故障是因為別的部門放量導致,但是還是有太多疑問沒有答案,下次還會再出現,作為技術人員,線上環境是非常神圣的地方是禁區,一定要找到每次故障的 root cause,否則沒辦法給自己一個交代,我們開始逐層剝洋蔥,

我們來梳理下疑問點:

1.302是什么原因,為什么做了域名切換就整體恢復了?
2.兩邊的系統在鏈路上有什么交集?如果應用鏈路沒有交集,那么在網路鏈路上是否有交集?
3.我們業務網關中的“下游服務器超時”為什么其他系統沒有影響?對日志的解讀或者描述是否有歧義?
4.504是觸發sidecar proxy 超時斷開連接,網關服務設定的超時為什么沒起作用?

1.302是什么原因,為什么做了域名切換就整體恢復了?

經過我們的運維和阿里云專家的排查,出現大量302是因為訪問的域名觸發DDos/CC高防策略,由于訪問的域名配置了DDos/CC高防策略,大量請求觸發了其中一條規則導致拒絕請求(具體觸發了什么規則就不方便透露),所以會回傳302,通過添加白名單可以解決被誤殺的情況,
(從合理性角度講內部呼叫不應該走到外網,有一部分是歷史遺留問題,)

2.兩邊的系統在鏈路上有什么交集?如果應用鏈路沒有交集,那么在網路鏈路上是否有交集?

所有人焦點都集中在高防上,認為網關故障就是因為也走到了被高防的地址上,但是我們的網關配置里根本沒有這個高防地址,而且我們內部系統是不會有外網地址的,

排查提現系統問題,提現系統的配置里確實有用到被高防的外網地址,認為提現打款擠壓也是因為走到了高防地址,但是這個高防地址只是一個旁路作用,不會影響打款流程,但是配置里確實有配置到,所以有理由判斷肯定使用到了才會影響,這在當時確實是個很重要的線索,是個突破口,

根據這個線索認為網關系統雖然本身沒有呼叫到高防地址,但是呼叫的下游也有可能會走到才會導致整個鏈路出現雪崩的問題,

通過大量排查下游服務,翻代碼、看日志,基本上在應用層呼叫鏈路沒有找到任何線索,開始在網路層面尋找線索,由于是內網呼叫所以路線是比較簡單的,client->slb->gateway->slb->sidecar proxy->ecs,幾個下游被呼叫系統請求一切正常,slb、sidecar proxy監控也一切正常,應用層、網路層都沒有找到答案,

sidecar proxy 因為沒有打開日志所以看不到請求(其實有一部分呼叫沒有直連還是通過slb、vtm中轉),從監控上看下游的 sidecar proxy 也一切正常,如果網路問題肯定是連鎖反應,

百般無解之后,開始仔細檢查當天出現故障的所有系統日志(由于現在流行Microservice所以服務比較多,錯誤日志量也比較大),在排查到支付系統的渠道服務時發現有一些線索,在事故發生期間有一些少量的 connection reset by peer,這個錯誤基本上多數出現在連接池化技術中使用了無效連接,或者下游服務器發生重啟導致,但是在事故當時并沒有發布,

通過對比前一周日志沒有發生此類錯誤,那很有可能是很重要的線索,聯系阿里云開始幫忙排查當時ecs實體在鏈路上是否有問題,驚喜的是阿里云反饋在事故當時出現 nat網關 限流丟包,一下子疑問全部解開了,
vim

限流丟包才是引起我們系統大量錯誤的主要原因,所以整個故障原因是這樣的,由于做活動放量導致高防302和出網限流丟包,而我們系統受到影響都是因為需要走外網,提現打款需要用到支付寶、微信等支付渠道,而支付系統也是需要出外網用到支付寶、微信、銀聯等支付渠道,
(由于當時我們并沒有nat網關的報警導致我們都一致認為是高防攔截了流量,)

問題又來了,為什么網關呼叫內部系統會出現問題,但是答案已經很明顯,簡單的檢查了下其中一個呼叫會走到外網,網關的介面會呼叫下游三個服務,其中第一個服務呼叫就是會出外網,

這個問題是找到了,但是為什么下游設定的超時錯誤一個沒看見,而且“下游服務器超時”的錯誤日志stack trace 堆疊資訊是內網呼叫,這個還是沒搞明白,

3.我們業務網關中的“下游服務器超時”為什么其他系統沒有影響?對日志的解讀或者描述是否有歧義?

通過分析代碼,這個日志的輸出并不是直接呼叫某個服務發生超時timeout,而是 go Context.Done() channel 的通知,我們來看下代碼:

func Send(ctx context.Context, serverName, method, path string, in, out interface{}) (err error) {
	e := make(chan error)
	go func() {
	    opts := []utils.ClientOption{
			utils.WithTimeout(time.Second * 1),
		}
		if err = utils.HttpSend(method, path, in, out, ops, opts...); err != nil {
			e <- err
			return
		}
		e <- nil
	}()

	select {
	case err = <-e:
		return
	case <-ctx.Done():
		err = errors.ErrClientTimeOut
		return
	}
}

Send 的方法通過 goroutine 啟動一個呼叫,然后通過 select channel 感知http呼叫的結果,同時通過 ctx.Done() 感知本次上游http連接的 canceled

err = errors.ErrClientTimeOut
ErrClientTimeOut         = ErrType{64012, "下游服務器超時"}

這里的 errors.ErrClientTimeOut 就是日志“下游服務器超時”的錯誤物件,

很奇怪,為什么呼叫下游服務器沒有超時錯誤,明明設定了timeout時間為1s,

        opts := []utils.ClientOption{
        			utils.WithTimeout(time.Second * 1),
        		}
		if err = utils.HttpSend(method, path, in, out, ops, opts...); err != nil {
			e <- err
			return
		}

這個 utils.HttpSend 是有設定呼叫超時的,為什么一條呼叫超時錯誤日志沒有,跟蹤代碼發現雖然opts物件傳給了utils.HttpSend方法,但是里面卻沒有設定到 __http.Client__物件上,

client := &http.Client{}
	// handle option
	{
		options := defaultClientOptions
		for _, o := range opts {
			o(&options)
		}
		for _, o := range ops {
			o(req)
		}
        
        //set timeout
		client.Timeout = options.timeout

	}

	// do request
	{
		if resp, err = client.Do(req); err != nil {
			err = err502(err)
			return
		}
		defer resp.Body.Close()
	}

就是缺少一行 client.Timeout = options.timeout 導致http呼叫未設定超時時間,加上之后呼叫一旦超時會拋出 “net/http: request canceled (Client.Timeout exceeded while awaiting headers)” timeout 錯誤,

問題我們大概知道了,就是因為我們沒有設定下游服務呼叫超時時間,導致上游連接超時關閉了,繼而觸發context.canceled事件,

上層呼叫會逐個同步進行,

    couponResp, err := client.Coupon.GetMyCouponList(ctx, r)
	// 不回傳錯誤 降級為沒有優惠券
	if err != nil {
		logutil.Logger.Error("get account coupon  faield",zap.Any("err", err))
	}
	coins, err := client.Coin.GetAccountCoin(ctx, cReq.UserID)
	// 不回傳錯誤 降級為沒有金幣
	if err != nil {
		logutil.Logger.Error("get account coin faield",zap.Any("err", err))
	}
	subCoins, err := client.Coin.GetSubAccountCoin(ctx, cReq.UserID)
	// 不回傳錯誤 降級為沒有金幣
	if err != nil {
		logutil.Logger.Error("get sub account coin faield",zap.Any("err", err))
	}

client.Coupon.GetMyCouponList 獲取優惠券
client.Coin.GetAccountCoin 獲取金幣賬戶
client.Coin.GetSubAccountCoin 獲取金幣子賬戶

這三個方法內部都會呼叫Send方法,這個介面邏輯就是獲取用戶名下所有的現金抵扣權益,并且在超時時間內做好業務降級,但是這里處理有一個問題,就是沒有識別Send方法回傳的錯誤型別,其實連接斷了之后程式再往下走已經沒有意義也就失去了Context.canceld的意義,
(go和其他主流編程語言在執行緒(Thread)概念上有一個很大的區別,go是沒有執行緒概念的(底層還是通過執行緒在調度),都是goroutine,go也是完全隱藏routine的,你無法通過類似Thread Id 或者 Thread local執行緒本地存盤等技術,所有的routine都是通過context.Context物件來協作,比如在java 里要想取消一個執行緒必須依賴Thread.Interrupt中斷,同時要捕獲和傳遞中斷信號,在go里需要通過捕獲和傳遞Context信號,)

4.504是觸發sidecar proxy 超時斷開連接,網關服務器設定的超時為什么沒起作用?

sidecar proxy 斷開連接有三個場景:

1.499同時會關閉下游連接
2.504超時直接關閉下游連接
3.空閑超過60s關閉下游連接

事故當時499、504 sidecar proxy 主動關閉連接,網關服務Context.Done()方法感知到連接取消拋出例外,上層方法輸出日志“下游服務器超時”,那為什么我們網關服務器本身的超時沒起作用,

http/server.Server物件有四個超時引數我們并沒有設定,而且這一類引數通常會被忽視,作為一個服務器本身對所有進來的請求是有最長服務要求,我們一般關注比較多的是下游超時會忽視服務本身的超時設定,

type Server struct {
	// ReadTimeout is the maximum duration for reading the entire
	// request, including the body.
	//
	// Because ReadTimeout does not let Handlers make per-request
	// decisions on each request body's acceptable deadline or
	// upload rate, most users will prefer to use
	// ReadHeaderTimeout. It is valid to use them both.
	ReadTimeout time.Duration

	// ReadHeaderTimeout is the amount of time allowed to read
	// request headers. The connection's read deadline is reset
	// after reading the headers and the Handler can decide what
	// is considered too slow for the body.
	ReadHeaderTimeout time.Duration

	// WriteTimeout is the maximum duration before timing out
	// writes of the response. It is reset whenever a new
	// request's header is read. Like ReadTimeout, it does not
	// let Handlers make decisions on a per-request basis.
	WriteTimeout time.Duration

	// IdleTimeout is the maximum amount of time to wait for the
	// next request when keep-alives are enabled. If IdleTimeout
	// is zero, the value of ReadTimeout is used. If both are
	// zero, ReadHeaderTimeout is used.
	IdleTimeout time.Duration
}

這些超時時間都會通過setDeadline計算成絕對時間點設定到netFD物件(Network file descriptor.)上,
由于沒有設定超時時間所以相當于所有的連接關閉都是通過sidecar proxy觸發傳遞下來的,

我們已經知道 sidecar proxy 關閉連接的1、2兩種原因,第3種情況出現在http長連接上,但是這類連接關閉是無感知的,

默認的tcpKeepAliveListener物件的keepAlive是3分鐘,

func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return nil, err
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}

我們服務host是使用endless框架,默認也是3分鐘,這其實是個約定90s,過小會影響上游代理,

func (el *endlessListener) Accept() (c net.Conn, err error) {
	tc, err := el.Listener.(*net.TCPListener).AcceptTCP()
	if err != nil {
		return
	}

	tc.SetKeepAlive(true)                  // see http.tcpKeepAliveListener
	tc.SetKeepAlivePeriod(3 * time.Minute) // see http.tcpKeepAliveListener

	c = endlessConn{
		Conn:   tc,
		server: el.server,
	}

	el.server.wg.Add(1)
	return
}

sidecar proxy 的超時是60s,就算我們要設定keepAlive的超時時間也要大于60s,避免sidecar proxy使用了我們關閉的連接,
(但是這在網路不穩定的情況下會有問題,如果發生HA Failover 然后在一定小概率的心跳視窗內,服務狀態并沒有傳遞到注冊中心,導致sidecar proxy重用了之前的http長連接,這其實也是個權衡,如果每次都檢查連接狀態一定會影響性能,)

這里有個好奇問題,http是如何感知到四層tcp的狀態,如何將Context.cancel的事件傳遞上來的,我們來順便研究下,

type conn struct {
	// server is the server on which the connection arrived.
	// Immutable; never nil.
	server *Server

	// cancelCtx cancels the connection-level context.
	cancelCtx context.CancelFunc
}
func (c *conn) serve(ctx context.Context) {
	
	// HTTP/1.x from here on.
	
	ctx, cancelCtx := context.WithCancel(ctx)
	c.cancelCtx = cancelCtx
	defer cancelCtx()

	c.r = &connReader{conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

	for {
		w, err := c.readRequest(ctx)

		if !w.conn.server.doKeepAlives() {
			// We're in shutdown mode. We might've replied
			// to the user without "Connection: close" and
			// they might think they can send another
			// request, but such is life with HTTP/1.1.
			return
		}

		if d := c.server.idleTimeout(); d != 0 {
			c.rwc.SetReadDeadline(time.Now().Add(d))
			if _, err := c.bufr.Peek(4); err != nil {
				return
			}
		}
		c.rwc.SetReadDeadline(time.Time{})
	}
}
// handleReadError is called whenever a Read from the client returns a
// non-nil error.
//
// The provided non-nil err is almost always io.EOF or a "use of
// closed network connection". In any case, the error is not
// particularly interesting, except perhaps for debugging during
// development. Any error means the connection is dead and we should
// down its context.
//
// It may be called from multiple goroutines.
func (cr *connReader) handleReadError(_ error) {
	cr.conn.cancelCtx()
	cr.closeNotify()
}

// checkConnErrorWriter writes to c.rwc and records any write errors to c.werr.
// It only contains one field (and a pointer field at that), so it
// fits in an interface value without an extra allocation.
type checkConnErrorWriter struct {
	c *conn
}

func (w checkConnErrorWriter) Write(p []byte) (n int, err error) {
	n, err = w.c.rwc.Write(p)
	if err != nil && w.c.werr == nil {
		w.c.werr = err
		w.c.cancelCtx()
	}
	return
}

其實tcp的狀態不是通過主動事件觸發告訴上層http的,而是每當http主動去發現,

read時使用connReader來感知tcp狀態,writer時使用checkConnErrorWriter物件來感知tcp狀態,然后通過server.conn物件中的cancelCtx來遞回傳遞,

type conn struct {
	// server is the server on which the connection arrived.
	// Immutable; never nil.
	server *Server

	// cancelCtx cancels the connection-level context.
	cancelCtx context.CancelFunc
}

總結

此次故障排查了整整兩天半,很多點是需要去反思和優化的,

1.所有的網路呼叫沒有拋出最原始error資訊,(經過加工之后的日志會嚴重誤匯入,)
2.超時時間的設定未能起到作用,未經過完整的壓測和故障演練,所以超時時間很容易無效,
3.內外網域名沒有隔離,需要區分內外網呼叫,做好環境隔離,
4.http服務器本身的超時沒有設定,如果程式內部出現問題導致處理超時,并發會把服務器拖垮,
5.對云上的呼叫鏈路和網路架構需要非常熟悉,這樣才能快速定位問題,

其實系統一旦上云之后整個網路架構變得復雜,干擾因素太多,排查也會面臨比較大的依賴,監控告警覆寫面和基數也比較大很難察覺到個別業務線,(其實有些問題根本找不到答案,)
所有無法復現的故障是最難排查的,因為只能事后靠證據一環環解釋,涉及到網路問題情況就更加復雜,

作者:王清培(趣頭條 Tech Leader)

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/63359.html

標籤:Go

上一篇:【go學習筆記】二、變數、常量【連載】

下一篇:go-面向物件編程(下)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more