“造輪運動”之 ORM框架系列(三)~ 乾貨呈上

   這一趴裏面,我就來正式介紹一下CoffeeSQL的乾貨。

    首先要給CoffeeSQL來個定位:最開始就是由於本人想要了解ORM框架內部的原理,所以就四處搜尋有關的博客與學習資料,就是在那個夏天,在博客園上看到了一位7tiny老哥的博客(https://www.cnblogs.com/7tiny/p/9575230.html),裏面基本上包含了我所想要了解的全套內容。幸得7tiny老哥的博客和代碼都寫的非常清晰,所以沒花多久時間就看完了源碼並洞悉其中奧妙,於是自己就有個想法:在7tiny的開源代碼的基礎上歸納自己的ORM框架。於是出於學習與自我使用的目的就開始了擴展功能的道路,到現在為止,自己已經在公司的一個項目中用上了,效果還不錯。在這裏也感謝7tiny老哥對我提出的一些問題及時的回復和指導,真心感謝。

一、框架模塊介紹

  根據CoffeeSQL的功能模塊組成來劃分,可以分為:數據庫連接管理、SQL命令執行入口、SQL命令生成器、SQL查詢引擎、ORM緩存機制、實體數據驗證 這六個部分,CoffeeSQL的操作入口與其他的ORM框架一樣,都是以數據庫上下文(DBContext)的方式進行操作。整體結構圖如下:

 

下面就大致地介紹一下每一個模塊的具體功能與實現的思路:

1、數據庫連接管理(DBConnectionManagement)

   數據庫連接的管理實際上就是對數據庫連接字符串與其對應的數據庫連接對象的管理機制,它可以保證在進行一主多從的數據庫部署時ORM幫助我們自動地切換連接的數據庫,而且還支持 <最小使用>與 <輪詢>兩種數據庫連接切換策略。

 

2、SQL命令執行入口(QueryExecute)

   QueryExecute是CoffeeSQL生成的所有sql語句執行的入口,執行sql語句並返回結果,貫穿整個CoffeeSQL最核心的功能就是映射sql查詢結果到實體,這裏採用的是構建表達式樹的技術,性能大大優於反射獲取實體的方式,具體的兩者速度對比的實驗在7tiny的博客中有詳細介紹,大家可以移步觀看(https://www.cnblogs.com/7tiny/p/9861166.html),在我的博客(https://www.cnblogs.com/MaMaNongNong/p/12173620.html)中我使用表達式樹的技術造了個簡練版的OOM框架。

   這裏貼出核心代碼,方便查看:

   

  1     /// <summary>
  2     /// Auto Fill Adapter
  3     /// => Fill DataRow to Entity
  4     /// </summary>
  5     public class EntityFillAdapter<Entity>
  6     {
  7         private static readonly Func<DataRow, Entity> funcCache = GetFactory();
  8 
  9         public static Entity AutoFill(DataRow row)
 10         {
 11             return funcCache(row);
 12         }
 13 
 14         private static Func<DataRow, Entity> GetFactory()
 15         {
 16             #region get Info through Reflection
 17             var entityType = typeof(Entity);
 18             var rowType = typeof(DataRow);
 19             var convertType = typeof(Convert);
 20             var typeType = typeof(Type);
 21             var columnCollectionType = typeof(DataColumnCollection);
 22             var getTypeMethod = typeType.GetMethod("GetType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string) }, null);
 23             var changeTypeMethod = convertType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(object), typeof(Type) }, null);
 24             var containsMethod = columnCollectionType.GetMethod("Contains");
 25             var rowIndexerGetMethod = rowType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, new[] { new ParameterModifier(1) });
 26             var columnCollectionIndexerGetMethod = columnCollectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int) }, new[] { new ParameterModifier(1) });
 27             var entityIndexerSetMethod = entityType.GetMethod("set_Item", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(object) }, null);
 28             var properties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
 29             #endregion
 30 
 31             #region some Expression class that can be repeat used
 32             //DataRow row
 33             var rowDeclare = Expression.Parameter(rowType, "row");
 34             //Student entity
 35             var entityDeclare = Expression.Parameter(entityType, "entity");
 36             //Type propertyType
 37             var propertyTypeDeclare = Expression.Parameter(typeof(Type), "propertyType");
 38             //new Student()
 39             var newEntityExpression = Expression.New(entityType);
 40             //row == null
 41             var rowEqualnullExpression = Expression.Equal(rowDeclare, Expression.Constant(null));
 42             //row.Table.Columns
 43             var rowTableColumns = Expression.Property(Expression.Property(rowDeclare, "Table"), "Columns");
 44             //int loopIndex
 45             var loopIndexDeclare = Expression.Parameter(typeof(int), "loopIndex");
 46             //row.Table.Columns[loopIndex].ColumnName
 47             var columnNameExpression = Expression.Property(Expression.Call(rowTableColumns, columnCollectionIndexerGetMethod, loopIndexDeclare), "ColumnName");
 48             //break;
 49             LabelTarget labelBreak = Expression.Label();
 50             //default(Student)
 51             var defaultEntityValue = Expression.Default(entityType);
 52             #endregion
 53 
 54             var setRowNotNullBlockExpressions = new List<Expression>();
 55                         
 56             #region entity = new Student();loopIndex = 0;
 57             setRowNotNullBlockExpressions.Add(Expression.Assign(entityDeclare, newEntityExpression));
 58             setRowNotNullBlockExpressions.Add(Expression.Assign(loopIndexDeclare, Expression.Constant(0)));
 59 
 60             #endregion
 61 
 62             #region loop Fill DataRow's field to Entity Indexer
 63             /*
 64              * while (true)
 65              * {
 66              *     if (loopIndex < row.Table.Columns.Count)
 67              *     {
 68              *         entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
 69              *         loopIndex++;
 70              *     }
 71              *     else break;
 72              * } 
 73              */
 74 
 75             setRowNotNullBlockExpressions.Add(
 76 
 77                 Expression.Loop(
 78                     Expression.IfThenElse(
 79                         Expression.LessThan(loopIndexDeclare, Expression.Property(rowTableColumns, "Count")),
 80                         Expression.Block(
 81                             Expression.Call(entityDeclare, entityIndexerSetMethod, columnNameExpression, Expression.Call(rowDeclare, rowIndexerGetMethod, columnNameExpression)),
 82                             Expression.PostIncrementAssign(loopIndexDeclare)
 83                         ),
 84                         Expression.Break(labelBreak)
 85                     ),
 86                     labelBreak
 87                 )
 88             );
 89             #endregion
 90 
 91             #region assign for Entity property
 92             foreach (var propertyInfo in properties)
 93             {
 94                 var columnAttr = propertyInfo.GetCustomAttribute(typeof(ColumnAttribute), true) as ColumnAttribute;
 95 
 96                 // no column , no translation
 97                 if (null == columnAttr) continue;
 98 
 99                 if (propertyInfo.CanWrite)
100                 {
101                     var columnName = Expression.Constant(columnAttr.GetName(propertyInfo.Name), typeof(string));
102 
103                     //entity.Id
104                     var propertyExpression = Expression.Property(entityDeclare, propertyInfo);
105                     //row["Id"]
106                     var value = Expression.Call(rowDeclare, rowIndexerGetMethod, columnName);
107                     //default(string)
108                     var defaultValue = Expression.Default(propertyInfo.PropertyType);
109                     //row.Table.Columns.Contains("Id")
110                     var checkIfContainsColumn = Expression.Call(rowTableColumns, containsMethod, columnName);
111                     //!row["Id"].Equals(DBNull.Value)
112                     var checkDBNull = Expression.NotEqual(value, Expression.Constant(System.DBNull.Value));
113                     
114                     var propertyTypeName = Expression.Constant(propertyInfo.PropertyType.ToString(), typeof(string));
115 
116                     /*
117                      * if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
118                      * {
119                      *     propertyType = Type.GetType("System.String");
120                      *     entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
121                      * }
122                      * else
123                      *     entity.Id = default(string);
124                      */
125                     setRowNotNullBlockExpressions.Add(
126 
127                         Expression.IfThenElse(
128                             Expression.AndAlso(checkIfContainsColumn, checkDBNull),
129                             Expression.Block(
130                                 Expression.Assign(propertyTypeDeclare, Expression.Call(getTypeMethod, propertyTypeName)),
131                                 Expression.Assign(propertyExpression, Expression.Convert(Expression.Call(changeTypeMethod, value, propertyTypeDeclare), propertyInfo.PropertyType))
132                             ),
133                             Expression.Assign(propertyExpression, defaultValue)
134                         )
135                     );
136                 }
137             }
138 
139             #endregion
140 
141             var checkIfRowIsNull = Expression.IfThenElse(
142                 rowEqualnullExpression,
143                 Expression.Assign(entityDeclare, defaultEntityValue),
144                 Expression.Block(setRowNotNullBlockExpressions)
145             );
146 
147             var body = Expression.Block(
148 
149                 new[] { entityDeclare, loopIndexDeclare, propertyTypeDeclare },
150                 checkIfRowIsNull,
151                 entityDeclare   //return Student;
152             );
153 
154             return Expression.Lambda<Func<DataRow, Entity>>(body, rowDeclare).Compile();
155         }
156     }
157 
158     #region
159     //public class Student : EntityDesign.EntityBase
160     //{
161     //    [Column]
162     //    public string Id { get; set; }
163 
164     //    [Column("StudentName")]
165     //    public string Name { get; set; }
166     //}
167     ////this is the template of "GetFactory()" created.
168     //public static Student StudentFillAdapter(DataRow row)
169     //{
170     //    Student entity;
171     //    int loopIndex;
172     //    Type propertyType;
173 
174     //    if (row == null)
175     //        entity = default(Student);
176     //    else
177     //    {
178     //        entity = new Student();
179     //        loopIndex = 0;
180 
181     //        while (true)
182     //        {
183     //            if (loopIndex < row.Table.Columns.Count)
184     //            {
185     //                entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
186     //                loopIndex++;
187     //            }
188     //            else break;
189     //        }
190 
191     //        if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
192     //        {
193     //            propertyType = Type.GetType("System.String");
194     //            entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
195     //        }
196     //        else
197     //            entity.Id = default(string);
198 
199     //        if (row.Table.Columns.Contains("StudentName") && !row["StudentName"].Equals(DBNull.Value))
200     //        {
201     //            propertyType = Type.GetType("System.String");
202     //            entity.Name = (string)Convert.ChangeType(row["StudentName"], propertyType);
203     //        }
204     //        else
205     //            entity.Name = default(string);
206     //    }
207 
208     //    return entity;
209     //}
210     #endregion

