詳解 Seata Golang 客戶端 AT 模式及其使用

源碼seata-golang

概述

  我們知道 Seata Java Client 的 AT 模式,通過代理數據源,實現了對業務代碼無侵入的分佈式事務協調機制,將與 Transaction Coordinator (TC) 交互的邏輯、Commit 的邏輯、Rollback 的邏輯,隱藏在切面和代理數據源相應的代碼中,使開發者無感知。那如果這個方法,要用 Golang 來實現一遍,應該如何操作呢?關於這個問題,我想了很久,最初的設想是,對 database/sql 的 mysql driver 進行增強,在對包 github.com/go-sql-driver/mysql 研究了一段時間后,還是沒有頭緒,不知如何下手,最後轉而增強 database/sql 包。由於 AT 模式必須保證本地事務的正確處理,在具體業務開發時,首先要通過 db.Begin() 獲得一個 Tx 對象,然後再 tx.Exec() 執行數據庫操作,最後 tx.Commit() 提交或 tx.Rollback() 回滾。這種處理方式算是一個 Golang 數據庫事務處理的基本操作。 所以對 database/sql 的增強,我們重點關注這幾個方法 db.Begin()tx.Exec()tx.Commit()tx.Rollback

事務提交、回滾

  通過 Seata Java Client 的相關代碼,我們知道,在本地事務提交的時候,主要是將分支事務註冊到 TC 上,並將數據庫操作產生的 undoLog 一起寫入到 undoLog 表;本地事務回滾的時候,需要將分支事務(即本地事務)的執行狀態報告給 TC,使 TC 好知道是否通知參与全局事務的其他分支回滾。

func (tx *Tx) Commit() error {
        //註冊分支事務
	branchId,err := tx.register()
	if err != nil {
		return errors.WithStack(err)
	}
	tx.tx.Context.BranchId = branchId

	if tx.tx.Context.HasUndoLog() {
                //將 undoLog 寫入 undoLog 表
		err = manager.GetUndoLogManager().FlushUndoLogs(tx.tx)
		if err != nil {
			err1 := tx.report(false)
			if err1 != nil {
				return errors.WithStack(err1)
			}
			return errors.WithStack(err)
		}
		err = tx.tx.Commit()
		if err != nil {
			err1 := tx.report(false)
			if err1 != nil {
				return errors.WithStack(err1)
			}
			return errors.WithStack(err)
		}
	} else {
		return tx.tx.Commit()
	}
	if tx.reportSuccessEnable {
		tx.report(true)
	}
	tx.tx.Context.Reset()
	return nil
}

  db.Begin() 會產生一個 Tx 對象,tx.Exec() 會產生 undoLog,tx.Commit() 將 undoLog 刷到數據庫中。那麼 undoLog 保存到哪裡呢?答案是 TxContext 中。

type TxContext struct {
	*context.RootContext
	Xid string
	BranchId int64
	IsGlobalLockRequire bool

	LockKeysBuffer *model.Set
	SqlUndoItemsBuffer []*undo.SqlUndoLog
}

  Commit() 方法中的 tx.tx.Context,第一個 tx 是封裝的 Tx 對象,第二個 tx 是 database/sql 的 Tx,tx.tx.Context 則是 TxContext。UndoLogManager 則是操作 undoLog 的核心對象,處理 undoLog 的插入、刪除,並查詢出 undoLog 用於回滾。

func (tx *Tx) Rollback() error {
	err := tx.tx.Rollback()
	if tx.tx.Context.InGlobalTransaction() && tx.tx.Context.IsBranchRegistered() {
                // 報告 TC 分支事務執行失敗
		tx.report(false)
	}
	tx.tx.Context.Reset()
	return err
}

  通過上面的代碼呢,我們知道增強型 Tx 對象需要向 TC 註冊分支事務,並報告分支事務的執行狀態,相應代碼如下:

func (tx *Tx) register() (int64,error) {
	return dataSourceManager.BranchRegister(meta.BranchTypeAT,tx.tx.ResourceId,"",tx.tx.Context.Xid,
		nil,tx.tx.Context.BuildLockKeys())
}

func (tx *Tx) report(commitDone bool) error {
	retry := tx.reportRetryCount
	for retry > 0 {
		var err error
		if commitDone {
			err = dataSourceManager.BranchReport(meta.BranchTypeAT, tx.tx.Context.Xid, tx.tx.Context.BranchId,
				meta.BranchStatusPhaseoneDone,nil)
		} else {
			err = dataSourceManager.BranchReport(meta.BranchTypeAT, tx.tx.Context.Xid, tx.tx.Context.BranchId,
				meta.BranchStatusPhaseoneFailed,nil)
		}
		if err != nil {
			logging.Logger.Errorf("Failed to report [%d/%s] commit done [%t] Retry Countdown: %d",
				tx.tx.Context.BranchId,tx.tx.Context.Xid,commitDone,retry)
			retry = retry -1
			if retry == 0 {
				return errors.WithMessagef(err,"Failed to report branch status %t",commitDone)
			}
		}
	}
	return nil
}

  和 TC 進行通信的主要邏輯還是在 DataSourceManager 裏面。AT 模式涉及的兩個關鍵對象 DataSourceManager、UndoLogManager 就浮出水面。一個用於遠程 TC 交互,一個用於本地數據庫處理。

事務執行

func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
	var parser = p.New()
        // 解析業務 sql
	act,_ := parser.ParseOneStmt(query,"","")
	deleteStmt,isDelete := act.(*ast.DeleteStmt)
	if isDelete {
		executor := &DeleteExecutor{
			tx:            tx.tx,
			sqlRecognizer: mysql.NewMysqlDeleteRecognizer(query,deleteStmt),
			values:        args,
		}
		return executor.Execute()
	}

	insertStmt,isInsert := act.(*ast.InsertStmt)
	if isInsert {
		executor := &InsertExecutor{
			tx:            tx.tx,
			sqlRecognizer: mysql.NewMysqlInsertRecognizer(query,insertStmt),
			values:        args,
		}
		return executor.Execute()
	}

	updateStmt,isUpdate := act.(*ast.UpdateStmt)
	if isUpdate {
		executor := &UpdateExecutor{
			tx:            tx.tx,
			sqlRecognizer: mysql.NewMysqlUpdateRecognizer(query,updateStmt),
			values:        args,
		}
		return executor.Execute()
	}

	return tx.tx.Tx.Exec(query,args)
}

  執行業務 sql,並生成 undoLog 的關鍵,在於識別業務 sql 執行了什麼操作:插入?刪除?修改?這裏使用 tidb 的 sql parser 去解析業務 sql,再使用相應的執行器去執行業務 sql,生成 undoLog 保存在 Tx_Context 中。

事務開啟

  db.Begin() 返回增強型的 Tx 對象。

func (db *DB) Begin(ctx *context.RootContext) (*Tx,error) {
	tx,err := db.DB.Begin()
	if err != nil {
		return nil,err
	}
	proxyTx := &tx2.ProxyTx{
		Tx:         tx,
		DSN:        db.conf.DSN,
		ResourceId: db.GetResourceId(),
		Context:    tx2.NewTxContext(ctx),
	}
	return &Tx{
		tx: proxyTx,
		reportRetryCount: db.conf.ReportRetryCount,
		reportSuccessEnable: db.conf.ReportSuccessEnable,
	},nil
}

seata-golang at 模式的使用

sample 代碼

  • 首先執行 scripts 腳本,初始化數據庫
    如果之前沒有初始化過 seata 數據庫,先執行 seata-golang/scripts/server/db/mysql.sql 腳本
  • 修改 dsn 數據庫配置,修改下列文件:
seata-golang/tc/app/profiles/dev/config.yml
seata-golang/samples/at/product_svc/conf/client.yml
seata-golang/samples/at/product_svc/conf/client.yml
  • 將下列文件中的 configPath 修改為 client.yml 配置文件的路徑
seata-golang/samples/at/product_svc/main.go
seata-golang/samples/at/order_svc/main.go
seata-golang/samples/at/aggregation_svc/main.go
  • 依次運行 tc、order_svc、product_svc、aggragation_svc,訪問下列地址開始測試:
http://localhost:8003/createSoCommit
http://localhost:8003/createSoRollback

TC 啟動參考參与 Seata 社區到 go 與 Seata 的邂逅

