好程序員分享大數(shù)據(jù)的架構(gòu)體系:
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡(jiǎn)單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:申請(qǐng)域名、虛擬空間、營銷軟件、網(wǎng)站建設(shè)、慶云網(wǎng)站維護(hù)、網(wǎng)站推廣。
????????????flume采集數(shù)據(jù)
????????????MapReduce
????????????HBse (HDFS)
????????????Yarn ??資源調(diào)度系統(tǒng)
??展示平臺(tái) 數(shù)據(jù)平臺(tái)
????????????1,提交任務(wù)
????????????2,展示結(jié)果數(shù)據(jù)
??spark 分析引擎 ?S3 ??可以進(jìn)行各種的數(shù)據(jù)分析 , 可可以和hive進(jìn)行整合 ,spark任務(wù)可以運(yùn)行在Yarn
?
提交任務(wù)到集群的入口類 ???SC
?
為什么用spark : ??速度快,易用,通用,兼容性高
?
hadoop
scala
jdk
spark
?
如果結(jié)果為定長(zhǎng)的 ?toBuffer編程變長(zhǎng)的
?
啟動(dòng)流程
?
spark集群?jiǎn)?dòng)流程 ?和任務(wù)提交
?
主節(jié)點(diǎn) master
子節(jié)點(diǎn)work ??多臺(tái)
start-all。sh腳本 先啟動(dòng)master服務(wù) ?????啟動(dòng)work
master ?提交注冊(cè)信息 ??work響應(yīng) ??work會(huì)定時(shí)發(fā)送心跳信息
?
?
集群?jiǎn)?dòng)流程
??????1、調(diào)用start-all腳本 ??,開始啟動(dòng)Master
??????2、master啟動(dòng)以后,preStart方法調(diào)用了一個(gè)定時(shí)器,定時(shí)的檢查超時(shí)的worker
??????3、啟動(dòng)腳本會(huì)解析slaves配置文件,找到啟動(dòng)work的相應(yīng)節(jié)點(diǎn),開始啟動(dòng)worker
??????4、worker服務(wù)啟動(dòng)后開始調(diào)用prestart方法(生命周期方法)開始向所有的master注冊(cè)
??????5、master接收到work發(fā)送過來的注冊(cè)信息,master開始保存注冊(cè)信息并把自己的URL響應(yīng)給worker
??????6、worker接收到master的URL后并更新,開始掉用一個(gè)定時(shí)器,定時(shí)的向master發(fā)送心跳信息
?
?
任務(wù)提交流程
?
將任務(wù)rdd通過客戶端submit提交給Master ?的管道 (隊(duì)列:先進(jìn)先出)
??????????????????????worker啟動(dòng)Executor子進(jìn)程 ??來從master拿取任務(wù)信息
??????????????????????Executor ?向客戶端Driver端注冊(cè)
?????????????????????????客戶端收到注冊(cè)信息 ?客戶端就會(huì)將任務(wù)給 Executor進(jìn)行人物計(jì)算
?
任務(wù)提交流程
????????1、首先Driver端會(huì)通過spark-submit腳本啟動(dòng)sparkSubmint進(jìn)程,此時(shí)開始創(chuàng)建重要的對(duì)象(SparkContext),啟動(dòng)后開始向Master發(fā)送信息開始通信
????????2、Master接收到發(fā)送過來的信息后,開始生成任務(wù)信息,并把任務(wù)信息放到隊(duì)列中
????????3、master開始把所有有效的worker過濾出來并進(jìn)行排序,按照空閑的資源進(jìn)行排序
????????4、Master開始向有效的worker通知拿取任務(wù)信息,并啟動(dòng)相應(yīng)的Executor
????????5、worker啟動(dòng)Executor,并向Driver反向注冊(cè)
????????6、Driver開始把生成的task發(fā)送給相應(yīng)的Executor,Executor
?
?
WordCount中產(chǎn)生的RDD
?
hdfs上有三個(gè)文件 ?sc.textFile(“路徑”)方法 ?生成第一個(gè)RDD ?HadoopRDD ???第二個(gè)RDD ?MapPartitionsRDD ?flatMap(_.split()"")生成 第三個(gè)RDD ?MapPartitionsRDD
????????????????????????????????map((_,1))生成第四個(gè)RDD ?MapPartitionsRDD ???reduceByKey ?生成第五個(gè)ShuffledRDD ?????saveAsTextFile ?生成第六個(gè)RDD MapPartitionsRDD
?
.toDebugString ?可以看出RDD
?
分區(qū)
Partition ?后跟分區(qū) ?分區(qū)本身不會(huì)改變 ?會(huì)生成以一個(gè)新的RDD分區(qū)為修改后 ?因?yàn)閞dd本身不可變 ??修改后大于原本分區(qū)的會(huì)發(fā)生shullfer ?小于的不會(huì)發(fā)生
?
coalesce ???后跟分區(qū)少于原來的分區(qū)則會(huì)改變 ?因?yàn)椴粫?huì)發(fā)生shuffle ?大于時(shí)則不可改變
?
PartitionBy ?后跟新的分區(qū)器new ?全名稱的分區(qū)器org.apache.spark.hparPartition
?
客戶端提交Job任務(wù)信息給Master
Master生成任務(wù)信息master生成任務(wù)信息描述任務(wù)的數(shù)據(jù) ??通知work創(chuàng)建相應(yīng)的Executor
??????客戶端將job信息給work ?work讓Executor進(jìn)行計(jì)算數(shù)據(jù)
?
object Demo {
??def main(args: Array[String]): Unit = {
?
//SparkConf:構(gòu)架配置信息類,優(yōu)先于集群配置文件
//setAppName:指定應(yīng)用程序名稱,如果不指定,會(huì)自動(dòng)生成類似于uuid產(chǎn)生的名稱
//setMaster:指定運(yùn)行模式:local[1]-用一個(gè)線程模擬集群運(yùn)行,local[2] -用兩個(gè)集群模擬線程集群運(yùn)行,local[*] -有多少線程就用多少線程運(yùn)行
?
????val conf= new SparkConf().setAppName("") ???// setAppName起名稱 ?setMaster ?在哪里運(yùn)行 ?是本地還是 ?[]是調(diào)用多少線程來運(yùn)行
?.setMaster("local[2]") //在打包上傳集群時(shí) ?不需要這一步直接刪除或是注釋掉
//創(chuàng)建提交任務(wù)到集群的入口類(上下文對(duì)象)
??????val sc = ?new SparkContext(conf)
//獲取hdfs的數(shù)據(jù)
val lines = sc.textFile("hdfs://suansn:9000/wc")
val words= lines.flatMap(_.split(" ")) // 切分后生成單詞
val tuples=words.map((_,1)) ??//將單詞生成一個(gè)元組
val ?sum= tuples.reduceBykey(_+_) ?// 進(jìn)行聚合
val PX = sum.sortBy(_._2,false) ?// 倒敘拍尋
print(PX.collect.toBuffer) // 打印至控制臺(tái) ??在打包上傳集群時(shí) ?不需要這一步直接刪除或是注釋掉
?
PX.saveAsTextFile("hdfs://suansn:9000/ssss")
sc.stop ????//釋放資源
?
?
?
??}
?
}
?
?
RDD ????提供的方法 ?叫做算子
?
RDD ?數(shù)據(jù)集 數(shù)據(jù)的抽象 ?是一種類型,提供方法處理數(shù)據(jù) ???分布式 ?僅僅是指向數(shù)據(jù) , ??不可變 如果想要其他的操作 就在另外定義一個(gè)RDD。 可分區(qū) ??如果一個(gè)文件 小于128M ?就是一個(gè)分區(qū) 如果大于將根據(jù)大小來分區(qū)
?
一組分片 ??一個(gè)計(jì)算每個(gè)分區(qū)的函數(shù) ??RDD之間的依賴關(guān)系 ??一個(gè)Partitioner,即RDD的分片函數(shù)。 ??一個(gè)列表,存儲(chǔ)存取每個(gè)Partition的優(yōu)先位置(preferred location)。
?
?
RDD ?有兩種類型 ?????一個(gè)算子對(duì)應(yīng)一個(gè)Action的job
??????1 、Transformation ?轉(zhuǎn)換的類型 ???延遲加載 ??只是記錄計(jì)算過程 并不執(zhí)行 ????只有調(diào)用 ?Action類型的算子后 ?觸發(fā)job生成計(jì)算
??????????????????????如果沒有Transformation 算子 ?而全是Action算子 ?就無法優(yōu)化 ?集群一直處于繁忙狀態(tài)。
?
??????2、Action
?sc.parallelize 并行方法創(chuàng)建RDD
?
?
?val rdd1 = sc.parallelize(List(3,4,6,5,8,7,9,2,1),2)
?
???每個(gè)數(shù)據(jù)乘10
?val rdd2 = rdd1.map(_*10)
?? ? ?Array[Int] = Array(30, 40, 60, 50, 80, 70, 90, 20, 10)
?
?
利用分區(qū)計(jì)算 ?mapPartitions
?
val rdd2= rdd1.mapPartitions(_.map(_*10)) ????//map前_ ?表示每個(gè)分區(qū)的數(shù)據(jù) ?封裝到Iterator
Array[Int] = Array(30, 40, 60, 50, 80, 70, 90, 20, 10)
?
mapWith ???????//map的變異 ?也可將元素?cái)?shù)據(jù)遍歷出來 ?將分區(qū)號(hào)作為輸入 返回到A類型作為輸出
(constructA: Int => A)(f: (T, A) => RDD[U])
參數(shù)列表:(constructA: Int => A, preservesPartitioning: Boolean = false)(f: (T, A) => U) ???// Int => A 操作的每個(gè)分區(qū)的分區(qū)號(hào),preservesPartitioning: Boolean = false ?是否記錄rdd的分區(qū)信息 ? ?(T, A) ??T時(shí)rdd中的元素
// 實(shí)現(xiàn)了柯里化的步驟 ?兩個(gè)A的傳入
?
rdd1.mapWith(i => i*10)((a, b) => b+2).collect ???//分區(qū)號(hào)i ?乘以10 ??B接收A時(shí)RDD的元素
Array[Int] = Array(2,2,2,2,12,12,12,12,12)
?
?
flatMapWith ??//分區(qū)排序
?
?
?(constructA: Int => A)(f: (T, A) => Seq[U])
參數(shù)列表:(constructA: Int => A, preservesPartitioning: Boolean = false)(f: (T, A) => Seq[U])
rdd1.flatMapWith(i => i, true)((x, y) => List((y, x))).collect ?// i為分區(qū)號(hào) ??原樣不懂輸出 ??true相當(dāng)于 允許記錄分區(qū)信息 ???Y為拿到的分區(qū)號(hào) ?X為RDD的元素
Array[(Int,Int)] = Array((0,3)(0,4)(0,6)(0,5)(1,8)(1,7)(1,9)(1,2)(1,1))
?
mapPartitions ??f: Iterator[T] => Iterator[U]
rdd1.mapPartitions(_.toList.reverse.iterator).collect ???// ?每個(gè)分區(qū)顛倒排列
?
Array[Int] = Array(5, 6, 4, 3, 1, 2, 9, 7, 8)
?
mapPartitionsWithIndex ??????????循環(huán)分區(qū)并可以操作分區(qū)號(hào)
參數(shù)列表:(f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false) ?????//Iterator[(Int) ?分區(qū)信息 ??index: Int ?分區(qū)號(hào)
val func = (index: Int, iter: Iterator[(Int)]) => {
??iter.toList.map(x => "[partID:" + ?index + ", val: " + x + "]").iterator
}
val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)
rdd1.mapPartitionsWithIndex(func).collect
?Array[String] = Array([partID:0, val: 1], [partID:0, val: 2], [partID:0, val: 3], [partID:0, val: 4], [partID:1, val: 5], [partID:1, val: 6], [partID:1, val: 7], [partID:1, val: 8], [partID:1, val: 9])
?
aggregate ???// ?聚合算子
(zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
?
def func1(index: Int, iter: Iterator[(Int)]) : Iterator[String] = {
??iter.toList.map(x => "[partID:" + ?index + ", val: " + x + "]").iterator
}
val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)
rdd1.mapPartitionsWithIndex(func1).collect
Array[String] = Array([partID:0, val: 1], [partID:0, val: 2], [partID:0, val: 3], [partID:0, val: 4], [partID:1, val: 5], [partID:1, val: 6], [partID:1, val: 7], [partID:1, val: 8], [partID:1, val: 9])
?
?
rdd1.aggregate(0)(math.max(_, _), _ + _) ???// 循環(huán)時(shí) ?第一個(gè)_拿到的時(shí)初始值0 ?第二個(gè)_拿到的0分區(qū)第一個(gè)元素 ?然后判斷最大值 ?依次類推 ????局部聚合完,最后全局聚合時(shí) ??初始值+第0分區(qū)的最大值。第1分區(qū)的最大值
Int=13
rdd1.aggregate(5)(math.max(_, _), _ + _) ??//原理和上面的相同不過初始值時(shí)5 ??這樣得到的第0分區(qū)的最大值就是 初始值 ?5 ?第1分區(qū)的最大值還是9 ???最后的全局聚合時(shí) ?就是5 + 5+9
Int=19
?
val rdd2 = sc.parallelize(List("a","b","c","d","e","f"),2)
def func2(index: Int, iter: Iterator[(String)]) : Iterator[String] = {
??iter.toList.map(x => "[partID:" + ?index + ", val: " + x + "]").iterator
}
rdd2.mapPartitionsWithIndex(func2).collect
Array[String] = Array([partID:0, val: a], [partID:0, val: b], [partID:0, val: c], [partID:1, val: d], [partID:1, val: e], [partID:1, val: f])
?
?
rdd2.aggregate("")(_ + _, _ + _) ?//全局聚合和局部聚合 ???都屬于字符串拼接 ??初始值為空
String = abcdef ??String = defabc ?//因?yàn)椴淮_定那個(gè)分區(qū)先完成任務(wù)所以 會(huì)出現(xiàn)兩種結(jié)果
rdd2.aggregate("=")(_ + _, _ + _)
String = ==abc=def
?
val rdd3 = sc.parallelize(List("12","23","345","4567"),2)
rdd3.aggregate("")((x,y) => math.max(x.length, y.length).toString, (x,y) => x + y) // 取每個(gè)字符串的長(zhǎng)度 ??第一次與初始值 比較 而后用第二個(gè)數(shù)據(jù)的長(zhǎng)度與上一次比較后的長(zhǎng)度相比較, ??最后全局聚合時(shí) 兩個(gè)分區(qū)最長(zhǎng)的字符串和初始值相加
String = 24 ?String = 42
val rdd4 = sc.parallelize(List("12","23","345",""),2)
rdd4.aggregate("")((x,y) => math.min(x.length, y.length).toString, (x,y) => x + y) ?// ?運(yùn)算方法與上面的相同 這個(gè)求的字符串是最短的 因?yàn)樵诘诙€(gè)分區(qū)內(nèi)有個(gè)空數(shù)據(jù)字符串為0 ??第一個(gè)分區(qū)的因?yàn)槌跏贾狄矠榭?所以為空 ??tostring后第一次的變?yōu)樽址?長(zhǎng)度為1全局后為10
?String = 10
val rdd5 = sc.parallelize(List("12","23","","345"),2)
rdd5.aggregate("")((x,y) => math.min(x.length, y.length).toString, (x,y) => x + y) 與上面相同
String = 11
?
aggregateByKey ??通過相同的key進(jìn)行聚合
(zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) => U, combOp: (U, U) => U): RDD[(K, U)
//Partitioner ?分區(qū)器
val pairRDD = sc.parallelize(List(("mouse", 2),("cat",2), ("cat", 5), ("mouse", 4),("cat", 12), ("dog", 12)), 2)
def func2(index: Int, iter: Iterator[(String, Int)]) : Iterator[String] = {
??iter.toList.map(x => "[partID:" + ?index + ", val: " + x + "]").iterator
??}
pairRDD.mapPartitionsWithIndex(func2).collect
??// ???????全局聚合時(shí) 不會(huì)加 初始值
????pairRDD.aggregateByKey(0)(math.max(_, _), _ + _).collect ?// 相同的key的value值進(jìn)行操作
??pairRDD.aggregateByKey(100)(math.max(_, _), _ + _).collect
?
??combineByKey ??// 聚合的算子
??????(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)
??val rdd1 = sc.textFile("hdfs://node01:9000/wc").flatMap(_.split(" ")).map((_, 1))
val rdd2 = rdd1.combineByKey(x => x, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n)
??rdd2.collect
?
??val rdd3 = rdd1.combineByKey(x => x + 10, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n)
rdd.collect
?
?
??val rdd4 = sc.parallelize(List("dog","cat","gnu","salmon","rabbit","turkey","wolf","bear","bee"), 3)
??val rdd5 = sc.parallelize(List(1,1,2,2,2,1,2,2,2), 3)
??val rdd6 = rdd5.zip(rdd4)
??val rdd7 = rdd6.combineByKey(List(_), (x: List[String], y: String) => x :+ y, (m: List[String], n: List[String]) => m
?
??countByKey
?
??val rdd1 = sc.parallelize(List(("a", 1), ("b", 2), ("b", 2), ("c", 2), ("c", 1)))
??rdd1.countByKey ??//相同key的value的個(gè)數(shù)
??rdd1.countByValue // 把整個(gè)rdd看成Value
?
?
??filterByRange ?//給定范圍 ?求
?
??val rdd1 = sc.parallelize(List(("e", 5), ("c", 3), ("d", 4), ("c", 2), ("a", 1)))
??val rdd2 = rdd1.filterByRange("c", "d")
??rdd2.collect
?
?
??flatMapValues
??val rdd3 = sc.parallelize(List(("a", "1 2"), ("b", "3 4")))
??rdd3.flatMapValues(_.split(" "))
?
?
??foldByKey
?
??val rdd1 = sc.parallelize(List("dog", "wolf", "cat", "bear"), 2)
??val rdd2 = rdd1.map(x => (x.length, x))
??val rdd3 = rdd2.foldByKey("")(_+_)
?
??val rdd = sc.textFile("hdfs://node01:9000/wc").flatMap(_.split(" ")).map((_, 1))
??rdd.foldByKey(0)(_+_)
?
?
??foreachPartition ?//
??val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9), 3)
??rdd1.foreachPartition(x => println(x.reduce(_ + _))) ?表示每個(gè)分區(qū)的數(shù)據(jù)的聚合值
?
?
??keyBy
??val rdd1 = sc.parallelize(List("dog", "salmon", "salmon", "rat", "elephant"), 3)
??val rdd2 = rdd1.keyBy(_.length) ??元素?cái)?shù)據(jù)的長(zhǎng)度生成為key元素?cái)?shù)據(jù)生成為value
??rdd2.collect
?
??keys values
??val rdd1 = sc.parallelize(List("dog", "tiger", "lion", "cat", "panther", "eagle"), 2)
??val rdd2 = rdd1.map(x => (x.length, x))
??rdd2.keys.collect
??rdd2.values.collect
?
?
??checkpoint
??sc.setCheckpointDir("hdfs://node01:9000/cp")
??val rdd = sc.textFile("hdfs://node01:9000/wc").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_)
??rdd.checkpoint ??將checkpoint后的文件 準(zhǔn)備存儲(chǔ) ?還未存儲(chǔ) 沒有Action算子沒有運(yùn)行job
??rdd.isCheckpointed ?查看是否運(yùn)行checkpoint
??rdd.count ????隨便調(diào)動(dòng)Avtion的算子 提交job
??rdd.isCheckpointed
??rdd.getCheckpointFile ??查看checkpoint的文件存儲(chǔ)的位置
?
??repartition, coalesce, partitionBy
??val rdd1 = sc.parallelize(1 to 10, 3)
??val rdd2 = rdd1.coalesce(2, false)
??rdd2.partitions.length
?
??collectAsMap ??Array 轉(zhuǎn)換map(kv)對(duì)
??val rdd = sc.parallelize(List(("a", 1), ("b", 2)))
??rdd.collectAsMap
?
?
在一定時(shí)間范圍內(nèi),求所有用戶在經(jīng)過所有基站停留時(shí)間最長(zhǎng)的TOP2
?
?
思路:獲取用戶產(chǎn)生的log日志并切分
??????用戶在基站停留的總時(shí)長(zhǎng)
??????過去基站的基礎(chǔ)信息
??????把經(jīng)緯度信息join到用戶數(shù)據(jù)中
??????求出用戶在某些基站停留的時(shí)長(zhǎng)的TOP2
?
?
??????object Demo ?{
????????def main(args: Array[String]): Unit = {
//模板代碼
val ??conf = new SparkConf()
.setAppName("ML")
.setMaster("local[2]")
val sc= new SparkContext(conf)
?
?
//獲取用戶訪問基站的log
val files=sc.textFile("地址")
//切分用戶的log
val userInfo=files.map(line=>{
val fields=line.split(",")
val phone = fields(0)//用戶手機(jī)號(hào)
val time = fields(1).toLong//時(shí)間戳
val lac = fields(2) //基站ID
val eventType = fields(3)//事件類型
val time_long = if(eventType.equals("1")) -time else time
?
?
((phone,lac),time_long)
})
?
//用戶在相同的基站停留的總時(shí)長(zhǎng)
val ?sumedUserAndTime ?= userInfo.reduceByKey(_+_)
?
//為了便于和基站基礎(chǔ)信息進(jìn)行Join需要把數(shù)據(jù)調(diào)整,把基站ID作為key
val lacAndPhoneAndTime sumedUserAndTime.map(tup =>{
?
val phone = tup._1._1 //用戶手機(jī)號(hào)
val lac= tup._1._2//基站的ID
val time = tup._2 //用戶在某個(gè)基站停留的總時(shí)長(zhǎng)
(lac,(phone,time))
})
?//獲取基站的基礎(chǔ)信息
?val lacInfo= sc.textFile("路徑")
//切分基站基礎(chǔ)數(shù)據(jù)
?val lacAndXY=lacInfo.map (line =>{
val fields = line.split(",")
val lac= fields(0)//基站ID
val x = files(1)//經(jīng)度
val y = fields(2)//緯度
?
(lac,(x,y))
?
?})
?
//把經(jīng)緯度信息join到用戶的訪問信息
val ?joined=lacAndPhoneAndTime join ?lacAndXY
?
//為了便于以后發(fā)呢組排序計(jì)算,需要整合數(shù)據(jù)
val phoneAndTimeAndXY=joined,map(tup=>{
val phone = tup._2._1._1//手機(jī)號(hào)
val lac = tup._1// ID
val time ?= tup._2._1._2
val xy = tup._2._2 //經(jīng)緯度
(phone,time,xy)
?
})
//按照用戶手機(jī)號(hào)進(jìn)行分組
val grouped=phoneAndTimeAndXY.groupBy(_._1)
//按照時(shí)長(zhǎng)進(jìn)行組內(nèi)排序
//val ?sorted = grouped.map(x => (x._,x._2.toList.sortBy(_._2).reverse))
val ?sorted = grouped.mapValues(_.toList.sortBy(_._2).reverse)
//整合數(shù)據(jù)
val filterede=sorted.map(tup =>{
val phone= tup._1
?
val list = tup._2
val filteredList=list.map(x =>{
?
val time ?= x._2
val xy = x._3
?
??List(time,xy)
})
?
?
(phone,filteredList)
?
})
?
?
?
val res = filterede.mapValues(_.take(2))
?
?
?
sc.stop()
????????}
?
??????}
網(wǎng)站標(biāo)題:好程序員分享大數(shù)據(jù)的架構(gòu)體系
本文網(wǎng)址:http://chinadenli.net/article36/ihjopg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供做網(wǎng)站、關(guān)鍵詞優(yōu)化、外貿(mào)建站、云服務(wù)器、定制開發(fā)、電子商務(wù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)