EntityFillAdapter(表達式樹技術)

 

3、SQL查詢引擎(QueryEngine)

  SQL查詢引擎的功能主要就是以函數的形式來構建查詢SQL的結構。將sql語句使用高級語言的函數來進行構建能大大減輕程序員必須一絲不苟編寫sql語句的壓力。特別是在使用強類型查詢引擎時以Lambda表達式的方式編寫程序,相當舒適的體驗;對於稍微複雜的sql,建議使用弱類型查詢引擎來構建sql查詢語句,同時也提供方便的分頁功能,用法與Dapper類似;再複雜一點的數據庫查詢邏輯可能你就要考慮使用存儲過程查詢引擎了,總之,有了這三個查詢引擎,所有的查詢需求都能滿足了。最後一個是update的執行引擎,它被用來構建update的語句。

 

4、實體數據驗證(EntityValidation)

  實體數據驗證是完全獨立的一部分,主要用來檢驗實體類中字段值的合法性,相當於在高級語言層面對即將持久化到數據庫表中的數據進行預先的字段合法性校驗,避免在持久化過程中發生不必要的字段格式不合法的錯誤。

 

5、ORM緩存機制(ORMCache)

  這裏的ORM緩存主要分為兩級緩存,一級緩存為以sql語句為緩存鍵的緩存,緩存的內容就是當前執行的sql語句的執行結果;而二級緩存則是以表名為緩存鍵的表緩存,就是會把一整個表的數據全部存入緩存中,所以表緩存最適合那些數據量不大且查詢頻繁的表

 

6、SQL命令生成器【強類型】(CommandTextGenerator)

  在使用諸如強類型查詢引擎、Update執行引擎等進行了強類型的SQL語句構造后,相應的sql構造信息都要通過SQL命令生成器來生成最終可由數據庫執行的sql語句。SQL命令生成器扮演的就是類似於翻譯官的角色,將高級語言中的語句轉化為數據庫中的sql語句。在實際的應用場景中還可以根據不同的數據庫類型將SQL命令生成器擴展成諸如Mysql-SQL命令生成器或者Oracle-SQL命令生成器以符合不同類型數據庫的不同sql語法。

 

7、數據庫上下文(DBContext)

  作為整個CoffeeSQL的操作入口,DBContext類涵蓋了各種配置參数字段與增刪改查的API調用函數。其中在事務處理中,由於寫操作都是通過對主庫的操作,所以在事務處理中是以主庫作為事務處理的對象。

二、使用方式

  下載CoffeeSql源碼進行編譯,你會得到 CoffeeSql.Core.dll、CoffeeSql.Oracle.dll、CoffeeSql.Mysql.dll 三個dll文件,其中CoffeeSql.Core.dll為必選,然後根據你的數據庫類型選擇是CoffeeSql.Oracle.dll或者CoffeeSql.Mysql.dll,目前還只支持這兩種數據庫,後續會支持更多數據庫。

 

 

三、展望

  路漫漫其修遠兮,吾將上下而求索,對比市面上火熱的ORM框架,CoffeeSQL還是缺少了一些實用的功能,對這個ORM框架的展望中我會考慮以下一些功能:

    1、CodeFirst、DbFirst功能的支持,可以快捷方便地進行實體類與數據庫建表sql的生成;

    2、批量插入操作的實現,可以提高批量插入數據的性能;

    3、對多表聯合查詢的lambda語法支持;

  

  介紹的再多都不如讀一遍源碼來的實在,有想深入了解orm原理的小夥伴可以閱讀一下源碼,真的SO EASY!

   源碼地址:https://gitee.com/xiaosen123/CoffeeSqlORM

   本文為作者原創,轉載請註明出處:https://www.cnblogs.com/MaMaNongNong/p/12896787.html

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

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

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

Alink漫談(六) : TF-IDF算法的實現

Alink漫談(六) : TF-IDF算法的實現

目錄

  • Alink漫談(六) : TF-IDF算法的實現
    • 0x00 摘要
    • 0x01 TF-IDF
      • 1.1 原理
      • 1.2 計算方法
    • 0x02 Alink示例代碼
      • 2.1 示例代碼
      • 2.2 TF-IDF模型
      • 2.3 TF-IDF預測
    • 0x03 分詞 Segment
      • 3.1 結巴分詞
      • 3.2 分詞過程
    • 0x04 訓練
      • 4.1 計算IDF
      • 4.2 排序
        • 4.2.1 SortUtils.pSort
          • 採樣SampleSplitPoint
          • 歸併 SplitPointReducer
          • SplitData把真實數據IDF插入
          • reduceGroup計算同類型單詞數目
        • 4.2.2 localSort
      • 4.3 過濾
    • 0x05 生成模型
      • 5.1 DocCountVectorizerModelData
      • 5.2 BuildDocCountModel
    • 0x06 預測
    • 0x07 參考

0x00 摘要

Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習算法平台,是業界首個同時支持批式算法、流式算法的機器學習平台。TF-IDF(term frequency–inverse document frequency)是一種用於信息檢索與數據挖掘的常用加權技術。本文將為大家展現Alink如何實現TF-IDF。

0x01 TF-IDF

TF-IDF(term frequency–inverse document frequency)是一種統計方法,一種用於信息檢索與數據挖掘的常用加權技術。

TF是詞頻(Term Frequency),IDF是逆文本頻率指數(Inverse Document Frequency)。

為什麼要用TF-IDF?因為計算機只能識別数字,對於一個一個的單詞,計算機是看不懂的,更別說是一句話,或是一篇文章。而TF-IDF就是用來將文本轉換成計算機看得懂的語言,或者說是機器學習或深度學習模型能夠進行學習訓練的數據集

1.1 原理

TF-IDF用以評估一個詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。

TF-IDF的主要思想是:如果某個詞或短語在一篇文章中出現的頻率TF高,並且在其他文章中很少出現,則認為此詞或者短語具有很好的類別區分能力,適合用來分類。

TF-IDF實際上是:TF * IDF,TF詞頻(Term Frequency),IDF逆向文件頻率(Inverse Document Frequency)。

詞頻(term frequency,TF)指的是某一個給定的詞語在該文件中出現的頻率。這個数字是對詞數(term count)的歸一化,以防止它偏向長的文件(同一個詞語在長文件里可能會比短文件有更高的詞數,而不管該詞語重要與否)。

IDF逆向文件頻率 (inverse document frequency, IDF)反應了一個詞在所有文本(整個文檔)中出現的頻率,如果一個詞在很多的文本中出現,那麼它的IDF值應該低。而反過來如果一個詞在比較少的文本中出現,那麼它的IDF值應該高。比如一些專業的名詞如“Machine Learning”。這樣的詞IDF值應該高。一個極端的情況,如果一個詞在所有的文本中都出現,那麼它的IDF值應該為0。

如果單單以TF或者IDF來計算一個詞的重要程度都是片面的,因此TF-IDF綜合了TF和IDF兩者的優點,用以評估一字詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。上述引用總結就是:一個詞語在一篇文章中出現次數越多, 同時在所有文檔中出現次數越少, 越能夠代表該文章,越能與其它文章區分開來。

1.2 計算方法

TF的計算公式如下:

\[TF_w = \frac {N_w}{N} \]

其中 N_w 是在某一文本中詞條w出現的次數,N 是該文本總詞條數。

IDF的計算公式如下:

\[IDF_w = log(\frac {Y}{Y_w + 1}) \]

其中 Y 是語料庫的文檔總數,Y_w 是包含詞條w的文檔數,分母加一是為了避免w 未出現在任何文檔中從而導致分母為0 的情況。

TF-IDF 就是將TF和IDF相乘 :

\[TF-IDF_w = TF_w * IDF_w \]

從以上計算公式便可以看出,某一特定文件內的高詞語頻率,以及該詞語在整個文件集合中的低文件頻率,可以產生出高權重的TF-IDF。因此,TF-IDF傾向於過濾掉常見的詞語,保留重要的詞語。

0x02 Alink示例代碼

2.1 示例代碼

首先我們給出示例代碼,下文是通過一些語料來訓練出一個模型,然後用這個模型來做預測:

public class DocCountVectorizerExample {

    AlgoOperator getData(boolean isBatch) {
        Row[] rows = new Row[]{
                Row.of(0, "二手舊書:醫學電磁成像"),
                Row.of(1, "二手美國文學選讀( 下冊 )李宜燮南開大學出版社 9787310003969"),
                Row.of(2, "二手正版圖解象棋入門/謝恩思主編/華齡出版社"),
                Row.of(3, "二手中國糖尿病文獻索引"),
                Row.of(4, "二手郁達夫文集( 國內版 )全十二冊館藏書")
        };

        String[] schema = new String[]{"id", "text"};

        if (isBatch) {
            return new MemSourceBatchOp(rows, schema);
        } else {
            return new MemSourceStreamOp(rows, schema);
        }
    }