seata-golang 後續安排

  接下來不打算再增加新的 feature。Java 版 Seata 畢竟發展了一年多時間,並且有很多社區成員一起維護,Go 版本目前主要是我在開發,時間不到2個月,現有的代碼,僅是完成了框架,還需要大量優化,改bug,後續的工作重心在於使 seata-golang 穩定運行,生產可用,希望對分佈式事務感興趣且對 Go 感興趣的同學一起加入進來,一起做些事情。進入微信群,請加我微信:scottlewis,備註進群。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

Kubernetes日誌的6個最佳實踐

本文轉自Rancher Labs

Kubernetes可以幫助管理部署在Pod中的上百個容器的生命周期。它是高度分佈式的並且各個部分是動態的。一個已經實現的Kubernetes環境通常涉及帶有集群和節點的幾個系統,這些系統託管着幾百個容器,而這些容器不斷地基於工作負載啟動、毀滅。

當在Kubernetes中處理大量的容器化應用和工作負載時,主動進行監控和調試錯誤十分重要。在容器、節點或集群級別,這些錯誤都能在容器中看到。Kubernetes的日誌機制是一個十分重要的組件,可以用來管理和監控服務以及基礎設施。在Kubernetes中,日誌可以讓你跟蹤錯誤甚至可以調整託管應用程序的容器的性能。

配置stdout(標準輸出)和stderr(標準錯誤)數據流

圖片來源:kubernetes.io

第一步是理解日誌是如何生成的。通過Kubernetes,日誌會被發送到兩個數據流——stdout和stderr。這些數據流將寫入JSON文件,並且此過程由Kubernetes內部處理。你可以配置將哪個日誌發送到哪個數據流中。而一個最佳實踐的建議是將所有應用程序日誌都發送到stdout並且所有錯誤日誌都發送到stderr。

決定是否使用Sidecar模型

Kubernetes建議使用sidecar容器來收集日誌。在這一方法中,每個應用程序容器將有一個鄰近的“streaming容器”,該容器將會將所有日誌流傳輸到stdout和stderr。Sidecar模型可以幫助避免在節點級別公開日誌,並且它可以讓你控制容器級別的日誌。

然而,這一模型的問題是它能夠適用於小容量的日誌記錄,如果面對大規模的日誌記錄,可能會造成大量資源被佔用。因此,你需要為每個正在運行的應用程序容器單獨運行一個日誌容器。在Kubernetes文檔中,將sidecar模型形容為“幾乎沒有很大的開銷”。需要由你決定是否嘗試這一模型並在選擇它之前查看它所消耗的資源類型。

替代方法是使用日誌代理,該代理在節點級別收集日誌。這樣可以減少開銷,並確保安全地處理日誌。Fluentd已成為大規模聚合Kubernetes日誌的最佳選擇。它充當Kubernetes與你要使用Kubernetes日誌的任意數量的端點之間的橋樑。你也可以選擇像Rancher這樣的Kubernetes管理平台,在應用商店已經集成了Fluentd,無需從頭開始安裝配置。

確定Fluentd可以更好地匯總和路由日誌數據后,下一步就是確定如何存儲和分析日誌數據。

選擇日誌分析工具:EFK或專用日誌記錄

傳統上,對於以本地服務器為中心的系統,應用程序日誌存儲在系統中的日誌文件中。這些文件可以在定義的位置看到,也可以移動到中央服務器。但是對於Kubernetes,所有日誌都發送到磁盤上/var/log的JSON文件中。這種類型的日誌聚合併不安全,因為節點中的Pod可以是臨時的也可以是短暫的。刪除Pod時,日誌文件將丟失。如果你需要嘗試對部分日誌數據丟失進行故障排除時,這可能很難。

Kubernetes官方推薦使用兩個選項:將所有日誌發送到Elasticsearch,或使用你選擇的第三方日誌記錄工具。同樣,這裏存在一個潛在的選擇。採用Elasticsearch路線意味着你需要購買一個完整的堆棧,即EFK堆棧,包括Elasticsearch、Fluentd和Kibana。每個工具都有其自己的作用。如上所述,Fluentd可以聚合和路由日誌。Elasticsearch是分析原始日誌數據並提供可讀輸出的強大平台。Kibana是一種開源數據可視化工具,可以從你的日誌數據創建漂亮的定製dashboard。這是一個完全開源的堆棧,是使用Kubernetes進行日誌記錄的強大解決方案。

儘管如此,有些事情仍然需要牢記。Elasticsearch除了由名為Elastic的組織構建和維護,還有龐大的開源社區開發人員為其做貢獻。儘管經過大量的實踐檢驗,它可以快速、強大地處理大規模數據查詢,但在大規模操作時可能會出現一些問題。如果採用的是自我管理(Self-managed)的Elasticsearch,那麼需要有人了解如何構建大規模平台。

替代方案是使用基於雲的日誌分析工具來存儲和分析Kubernetes日誌。諸如Sumo Logic和Splunk等工具都是很好的例子。其中一些工具利用Fluentd來將日誌路由到他們平台,而另一些可能有它們自己的自定義日誌代理,該代理位於Kubernetes中的節點級別。這些工具的設置十分簡單,並且使用這些工具可以花費最少的時間從零搭建一個可以查看日誌的dashboard。

使用RBAC控制對日誌的訪問

在Kubernetes中身份驗證機制使用的是基於角色訪問控制(RBAC)以驗證一個用戶的訪問和系統權限。根據用戶是否具有特權(authorization.k8s.io/decision )並向用戶授予原因(authorization.k8s.io/reason ),對在操作期間生成的審核日誌進行註釋。默認情況下,審核日誌未激活。建議激活它以跟蹤身份驗證問題,並可以使用kubectl進行設置。

保持日誌格式一致

Kubernetes日誌由Kubernetes架構中不同的部分生成。這些聚合的日誌應該格式一致,以便諸如Fluentd或FluentBit的日誌聚合工具更易於處理它們。例如,當配置stdout和stderr或使用Fluentd分配標籤和元數據時,需要牢記這一點。這種結構化日誌提供給Elasticsearch之後,可以減少日誌分析期間的延遲。

在日誌收集守護進程上設置資源限制

由於生成了大量日誌,因此很難在集群級別上管理日誌。DaemonSet在Kubernetes中的使用方式與Linux類似。它在後台運行以執行特定任務。Fluentd和filebeat是Kubernetes支持的用於日誌收集的兩個守護程序。我們必須為每個守護程序設置資源限制,以便根據可用的系統資源來優化日誌文件的收集。

結 論

Kubernetes包含多個層和組件,因此對其進行良好地監控和跟蹤能夠讓我們在面對故障時從容不迫。Kubernetes鼓勵使用無縫集成的外部“Kubernetes原生”工具進行日誌記錄,從而使管理員更輕鬆地獲取日誌。文章中提到的實踐對於擁有一個健壯的日誌記錄體繫結構很重要,該體繫結構在任何情況下都可以正常工作。它們以優化的方式消耗計算資源,並保持Kubernetes環境的安全性和高性能。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

8.38萬元起同級別最精緻的SUV車主怎麼說

但是就是動力不咋的,不符合主流的1。5T發動機的動力表現。這估計就是在你看得見摸得着的地方做的很好,但是內在就不好說了。不滿意的:點煙器和車載電腦的USB接口在扶手箱下面的一個小盒子里,使用極不方便,儲物空間太小,畢竟這個車子的尺寸比較小。

其實剛開始寫這篇文章我是拒絕的,因為H2s才剛上市,車主根本不好找,但是很多讀者都想進一步了解一下H2s,想知道車主的使用情況。沒辦法,編者只有硬着頭皮,各種找朋友問同學,費了好大的勁才找到了一些車主,下面一起來看看這些車主對他們H2s的評價。

長城汽車-哈弗H2s

指導價:8.38-10.28萬

哈弗H2s是在11月18日廣州車展上市的,憑藉更亮麗的外觀和更精緻的內飾,再加上新的動力系統贏得了很大的關注度。

車主:雙截棍

購買車型:2017款 藍標 1.5T 手動精英型

裸車購買價:8.78萬

最滿意的地方:外觀很好看,我買車最看重的是顏值,當時對H2s一見傾心。風琴式的中控台個人認為很不錯,有點天馬星空的感覺。不過就是不好打理。萬年不變的1.5T發動機提速較慢,不過畢竟是帶渦輪的發動機,速度上來使勁踩油門提速還是有一點的。我身高1.75米,體重150斤,膝蓋老是碰到車前面的塑料蓋,所以大長腿的慎買啊!