    public static void main(String[] args) throws Exception {
        DocCountVectorizerExample test = new DocCountVectorizerExample();
        BatchOperator batchData = (BatchOperator) test.getData(true);

         // 分詞
        SegmentBatchOp segment = new SegmentBatchOp() 
                                                .setSelectedCol("text")
                                                .linkFrom(batchData);
        // TF-IDF訓練
        DocCountVectorizerTrainBatchOp model = new DocCountVectorizerTrainBatchOp()
                                                .setSelectedCol("text")
                                                .linkFrom(segment);
        // TF-IDF預測
        DocCountVectorizerPredictBatchOp predictBatch = new 
            																		DocCountVectorizerPredictBatchOp()
                                                .setSelectedCol("text")
                                                .linkFrom(model, segment);
        model.print();
        predictBatch.print();
    }
}

2.2 TF-IDF模型

TF-IDF模型打印出來如下:

model_id|model_info
--------|----------
0|{"minTF":"1.0","featureType":"\"WORD_COUNT\""}
1048576|{"f0":"二手","f1":0.0,"f2":0}
2097152|{"f0":"/","f1":1.0986122886681098,"f2":1}
3145728|{"f0":"出版社","f1":0.6931471805599453,"f2":2}
4194304|{"f0":")","f1":0.6931471805599453,"f2":3}
5242880|{"f0":"(","f1":0.6931471805599453,"f2":4}
6291456|{"f0":"入門","f1":1.0986122886681098,"f2":5}
......
36700160|{"f0":"美國","f1":1.0986122886681098,"f2":34}
37748736|{"f0":"謝恩","f1":1.0986122886681098,"f2":35}
38797312|{"f0":"象棋","f1":1.0986122886681098,"f2":36}

2.3 TF-IDF預測

TF-IDF預測結果如下:

id|text
--|----
0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0
1|$37$0:1.0 1:1.0 2:1.0 4:1.0 11:1.0 15:1.0 16:1.0 19:1.0 20:1.0 32:1.0 34:1.0
2|$37$0:1.0 3:2.0 4:1.0 5:1.0 8:1.0 22:1.0 23:1.0 24:1.0 29:1.0 35:1.0 36:1.0
3|$37$0:1.0 12:1.0 27:1.0 31:1.0 33:1.0
4|$37$0:1.0 1:1.0 2:1.0 7:1.0 9:1.0 13:1.0 14:1.0 17:1.0 18:1.0 21:1.0 30:1.0

0x03 分詞 Segment

中文分詞(Chinese Word Segmentation) 指的是將一個漢字序列切分成一個一個單獨的詞。分詞就是將連續的字序列按照一定的規範重新組合成詞序列的過程。

示例代碼中,分詞部分如下:

    SegmentBatchOp segment = new SegmentBatchOp() 
                                            .setSelectedCol("text")
                                            .linkFrom(batchData);

分詞主要是如下兩個類,其作用就是把中文文檔分割成單詞。

public final class SegmentBatchOp extends MapBatchOp <SegmentBatchOp>
	implements SegmentParams <SegmentBatchOp> {

	public SegmentBatchOp(Params params) {
		super(SegmentMapper::new, params);
	}
}

public class SegmentMapper extends SISOMapper {
	private JiebaSegmenter segmentor;
}

3.1 結巴分詞

有經驗的同學看到這裏就會露出微笑:結巴分詞。

jieba分詞是國內使用人數最多的中文分詞工具https://github.com/fxsjy/jieba。jieba分詞支持四種分詞模式:

  • 精確模式,試圖將句子最精確地切開,適合文本分析;
  • 全模式,把句子中所有的可以成詞的詞語都掃描出來, 速度非常快,但是不能解決歧義;
  • 搜索引擎模式,在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞。
  • paddle模式,利用PaddlePaddle深度學習框架,訓練序列標註(雙向GRU)網絡模型實現分詞。

Alink使用了com.alibaba.alink.operator.common.nlp.jiebasegment.viterbi.FinalSeg;來 完成分詞。具體是在https://github.com/huaban/jieba-analysis的基礎上稍微做了調整。

public class JiebaSegmenter implements Serializable {
    private static FinalSeg finalSeg = FinalSeg.getInstance();
    private WordDictionary wordDict;
    ......
    private Map<Integer, List<Integer>> createDAG(String sentence) 
}

從Alink代碼中看,實現了索引分詞和查詢分詞兩種模式,應該是有分詞粒度粗細之分。

createDAG函數的作用是 :在處理句子過程中,基於前綴詞典實現高效的詞圖掃描,生成句子中漢字所有可能成詞情況所構成的有向無環圖 (DAG)。

結巴分詞對於未登錄詞,採用了基於漢字成詞能力的 HMM 模型,使用了 Viterbi 算法。

3.2 分詞過程

分詞過程主要是在SegmentMapper.mapColumn函數中完成的,當輸入是 “二手舊書:醫學電磁成像”,結巴分詞將這個句子分成了六個單詞。具體參見如下:

input = "二手舊書:醫學電磁成像"
tokens = {ArrayList@9619}  size = 6
 0 = {SegToken@9630} "[二手, 0, 2]"
 1 = {SegToken@9631} "[舊書, 2, 4]"
 2 = {SegToken@9632} "[:, 4, 5]"
 3 = {SegToken@9633} "[醫學, 5, 7]"
 4 = {SegToken@9634} "[電磁, 7, 9]"
 5 = {SegToken@9635} "[成像, 9, 11]"
 
mapColumn:44, SegmentMapper (com.alibaba.alink.operator.common.nlp)
apply:-1, 35206803 (com.alibaba.alink.common.mapper.SISOMapper$$Lambda$646)
handleMap:75, SISOColsHelper (com.alibaba.alink.common.mapper)
map:52, SISOMapper (com.alibaba.alink.common.mapper)
map:21, MapperAdapter (com.alibaba.alink.common.mapper)
map:11, MapperAdapter (com.alibaba.alink.common.mapper)
collect:79, ChainedMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
invoke:196, DataSourceTask (org.apache.flink.runtime.operators)

0x04 訓練

訓練是在DocCountVectorizerTrainBatchOp類完成的,其通過linkFrom完成了模型的構建。其實計算TF IDF相對 簡單,複雜之處在於之後的大規模排序。

public DocCountVectorizerTrainBatchOp linkFrom(BatchOperator<?>... inputs) {
        BatchOperator<?> in = checkAndGetFirst(inputs);
  
        DataSet<DocCountVectorizerModelData> resDocCountModel = generateDocCountModel(getParams(), in);

        DataSet<Row> res = resDocCountModel.mapPartition(new MapPartitionFunction<DocCountVectorizerModelData, Row>() {
            @Override
            public void mapPartition(Iterable<DocCountVectorizerModelData> modelDataList, Collector<Row> collector) {
                new DocCountVectorizerModelDataConverter().save(modelDataList.iterator().next(), collector);
            }
        });
        this.setOutput(res, new DocCountVectorizerModelDataConverter().getModelSchema());
        return this;
}

4.1 計算IDF

計算 IDF 的工作是在generateDocCountModel完成的,具體步驟如下:

第一步 通過DocWordSplitCount和UDTF的混合使用得到了文檔中的單詞數目docWordCnt

BatchOperator<?> docWordCnt = in.udtf(
        params.get(SELECTED_COL),
        new String[] {WORD_COL_NAME, DOC_WORD_COUNT_COL_NAME},
        new DocWordSplitCount(NLPConstant.WORD_DELIMITER),
        new String[] {});

DocWordSplitCount.eval的輸入是已經分詞的句子,然後按照空格分詞,按照單詞計數。其結果是:

map = {HashMap@9816}  size = 6
 "醫學" -> {Long@9833} 1
 "電磁" -> {Long@9833} 1
 ":" -> {Long@9833} 1
 "成像" -> {Long@9833} 1
 "舊書" -> {Long@9833} 1
 "二手" -> {Long@9833} 1

第二步 得到了文檔數目docCnt

BatchOperator docCnt = in.select("COUNT(1) AS " + DOC_COUNT_COL_NAME);

這個數目會廣播出去 .withBroadcastSet(docCnt.getDataSet(), "docCnt");,後面的CalcIdf會繼續使用,進行 行數統計。

第三步 會通過CalcIdf計算出每一個單詞的DF和IDF

open時候會獲取docCnt。然後reduce會計算IDF,具體計算如下:

double idf = Math.log((1.0 + docCnt) / (1.0 + df));
collector.collect(Row.of(featureName, -wordCount, idf));

具體得到如下

df = 1.0
wordCount = 1.0
featureName = "中國"
idf = 1.0986122886681098
docCnt = 5

這裏一個重點是:返回值中,是 -wordCount,因為單詞越多權重越小,為了比較所以取負

4.2 排序

得到所有單詞的IDF之後,就得到了一個IDF字典,這時候需要對字典按照權重進行排序。排序具體分為兩步。

4.2.1 SortUtils.pSort

第一步是SortUtils.pSort,大規模并行抽樣排序。

Tuple2<DataSet<Tuple2<Integer, Row>>, DataSet<Tuple2<Integer, Long>>> partitioned = SortUtils.pSort(sortInput, 1);

這步非常複雜,Alink參考了論文,如果有興趣的兄弟可以深入了解下。

* reference: Yang, X. (2014). Chong gou da shu ju tong ji (1st ed., pp. 25-29).
* Note: This algorithm is improved on the base of the parallel sorting by regular sampling(PSRS).

pSort返回值是:

* @return f0: dataset which is indexed by partition id, f1: dataset which has partition id and count.

pSort中又分如下幾步

採樣SampleSplitPoint