不滿意的地方:新車異味好大,異響暫時沒發現,空間比較小,車子沒有胎壓監測。

車主:馬小虎

購買車型:2017款 紅標 1.5T 手動精英型

裸車購買價:8.88萬

最滿意的地方:當初在紅藍標之間猶豫,後來覺得年輕么。就要選個霸氣的外觀,大嘴的紅標也許更適合自己,雖然紅標比藍標貴了1000塊錢,但是卻多了胎壓監測裝置、后視鏡電動摺疊,這兩個配置還是值1000塊錢的。H2s的整體配置挺高的,內飾做工也比較好。但是就是動力不咋的,不符合主流的1.5T發動機的動力表現。這估計就是在你看得見摸得着的地方做的很好,但是內在就不好說了。

不滿意的:點煙器和車載電腦的USB接口在扶手箱下面的一個小盒子里,使用極不方便,儲物空間太小,畢竟這個車子的尺寸比較小。車子跑了300公里,油耗9L,新車沒有太大的參考性。

車主:狗子

購買車型:2017款 紅標 1.5T 自動精英型

裸車購買價:9.88萬

最滿意的地方:價格很實惠,配置比較高,內飾很精緻,做工也很好。其中變速箱為格特拉克的7速濕式雙離合變速箱。十萬的車子用上了濕式雙離合,看來長城這次是真的下本了。以前備受吐槽的哈弗變速箱終於在H2s上得到了改善。這款變速箱反應速度很快,邏輯清晰,平順性很好,底盤調教很紮實,行駛質感很好,但是還是受到那個老舊的發動機的拖累,提速較差。如果換個1.5T發動機,體驗會好很多吧!

不滿意的:1.5T發動機該換了,座椅靠前了安全帶就跑到了後面,懸架有點硬,兒童座椅的接口處理的很粗糙。油耗目前不到9L。

總結:H2s用上了7速濕式雙離合變速箱,這一點值得表揚,同時外觀靚麗,配置較高,內飾精緻,行駛質感較好。但是1.5T發動機確實該換了。如果很在乎動力,那麼H2s會讓你失望的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

國產SUV稱霸!最接地氣的銷量榜解析

期望非常大,如果又遇到黑它的人,請大家備好磚頭,你懂的。

哈弗H6的銷量如此火爆,下個月還能賣7萬輛嗎?期望非常大,如果又遇到黑它的人,請大家備好磚頭,你懂的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

網頁設計最專業,超強功能平台可客製化

價錢低逼格不低,小弟車型這麼屌,叫大哥怎麼混?

由於全系的X1使用了UKL平台,橫置布局的發動機,再加上對車內空間的優化,最終的表現非常出色,加上X1的第二排座椅還可以前後移動和調節靠背角度,空間表現已經超越了其大哥X3。X1 xDrive25Li使用了代號為B48A20Ο0的高功率版2。

在中國的傳統家庭里,有一個非常有趣的現象,如果你有哥哥或者姐姐,一般情況下都不能比哥哥姐姐早結婚,父母要催婚,肯定是先從哥哥姐姐下手,如果弟弟妹妹先結婚了,哥哥姐姐會過上很慘的被催婚生活。

其實在很多領域都存在着這樣的現象,例如考量業績的銷售業,鄉鎮級分銷點的銷量比縣城級分銷點高,那就非常尷尬了。

但是在汽車行業,卻是截然不同,消費者非常樂意看到某車型超越自己的大哥,最典型的例子就是經常被冠以“小S”稱號的奔馳E級。

我們拿同樣是320 L的奔馳E級與S級進行對比,雖然兩者採用了同樣的設計語言,但是在車身尺寸上還是相差甚遠的,奔馳E 320 L 4MATIC的車身尺寸為:5065x1860x1482 mm,軸距:3079 mm,奔馳S 320 L 商務型的車身尺寸為:5250x1899x1494 mm,軸距:3165 mm。

奔馳E級雖然後排座椅的橫向空間和頭部空間不及S級,但是後排的腿部縱向空間表現幾乎一樣,非常出色。奔馳E 320 L 4MATIC指導價:62.98萬,奔馳S 320 L 商務型指導價:93.80萬,兩者同樣擁有后風擋遮陽簾和後排側遮陽簾,E 320L還多出了後排側隱私玻璃。

兩車的指導價雖然相差30.82萬,但是發動機同樣是3.0T雙渦輪增壓V6發動機,E 320 L 4MATIC更是擁有9AT和全時四驅系統,配置上比S 320 L 商務型多出了無鑰匙進入、電動/感應後備廂、方向盤/電動座椅/后視鏡記憶功能,還有自適應巡航、主動剎車等一系列高科技配置。

雖然奔馳E級和S級在氣場上還是有不少差距,但如果讓選擇,還是會選擇E級,因為是配置控,同時E 320的机械品質足以滿足絕大多數情景的需求。

接下來的這個對比更加有看點,寶馬X1 xDrive25Li 豪華型對比寶馬X3 sDrive20i,兩者的指導價分別是43.9萬和42.1萬,價格非常接近,重點是兩車的車身尺寸差距很小,寶馬X1的軸距也只是比X3短了30mm而已。

由於全系的X1使用了UKL平台,橫置布局的發動機,再加上對車內空間的優化,最終的表現非常出色,加上X1的第二排座椅還可以前後移動和調節靠背角度,空間表現已經超越了其大哥X3。

X1 xDrive25Li使用了代號為B48A20Ο0的高功率版2.0T發動機,X3 sDrive20i則是使用N20B20的低功率版2.0T發動機,X1的動力表現比X3要好出不少,而且X1 xDrive25Li是前置適時四驅,X3 sDrive20i只是前置后驅。

在配置方面,X1 xDrive25Li多出了無鑰匙進入系統、電動/感應後備廂方向盤換擋、HUD抬頭显示、GpS導航、藍牙、LED大燈、後排出風口、自動泊車、車道偏離預警等等,第二排座椅還可以前後移動和調節靠背角度。

與奔馳的E級和S級不同,X1 xDrive25Li和X3 sDrive20i在價格上相差無幾,X1使用了新的平台、新的動力總成、新的設計,可以說是把還沒換代的老X3打敗了,動力更強、配置更高。

林肯的MKC和MKX這兩款SUV的情況和X1、X3的情況有點相似,我們拿MKC的2017款 2.3T 四驅總統系列與MKX的2015款 2.0T 兩驅尊享版作對比,兩者的指導價分別是43.88萬和44.98萬。

先來進行配置對比MKC僅多出了方向盤加熱、前排座椅通風、後排座椅加熱、自適應遠近光、感應雨刷、自動泊車入位、自適應巡航、車道偏離預警和併線輔助,兩者在配置上的差異其實並不是很大,主要是因為最低配的MKX配置水平真心不低。

MKC和MKX的外觀內飾設計都非常相似,慶幸的是兩車都還沒有使用林肯MKZ的那個最新前臉設計,它們都很好的保留了林肯家族該有的美式設計美學,霸氣的中網和貫穿式的尾燈非常漂亮。

空間表現並不是它們的優勢,重點是MKC和MKX的內飾氛圍都非常豪華,僅看內飾的話,真的感覺不出MKC定位比MKX低,這就是消費者最喜歡的典型例子:花更少的錢得到更高級的視覺享受。

其實同品牌的雞頭鳳尾之選,還是有不少的,例如日產的軒逸和天籟,新款天籟的外觀設計真是佩服,相信有不少人把天籟硬生生看成軒逸,還有國內即將上市的寶馬5系,無論是設計元素還是各項配置,都在向著7系靠攏。

雖然說上面提到的在很多方面都向著大哥靠攏,但我們還是要理性對待,外觀內飾的設計,還有車輛的配置,這些都能夠做到互相媲美,但是在車輛的行駛品質,動態體驗方面,不同級別的車型還是存在着本質區別的,是否值得購買,就要看你注重車輛的是哪個方面了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

這些國產“豪車”價錢可不豪,十幾萬就能考慮入手了!

觀致身處的這個惡性循環:產品力不足 → 經銷商加盟量少 → 銷量沒起色 → 成本居高不下 → 新產品性價比低 → 潛在經銷商更加望而卻步,如此循環。觀致在期待着一個救世主,挽救頹勢。觀致的全新跨界轎車觀致3 GT指導價為11。

稍有起色的自主車企都慢慢開始轉型,尋求向上發展,要涉足中高端路線,要麼推出高端化產品,要麼就再立門戶、開設新的子品牌來抗衡豐田、福特、大眾等一干合資大咖。

而成功“解決溫飽問題、活下來”的自主品牌,都基本具備了推出中高端品牌的造車技術經驗、品牌簇擁者以及市場營銷基礎,自下而上的往上發展亦無可厚非。

傳祺GM8

相比建立中高端子品牌,推出高端化產品來提升品牌形象的效果並沒有前者來得直接,前車之鑒就有行政級轎車東風A9、GA8,走越野路線的哈弗H9,地方補貼的新能源產物榮威E950,市場表現皆慘淡。當然也不乏市場熱烈的車型,吉利博瑞、傳祺GS8把准了時代的脈搏,一炮走紅,銷量當然理想。

更看好自主車企在推出高端化產品獲得成功后再去涉足中高端子品牌,循序漸進,遵循企業發展規律。期待廣汽傳祺今後能為我們繼續打造高端化產品(GA8、GS8、GM8三足鼎立),甚至乎帶來一個高端子品牌。

東風A9

傳祺GA8

哈弗H9

榮威e950

吉利博瑞

傳祺GS8

觀致可以說是該領域的先行者(紅旗打一開始就沒想過要走量、盈利,姑且不算),剛面世時,被外界大肆宣傳、捧得很高,但由於撿錯了敲門磚,觀致選擇以轎車作為第一款面向消費者的產品,而冷落了受眾面更廣的SUV,加上觀致3自身產品力不足、經銷商營銷不力以及售價偏高,導致最終無人問津、摔得很痛。

觀致5

但我們並不能單單隻看到觀致試水失敗這個表象,認為觀致更起到了帶頭作用,吹響了自主品牌走出國門、邁向高端的號角,一石激起千層浪,寶沃、吉利、長城等車企都紛紛覬覦這塊待開發的市場,哪怕它們選取的路徑、理念不同,但他們都為中華汽車製造業爭一口氣,擺脫以往廉價、低端的形象,從事汽車行業的甚是欣慰。

寶沃BX7

下面來為大家介紹自主高端品牌剛推出以及即將推出的量產車,一同拭目以待。

LYNK & CO

LYNK & CO的品牌發布會地點在德國柏林,現場充滿前衛、時尚元素。吉利集團高級設計副總裁彼得·霍布里,過往阿斯頓馬丁、捷豹、路虎和沃爾沃等品牌不少作品都出自他手,博瑞和博越皆由他帶領的設計團隊完成, LYNK & CO 01同樣如此。彼得·霍布里:“車型應當風格鮮明、引人注目,能夠吸引包括中國、歐洲和美國在內的全球消費者。”反正是被吸引住了。

分體式大燈組,LED光帶式日間行車燈,L形尾燈設計前瞻、個性,糅合到一起卻相當和諧,毫不違和,延伸至引擎蓋上的LED日間行車燈靈感源自北歐上空的極光,絢麗奪目,不得不感嘆設計團隊的功架。

相信這款車將來量產後會消化不少來自沃爾沃的技術,尤其在動力總成和主動安全技術方面。(需要指出的是,吉利僅僅是收購了沃爾沃乘用車,而不是指整個沃爾沃集團,扮演着控股的角色,並未達到為所欲為的地步。)

WEY

WEY:長城的高端品牌WEY就這樣低調、悄無聲息出現在我們眼前,相對LYNK & CO要低調,魏建軍講到的:“民營企業沒有後路才能發展”,表達了長城一往無前、決意要做出成績的堅定決心,決意破釜沉舟。

哈弗系列的動力和底盤總成缺乏新意,W01的底盤結構與哈弗H7十分相像,就連動力總成也是2.0T搭配7速雙離合變速箱。而W02的底盤則與哈弗H6相同。

W 01

W 02

核心三大件沒升級、行駛質感沒得到提升的話,再多的噱頭也只是徒勞,希望WEY最後出來的產品不會令失望。

觀致

奇瑞與以色列集團各持股50%的方式成立觀致,給觀致帶來一定的合資背景,但銷量始終不如人意,在歐洲銷售期間更是無人問津。觀致身處的這個惡性循環:產品力不足 → 經銷商加盟量少 → 銷量沒起色 → 成本居高不下 → 新產品性價比低 → 潛在經銷商更加望而卻步,如此循環。觀致在期待着一個救世主,挽救頹勢。

觀致的全新跨界轎車觀致3 GT指導價為11.09-13.99萬元。增加了一套跨界風格的車身套件,如前後的保險杠下護板、運動包圍、黑色輪眉,相信年輕人會對其青睞有加。

觀致3 GT採用1.6T發動機,最大功率為156ps,峰值扭矩230N·m,參數要比起轎車版要高點,匹配6速手動或6速雙離合變速箱。

希望觀致能推出更多競爭力強的產品去豐富產品線、整頓經營,力挽狂瀾。挫折並不像江河那樣不可逾越,而是前進的動力,觀致彆氣餒!

寶沃

寶沃BX5在2016年廣州車展正式亮相。寶沃BX5定位緊湊型SUV,長寬高分別是4483×1876×1677mm,軸距2685mm,在同級別車型中,它是屬於規格相對比較大的類型。

新車依舊採用寶沃家族式的多邊形格柵設計,后包圍的裝飾件帶有濃濃地運動氣息,寶沃這次將目標人群瞄準在年輕人。

寶沃BX5將提供1.4T混動和1.8T汽油發動機版本。1.8T發動機最大功率190ps,峰值扭矩280N·m,搭配6速手自一體變速箱,如無意外將會是BX7上那副AISIN愛信6AT。預計寶沃還將推出BX7 TS和BX6 TS,進一步豐富產品線,實現真正的品牌復興。

寶沃BX7 TS

左為寶沃BX6 TS,右為寶沃BX5

總結:很慶幸出生在這個時代,能見證着自主汽車的起步,在市場摸爬滾打,獨當一面再到後來往上發展,開拓高端品牌、突破自我,自豪感油然而生。但不少人卻對自主汽車嗤之以鼻,用他們有限的認知、先入為主地去詆毀它們,這是所不能接受的。

自主汽車目前所經歷的發展階段是一種歷史的必然,哪怕是鍵盤車神們跪舔的德系、日系亦同樣經歷過,給自主汽車更多耐心和鼓勵,它一定會用更多低價格、高品質、效費比更理想的產品來回報國民,證明我大中華也是汽車強國。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

7.89萬起的SUV配置竟然這麼高,教授看好他的銷量喔!

新車落地時,可以在選配廠家幫你加裝的行車記錄儀,集合在駕駛艙的后視鏡裏面,通過中控台的按鍵,可以將畫面轉到這個9寸彩色大屏幕上,可以手動操作拍照和錄製功能,非常方便。除了最低配之外,其他配置都是真皮包裹座椅,高配則還有座椅加熱和電動調節功能,乘坐起來給人的感覺還可以,雖然不能說包裹性很強,但是舒適性還是夠的,至於後排空間,如果一個正常體型的180CM身高小伙子坐進去,大概腿部空間還能夠有近兩拳的距離。

自從上次發完森雅R7的產品介紹后,後台很多粉絲都在追問這車新搭載的自動擋究竟開起來怎麼樣,愛信的6AT是否能夠做到眾望所歸?諸如此類問題。沒錯,這一期將詳細地往“體驗”這方面,來講一講這台車,除了價格很實惠之外,是否真的能夠為我們帶來些什麼收穫?

森雅R7作為一汽的產品,在手動擋上市時,就已經有這3個亮點,一個是超高顏值,一個是同級中最長的軸距,最後一個則是越級的配置,定價便宜,非常符合三四線城市人們購車的需求,所以在當時就已經備受粉絲關注,然而這次自動擋6AT的到來,更為許多“不會開手動擋”的消費者解決了另一個難題。

貌似在中國,有着這麼一個不成文的規定,但凡是新車,首先一定要在外觀上足夠吸引人,才能稱為成功了一半,眾泰走抄襲之路贏得了群眾的眼球,陸風靠着路虎的外觀檔次一下高了不少,而作為一汽旗下的森雅R7,則靠着與眾不同的雄鷹外觀,也贏得了許多人的關注,大燈犀利,整個車頭看起來讓人有種舒服的感覺,腰線優雅,作為一款小型SUV,整體風格小巧儒雅。