SortUtils.SampleSplitPoint.mapPartition這裏完成了採樣。

DataSet <Tuple2 <Object, Integer>> splitPoints = input
   .mapPartition(new SampleSplitPoint(index))
   .reduceGroup(new SplitPointReducer());

這裏的輸入row就是上文IDF的返回數值。

用allValues記錄了本task目前處理的句子有多少個單詞。

用splitPoints做了採樣。如何選擇呢,通過genSampleIndex函數。

public static Long genSampleIndex(Long splitPointIdx, Long count, Long splitPointSize) {
   splitPointIdx++;
   splitPointSize++;

   Long div = count / splitPointSize;
   Long mod = count % splitPointSize;

   return div * splitPointIdx + ((mod > splitPointIdx) ? splitPointIdx : mod) - 1;
}

後續操作也使用同樣的genSampleIndex函數來做選擇,這樣保證在操作所有序列上可以選取同樣的採樣點。

allValues = {ArrayList@10264}  size = 8  //本task有多少單詞
 0 = {Double@10266} -2.0
 1 = {Double@10271} -1.0
 2 = {Double@10272} -1.0
 3 = {Double@10273} -1.0
 4 = {Double@10274} -1.0
 5 = {Double@10275} -1.0
 6 = {Double@10276} -1.0
 7 = {Double@10277} -1.0
 
splitPoints = {ArrayList@10265}  size = 7 //採樣了7個
 0 = {Double@10266} -2.0
 1 = {Double@10271} -1.0
 2 = {Double@10272} -1.0
 3 = {Double@10273} -1.0
 4 = {Double@10274} -1.0
 5 = {Double@10275} -1.0
 6 = {Double@10276} -1.0

最後返回採樣數據,返回時候附帶當前taskIDnew Tuple2 <Object, Integer>(obj,taskId)

這裡有一個trick點

  for (Object obj : splitPoints) {
     Tuple2 <Object, Integer> cur
        = new Tuple2 <Object, Integer>(
        obj,
        taskId); //這裏返回的是類似 (-5.0,2) :其中2就是task id,-5.0是-wordcount。
     out.collect(cur);
  }

  out.collect(new Tuple2(
     getRuntimeContext().getNumberOfParallelSubtasks(),
     -taskId - 1));//這裏返回的是一個特殊元素,類似(4,-2) :其中4是本應用中并行task數目,-2是當前-taskId - 1。這個task數目後續就會用到。

具體數據參見如下:

row = {Row@10211} "中國,-1.0,1.0986122886681098"
 fields = {Object[3]@10214} 
 
cur = {Tuple2@10286} "(-5.0,2)" // 返回採樣數據,返回時候附帶當前taskID
 f0 = {Double@10285} -5.0 // -wordcount。
 f1 = {Integer@10300} 2 // 當前taskID
歸併 SplitPointReducer

歸併所有task生成的sample。然後再次sample,把sample數據組成一個數據塊,這個數據塊選擇的原則是:每個task都盡量選擇若干sample

這裏其實是有一個轉換,就是從正常單詞的抽樣 轉換到 某一類單詞的抽樣,這某一類的意思舉例是:出現次數為一,或者出現次數為五 這種單詞

這裏all是所有採樣數據,其中一個元素內容舉例 (-5.0,2) :其中2就是task id,-5.0是-wordcount。

這裏用 Collections.sort(all, new PairComparator()); 來對所有採樣數據做排序。排序基準是首先對 -wordcount,然後對task ID。

SplitPointReducer的返回採樣數值就作為廣播變量存儲起來:.withBroadcastSet(splitPoints, "splitPoints");

這裏的trick點是:

for (Tuple2 <Object, Integer> value : values) {
   if (value.f1 < 0) { 
      instanceCount = (int) value.f0;  // 特殊數據,類似(4,-2) :其中4是本應用中task數目,這個就是後續選擇哪些taskid的基準
      continue;
   }
   all.add(new Tuple2 <>(value.f0, value.f1)); // (-5.0,2) 正常數據
}

選擇sample index splitPoints.add(allValues.get(index));也使用了同樣的genSampleIndex。

計算中具體數據如下:

for (int i = 0; i < splitPointSize; ++i) {
		int index = genSampleIndex(
					Long.valueOf(i),
					Long.valueOf(count),
					Long.valueOf(splitPointSize))
					.intValue();
		spliters.add(all.get(index));
}
for (Tuple2 <Object, Integer> spliter : spliters) {
		out.collect(spliter);
}

count = 33
all = {ArrayList@10245}  size = 33 // 所有採樣數據,
0 = {Tuple2@10256} "(-5.0,2)"// 2就是task id,-5.0是-wordcount。
1 = {Tuple2@10285} "(-2.0,0)"
......
6 = {Tuple2@10239} "(-1.0,0)"
7 = {Tuple2@10240} "(-1.0,0)"
8 = {Tuple2@10241} "(-1.0,0)"
9 = {Tuple2@10242} "(-1.0,0)"
10 = {Tuple2@10243} "(-1.0,0)"
11 = {Tuple2@10244} "(-1.0,1)"
......
16 = {Tuple2@10278} "(-1.0,1)"
......
24 = {Tuple2@10279} "(-1.0,2)"
......
32 = {Tuple2@10313} "(-1.0,3)"
  
// spliters是返回結果,這裏分別選取了all中index為8,16,24這個三個record。每個task都選擇了一個元素。
spliters = {HashSet@10246}  size = 3
 0 = {Tuple2@10249} "(-1.0,0)" // task 0 被選擇。就是說,這裏從task 0中選擇了一個count是1的元素,具體選擇哪個單詞其實不重要,就是為了選擇count是1的這種即可。
 1 = {Tuple2@10250} "(-1.0,1)" // task 1 被選擇。具體同上。
 2 = {Tuple2@10251} "(-1.0,2)" // task 2 被選擇。具體同上。
SplitData把真實數據IDF插入

use binary search to partition data into sorted subsets。前面函數給出的是詞的count,但是沒有IDF。這裏將用二分法查找 找到IDF,然後把IDF插入到partition data中。

首先要注意一點:splitData的輸入就是原始輸入input, 和splitPoints的輸入是一樣 的

DataSet <Tuple2 <Integer, Row>> splitData = input
   .mapPartition(new SplitData(index))
   .withBroadcastSet(splitPoints, "splitPoints");

open函數中會取出廣播變量 splitPoints。

splitPoints = {ArrayList@10248}  size = 3
 0 = {Tuple2@10257} "(-1.0,0)"
 1 = {Tuple2@10258} "(-1.0,1)"
 2 = {Tuple2@10259} "(-1.0,2)"

本函數的輸入舉例

row = {Row@10232} "入門,-1.0,1.0986122886681098"

會在splitPoints中二分法查找,得到splits中每一個 sample 對應的真實IDF。然後發送出去。

這裏需要特殊說明下,這個二分法查找查找的是IDF數值,比如count為1的這種單詞對應的IDF數值,可能很多單詞都是count為1,所以找到一個這樣單詞的IDF即可

splitPoints = {ArrayList@10223}  size = 3
 0 = {Tuple2@10229} "(-1.0,0)"
 1 = {Tuple2@10230} "(-1.0,1)"
 2 = {Tuple2@10231} "(-1.0,2)"
curTuple.f0 = {Double@10224} -1.0
  
int bsIndex = Collections.binarySearch(splitPoints, curTuple, new PairComparator());

		int curIndex;
		if (bsIndex >= 0) {
			curIndex = bsIndex;
		} else {
			curIndex = -bsIndex - 1;
		}

// 假設單詞是 "入門",則發送的是 "入門" 這類單詞在本partition的index,和 "入門" 的單詞本身
// 其實,從調試過程看,是否發送單詞信息本身並不重要,因為接下來的那一步操作中,並沒有用到單詞本身信息
out.collect(new Tuple2 <>(curIndex, row)); 
reduceGroup計算同類型單詞數目

這裡是計算在某一partition中,某一種類單詞的數目。比如count為1的單詞,這種單詞總共有多少個

後續會把new Tuple2 <>(id, count)作為partitionCnt廣播變量存起來。

id就是這類單詞在這partition中間的index,我們暫時稱之為partition index。count就是這類單詞在本partition的數目。

// 輸入舉例
value = {Tuple2@10312} "(0,入門,-1.0,1.0986122886681098)"
 f0 = {Integer@10313} 0
 
// 計算數目
for (Tuple2 <Integer, Row> value : values) {
		id = value.f0;
		count++;
}

out.collect(new Tuple2 <>(id, count));  
  
// 輸出舉例,假如是序號為0的這類單詞,其總體數目是12。這個序號0就是這類單詞在某一partition中的序號。就是上面的 curIndex。
id = {Integer@10313} 0
count = {Long@10338} 12

4.2.2 localSort

第二步是localSort。Sort a partitioned dataset. 最終排序並且會返回最終數值,比如 (29, “主編,-1.0,1.0986122886681098″), 29就是”主編” 這個單詞在 IDF字典中的序號。

DataSet<Tuple2<Long, Row>> ordered = localSort(partitioned.f0, partitioned.f1, 1);

open函數中會獲取partitionCnt。然後計算出某一種類單詞,其在本partition之前所有partition中,這類單詞數目。

public void open(Configuration parameters) throws Exception {
		List <Tuple2 <Integer, Long>> bc = getRuntimeContext().getBroadcastVariable("partitionCnt");
		startIdx = 0L;
		int taskId = getRuntimeContext().getIndexOfThisSubtask();
		for (Tuple2 <Integer, Long> pcnt : bc) {
			if (pcnt.f0 < taskId) {
					startIdx += pcnt.f1;
			}
		}
}