全車前大燈都採用鹵素光源,但都帶有日間行車燈,視覺效果蠻不錯,17英寸鋁合金輪轂同樣為全系標配,加上紅色的剎車卡鉗,運動感一下子上來了,在之前,後台就有很多粉絲在評論說,車子顏值是不錯,不過可惜就可惜在車標上,但覺得現在自主品牌實力不斷地雄厚,只要車子質量好,車標改不改,都是事後的問題了,不必過於糾結。

其實買車群眾可以分為兩種人,一種是堅持買自主品牌車,一種是只考慮合資車,但你瞧我們自主品牌的內飾風格,不說那些抄襲寶馬奧迪內飾的其他牌子,就拿森雅R7的內飾作為例子,7.89萬自動擋版本,能夠有這種設計還是少有的,按鍵實在,使用起來非常簡單,屏幕也夠大,觸控反應靈敏。

除了自動舒適型之外,其他自動車型全配有真皮方向盤,ESp車身穩定系統,上坡輔助,定速巡航,多功能方向盤,發動機啟停裝置,頂配則多了全景攝像頭,雖然這些每次都會說,但有了就非常不同,買配置也是我們中國人選車所考慮的因素之一,凡是熱門的車,肯定有着一套非常齊整的配置,才能稱之為性價比高。

新車落地時,可以在選配廠家幫你加裝的行車記錄儀,集合在駕駛艙的后視鏡裏面,通過中控台的按鍵,可以將畫面轉到這個9寸彩色大屏幕上,可以手動操作拍照和錄製功能,非常方便。

除了最低配之外,其他配置都是真皮包裹座椅,高配則還有座椅加熱和電動調節功能,乘坐起來給人的感覺還可以,雖然不能說包裹性很強,但是舒適性還是夠的,至於後排空間,如果一個正常體型的180CM身高小伙子坐進去,大概腿部空間還能夠有近兩拳的距離。

先說說這台1.6L的自然吸氣發動機,技術是基於大眾EA系列發動機自主研發而成的,具有進氣側的可變氣門正時技術,最大功率116匹馬力,峰值扭矩155牛米,在城市中跟車行駛的話,搭配着油門踏板,初段給人的加速感還是有的,反應积極,匹配着日本愛信第三代6速手自一體變速器,加速感覺還是比較平順。

當繼續深踩油的時候,中後段的動力則有點力不從心了,但畢竟這是一台1.6L自然吸氣發動機,並沒有像其他發動機一樣有渦輪增壓器的介入,但1.6L自然吸氣+愛信成熟的6AT,可以很好地控制油耗表現。

在平時的道路上,這款自動變速箱在升擋的節奏上,還是能夠與我們駕駛員做到節奏一致的,而在上坡的時候,變速箱則退到低速擋,同時將發動機轉速升到3000左右,將這股力氣供給前輪,動力不會很突兀,高轉數難免發動機噪音會有一些,這是難免的,但在平時的駕駛中,經過隔音棉,噪音得到了挺好的控制,不會讓人感覺到這款國產車一加速就立馬掉檔次。

底盤懸架則採用前麥弗遜后扭力梁設計,這種設計無論是在這個價位,還是這個等級,都非常之常見,在行駛過程中,來自底盤的噪音並不大,減振器在濾振方面,確實挺到位,開起來確實還是有質感的,這種調校給人一種厚實的感覺。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

程序員實用JDK小工具歸納,工作用得到

在JDK的安用裝目錄bin下,有一些有非常實用的小工具,可用於分析JVM初始配置、內存溢出異常等問題,我們接下來將對些常用的工具進行一些說明。

JDK小工具簡介

在JDK的bin目錄下面有一些小工具,如javac,jar,jstack,jstat等,在日常編譯運行過程中有着不少的“額外”功能,那麼它們是怎麼工作的呢?雖然這些文件本身已經被編譯成可執行二進制文件了,但是其實它們的功能都是由tools.jar這個工具包(配合一些dll或者so本地庫)完成的,每個可執行文件都對應一個包含main函數入口的java類(有興趣可以閱讀openJDK相關的源碼,它們的對應關係如下(更多可去openJDK查閱):

javac com.sun.tools.javac.Main
jar sun.tools.jar.Main
jps sun.tools.jps.Jps
jstat sun.tools.jstat.Jstat
jstack    sun.tools.jstack.JStack
...

tools.jar的使用

我們一般開發機器上都會安裝JDK+jre,這時候,要用這些工具,直接運行二進制可執行文件就行了,但是有時候,機器上只有jre而沒有JDK,我們就無法用了么?

如果你知道如上的對應關係的話,我們就可以”構造”出這些工具來(當然也可以把JDK安裝一遍,本篇只是介紹另一種選擇),比如我們編寫

//Hello.java
public class Hello{
    public static void main(String[] args)throws Exception{
        while(true){
            test1();
            Thread.sleep(1000L);
        }
    }
    public static void test1(){
        test2();
    }
    public static void test2(){
        System.out.println("invoke test2");
    }
}

可以驗證如下功能轉換關係

1.編譯源文件:

javac Hello.java => java -cp tools.jar com.sun.tools.javac.Main Hello.java

結果一樣,都可以生成Hello.class文件
然後我們開始運行java -cp . Hello

2.查看java進程:

jps => java -cp tools.jar sun.tools.jps.Jps

結果一樣,如下:

4615 Jps
11048 jar
3003 Hello

3.動態查看內存:

jstat -gcutil 3003 100 3 => java -cp tools.jar sun.tools.jstat.Jstat -gcutil 3003 100 3

發現結果是一樣的

  S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000

4.查看當前運行棧信息
正常情況,執行如下命令結果也是一樣,可以正常輸出

jstack 3003 =》 java -cp tools.jar sun.tools.jstack.JStack 3003

但是有的jre安裝不正常的時候,會報如下錯誤

Exception in thread "main" java.lang.UnsatisfiedLinkError: no attach in java.library.path

這是因為jstack的運行需要attach本地庫的支持,我們需要在系統變量裏面配置上其路徑,假如路徑為/home/JDK/jre/bin/libattach.so
命令轉換成

jstack 3003 =》 java -Djava.library.path=/home/JDK/jre/bin -cp tools.jar sun.tools.jstack.JStack 3003

就可以實現了
在linux系統中是libattach.so,而在windows系統中是attach.dll,它提供了一個與本機jvm通信的能力,利用它可以與本地的jvm進行通信,許多java小工具就可能通過它來獲取jvm運行時狀態,也可以對jvm執行一些操作

attach使用

1. 編寫agent.jar代理包

  • 編寫一個Agent類
//Agent.java
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        System.out.println("agent : " + args);
    }
}
  • 編譯Agent
java -cp tools.jar com.sun.tools.javac.Main Agent.java
//或者
javac Agent.java
  • 再編manifest.mf文件
//manifest.mf
Manifest-Version: 1.0
Agent-Class: Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
  • 把Agent.class和manifest.mf進行打包成agent.jar
java -cp tools.jar sun.tools.jar.Main -cmf manifest.mf agent.jar Agent.class
//或者
jar -cmf manifest.mf agent.jar Agent.class

2.attach進程

  • 編寫如下attach類,編譯並執行
//AttachMain.java
public class AttachMain {
    public static void main(String[] args) throws Exception {
        com.sun.tools.attach.VirtualMachine vm = com.sun.tools.attach.VirtualMachine.attach(args[0]);
        vm.loadAgent("agent.jar", "inject params");
        vm.detach();
    }
}
  • 編譯:
java -cp tools.jar com.sun.tools.javac.Main -cp tools.jar AttachMain.java
//或者
javac -cp tools.jar AttachMain.java
  • 執行attach
java -cp .:tools.jar AttachMain 3003
  • 查看Hello進程有如下輸出:
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
agent : inject params
invoke test2

說明attach成功了,而且在目標java進程中引入了agent.jar這個包,並且在其中一個線程中執行了manifest文件中agentmain類的agentmain方法,詳細原理可以見JVMTI的介紹,例如oracle的介紹

3. 用attach製作小工具

  • 寫一個使進程OutOfMemory/StackOverFlow的工具
    有了attach的方便使用,我們可以在agentmain中新起動一個線程(為避免把attach線程污染掉),在裏面無限分配內存但不回收,就可以產生OOM或者stackoverflow
    代碼如下:
//Agent.java for OOM
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        new Thread() {
            @Override
            public void run() {
                java.util.List<byte[]> list = new java.util.ArrayList<byte[]>();
                try {
                    while(true) {
                        list.add(new byte[100*1024*1024]);
                        Thread.sleep(100L);
                    }
                } catch (InterruptedException e) {
                }
            }
        }.start();
    }
}
//Agent.java for stackoverflow
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        new Thread() {
            @Override
            public void run() {
                stackOver();
            }
            private void stackOver(){
                stackOver();
            }
        }.start();
    }
}

當測試OOM的時候,hello進程的輸出為:

invoke test2
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
        at Agent$1.run(Agent.java:9)
invoke test2
invoke test2
invoke test2

說明發生OOM了, 但是OOM線程退出了,其它線程還在正常運行。

如果我們需要進程在OOM的時候產生一些動作,我們可以在進程啟動的時候增加一些OOM相關的VM參數

  • OOM的時候直接kill掉進程:-XX:OnOutOfMemoryError=”kill -9 %p”
    結果如下:
invoke test2
invoke test2
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="kill -9 %p"
#   Executing /bin/sh -c "kill -9 26829"...
Killed
  • OOM的時候直接退出進程:-XX:+ExitOnOutOfMemoryError
    結果如下:
invoke test2
invoke test2
Terminating due to java.lang.OutOfMemoryError: Java heap space
  • OOM的時候進程crash掉:-XX:+CrashOnOutOfMemoryError
    結果如下:
invoke test2
invoke test2
Aborting due to java.lang.OutOfMemoryError: Java heap space
invoke test2#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (debug.cpp:308)
, pid=42675, tid=0x00007f3710bf4700
#  fatal error: OutOfMemory encountered: Java heap space
#
# JRE version: Java(TM) SE Runtime Environment (8.0_171-b11) (build 1.8.0_171-b11)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode linux-amd64 compressed oops)
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /root/hanlang/test/hs_err_pid42675.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
#
Aborted
  • OOM的時候dump內存:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof
    結果生成dump文件

asm的應用

1.asm使用原理

asm是一個java字節碼工具,提供一種方便的函數/屬性級別修改已經編譯好的.class文件的方法, asm的簡單使用原理介紹如下:

  • 通過ClassReader讀取.class文件的字節碼內容,並生成語法樹;
  • ClassReader的方法accept(ClassVisitor classVisitor, int parsingOptions)功能是讓classVisitor遍歷語法樹,默認ClassVisitor是一個代理類,需要有一個具體的實現在遍歷語法樹的時候做一些處理;
  • 用ClassWriter是ClassVisitor的一個實現,它的功能是把語法樹轉換成字節碼;
  • 通常我們會定義一個自己的ClassVisitor,可以重寫裏面的一些方法來改寫類處理邏輯,然後讓ClassWriter把處理之後的語法樹轉換成字節碼;

2.下面是具體的實現步驟:

  • 引入asm依賴包
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>7.0</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-commons</artifactId>
    <version>7.0</version>
</dependency>
//或者引入如下包
asm-commons-7.0.jar
asm-analysis-7.0.jar
asm-tree-7.0.jar
asm-7.0.jar
  • 定義一個ClassVisitor,功能是在所有方法調用前和調用後分別通過System.out.println打印一些信息
    輸入為字節碼,輸出也是字節碼
//MyClassVisitor.java
public class MyClassVisitor extends ClassVisitor {
    private static final Type SYSTEM;
    private static final Type OUT;
    private static final Method PRINTLN;
    static {
        java.lang.reflect.Method m = null;
        try {
            m = PrintStream.class.getMethod("println", new Class<?>[] {String.class});
        } catch (Exception e) {
        }
        SYSTEM = Type.getType(System.class);
        OUT = Type.getType(PrintStream.class);
        PRINTLN = Method.getMethod(m);
    }

    private String cName;

    public MyClassVisitor(byte[] bytes) {
        super(Opcodes.ASM7, new ClassWriter(ClassWriter.COMPUTE_FRAMES));
        new ClassReader(bytes).accept(this, ClassReader.EXPAND_FRAMES);
    }
    String format(String name) {
        return name.replaceAll("<", "_").replaceAll("\\$|>", "");
    }
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        cName = format(name);
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if ((access & 256) != 0) {
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
        return new MyMethodAdapter(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc);
    }

    public byte[] getBytes() {
        return ((ClassWriter) cv).toByteArray();
    }

    class MyMethodAdapter extends AdviceAdapter {
        private String mName;

        public MyMethodAdapter(MethodVisitor methodVisitor, int acc, String name, String desc) {
            super(Opcodes.ASM7, methodVisitor, acc, name, desc);
            this.mName = format(name);
        }

        @Override
        protected void onMethodEnter() {
            getStatic(SYSTEM, "out", OUT);
            push(cName + "." + mName + " start");
            this.invokeVirtual(OUT, PRINTLN);
        }

        @Override
        protected void onMethodExit(int opcode) {
            getStatic(SYSTEM, "out", OUT);
            push(cName + "." + mName + " end");
            this.invokeVirtual(OUT, PRINTLN);
        }
    }
}
  • 定義一個簡單的classLoader來加載轉換后的字節碼
//MyLoader.java
class MyLoader extends ClassLoader {
    private String cname;
    private byte[] bytes;
    public MyLoader(String cname, byte[] bytes) {
        this.cname = cname;
        this.bytes = bytes;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = null;
        if (clazz == null && cname.equals(name)) {
            try {
                clazz = findClass(name);
            } catch (ClassNotFoundException e) {
            }
        }
        if (clazz == null) {
            clazz = super.loadClass(name, resolve);
        }
        return clazz;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clazz = this.findLoadedClass(name);
        if (clazz == null) {
            clazz = defineClass(name, bytes, 0, bytes.length);
        }
        return clazz;
    }
}
  • 加載轉換Hello類,然後反向調用其方法

//將如下main函數加入MyClassVisitor.java中

public static void main(String[] args) throws Exception {
    try (InputStream in = Hello.class.getResourceAsStream("Hello.class")) {
        byte[] bytes = new byte[in.available()];
        in.read(bytes);
        String cname = Hello.class.getName();
        Class<?> clazz = new MyLoader(cname, new MyClassVisitor(bytes).getBytes()).loadClass(cname);
        clazz.getMethod("test1").invoke(null);
    }
}
  • 編譯
java -cp tools.jar com.sun.tools.javac.Main -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
//或者
javac -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
  • 運行
java -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. MyClassVisitor
//結果如下:
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end

asm的使用很廣泛,最常用的是在spring aop裏面切面的功能就是通過asm來完成的

3. 利用asm與Instrument製作調試工具

  • Instrument工具

Instrument類有如下方法,可以增加一個類轉換器

addTransformer(ClassFileTransformer transformer, boolean canRetransform)

執行如下方法的時候,對應的類將會被重新定義

retransformClasses(Class<?>... classes)
  • 與asm配合使用
    當我們修改Agent.java代碼為下面內容
//Agent
public class Agent {
    public static void agentmain(String args, Instrumentation inst) {
        try {
            URLClassLoader loader = (URLClassLoader)Agent.class.getClassLoader();
            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            method.setAccessible(true);//代碼級引入依賴包
            method.invoke(loader, new File("asm-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-analysis-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-tree-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-commons-7.0.jar").toURI().toURL());
            inst.addTransformer(new ClassFileTransformer() {
                @Override
                public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                    ProtectionDomain protectionDomain, byte[] bytes) {
                    return new MyClassVisitor(bytes).getBytes();
                }
            }, true);
            inst.retransformClasses(Class.forName("Hello"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 編譯並打包成agent.jar
//編譯
javac -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
//打包
jar -cmf manifest.mf agent.jar MyLoader.class MyClassVisitor.class MyClassVisitor\$MyMethodAdapter.class Agent.class Agent\$1.class
  • attach進程修改字節碼
//執行
java -cp .:tools.jar AttachMain 3003
//執行前後Hello進程的輸出變化為
invoke test2
invoke test2
invoke test2
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end

利用asm及instrument工具來實現熱修改字節碼現在有許多成熟的工具,如btrace(https://github.com/btraceio/btrace,jvm-sandbox https://github.com/alibaba/jvm-sandbox)

 

點擊關注,第一時間了解華為雲新鮮技術~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

NetAnalyzer筆記 之 十四 NetAnalyzer 6.0 的使用方法 — 3.協議分析與統計

數據分析

完成了數據的抓取,那麼接下來就是NetAnalyzer的第二個重點部分了,協議分析作為整個軟件的核心之一,在最新的NetAnalyzer中已經得到了巨大的提升。NetAnalyzer中協議分析分為單數據包分析,和聯合分析兩種分析方式,對於聯合分析會根據不同的協議特性進行形成不同的分析方案,目前支持傳輸協議(TCP/UDP)協議分析, HTTP協議分析。在數據統計部分部分還增加了針對ARP協議的圖形化分析。對於協議分析,需要了解相關的網絡知識或是有相關專業背景支持。

單數據包分析,在獲取到數據包后,軟件工作界面數據包列表框中會显示所獲取的所用數據包,並且對這次數據做了一些簡單的分析,我們可以憑藉這些數據簡單判斷所對應的的數據包類型。

 

數據包列表

當我們選中一行,即選中一個數據包,我們可以看到對該數據包詳細的數據分析信息,並一樹狀結構樹呈現出來,並在右側显示該數據包原始信息。當我們選中協議樹中一個字段時,右側的數據就會定位到當前字端所分析數據的位置。

 

數據分析

然後通過對應的協議格式進行匹配與分析,如這部分的IP協議。

 

IPv4協議格式

需要注意的是,NetAnalyzer目前對於選中的字段只能精確到字節層次,對於一些協議,其中一個字節可能包含了多個字段,或是跨字節的字段,則會選擇全部的字節數據,比如IPv6協議。

 

IPv6協議格式

其中的版本字段只佔用了4bit(1字節為8bit),通信類型佔了8bit 也就是1字節,但是因為其中前面部分使用了版本字段所在字節後面的4bit,所以改字段為一個典型的跨字節字段,同樣流標籤字段使用了20bit,佔用第二個字節的4bit加上後面自身的2個字節(16bit)。

 

解析后的IPv6數據

對於類型的字段因為NetAnalyzer使用十六進制显示數據,並不能清晰表達bit層次的信息所以當選定字段后默認選中改字段所在的字節,如點擊版本選中方式如下,

 

IPv6版本信息

選中通信類型和流標籤則呈現方式如下。

 

通信類型和流標籤共用數據

數據分析標籤

雖然NetAnalyzer盡可能多分析每個數據包所包含的信息,但是依舊存在很多數據需要我們手動去解析。所以軟件增加了數據標籤。

 

數據分析

數據標籤頁點擊 显示 按鈕 就可以打開數據轉換窗口,當然也可以在常規轉換中點擊任意功能可以打開轉換窗口

 

轉換窗口管理

 

關閉按鈕為關閉轉換窗口,清空則是清空當前窗口內的數據。

點擊清空按鈕,則清空轉換信息。

 

常規轉換工具

NetAnalyzer中提供了一部分簡單的轉換功能,這些功能只有在載荷數據被選中的情況的才可以啟用,

如點擊二進制按鈕,則對所選的數據轉換為對應的二進制字符串。如下圖所示。

 

常規數據轉換窗口

除了一些簡單的轉換功能,還集成了MangoScript擴展方式和插件擴展方式(無可用插件的

時候不显示)的轉換。

 

擴展MangoScript的解析

如下面通過MangoScript針對某即時通信軟件的數據分析。

針對於MangoScript和插件兩種方式的轉換,將會在在《NetAnalyzer使用說明書 二 擴展與開發》中詳細說明,此處不再贅述。

 

 

定位轉換功能需要配合常規轉換進行使用,有時候我們確定某個字節會在一個確定的位置出現,比如IP地址字段,我們選中該位置,位置字段就會出現一串代碼 (10,1) [26]-4

(x,y)[offset] – length

 x: 十六進制編輯器水平方向的偏移量

 y :   十六進制編輯器垂直方向的偏移量

offset : 字節偏移量,offset = y * 16 + x

length :  當前選擇的數據長度

 

數據轉換

所以代碼 (10,1) [26]-4 確定了當前IP地址的位置,此時點擊 常規轉換 -> IPv4地址 則會在模式中記錄當前的轉換模式,然後點擊定位轉換,就會在當前數據包列表中針對每個數據包這個位置執行定位操作,這對於尋找所需要的數據非常重要。

 

選擇了IPv4轉換

 

執行定位轉換

對於MangoScript和插件擴展依然支持定位轉換。

 

區塊複製,主要是對一些已經選中的字節進行複製轉為代碼,字節數組,以及保存的功能,以及數據做手動分析,腳本分析以及自定義轉換等,後續將會說明,此處不再詳細介紹。

 

數據塊操作

 

字節定位,與定位轉換類似,但是字節定位主要是用來在數據包列表中查找相同位置出現相同字節序列的數據包。算作一個查找功能。

 

字節定位

分析標籤

分析標籤下個功能依託於數據包列表,分別有載荷數據提取,數據包標記,編碼轉換,數據查找,統計等相關功能,是聯合分析的主要功能,下面將會着重對一下功能進行說明。

 

數據分析標籤

TCP/UDP協議分析   前面介紹的都是基於單包的數據分析,而在協議分析中,我們大部分分析的數據都是依託於TCP/UDP的長連接數據,這部分數據的特點就是有多個數據包通過tcp或udp相關協議完成數據重組后才可以使用(基於udp的連接數據可能不是很嚴格)。

NetAnalyzer 除了提供基於單包的數據分析,更提供了基於連接數據的分析,而分析出來的數據不僅僅是在窗口上呈現一堆亂碼,更可以通過DocBar將獲取的數據提取出來進行使用。

開始 標籤最後一部分就是基於長連接的分析。點擊TCP/UDP 按鈕

 

基於TCP/UDP載荷數據查看

此時NetAnalyzer便會切換到載荷數據模式(該過程可以通過配置,使用獨立窗口打開)。在該模式下會打開專有的載荷數據菜單,數據區域也會變為對於載荷數據的分析,這裏先介紹一個NetAnalyzer中的DocBar工具,如下圖

 

DocBar

在文本模式下,分析載荷數據會显示該工具條,該工具條會提供針對當前數據塊的各種操作,當然在不動情況下,显示的工具和數量,都有所不同,下面是對當前各個功能的說明。

l   對當前數據塊進行摺疊

l   選中當前的分析數據

l   保存當前原始數據

l   查看原始數據(bytes數據)

l   MangScript解析數據

l   手動測試數據

 

對於其他情況下的工具在這裏不會一一介紹,但是碰到的時候會有說明,並且隨着後續功能點的增加,DocBar可能會有更多的功能添加進來。

 

tcp/udp 的分析分為 文本模式原始模式 ,文本模式主要是用於分析載荷數據為文本的數據,我們可以通過下面兩種方式更改文本編碼方式,分析數據。

文本模式下,呈現方式如下:

 

查看載荷數據

原始模式分析如下,可用通過TCP/UDP的下拉菜單命令 字節數據 切換為原始數據

 

 

字節查詢方式

 

字節方式呈現

對於在該功能下針對TCP的所有數據都已經進行過TCP重組,所以最終分析完成的數據並不是按照數據包方式做簡單呈現就可以的,都會做數據的篩查與整理。如果需要單包分析的使用者需要注意一下。

 

HTTP數據分析 http作為最有網絡代表意義的協議,NetAnalyzer提供了更加完善的分析,http基於tcp協議,所以數據還原等都建立在tcp數據還原的基礎之上。通過http分析,我們可以還原很多有意義的數據,如獲取到Http所傳輸的的html、js、css數據文件,還可以獲取到基於http協議分析得到的圖片,文件等信息,如下圖分別為還原后的圖片和zip壓縮包。

 

http方式分析出的圖片

 

http方式分析出的文件

對於常規的字符串或圖片可以直接在NetAnalyzer呈現,但是對於其他類型的文件,如視頻、音樂、以及上面提到的zip壓縮包文件,在在NetAnalyzer會簡單显示為二進制數據,該數據如果過長,則會截斷显示,但是在後面會加入【全部數據】下鑽選項,當點擊該數據后則會打開原始數據對話框,並且會完整显示當前的數據,如下圖所示。

 

查看原始數據

原始數據對話框中,提供了簡單的數據另存為和數據識別相關的功能。

 

原始數據保存

保存 保存當前窗口中的數據為一個文件。

保存選擇數據 是當選擇對話框中其中的一段數據保存為文件,有時候數據可能存在偏差,或者我們需要提取選定的數據保存為文件,可以通過下拉保存選定的數據進行保存。

數據識別功能。

轉為… 則是將當前的數據轉到編碼轉換工具中進行進一步分析。

自動識別 為了更加快速的實現數據提取,NetAnalyzer增加了數據識別模塊,通過整理不同文件的頭部或尾部字節形成數據識別特徵,當進行自動識別的時候,可以快速定位字節。

 

文件識別

添加特徵 將選定的指定字節添加為文件識別頭,並且添加相關信息,形成一個特徵。

 

添加文件識別

識別管理 管理特徵庫,在後續將詳細介紹該功能點。

 

載荷數據分析出的文件

除了使用常規的識別方式,在載荷數據提取中也加入了數據識別功能。在使用的時候點擊數據識別就可以在下方显示被識別到的數據類型,有時候可能會存在多個類型和誤識別的情況,使用的時候請務必注意。

有時候通過HTTP協議還原部分二進制數據,如下面還原ZIP文件,文檔會以二進制數據呈現,而我們可以通過0x50 0x4B(PK)推斷出該文件很有可能是zip文件 ,所以我們點擊全部數據 ,打開原始數據窗口,這部分數據正好是zip的全部數據。

   

保存的zip文件內容

此時點擊將當前數據保存為zip文件。減壓就可以看到對應的文件內容。

 

在載荷數據模式下,菜單會自動切換為,載荷模式菜單

 

載荷數據標籤

該菜單下提供了很多常用的字符串轉換工具

 

格式轉換工具

如下面通過通過Cookie格式化,格式化了http頭中的cookie字段

 

Cookie格式化

需要注意的是使用這些字段首先需要選中被轉換的文本,然後點擊需對應的功能項。其中如果點擊轉換為…,則啟動NetAnalyzer附帶的編碼轉換工具,進行集中處理。

 

編碼轉換工具

針對html字符串數據,還提供了過濾標籤和HTML預覽功能,因為該部分功能都很類型,且使用簡單,用戶自行嘗試使用即可。

 

 

時序圖 在數據分析中,除了對於數據本身的分析之外,有時候我們還要去評測一些數據質量等方面的內容。並且可以通過圖像化的方式表現出來。

 

TCP時序圖分析

時序圖模擬TCP/UDP在數據網絡中的數據傳輸過程,還原網絡通信場景,如該圖可以完整的反映TCP三次握手以及斷開連接四次揮手的情景。可以作為對當前分析數據從另外一個方面的反饋,更具有參考意義。

點擊

 

時序圖選項

就可以看到針對於當前tcp/udp 數據交互的情況。

 

 

數據標記

在分析標籤下面,有標記功能,實現對當前採集會話數據連接的進行快速識別。

 

數據標記

NetAnalyzer提供了四中顏色對數據包鏈接進行區分。

如TCP數據包,就會通過源IP地址+源端口地址+目標IP地址+目標端口 作為一個特徵來進行識別,此處的源和目標具有相對性。

注*  ctrl+鼠標左鍵 可以實現對數據會話的快速標記 顏色為紅色

 

標記完成的數據

通過點擊清理標記,可還原數據。

 

 

數據包查找 

 

數據包查找

在數據包列表模式下使用Ctrl+F即可以打開數據包查找功能。

該功能主要是實現快速查找數據包的功能,可以通過編號,協議,地址(mac/ip),端口,關鍵字等五種方式查找數據包。還可以通過數據列表導航按鈕進行數據包列表瀏覽。

 

 

編碼方式

在通過TCP/UDP 或HTTP 功能還原數據的時候,有時候會出現亂碼,尤其是對非英文字符。在HTTP協議中通常都會在頭部信息中攜帶編碼方法,通過提取就可以獲取到編碼方式,但是仍然後部分服務並不提供編碼字段,這時候就需要我們通過手動切換,來嘗試還原相關信息。

通過菜單欄或者是狀態欄都可以對編碼方案進行切換

 

字符編碼

 

狀態欄字符編碼

這裏需要注意的是如果http頭部包含了編碼方式,則使用頭部提供的編碼方式。

 

 

數據統計

目前NetAnalyzer显示了大量的統計方式,涵蓋了數據報表、流量分析、主機通信矩,傳輸報告、ARP報告等多種統計方式。

 

數據報表

 

報表信息

對當前捕獲的數據表中的數據進行統計與歸類。呈現方式如有圖所示。

 

報表內容

包含一些基本信息,數據量與時間直線圖,數據量佔比,關係圖等信息

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

為什麼阿里巴巴Java開發手冊中不允許魔法值出現在代碼中?

在閱讀《阿里巴巴Java開發手冊》時,發現有一條關於關於常量定義的規約,具體內容如下:

圖中的反例是將數據緩存起來,並使用魔法值加鏈路 id 組成 key,這就可能會出現其他開發人員在複製粘貼的時候,少複製 _ 的情況發生,這種錯誤很難去檢查到,因為讀取緩存不存在,可能會去數據庫讀取,很難察覺到。

如果在生產環境中,大量的請求進來,緩存全部失效,直接請求數據庫,導致數據庫連接過多,查詢效率變低的問題發生,因此看來魔法值確實應該避免出現在代碼中。

另外在 《Clean Code》 和 《重構》 等書中也提到了類似的問題,在代碼中出現原始形態数字通常來說是壞現象,應該用命名良好的常量類隱藏它。

靜態常量取代魔法值

像下面這個例子:

if (billCount > 75) {
    //todo
} else {
    //todo
}

如果在不了解這塊的業務的同事,在讀到這塊代碼的時候,可能會想,75 是什麼鬼,為啥和這個數比較,背後深藏着什麼秘密嗎?可能只有當時的開發人員記得了,導致代碼可讀性和可維護性極差。

如果聲明一個常量,來替換該魔法值,可能就會使代碼的可讀性和可維護性大大增加。

static final Integer BASIC_BILL_COUNT = 75;

還有些魔法表達式,比如:

if (value > 60 && value <= 80 && type = 1) {
    // todo
}

比如這個表達式是表示狀態為正常且項目活躍,就可以定義:

boolean isActiveProject = value > 60 && value <= 80 && type = 1;

這樣是不是可讀性就提高了,一眼就可以看出來這塊代碼的邏輯。

枚舉類取代魔法值

還有一種消除魔法值的方式是使用枚舉類代替,下面讓我們舉個例子:

if (eventId == 1) {
    System.out.println("睡覺");
} else if (eventId == 2) {
    System.out.println("吃飯");
} else if (eventId == 3) {
    System.out.println("打豆豆");
}

如上代碼是針對事件 id 去執行相應的事件,如果事件比較少,大家還可以勉強記住每個 eventId 對應的含義,但是隨着事件 id 的增多,很可能會發生,新來的員工把事件 id 給搞混了,導致執行錯誤的事件,發生 bug。

那麼我們可以使用枚舉類來表示相應的事件:

public enum EventEnum {

    /**
     * 睡覺
     */
    SLEEP_EVENT(1, "睡覺"),

    /**
     * 吃飯
     */
    EAT_EVENT(2, "吃飯"),

    /**
     * 打豆豆
     */
    FIGHT_PEA_EVENT(3, "打豆豆");

    private int eventId;
    private String desc;

    EventEnum(int eventId, String desc) {
        this.eventId = eventId;
        this.desc = desc;
    }

    public int getEventId() {
        return eventId;
    }

    public String getDesc() {
        return desc;
    }
}

修改完之後的代碼如下:

if (eventId == EventEnum.SLEEP_EVENT.getEventId()) {
    System.out.println("睡覺");
} else if (eventId == EventEnum.EAT_EVENT.getEventId()) {
    System.out.println("吃飯");
} else if (eventId == EventEnum.FIGHT_PEA_EVENT.getEventId()) {
    System.out.println("打豆豆");
}

是不是可讀性急劇提升,還不快看看自己代碼中有沒有這樣的魔法值出現,有的話趕緊改造起來。

還有如果你需要在不同的地點引用同一數值,魔法數會讓你煩惱不已,因為一旦這些数字發生改變,就必須在程序中找到所有的魔法值,並將它們全部修改一遍,這樣就太費時費力了。

其實不只是 Java 不應該在代碼中使用魔法值,其他語言亦是如此。

總結

本文主要介紹了為什麼不允許在代碼中出現魔法值以及如何將代碼中已有的魔法值去除掉。

代碼可讀性還是比較重要的,你肯定不希望別人在接手你的代碼的時候,罵到這数字啥意思,這代碼寫得跟粑粑一樣。

最好的關係就是互相成就,大家的在看、轉發、留言三連就是我創作的最大動力。

參考

《Java開發手冊》泰山版

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準