bc = {ArrayList@10303}  size = 4
 0 = {Tuple2@10309} "(0,12)"  // 就是task0裏面,這種單詞有12個
 1 = {Tuple2@10310} "(2,9)"// 就是task1裏面,這種單詞有2個
 2 = {Tuple2@10311} "(1,7)"// 就是task2裏面,這種單詞有1個
 3 = {Tuple2@10312} "(3,9)"// 就是task3裏面,這種單詞有3個
// 如果本task id是4,則其startIdx為30。就是所有partition之中,它前面index所有單詞的和。  

然後進行排序。Collections.sort(valuesList, new RowComparator(field));

valuesList = {ArrayList@10405}  size = 9
 0 = {Row@10421} ":,-1.0,1.0986122886681098"
 1 = {Row@10422} "主編,-1.0,1.0986122886681098"
 2 = {Row@10423} "國內,-1.0,1.0986122886681098"
 3 = {Row@10424} "文獻,-1.0,1.0986122886681098"
 4 = {Row@10425} "李宜燮,-1.0,1.0986122886681098"
 5 = {Row@10426} "糖尿病,-1.0,1.0986122886681098"
 6 = {Row@10427} "美國,-1.0,1.0986122886681098"
 7 = {Row@10428} "謝恩,-1.0,1.0986122886681098"
 8 = {Row@10429} "象棋,-1.0,1.0986122886681098"
  
  
// 最後返回時候,就是  (29, "主編,-1.0,1.0986122886681098"),29就是“主編”這個單詞在最終字典中的序號。
// 這個序號是startIdx + cnt,startIdx是某一種類單詞,其在本partition之前所有partition中,這類單詞數目。比如在本partition之前,這類單詞有28個,則本partition中,從29開始計數。就是最終序列號
	for (Row row : valuesList) {
		out.collect(Tuple2.of(startIdx + cnt, row));
		cnt++; // 這裏就是在某一類單詞中,單調遞增,然後賦值一個字典序列而已
	}  
cnt = 1
row = {Row@10336} "主編,-1.0,1.0986122886681098"
 fields = {Object[3]@10339} 
startIdx = 28

4.3 過濾

最後還要進行過濾,如果文字個數超出了字典大小,就拋棄多餘文字。

ordered.filter(new FilterFunction<Tuple2<Long, Row>>() {
    @Override
    public boolean filter(Tuple2<Long, Row> value) {
        return value.f0 < vocabSize;
    }
})

0x05 生成模型

具體生成模型代碼如下。

DataSet<DocCountVectorizerModelData> resDocCountModel = ordered.filter(new FilterFunction<Tuple2<Long, Row>>() {
    @Override
    public boolean filter(Tuple2<Long, Row> value) {
        return value.f0 < vocabSize;
    }
}).mapPartition(new BuildDocCountModel(params)).setParallelism(1);
return resDocCountModel;

其中關鍵類是 DocCountVectorizerModelData 和 BuildDocCountModel。

5.1 DocCountVectorizerModelData

這是向量信息。

/**
 * Save the data for DocHashIDFVectorizer.
 *
 * Save a HashMap: index(MurMurHash3 value of the word), value(Inverse document frequency of the word).
 */
public class DocCountVectorizerModelData {
    public List<String> list;
    public String featureType;
    public double minTF;
}

5.2 BuildDocCountModel

最終生成的模型信息如下,這個也就是之前樣例代碼給出的輸出。

modelData = {DocCountVectorizerModelData@10411} 
 list = {ArrayList@10409}  size = 37
  0 = "{"f0":"9787310003969","f1":1.0986122886681098,"f2":19}"
  1 = "{"f0":"下冊","f1":1.0986122886681098,"f2":20}"
  2 = "{"f0":"全","f1":1.0986122886681098,"f2":21}"
  3 = "{"f0":"華齡","f1":1.0986122886681098,"f2":22}"
  4 = "{"f0":"圖解","f1":1.0986122886681098,"f2":23}"
  5 = "{"f0":"思","f1":1.0986122886681098,"f2":24}"
  6 = "{"f0":"成像","f1":1.0986122886681098,"f2":25}"
  7 = "{"f0":"舊書","f1":1.0986122886681098,"f2":26}"
  8 = "{"f0":"索引","f1":1.0986122886681098,"f2":27}"
  9 = "{"f0":":","f1":1.0986122886681098,"f2":28}"
  10 = "{"f0":"主編","f1":1.0986122886681098,"f2":29}"
  11 = "{"f0":"國內","f1":1.0986122886681098,"f2":30}"
  12 = "{"f0":"文獻","f1":1.0986122886681098,"f2":31}"
  13 = "{"f0":"李宜燮","f1":1.0986122886681098,"f2":32}"
  14 = "{"f0":"糖尿病","f1":1.0986122886681098,"f2":33}"
  15 = "{"f0":"美國","f1":1.0986122886681098,"f2":34}"
  16 = "{"f0":"謝恩","f1":1.0986122886681098,"f2":35}"
  17 = "{"f0":"象棋","f1":1.0986122886681098,"f2":36}"
  18 = "{"f0":"二手","f1":0.0,"f2":0}"
  19 = "{"f0":")","f1":0.6931471805599453,"f2":1}"
  20 = "{"f0":"/","f1":1.0986122886681098,"f2":2}"
  21 = "{"f0":"出版社","f1":0.6931471805599453,"f2":3}"
  22 = "{"f0":"(","f1":0.6931471805599453,"f2":4}"
  23 = "{"f0":"入門","f1":1.0986122886681098,"f2":5}"
  24 = "{"f0":"醫學","f1":1.0986122886681098,"f2":6}"
  25 = "{"f0":"文集","f1":1.0986122886681098,"f2":7}"
  26 = "{"f0":"正版","f1":1.0986122886681098,"f2":8}"
  27 = "{"f0":"版","f1":1.0986122886681098,"f2":9}"
  28 = "{"f0":"電磁","f1":1.0986122886681098,"f2":10}"
  29 = "{"f0":"選讀","f1":1.0986122886681098,"f2":11}"
  30 = "{"f0":"中國","f1":1.0986122886681098,"f2":12}"
  31 = "{"f0":"書","f1":1.0986122886681098,"f2":13}"
  32 = "{"f0":"十二冊","f1":1.0986122886681098,"f2":14}"
  33 = "{"f0":"南開大學","f1":1.0986122886681098,"f2":15}"
  34 = "{"f0":"文學","f1":1.0986122886681098,"f2":16}"
  35 = "{"f0":"郁達夫","f1":1.0986122886681098,"f2":17}"
  36 = "{"f0":"館藏","f1":1.0986122886681098,"f2":18}"
 featureType = "WORD_COUNT"
 minTF = 1.0

0x06 預測

預測業務邏輯是DocCountVectorizerModelMapper

首先我們可以看到 FeatureType,這個可以用來配置輸出哪種信息。比如可以輸出以下若干種:

public enum FeatureType implements Serializable {
    /**
     * IDF type, the output value is inverse document frequency.
     */
    IDF(
        (idf, termFrequency, tokenRatio) -> idf
    ),
    /**
     * WORD_COUNT type, the output value is the word count.
     */
    WORD_COUNT(
        (idf, termFrequency, tokenRatio) -> termFrequency
    ),
    /**
     * TF_IDF type, the output value is term frequency * inverse document frequency.
     */
    TF_IDF(
        (idf, termFrequency, tokenRatio) -> idf * termFrequency * tokenRatio
    ),
    /**
     * BINARY type, the output value is 1.0.
     */
    BINARY(
        (idf, termFrequency, tokenRatio) -> 1.0
    ),
    /**
     * TF type, the output value is term frequency.
     */
    TF(
        (idf, termFrequency, tokenRatio) -> termFrequency * tokenRatio
    );
}

其次,在open函數中,會加載模型,比如:

wordIdWeight = {HashMap@10838}  size = 37
 "醫學" -> {Tuple2@10954} "(6,1.0986122886681098)"
 "選讀" -> {Tuple2@10956} "(11,1.0986122886681098)"
 "十二冊" -> {Tuple2@10958} "(14,1.0986122886681098)"
...
 "華齡" -> {Tuple2@11022} "(22,1.0986122886681098)"
 "索引" -> {Tuple2@11024} "(27,1.0986122886681098)"
featureType = {DocCountVectorizerModelMapper$FeatureType@10834} "WORD_COUNT"

最後,預測時候調用predictSparseVector函數,會針對輸入 二手 舊書 : 醫學 電磁 成像來進行匹配。生成稀疏向量SparseVector。

0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0

以上表示那幾個單詞 分別對應0 6 10 25 26 28 這幾個字典中對應序號的單詞,其在本句對應的出現數目都是一個。

0x07 參考

Tf-Idf詳解及應用

https://github.com/fxsjy/jieba

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

kubernetes pod內抓包,telnet檢查網絡連接的幾種方式

背景

在日常kubernetes的運維中,經常遇到pod的網絡問題,如pod間網絡不通,或者端口不通,更複雜的,需要在容器裏面抓包分析才能定位。而kubertnets的場景,pod使用的鏡像一般都是盡量精簡,很多都是基於alpine基礎鏡像製作的,因而pod內沒有ping,telnet,nc,curl命令,更別說tcpdump這種複雜的工具了。除了在容器或者鏡像內直接安裝這些工具這種最原始的法子,我們探討下其他法子。

實現

kubectl debug插件方式

項目地址 kubect debug,https://github.com/aylei/kubectl-debug

kubectl-debug 是一個簡單的 kubectl 插件,能夠幫助你便捷地進行 Kubernetes 上的 Pod 排障診斷。背後做的事情很簡單: 在運行中的 Pod 上額外起一個新容器,並將新容器加入到目標容器的 pid, network, user 以及 ipc namespace 中,這時我們就可以在新容器中直接用 netstat, tcpdump 這些熟悉的工具來解決問題了, 而舊容器可以保持最小化,不需要預裝任何額外的排障工具。操作流程可以參見官方項目地址文檔。

一條 kubectl debug命令背後是這樣的

步驟分別是:

  1. 插件查詢 ApiServer:demo-pod 是否存在,所在節點是什麼
  2. ApiServer 返回 demo-pod 所在所在節點
  3. 插件請求在目標節點上創建 Debug Agent Pod
  4. Kubelet 創建 Debug Agent Pod
  5. 插件發現 Debug Agent 已經 Ready,發起 debug 請求(長連接)
  6. Debug Agent 收到 debug 請求,創建 Debug 容器並加入目標容器的各個 Namespace 中,創建完成后,與 Debug 容器的 tty 建立連接

接下來,客戶端就可以開始通過 5,6 這兩個連接開始 debug 操作。操作結束后,Debug Agent 清理 Debug 容器,插件清理 Debug Agent,一次 Debug 完成。

直接進入容器net ns方式

有2種進入pod 所在net ns的方式,前提都是需要登錄到pod所在宿主機,且需要找出pod對應的容器ID或者名字。

ip netns方式

  • 獲取pod對應容器的ID或者name

    pid="$(docker inspect -f '{{.State.Pid}}' <container_name | uuid>)" #替換為環境實際的容器名字或者uuid
    
  • 創建容器對應netns

    ip netns會到/var/run/netns目錄下尋找network namespace,把容器進程中netns連接到這個目錄中后,ip netns才會感知到

    $ sudo mkdir -p /var/run/netns
    
    #docker默認不會創建這個鏈接,需要手動創建,這時候執行ip netns,就應當看到鏈接過來的network namespace
    $ sudo ln -sf /proc/$pid/ns/net "/var/run/netns/<container_name|uuid>" 
    
  • 執行ip netns <<container_name|uuid > bash,進入容器ns

    ip netns exec <container_name|uuid>  bash
    
  • 執行telnet,tcpdump等命令,此時執行ip a或者ifconfig,只能看到容器本身的IP

如下圖,執行ifconfig,只看到容器本身的IP,此時執行telnet,tcpdump等於直接在容器內操作

nsenter方式

nsenter為util-linux裏面的一個工具,除了進入容器net ns,還支持其他很多操作,可以查看官方文檔。

pid="$(docker inspect -f '{{.State.Pid}}' <container_name | uuid>)"
nsenter -t $pid -n /bin/bash
tcpdump -i eth0 -nn  #此時利用宿主機的tcpdump執行抓包操作,等於在容器內抓包

總結

  1. kubectl debug方式功能更強大,缺點是需要附加鏡像,要在目標pod創建debug agent的容器,比較笨重,但是優點是能使用的工具更多,不需要ssh到pod所在節點,除了netstat,tcpdump工具,還能使用htop,iostat等其他高級工具,不僅能對網絡進行debug,還能對IO等其他場景進行診斷,適用更複雜的debug場景。
  2. 直接進入容器net ns方式相對比較輕量,復用pod所在宿主機工具,但魚和熊掌不可兼得,缺點是只能進行網絡方面的debug,且需要ssh登錄到pod所在節點操作。

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

2017 CTOY 花落福田戴姆勒汽車

在頒獎典禮上,中國年度卡車評委會主席於晶對歐曼EST超級卡車給予了高度評價:歐曼EST超級卡車不僅秉承了歐洲重卡設計理念,在質量和效率方面也引進了戴姆勒的體系和標準,在動力、環保、安全、智能化方面展示出富有前瞻性的技術水平,在實際測試過程中,歐曼EST的加速時間、制動距離以及車內噪聲等多項數據都處於行業領先水平。

2016年11月17日,“2017中國年度卡車”(CToY)頒獎典禮暨廣州商用車展媒體之夜”在廣州舉行;中國國際貿易促進委員會汽車行業分會副會長楊琳、國際年度卡車組委會主席Gianenrico Griffini(格里菲尼)、中國年度卡車評委會主席於晶以及來自整車及零部件生產企業的代表、國內主流行業媒體代表,共同見證了這一中國卡車行業的年度盛事。

在經過包括技術創新、舒適性、安全性、操控性、燃油經濟性、清潔環保和總體擁有成本等方面的綜合評價后,11月正式中國上市的歐曼EST超級卡車最終摘得了“2017中國年度卡車”桂冠,成為中國首個獲得此項殊榮的重卡車型;這是歐曼EST在歷經了“4年的歐洲研發、德國DEKRA的歐洲嚴苛測試、德國漢諾威全球上市”的品質驗證后,獲得的首個極具含金量的年度大獎;這是國際年度卡車評委會、中國年度卡車評委會對歐曼EST超級卡車的卓越品質給予的高度認可,也是對福田戴姆勒汽車在推動中國重卡市場高端化發展方面給予的肯定。

2017中國年度卡車大獎含金量有多高?

中國年度卡車是國內商用車行業首個引入“國際年度卡車”獎項評選標準及規則的獎項, 並結合中國道路運輸實際情況,以客觀公正的平台、專業縝密的視角評選出年度最具標杆意義的中國卡車車型。

“國際年度卡車”獎是目前國際上廣泛認可的權威評選卡車類獎項,代表了歐洲最嚴苛的評價指標。作為世界最大的商用車生產商梅賽德斯-奔馳憑藉旗下多款創新車型,曾8次摘得“國際年度卡車”獎項,成為榮獲此獎項次數最多的品牌。 “中國年度卡車”與“國際年度卡車”評選有着同樣的含金量,受邀出席頒獎典禮的國際年度卡車評委會主席Gianenrico Griffini(格里菲尼)也表達了對“2017中國年度卡車”評選的肯定,並表示國際年度卡車評委會將為中國年度卡車評選提供最大的支持。

“2017中國年度卡車”大獎的所有參評車型,必須經過由國際年度卡車評委會成員、媒體代表、用戶及第三方測試機構組成的專業評委會客觀公正、專業縝密的評測;其評測標準也是依據國際年度卡車評選及歐洲卡車1000分評測方法,涉及駕駛室空間及內飾設計、駕乘舒適性、操控性能、車輛總擁有成本、安全性、動力性等6個大項、26個小項;無論是從評價體系的成熟性上還是從評測人員及項目的專業性上,“2017中國年度卡車”獎項的含金量可謂首屈一指。

在頒獎典禮上,中國年度卡車評委會主席於晶對歐曼EST超級卡車給予了高度評價:歐曼EST超級卡車不僅秉承了歐洲重卡設計理念,在質量和效率方面也引進了戴姆勒的體系和標準,在動力、環保、安全、智能化方面展示出富有前瞻性的技術水平,在實際測試過程中,歐曼EST的加速時間、制動距離以及車內噪聲等多項數據都處於行業領先水平。

超級卡車憑什麼能獲得年度卡車大獎?

“2017中國年度卡車大獎”旨在頒發給最近12個月內發布的、從多方面對道路運輸效率做出最大貢獻的中國品牌卡車;更高的質量、更強的性能、更快的效率、更低的成本,歐曼EST超級卡車在提升整个中國重卡技術與品質方面做出了巨大貢獻,換句話說,歐曼EST超級卡車成為“2017中國年度卡車”實至名歸。“歐曼EST超級卡車充分吸收了‘超級卡車全球創新聯盟’成員的先進科技成果,以“北京超級卡車創新中心”為依託,實現了自動駕駛、新能源及車聯網等智慧科技的應用,使歐曼EST超級卡車擁有了媲美國際重卡的技術實力。” 福田戴姆勒汽車品牌總監李健致辭中表示。

歐曼EST超級卡車是福田戴姆勒汽車鏈合德國戴姆勒整車及動力技術,攜手美國康明斯、德國采埃孚、德國大陸等超級卡車全球創新聯盟成員,面向準時高效、長途高附加值貨運等高端物流客戶,歷時4年以歐洲標準研發, 完成了歐洲最嚴苛的德國DEKRA測試,以及歷經1000萬公里實際道路測試,實現了油耗降低5-10%,碳排放減少10-15%,貨運效率提升30%,實現智能輔助駕駛、B10壽命達到150萬公里以及10萬公里的超長保養周期的卓越性能,全面提升了中國現代化物流體系在智能化、安全性、高效性方面的發展速度。歐曼EST超級卡車於11月14日,在2016中國上海智能網聯汽車展覽會上,正式上市,登陸中國市場,為中國用戶在未來高效物流運輸市場競爭中,注入了來自“超級卡車”的超級競爭力!

參与測試的中國年度卡車評委會的評委一致認為:歐曼EST的駕駛室在空間和舒適性方面接近歐洲品牌卡車,非常適合超長距離運輸使用;其採用的采埃孚TraXon自動變速器與奔馳、康明斯發動機匹配完美,對於中國市場而言是一項巨大的進步,可以滿足中國用戶日益提高的使用需求;此外,歐曼EST超級卡車還具有良好的視野和高速行駛穩定性,多項主被動安全裝置讓車輛更加容易操控;採用氣囊減振的駕駛室及空氣懸架底盤,在測試過程中帶來了良好的駕乘舒適性;在有效載荷和保養間隔等方面能幫助用戶有效提高運營效率,作為一款專為中國市場打造的高端長途運輸車型,歐曼EST超級卡車全面展示了中國重卡的最新技術水平。

歐曼EST超級卡車在歷經了歐洲研發、歐洲測試、歐洲車展、歐洲上市之後,回到中國,憑藉在“智能化、安全性、可靠性、經濟性及舒適性”方面的卓越表現,獲得“2017中國年度卡車”大獎,以歐洲科技的品質內涵,確立了在中國重卡行業的技術領先地位,全面滿足重卡用戶高端化、高效化、智能化的需求!未來,福田戴姆勒汽車將再接再厲,充分吸收“超級卡車全球創新聯盟”成員的先進科技成果,以“北京超級卡車創新中心”為依託,以品質、技術和創新能力打造更具核心競爭力的產品,不斷提升中國卡車技術水平本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

北京伽途im系列亮相廣州車展 引領MPV市場升級

外觀方面,該車相比先期上市的北京伽途ix系列更具時尚氣息。中網格柵由大面積鍍鉻材質組成,層次感較為鮮明。尺寸方面,伽途im6長/寬/高依次為4700/1780/1820mm。而伽途im8的長/寬/高依次為4730×1810×1830mm,兩車軸距同為2760mm。內飾方面,中控台液晶屏尺寸較大,面板採用了碳纖維材質和仿桃木水轉印材質進行裝飾。

11月18日,福田汽車乘用車品牌在廣州車展重磅發布北京伽途im系列新品,引領MpV市場全面升級。該系列車型包含北京伽途im6和北京伽途im8兩款產品,前置前驅的配置,更大的空間,在保障產品安全性的同時,在舒適性、便利性和人性化方面更進一步,尤其是智能車聯網系統,通過大數據共享,將人與車通過網絡實時互聯,實現人與車有效互動,憑藉全面智能化升級,滿足用戶多元化的需求。

為大家庭時代品質出行而來

二胎政策出台意味着家庭結構將發生明顯的變化,大家庭的出行對車輛的空間、駕乘舒適性和安全性等均提出更高的要求,能夠滿足大家庭出行用車的MpV車型呈現出快速增長趨勢,同時大家庭的出現也對家庭第二輛車有較大的潛在需求。

定位“智能家庭車”的北京伽途,以“引領新汽車生活潮流、提升大家庭出行品質”為願景,致力於成就汽車消費者的“精智之道、愛家之選”。北京伽途中“伽”與中國濃厚的“家”文化傳統一脈相承,“伽途”寓意“家庭的美好前途”。

北京伽途作為融合科技、智能、時尚的未來智能汽車的代表,目標市場精準鎖定為中國乘用車需求最旺盛的大家庭用車市場。在福田汽車“互聯網汽車生態系統”基礎上,其以滿足用戶個性化需求為導向,基於網絡信息系統、OTD訂單系統和智能製造系統的定製化大規模生產方式,實現研發力、製造力、營銷力和品牌力的不斷提升,最大程度滿足大家庭消費者需求。

隨着消費者生活水平的提高,對MpV車型的需求也在向中高端發展。針對目前增長較快的前驅市場,北京伽途迅速抓住有利時機,推出了中高端重磅新品——im系列。該系列以其獨到的智能、舒適、時尚、安全可靠等多重優勢成為福田汽車集團鋪路乘用車領域的扛鼎之作。

大氣靈動智慧升級

北京伽途im系列車型延續了福田汽車工業4.0體系的“智造”水準,兼具大氣靈動的外觀與科幻感的座艙設計,以比肩世界的技術和品質,代表着福田汽車對中國汽車工業的誠意探索。

外觀方面,該車相比先期上市的北京伽途ix系列更具時尚氣息。中網格柵由大面積鍍鉻材質組成,層次感較為鮮明。尺寸方面,伽途im6長/寬/高依次為4700/1780/1820mm;而伽途im8的長/寬/高依次為4730×1810×1830mm,兩車軸距同為2760mm。

內飾方面,中控台液晶屏尺寸較大,面板採用了碳纖維材質和仿桃木水轉印材質進行裝飾。方向盤採用了三幅式設計,優質仿皮材質輪緣,握感舒適。整體造型較為硬朗,比較符合時下年輕人的審美需求。

動力方面,北京伽途im系列兩款產品採用了兩款不同的動力系統。北京伽途im6配備福田1.5L 4A15M發動機,最大功率81kW;北京伽途im8則採用東安1.5L DAM15D機型,最大功率85kW。

據悉,廣州車展是北京伽途im系列繼全國發布之後區域亮相的第一站,該系列產品將於2017年2月正式登陸全國市場,讓我們拭目以待。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

16款車評選投票結果公布,小米MAX獲獎名單在此!

按照規則,5篇文章中任意1篇,評論獲得支持率最高的用戶可以獲得小米MAX一台。支持率排名2-5位的用戶,可以獲得變形金剛吸塵器1個。支持率排名6-10位的用戶,可以獲得變形金剛皮套1個。注意:每位微信用戶在本次活動僅能獲得一個獎品,以最高排名的文章為準,其餘文章的排名作廢,獎品將順延至下一位。

經過4天時間的激烈角逐,玩車、車買買、玩車TV三大號聯合舉辦的“2016我就喜歡”年度車型評選投票,終於完滿結束。

本次參与投票的年度車型共有8款SUV和8款轎車,是由我們三大號資深編輯共同探討票選的,涵蓋10-50萬今年較為熱門的車型,名單如下所示。

活動評選規則為:由三大號5篇對比文章的投票數相加,得票最高的車型,將被評選為我們的“年度最受網友喜愛的車型”。

話不多說,馬上來看看本次評選的投票結果。

從數據中可以看出,三大平台用戶對車的喜好是相近的,投票的結果都差不多,TOp3車型更是符合小編的心理預期。

年度車型評選的投票結果公布完畢,但別忘了還有件正事——“送小米MAX”活動獲獎名單公布!

為了回饋我們三大號的粉絲,小編可是下了血本購回5台小米MAX,20個變形金剛大黃蜂吸塵器,以及25個變形金剛大黃蜂皮套來送給大家。活動規則公平,在微信平台上全程公開,所以人人的機會都是平等的。

按照規則,5篇文章中任意1篇,評論獲得支持率最高的用戶可以獲得小米MAX一台;支持率排名2-5位的用戶,可以獲得變形金剛吸塵器1個;支持率排名6-10位的用戶,可以獲得變形金剛皮套1個。

注意:每位微信用戶在本次活動僅能獲得一個獎品,以最高排名的文章為準,其餘文章的排名作廢,獎品將順延至下一位。

經過4天的龍爭虎鬥,跌宕起伏的排名終於在11月22日晚24點那一刻定格,獲獎名單如下:

請獲獎用戶按以下步驟聯繫我們:

1、獲獎評論的“留言入選提示”截圖

2、投票頁面截圖

3、編輯您的姓名、電話、郵寄地址

4、將2張截圖以及您的信息,用中獎微信號發送至文章所在公眾號的聊天界面(玩車、車買買、玩車TV)

最後,我們會有專人在工作日第一時間與您取得聯繫,中獎獎品將在確認信息后,在3個工作日內陸續發出。如果在11月25日晚24:00尚未領獎的,我們將順延至下一位用戶,以此類推。

活動詳情規則請點擊

寫在最後:

我們辦這個活動的初衷,就是以有趣的形式給大家帶來相關車型的對比,車型數龐大、內容精緻,讓用戶通過3個平台不同的對比能更直觀地了解一台車。最後活動也取得了非常高的關注度,還有用戶在看完我們的建議後果斷訂了車,總之非常感謝大家能給予我們支持,我們在日後還會給大家帶來更有價值的汽車內容。

回顧8款轎車/8款SUV精彩專題報道

請點擊此處:【廣州車展專題回顧】本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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

小喬累得全身濕透!如何拯救路上爆胎的美女?

此時你可以將被換下的輪胎墊在車底,謹防車身壓到

作為一個備胎,你是不是整天躺在後備箱,想着有一天可以逆襲?不用怕,今天讓我們的小喬姐姐親身示範,教你如何換備胎。

第一步 放置三角牌

如果你在馬路邊需要換胎,首先要做的就是在車後放置三角牌,最安全距離是150米以外,這樣可以提示後面來車,謹防追尾事故。

第二步 擰松輪胎螺絲

如果你所用的是非全尺寸備胎,同時很不幸的前輪受損,一般情況下是先將後輪換到前輪,再把備胎裝到後輪,因為前輪是屬於驅動輪,這樣換置會更安全些。而在本視頻中由於時間關係,所以只換了前胎作為示範。

在鬆開螺絲時,我們一般會用腳踩着扳手,這樣會更省力更便捷。

第三步 用千斤頂頂起車身

使用千斤頂是要先找到車底的凹槽位,需對準后才可以進行車輛的升起,否則容易卡壞凹槽部位和車底,也可能會發生安全事故。此外,很多老司機都會習慣先用千斤頂頂起車身,再鬆開螺絲拆卸輪胎,但是其實先鬆開輪胎螺絲再用千斤頂才是最方便的方法。

第四步 卸下輪胎

姿勢要到位,深蹲,抬起輪胎往外拔。

第五步 裝上備胎 預緊螺絲

在裝備胎時,要對準螺絲孔位,然後把螺絲裝上,用手預擰緊它。此時你可以將被換下的輪胎墊在車底,謹防車身壓到本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

15萬中日韓SUV大比拼 本田現代能比傳祺GS4更值得買嗎?

內飾內飾上傳祺GS4採用了環抱式設計,六邊形元素設計的中控台和按鍵比較新穎,搭配着鍍鉻和烤漆裝飾條,裝配工藝良好。雖然繽智為精英型車型,但是車內整體看上去並不覺得廉價,表現得中規中矩。現代ix25採用了家族式T形設計理念,整體布局合理且錯落有致。

在國內火爆的SUV市場中,狀態最火熱的非小型SUV和緊湊型SUV莫屬了,而其中的車型又實在太多太多了,小編在SUV排行榜中順着找下來,發現傳祺GS4、本田繽智、現代ix25這三款車是中日韓SUV中比較熱門的,那麼15萬左右的購車預算,他么之中哪款最適合你呢?

有人就好奇了,本田CR-V、現代途勝的銷量不是比他們好么,為什麼沒入選呢,原因是15萬左右的價格並不能買得到,所以。。。

北京現代ix25

2015款 1.6L 自動兩驅智能型GLS

廠商指導價 14.28萬

繽智

2015款 1.8L CVT兩驅精英型

廠商指導價 14.68萬

傳祺GS4

2016款 235T G-DCT豪華版

廠商指導價 14.38萬

傳祺GS4在外觀原創度上極高,顛覆了自主品牌的抄襲現象,整體造型很有個性,飽滿而結實,凌雲翼式的進氣格柵搭配犀利的前大燈,辨識度很高;繽智的設計則要時尚柔美得多,無論從哪個角度看去都給人圓潤飽滿的感覺,很耐看;而ix25的外觀則給人簡潔幹練的感覺,運用了更多的直線條設計理念,比較硬派。

內飾上傳祺GS4採用了環抱式設計,六邊形元素設計的中控台和按鍵比較新穎,搭配着鍍鉻和烤漆裝飾條,裝配工藝良好;雖然繽智為精英型車型,但是車內整體看上去並不覺得廉價,表現得中規中矩;現代ix25採用了家族式T形設計理念,整體布局合理且錯落有致。

配置上繽智和傳祺GS4均配備了电子駐車、自動駐車、多功能方向盤、倒車影像、中控彩色大屏等配置,而在無鑰匙啟動/進入系統、陡坡緩降、電動天窗等配置繽智是缺少的,唯一的亮點就是其配備了發動機啟停技術,傳祺GS4配置最為豐富,前後排頭部氣囊、胎壓監測、定速巡航、前排座椅加熱等配置。

從車身尺寸上面我們可以清楚的看出,傳祺GS4各方面都佔優勢,空間表現上更為出色,而繽智雖然定位於小型SUV,但是本田善於利用空間設計,乘坐空間表現非常好,ix25空間上則不佔優勢,只能說將將夠用吧。

傳祺GS4 1.5T渦輪增壓發動機馬力最大,但雙離合的耐用可靠性還有待考驗;本田1.8L發動機+CVT的動力組合駕駛平順性出色,燃油經濟性好;ix25的1.6L+6擋手自一體變速器動力總成技術成熟,但功率上稍微欠缺。

總結:有人就說小編了,你拿個緊湊型SUV和小型SUV比有點不公平啊,但是同一價位內,跨級別的錯位競爭必然是存在的,銷量上就證明了好多問題,這也是傳祺GS4性價比那麼高的原因所在。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

※回頭車貨運收費標準

都是1.0T先進發動機 這款大氣合資車比思域便宜3萬多?

所以我們不妨來對比一下三個車型的1。0T發動機,看看誰家的更好。思域和福特的1。0T發動機最大馬力相同,但是福特的1。0T發動機轉速到6000才可以爆發出來,但是最大扭矩在1500轉就可以爆發,所以你會感覺福克斯的1。0T提速會更快。

近期我們從國家工信部官網獲得了一些關於雪佛蘭科沃茲1.0T車型的申報信息,根據上面信息显示,1.0T車型在外觀和現款車型(1.5L)沒有什麼差別,但是換上了一台全新的1.0T發動機。

現款的科沃茲指導價為7.99-10.99萬元,在今年的9月2日(成都車展)上市,動力系統為1.5升 113馬力+5擋手動/6擋手自一體,車身尺寸為4544*1779*1467mm,軸距為2600mm,定位緊湊型轎車,在雪佛蘭車型體系裡面定位低於科魯茲。

科沃茲屬於中國特供車型,由於是根據中國國情研發的車子,再加上價格比較實惠,10月份的銷量就達到了13836輛,也算是小有成就,幫助雪佛蘭爭取了不少的銷量,但是現在小排量渦輪盛行,也是一個大勢所趨,所以科沃茲搭載小排量增壓發動機也是勢在必行。

科沃茲1.0T車型的外觀和現款車型保持一致,不同的車型將會有15英寸或16英寸的鋁合金輪圈、天窗等配置。

其實重點還是這款1.0T發動機。這款3缸發動機的最大輸出功率為116馬力,以此為判斷依據,它很有可能就是通用集團早在2014年就發布的一款1.0L SIDI渦輪增壓發動機。這款發動機最先搭載同年發布的歐寶Corsa車型,最大馬力116,最大扭矩166牛·米。

從數據上來看,這款發動機的技術比較先進,採用全鋁缸體,同時加入了可變氣門正時技術和缸內直噴技術,目前可能會在上汽通用武漢工廠進行生產。

沒有對比就沒傷害,我們買車的時候最喜歡就是看各種不同的車型對比,目前主流的1.0T 3缸發動機還有在福克斯和最近大紅大紫的思域上面搭載。所以我們不妨來對比一下三個車型的1.0T發動機,看看誰家的更好。

思域和福特的1.0T發動機最大馬力相同,但是福特的1.0T發動機轉速到6000才可以爆發出來,但是最大扭矩在1500轉就可以爆發,所以你會感覺福克斯的1.0T提速會更快。科沃茲1.0T發動機在功率和扭矩和思域、福克斯相差不大,因為缺科沃茲1.0T發動機最大功率和扭矩爆發的轉數,所以暫時還不能推測到它的駕駛感受。

不過我們還是相信通用的發動機技術的。不過變速箱和發動機匹配默契才能有更好的駕駛感受,不知道這一點,通用能做到何種程度?

售價方面肯定會比福克斯和思域要實惠多了,畢竟科沃茲的起售價就比他們低的多了。思域1.0T的最低售價也要11.59萬,福克斯1.0T車型的最低售價竟然需要13.08萬,價格確實有點貴了。

如果科沃茲可以把3缸發動機的抖動問題解決好了,再加上合理的售價,還是會有很大的市場的。畢竟國家在政策上向小排量渦輪增壓傾斜,同時降低油耗也是大勢所趨和企業必須要承擔的社會責任。另外除了除科沃茲外,上汽通用集團還會給旗下的更多小型車及緊湊型車搭載這款發動機。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

※回頭車貨運收費標準

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

2.0T+7速雙離合 神車哈弗H6又出新款競爭力超強?

藍標版的進氣格柵較小,看起來更加精緻,紅標版本將採用大尺寸的進氣格柵(這是紅標版的一貫造型)看起來運動感十足,非常霸氣,底部霧燈區域的造型的尺寸也會更大,可以說紅標版和藍標版的不同點主要集中在前臉部分。

如今SUV大賣熱賣,受到消費者的熱捧,但是說起國內的SUV,那就不得不提哈弗了,因為就銷量來看,哈弗是當之無愧的自主SUV大哥。

作為哈弗最成功的一個車型,H6給哈弗貢獻了太多的銷量,今年10月份H6賣了56667輛,1-10月份累計賣了429896輛,甩第二名好幾條街。

但是這個銷量是有貓膩的,細心的讀者可以留意到每次看到銷量榜的時候,無論怎麼樣仔細查找,都找不到H6 coupe這款車。其實答案是這樣的。H6 coupe和H6完全不是同一台車,是全新的一代車型,主打運動風,但是在統計銷量的時候,H6 coupe是算到H6車型裏面的。根據長城官方的透漏,H6 coupe的月銷量佔H6銷量的30%左右。

所以,你看到H6的銷量才會那麼高。同時,H6 coupe的市場容量也挺大的。目前在售的H6 coupe搭載1.5T和2.0T發動機,為藍標版車型,那麼對於喜歡拉皮換殼折騰的哈弗來說,這遠遠是不夠的。所以就像H2s推出紅標版和藍標版一樣。H6 coupe也打算這樣折騰,推出紅標版車型。

廣州車展,H6 Coupe紅標版車型已經亮相了,同時我們在國家工信部官網看到了一款哈弗全新SUV的申報信息,所以我們大膽的猜測,這個車子就是即將上市的H6 COUpE紅標版車型。

長城的用意很明確,就是造出盡可能多的車型,滿足不同的消費者對外觀的不同需求,爭取銷量最大化,H6 Coupe紅標版和在售的藍標版主要的變化體現在外觀和全新的2.0T發動機。

藍標版的進氣格柵較小,看起來更加精緻,紅標版本將採用大尺寸的進氣格柵(這是紅標版的一貫造型)看起來運動感十足,非常霸氣,底部霧燈區域的造型的尺寸也會更大,可以說紅標版和藍標版的不同點主要集中在前臉部分。

但是到了側面和尾部,差別就比較小了,畢竟還是“臉”最重要麼。不過,H6 coupe的懸浮式車頂的造型看上去倒是很時尚,賣點不少。

內飾造型和在售藍標版完全一致,看起來簡潔大方,內飾用料很實在,做工很精細,看起來很有檔次感。

其實說到這裏大家也都知道了,紅標版和藍標版的差別並不大,只是外觀有少許差別。另外新車將會搭載GW4G15B 1.5T和GW4C20 2.0T汽油發動機,匹配6速手動、7速雙離合變速箱。

紅標版車型的長寬高為4590/1845/1700mm,軸距為2720mm。同時我們根據申報的信息來看,1.5T車型的百公里綜合油耗申報值為7.4L/100km,2.0T車型的為8.5L/100km。當然實際油耗肯定不止這麼低。

總結:和H2s的紅藍標車型一樣,H6 coupe也是搞兩個外觀有差別,但是內在都一樣的車子,所以對於這種車型,你只需要考慮喜歡哪個外觀就行。不過紅標版的H6 coupe上市以後,也會面諸如博越、榮威RX5等這些強大的對手。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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