【Spring註解開發】組件註冊-使用@Configuration和@Bean給容器中註冊組件

寫在前面

在之前的Spring版本中,我們只能通過寫XML配置文件來定義我們的Bean,XML配置不僅繁瑣,而且很容易出錯,稍有不慎就會導致編寫的應用程序各種報錯,排查半天,發現是XML文件配置不對!另外,每個項目編寫大量的XML文件來配置Spring,也大大增加了項目維護的複雜度,往往很多個項目的Spring XML文件的配置大部分是相同的,只有很少量的配置不同,這也造成了配置文件上的冗餘。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

Spring IOC和DI

在Spring容器的底層,最重要的功能就是IOC和DI,也就是控制反轉和依賴注入。

IOC:控制反轉,將類的對象的創建交給Spring類管理創建。
DI:依賴注入,將類裏面的屬性在創建類的過程中給屬性賦值。
DI和IOC的關係:DI不能單獨存在,DI需要在IOC的基礎上來完成。

在Spring內部,所有的組件都會放到IOC容器中,組件之間的關係通過IOC容器來自動裝配,也就是我們所說的依賴注入。接下來,我們就使用註解的方式來完成容器組件的註冊、管理及依賴、注入等功能。

在介紹使用註解完成容器組件的註冊、管理及依賴、注入等功能之前,我們先來看看使用XML文件是如何注入Bean的。

通過XML文件注入JavaBean

首先,我們在工程的io.mykit.spring.bean包下創建Person類,作為測試的JavaBean,代碼如下所示。

package io.mykit.spring.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試實體類
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {
    private static final long serialVersionUID = 7387479910468805194L;
    private String name;
    private Integer age;
}

接下來,我們在工程的resources目錄下創建Spring的配置文件beans.xml,通過beans.xml文件將Person類注入到Spring的IOC容器中,配置如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id = "person" class="io.mykit.spring.bean.Person">
        <property name="name" value="binghe"></property>
        <property name="age" value="18"></property>
    </bean>
</beans>

到此,我們使用XML方式注入JavaBean就配置完成了。接下來,我們創建一個SpringBeanTest類來進行測試,這裏,我使用的是Junit進行測試,測試方法如下所示。

@Test
public void testXmlConfig(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    Person person = (Person) context.getBean("person");
    System.out.println(person);
}

運行testXmlConfig()方法,輸出的結果信息如下。

Person(name=binghe, age=18)

從輸出結果中,我們可以看出,Person類通過beans.xml文件的配置,已經注入到Spring的IOC容器中了。

通過註解注入JavaBean

通過XML文件,我們可以將JavaBean注入到Spring的IOC容器中。那使用註解又該如何實現呢?別急,其實使用註解比使用XML文件要簡單的多,我們在項目的io.mykit.spring.plugins.register.config包下創建PersonConfig類,並在PersonConfig類上添加@Configuration註解來標註PersonConfig類是一個Spring的配置類,通過@Bean註解將Person類注入到Spring的IOC容器中。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {
     @Bean
    public Person person(){
        return new Person("binghe001", 18);
    }
}

沒錯,通過PersonConfig類我們就能夠將Person類注入到Spring的IOC容器中,是不是很Nice!!主要我們在類上加上@Configuration註解,並在方法上加上@Bean註解,就能夠將方法中創建的JavaBean注入到Spring的IOC容器中。

接下來,我們在SpringBeanTest類中創建一個testAnnotationConfig()方法來測試通過註解注入的Person類,如下所示。

@Test
public void testAnnotationConfig(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    Person person = context.getBean(Person.class);
    System.out.println(person);
}

運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)

可以看出,通過註解將Person類注入到了Spring的IOC容器中。

到這裏,我們已經明確,通過XML文件和註解兩種方式都可以將JavaBean注入到Spring的IOC容器中。那麼,使用註解將JavaBean注入到IOC容器中時,使用的bean的名稱是什麼呢? 我們可以在testAnnotationConfig()方法中添加如下代碼來獲取Person類型下的註解名稱。

//按照類型找到對應的bean名稱數組
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);

完整的testAnnotationConfig()方法的代碼如下所示。

@Test
public void testAnnotationConfig(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    Person person = context.getBean(Person.class);
    System.out.println(person);

    //按照類型找到對應的bean名稱數組
    String[] names = context.getBeanNamesForType(Person.class);
    Arrays.stream(names).forEach(System.out::println);
}

運行testAnnotationConfig()方法輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person

那這裏的person是啥?我們修改下PersonConfig類中的person()方法,將person()方法修改成person01()方法,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {

    @Bean
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

此時,我們再次運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person01

看到這裏,大家應該有種豁然開朗的感覺了,沒錯!!使用註解注入Javabean時,bean在IOC中的名稱就是使用@Bean註解標註的方法名稱。我們可不可以為bean單獨指定名稱呢?那必須可以啊!只要在@Bean註解中明確指定名稱就可以了。比如下面的PersonConfig類的代碼,我們將person01()方法上的@Bean註解修改成@Bean(“person”)註解,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {

    @Bean("person")
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

此時,我們再次運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person

可以看到,此時,輸出的JavaBean的名稱為person。

結論:我們在使用註解方式向Spring的IOC容器中注入JavaBean時,如果沒有在@Bean註解中明確指定bean的名稱,就使用當前方法的名稱來作為bean的名稱;如果在@Bean註解中明確指定了bean的名稱,則使用@Bean註解中指定的名稱來作為bean的名稱。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

容器技術之Dockerfile(三)

  前面我們聊到了dockerfile的 FROM、COPY 、ADD、LABEL、MAINTAINER、ENV、ARG、WORKDIR、VOLUME、EXPOSE、RUN、CMD、ENTRYPOINT指令的使用和說明,回顧請參考https://www.cnblogs.com/qiuhom-1874/tag/Dockerfile/;今天我們來聊聊剩下的dockerfile指令的使用和說明;

  1、USER:該指令用於指定運行image時的或運行dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序時的用戶名或UID;默認情況下,container的運行身份為root用戶;語法格式 USER <UID>|<UserName>; 需要注意的是,<UID>可以為任意数字,但實踐中其必須為/etc/passwd中某用戶的有效UID,否則,docker run命令將運行失敗;

  示例: 

[root@node1 test]# cat Dockerfile 
FROM centos:7

LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

RUN useradd nginx

USER nginx

CMD ["sleep","3000"]

[root@node1 test]# 

  提示:以上dockerfile表示在鏡像運行成容器時,以nginx用戶運行 sleep 3000

  驗證:編譯成鏡像,啟動為容器,然後進入到容器里看看sleep 3000 是否是nginx用戶在運行?

[root@node1 test]# docker build . -t test:v1
Sending build context to Docker daemon  1.051MB
Step 1/7 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/7 : LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"
 ---> Running in 0f503dae4448
Removing intermediate container 0f503dae4448
 ---> d31363b96f38
Step 3/7 : LABEL version="1.0"
 ---> Running in 8dad05999903
Removing intermediate container 8dad05999903
 ---> 2281f36d7c3c
Step 4/7 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Running in d2be9ed44aee
Removing intermediate container d2be9ed44aee
 ---> 8de872e222fb
Step 5/7 : RUN useradd nginx
 ---> Running in 37bda6ba6b60
Removing intermediate container 37bda6ba6b60
 ---> dc681f95f5ca
Step 6/7 : USER nginx
 ---> Running in 97d2357826f9
Removing intermediate container 97d2357826f9
 ---> ed277ac0c482
Step 7/7 : CMD ["sleep","3000"]
 ---> Running in 0ea578fa10bc
Removing intermediate container 0ea578fa10bc
 ---> 461f6ceabc88
Successfully built 461f6ceabc88
Successfully tagged test:v1
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                v1                  461f6ceabc88        3 seconds ago       204MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name t1 --rm -d test:v1
37e46346d6ca0ab05b67f5350d4c2a7b6b86b8d34c8d1622d78ef70b7d3dff86
[root@node1 test]# docker ps 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
37e46346d6ca        test:v1             "sleep 3000"        3 seconds ago       Up 2 seconds                            t1
[root@node1 test]# docker exec -it t1 /bin/bash
[nginx@37e46346d6ca /]$ ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
nginx         1  0.1  0.0   4364   352 ?        Ss   10:02   0:00 sleep 3000
nginx         6  0.4  0.0  11828  1808 pts/0    Ss   10:02   0:00 /bin/bash
nginx        23  0.0  0.0  51756  1708 pts/0    R+   10:02   0:00 ps aux
[nginx@37e46346d6ca /]$ exit
exit
[root@node1 test]#

  提示:可以看到基於上面的dockerfile構建的鏡像運行為容器,裏面默認跑的進程就是我們在dockerfile中指定用戶運行的進程;使用USER指定用戶運行容器里的進程,需要注意該用戶要對運行進程所需資源的所有權限;否則容器運行不起來;

  2、HEALTHCHECK:該指令用於定義如何對容器做健康狀態檢測;運行為容器后,容器里的進程不掛掉,當然容器也就不會掛掉,但是存在一種情況,容器沒有掛掉,容器里的進程無法正常提供服務了,這個時候我們就需要通過一定的手段,第一時間知道容器里的進程是否健康(是否能夠正常提供服務);healthcheck指令就是用來定義如果去檢測容器內部進程是否健康;語法格式HEALTHCHECK [OPTIONS] CMD command;其中CMD是固定格式,而後面的command是對容器里的進程做健康狀態檢查的命令;而options是用來指定對容器做健康狀態檢查的周期時間相關信息;–interval=DURATION (default: 30s),該選項用於指定對容器做健康狀態檢查的頻率,默認是30s一次;–timeout=DURATION (default: 30s),該選項用於指定對容器內部的進程做健康狀態檢查的超時時長,默認是30秒;–start-period=DURATION (default: 0s)指定對容器中的進程做健康狀態檢查延遲時間,默認0表示不延遲;這裏補充一點,之所以要延遲多少秒做健康狀態檢查是因為,docker運行為容器以後,會立刻把該容器的狀態標記為running狀態,而對於有些初始化比較慢的容器,如果馬上對它做健康狀態檢查,可能是不健康的狀態,這樣一來我們對了解容器是否健康就不是很準確了;如果配合某些工具,很可能存在檢測到容器不健康就把該容器刪除,然後重新創建,以此重複;這樣就會導致我們的容器啟動不起來; –retries=N (default: 3)表示指定對容器做健康狀態檢查的重試次數,默認是3次;也就是說檢查到容器不健康的前提或健康的前提,它都會檢查3次,如果3次檢查都是失敗狀態那麼就標記該容器不健康;而對於我們指定的命令來講,命令的返回值就決定了容器是否健康,通常命令返回值為0表示我們執行的命令正常退出,也就意味着容器是健康狀態;命令返回值為1表示容器不健康;返回值為2我們通常都是保留不使用;HEALTHCHECK NONE就表示不對容器做健康狀態檢查;

  示例:

[root@node1 test]# cat Dockerfile 
FROM centos:7

LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

RUN yum install -y httpd 

ADD ok.html /var/www/html/

CMD ["/usr/sbin/httpd","-DFOREGROUND"]

HEALTHCHECK --interval=5s --timeout=5s --start-period=5s --retries=2 \
        CMD curl -f http://localhost/ok.html || exit 1

[root@node1 test]# 

  提示:以上HEALTHCHECK指令表示每5秒檢查一次,超時時長為5秒,延遲5秒開始檢查,重試2次;如果curl -f http://localhost/ok.html這條命令正常返回0,那麼就表示容器健康,否則就返回1,表示容器不健康;

  驗證:把以上dockerfile構建成鏡像啟動為容器,我們把ok.html刪除或移動到別的目錄,看看容器是否標記為不健康?

[root@node1 test]# docker build . -t test:v1.1
Sending build context to Docker daemon  1.052MB
Step 1/8 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/8 : LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> d31363b96f38
Step 3/8 : LABEL version="1.0"
 ---> Using cache
 ---> 2281f36d7c3c
Step 4/8 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 8de872e222fb
Step 5/8 : RUN yum install -y httpd
 ---> Running in 9964718a2c3e
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
 * base: mirrors.bfsu.edu.cn
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Resolving Dependencies
--> Running transaction check
---> Package httpd.x86_64 0:2.4.6-93.el7.centos will be installed
--> Processing Dependency: httpd-tools = 2.4.6-93.el7.centos for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: system-logos >= 7.92.1-1 for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Running transaction check
---> Package apr.x86_64 0:1.4.8-5.el7 will be installed
---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed
---> Package centos-logos.noarch 0:70.0.6-3.el7.centos will be installed
---> Package httpd-tools.x86_64 0:2.4.6-93.el7.centos will be installed
---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package             Arch          Version                    Repository   Size
================================================================================
Installing:
 httpd               x86_64        2.4.6-93.el7.centos        base        2.7 M
Installing for dependencies:
 apr                 x86_64        1.4.8-5.el7                base        103 k
 apr-util            x86_64        1.5.2-6.el7                base         92 k
 centos-logos        noarch        70.0.6-3.el7.centos        base         21 M
 httpd-tools         x86_64        2.4.6-93.el7.centos        base         92 k
 mailcap             noarch        2.1.41-2.el7               base         31 k

Transaction Summary
================================================================================
Install  1 Package (+5 Dependent packages)

Total download size: 24 M
Installed size: 32 M
Downloading packages:
warning: /var/cache/yum/x86_64/7/base/packages/apr-1.4.8-5.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
Public key for apr-1.4.8-5.el7.x86_64.rpm is not installed
--------------------------------------------------------------------------------
Total                                              2.0 MB/s |  24 MB  00:12     
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Importing GPG key 0xF4A80EB5:
 Userid     : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
 Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5
 Package    : centos-release-7-8.2003.0.el7.centos.x86_64 (@CentOS)
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : apr-1.4.8-5.el7.x86_64                                       1/6 
  Installing : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Installing : httpd-tools-2.4.6-93.el7.centos.x86_64                       3/6 
  Installing : centos-logos-70.0.6-3.el7.centos.noarch                      4/6 
  Installing : mailcap-2.1.41-2.el7.noarch                                  5/6 
  Installing : httpd-2.4.6-93.el7.centos.x86_64                             6/6 
  Verifying  : mailcap-2.1.41-2.el7.noarch                                  1/6 
  Verifying  : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Verifying  : httpd-2.4.6-93.el7.centos.x86_64                             3/6 
  Verifying  : apr-1.4.8-5.el7.x86_64                                       4/6 
  Verifying  : httpd-tools-2.4.6-93.el7.centos.x86_64                       5/6 
  Verifying  : centos-logos-70.0.6-3.el7.centos.noarch                      6/6 

Installed:
  httpd.x86_64 0:2.4.6-93.el7.centos                                            

Dependency Installed:
  apr.x86_64 0:1.4.8-5.el7                                                      
  apr-util.x86_64 0:1.5.2-6.el7                                                 
  centos-logos.noarch 0:70.0.6-3.el7.centos                                     
  httpd-tools.x86_64 0:2.4.6-93.el7.centos                                      
  mailcap.noarch 0:2.1.41-2.el7                                                 

Complete!
Removing intermediate container 9964718a2c3e
 ---> a931e93eea06
Step 6/8 : ADD ok.html /var/www/html/
 ---> 97e61f41911d
Step 7/8 : CMD ["/usr/sbin/httpd","-DFOREGROUND"]
 ---> Running in e91ccdef90c2
Removing intermediate container e91ccdef90c2
 ---> 7c8af9bb7eb3
Step 8/8 : HEALTHCHECK --interval=5s --timeout=5s --start-period=5s --retries=2         CMD curl -f http://localhost/ok.html || exit 1
 ---> Running in 80682ab087d3
Removing intermediate container 80682ab087d3
 ---> aa53cba15046
Successfully built aa53cba15046
Successfully tagged test:v1.1
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                v1.1                aa53cba15046        8 seconds ago       312MB
test                v1                  461f6ceabc88        57 minutes ago      204MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name t1 --rm -d test:v1.1
332590e683fcb29f60a28703548fce7aa83df715cbb840e1283472834867d6a1
[root@node1 test]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES
332590e683fc        test:v1.1           "/usr/sbin/httpd -DF…"   3 seconds ago       Up 2 seconds (health: starting)                       t1
[root@node1 test]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                   PORTS               NAMES
332590e683fc        test:v1.1           "/usr/sbin/httpd -DF…"   7 seconds ago       Up 6 seconds (healthy)                       t1
[root@node1 test]# 

  提示:可以看到基於我們寫的dockerfile構建的鏡像已經成功運行為容器,並且標記為healthy;接下來我們進入容器把ok.html幹掉,然後在看看容器是否標記為不健康狀態?

  提示:從上面的信息可以看到我們把ok.html移除后,容器狀態就變成不健康狀態了;我們再把ok.html還原到原有位置,看看容器是否會從不健康轉換為健康呢?

  提示:可以看到把ok.html還原到/var/www/html/目錄后,容器從不健康狀態變為了健康狀態;

  3、SHELL:該指令用於指定默認shell,該指令開始到下一個SHELL中間的命令都是SHELL指定的shell 運行,所以SHELL指令在dockerfile中可出現多次,後面的SHELL指令指定的shell會覆蓋前面所有SHELL指令指定的shell;默認在Linux上是[“/bin/sh”,”-c”]在Windows上述[“cmd”,”/s”,”/c”];SHELL指令必須是以json數組的格式定義;語法SHELL [“executable”, “parameters”];

  4、STOPSIGNAL:該指令用於定義停止容器的信號;默認停止容器是15號信號 SIGTERM;語法STOPSIGNAL signal

  5、ONBUILD:該指令用於在Dockerfile中定義一個觸發器;Dockerfile用於build映像文件,此映像文件亦可作為base image被另一個Dockerfile用作FROM指令的參數,並以之構建新的映像文件;在後面的這個Dockerfile中的FROM指令在build過程中被執行時,將會“觸發”創建其base image的Dockerfile文件中的ONBUILD指令定義的觸發器;用法格式ONBUILD <INSTRUCTION>;儘管任何指令都可註冊成為觸發器指令,但ONBUILD不能自我嵌套,且不會觸發FROM和MAINTAINER指令;使用包含ONBUILD指令的Dockerfile構建的鏡像應該使用特殊的標籤,例如ruby:2.0-onbuild;在ONBUILD指令中使用ADD或COPY指令應該格外小心,因為新構建過程的上下文在缺少指定的源文件時會失敗;

  示例:

[root@node1 test]# cat Dockerfile
FROM centos:7

LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"

ONBUILD RUN yum install -y httpd




[root@node1 test]# 

  提示:以上dockerfile表示在本次構建鏡像中不運行yum install -y httpd這條命令,而是在後面的dockerfile中以本dockerfile製作的進行作為基礎繼續時,yum install -y httpd這條命令就會被觸發執行;簡單講onbuild就是指定dockerfile指令延遲執行;這裏一定要記住一點onbuild指令後面一定是跟的是dockerfile指令;

  驗證:將上面的dockerfile編譯鏡像,看看yum install -y httpd 是否執行了?

[root@node1 test]# docker build . -t test:v1.5
Sending build context to Docker daemon  1.052MB
Step 1/3 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/3 : LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> d31363b96f38
Step 3/3 : ONBUILD RUN yum install -y httpd
 ---> Running in d3601fa1c3b7
Removing intermediate container d3601fa1c3b7
 ---> 370e3a843c3c
Successfully built 370e3a843c3c
Successfully tagged test:v1.5
[root@node1 test]# 

  提示:可以看到yum install -y httpd 這條命令並沒有執行;

  驗證:將我們上面製作好的鏡像作為基礎鏡像,再來製作其他鏡像,看看yum install -y httpd 被執行?

[root@node1 aaa]# pwd
/root/test/aaa
[root@node1 aaa]# ls
Dockerfile
[root@node1 aaa]# cat Dockerfile 
FROM test:v1.5

LABEL maintainer="qiuhom <admin@admin.com>"
[root@node1 aaa]# docker build . -t myweb:v1
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM test:v1.5
# Executing 1 build trigger
 ---> Running in cf93e9f03e89
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
 * base: mirrors.huaweicloud.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Resolving Dependencies
--> Running transaction check
---> Package httpd.x86_64 0:2.4.6-93.el7.centos will be installed
--> Processing Dependency: httpd-tools = 2.4.6-93.el7.centos for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: system-logos >= 7.92.1-1 for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Running transaction check
---> Package apr.x86_64 0:1.4.8-5.el7 will be installed
---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed
---> Package centos-logos.noarch 0:70.0.6-3.el7.centos will be installed
---> Package httpd-tools.x86_64 0:2.4.6-93.el7.centos will be installed
---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package             Arch          Version                    Repository   Size
================================================================================
Installing:
 httpd               x86_64        2.4.6-93.el7.centos        base        2.7 M
Installing for dependencies:
 apr                 x86_64        1.4.8-5.el7                base        103 k
 apr-util            x86_64        1.5.2-6.el7                base         92 k
 centos-logos        noarch        70.0.6-3.el7.centos        base         21 M
 httpd-tools         x86_64        2.4.6-93.el7.centos        base         92 k
 mailcap             noarch        2.1.41-2.el7               base         31 k

Transaction Summary
================================================================================
Install  1 Package (+5 Dependent packages)

Total download size: 24 M
Installed size: 32 M
Downloading packages:
warning: /var/cache/yum/x86_64/7/base/packages/apr-1.4.8-5.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
Public key for apr-1.4.8-5.el7.x86_64.rpm is not installed
--------------------------------------------------------------------------------
Total                                              7.2 MB/s |  24 MB  00:03     
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Importing GPG key 0xF4A80EB5:
 Userid     : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
 Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5
 Package    : centos-release-7-8.2003.0.el7.centos.x86_64 (@CentOS)
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : apr-1.4.8-5.el7.x86_64                                       1/6 
  Installing : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Installing : httpd-tools-2.4.6-93.el7.centos.x86_64                       3/6 
  Installing : centos-logos-70.0.6-3.el7.centos.noarch                      4/6 
  Installing : mailcap-2.1.41-2.el7.noarch                                  5/6 
  Installing : httpd-2.4.6-93.el7.centos.x86_64                             6/6 
  Verifying  : mailcap-2.1.41-2.el7.noarch                                  1/6 
  Verifying  : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Verifying  : httpd-2.4.6-93.el7.centos.x86_64                             3/6 
  Verifying  : apr-1.4.8-5.el7.x86_64                                       4/6 
  Verifying  : httpd-tools-2.4.6-93.el7.centos.x86_64                       5/6 
  Verifying  : centos-logos-70.0.6-3.el7.centos.noarch                      6/6 

Installed:
  httpd.x86_64 0:2.4.6-93.el7.centos                                            

Dependency Installed:
  apr.x86_64 0:1.4.8-5.el7                                                      
  apr-util.x86_64 0:1.5.2-6.el7                                                 
  centos-logos.noarch 0:70.0.6-3.el7.centos                                     
  httpd-tools.x86_64 0:2.4.6-93.el7.centos                                      
  mailcap.noarch 0:2.1.41-2.el7                                                 

Complete!
Removing intermediate container cf93e9f03e89
 ---> a89914bda4b5
Step 2/2 : LABEL maintainer="qiuhom <admin@admin.com>"
 ---> Running in e175e0542b5e
Removing intermediate container e175e0542b5e
 ---> 4f406abeaab7
Successfully built 4f406abeaab7
Successfully tagged myweb:v1
[root@node1 aaa]#

  提示:可以看到在我們的dockerfile中並沒有寫 RUN  yum install -y httpd  ,但build時卻執行了 yum install -y httpd ;這是因為onbuild指令被觸發了;我們可以理解為如果我們製作的鏡像有onbuild指令指定的命令,那麼該鏡像被其他dockerfile 作為基礎鏡像時(或者被其他docker FROM指令引用時)onbuild指定就會被激活,被執行;

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

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維修中心

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

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

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

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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

排量小動力猛!10.9萬起緊湊型家用車更貴了反而更值得買?

2T發動機最大馬力116匹,峰值扭矩185牛米,與之匹配的是一台模擬八個檔位的S-CVT無級變速箱,由於定位依舊是一款家用轎車,在轉向手感建立和油門響應的調校方式上還是保留了雷凌以往較為輕盈的特性,駕駛依舊平易近人。除了發動機,它貴在哪。

雷凌1.2T

自從卡羅拉推出了1.2T版本以後,作為與其同平台的雷凌也很快的裝配上了同樣的渦輪增壓發動機,由於雷凌的設計會顯得更加的運動激進化一些,這也是很多年輕人的首次購車選擇,但是在很多人眼裡,1.2T的雷凌上市以後比以往要貴了幾千塊,這車到底還值得購買嗎?

廣汽豐田雷凌1.2T

指導價格:10.98-13.38萬

外觀並無變化

廣汽豐田雷凌的新款1.2T渦輪增壓車型畢竟只是一款中期改款車型,在外觀上並沒有什麼過多的變化,僅僅是在頂配車型當中增加了一套LED光源的日間行車燈以及中網採用了鋼琴黑騎的裝飾,值得一提的是,廣汽豐田雷凌1.2T新增了一種名為琥珀棕的車身配色。

運動化的內飾是為亮點

內飾設計其實也沒多少改動,只是在原有的基礎上增添了紅色的裝飾,添置於中控台和門板上,增加了內飾視覺感官上的精緻度,並且將前排座椅改動成為帶兩側護翼的運動型座椅設計,縫線工藝所綉出來的LEVIN字樣彰顯着身份,也讓雷凌內飾看上去更加動感。

小排量渦輪增壓才是重點

我們可以注意到1.2T雷凌的尾標上標註的是D-4T的標識,意為:Dirct-injuction 4 stroke gasoline engine with Turbo.翻譯過來就是:四衝程缸內直噴渦輪增壓發動機。與市面上多數“少了排量就少了缸數”的小排量三缸渦輪增壓機不同,豐田這款1.2T發動機依然使用了直列四缸的布局,更多的缸數也意味着這款發動機在運轉過程中可以保持優良的平順性。

由於使用了豐田雙VVT-iW可變氣門正時技術、以及使用缸內直噴的噴油方式,這款發動機的升功率也會相應增高,而且這款發動機的內燃機熱力循環方式可以在奧拓循環和阿特金森循環之間切換,所以在保證了發動機工作效率的同時,也保證了燃油經濟性。

雷凌1.2T發動機最大馬力116匹,峰值扭矩185牛米,與之匹配的是一台模擬八個檔位的S-CVT無級變速箱,由於定位依舊是一款家用轎車,在轉向手感建立和油門響應的調校方式上還是保留了雷凌以往較為輕盈的特性,駕駛依舊平易近人。

除了發動機,它貴在哪?

很多人會覺得,既然換了渦輪增壓發動機肯定在技術成本上就變得更加昂貴了,但其實我們可以對比一下,作為以往雷凌的主力車型1.6L自然吸氣版本指導價格為10.78-13.08萬,而雷凌1.2T的指導價格為10.98-13.38萬,但雷凌1.2T的配置卻是對得起它的售價。

雷凌1.2T車型標配了發動機啟停系統,並且增加了車身穩定系統,牽引力控制系統,以及上坡輔助系統,這些主動安全配置的搭載在以往的1.6L車型中並沒有裝配,如此看來這兩三千的定價換來的是更多的安全配置,這波交易並不虧。

全文總結:與雙胞胎車型卡羅拉不同的是,雷凌1.2T的上市是作為全面取代1.6L自然吸氣版本的角色存在,所以從主動安全配置上看雷凌的性價比有所提高,而1.2T渦輪增壓的搭載動力表現也比原來作為主力的1.6L車型更好,所以從品控、定位以及車型本身的綜合產品力來說,雷凌仍舊是一款不錯的值得購買的合資緊湊型家用轎車。至於究竟是否值得購買,那就要看你對於日系車,對於豐田品牌的接受程度有多高了,本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

這台800萬的自主車 馬化騰 劉強東 雷軍人手一台

全球最快電動車發布 蔚來 Ep9動力方面搭載了4台高性能電機以及4個獨立變速箱,最終能夠輸出1,360匹馬力,0到200公里加速7。1秒,極速313公里。Ep9採用彈匣式可換電池系統,快充模式下充滿電僅需45分鐘,續航里程可達427公里。搭載的DRS可調擾流控制系統,包括三種可調模式的動態尾翼系統,和全尺寸底盤擴散器等空氣動力裝置,使得Ep9在每小時240公里的速度下能夠獲得高達24,000牛的下壓力。

思域1.0T正式發布

本次只有手動/自動各一個車型,價格分別是11.59萬和12.79萬元。相比1.5T的豪華型,前排側氣囊、前後排頭部氣簾、無鑰匙進入/啟動、電動天窗、后駐車雷達、全液晶儀錶盤、後排杯架、後排中央扶手、中控屏、前霧燈、后視鏡加熱、自動空調統統都沒有了,3缸發動機還簡配成這樣,大致意思就是說這已經是最低價了,所以 1.5T的車型該加價的加價,該等車的等車繼續等吧。

樂視 lucid motors

官方表明,從造電池到造車,Atieva算是進行了一個大的跨越,而其核心競爭力則是其獨特的電池冷卻和能量管理技術,電池的能量密度比其他競爭對手普遍高出20%,續航里程可以輕鬆超過480km,它的最大馬力可以達到1200匹,最後為了安全起見被設置在900匹。儘管如此,它的百公里加速時間也可以達到2.69秒,還沒看到實車在路上跑之前,我們就看看咯。

全球最快電動車發布 蔚來 Ep9

動力方面搭載了4台高性能電機以及4個獨立變速箱,最終能夠輸出1,360匹馬力,0到200公里加速7.1秒,極速313公里。Ep9採用彈匣式可換電池系統,快充模式下充滿電僅需45分鐘,續航里程可達427公里。

搭載的DRS可調擾流控制系統,包括三種可調模式的動態尾翼系統,和全尺寸底盤擴散器等空氣動力裝置,使得Ep9在每小時240公里的速度下能夠獲得高達24,000牛的下壓力。

紀錄片显示Ep9在10月12日德國紐博格林北環賽道進行的測試中,創造了7分05秒的最快電動汽車圈速,公司創始投資人包括雷軍,馬化騰,奶茶妹夫劉強東,整個發布會過程中透露Ep9的造價達到了120萬美元,據之前消息稱未來量產的首款車型將會是一款20萬左右的SUV車型,大家覺得怎麼樣?

新能源汽車騙補貼披露 金龍汽車罰款近8億

本月財政部向工信部抄送了《財政部行政處罰事項告知書》,確認蘇州金龍公司申報2015年度中央財政補貼資金的新能源汽車中,有1683輛車截至2015年底仍未完工,但在2015年卻提前辦理了機動車行駛證。涉及補助資金5.19億元。根據相關法規,工信部將責令蘇州金龍公司停止生產和銷售問題車型,暫停蘇州金龍公司申報新能源汽車推廣應用推薦車型資質,並將問題車型從《新能源汽車推廣應用推薦車型目錄》予以剔除,進行為期6個月整改,整改完成后,工信部將對整改情況進行驗收。

最終追回罰款達7.78億,其實不止金龍汽車,現在國內93家新能源汽車企業中有72家存在有騙補貼的行為,這一次的強力打壓會不會對以後的新能源電動車發展有所改進呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

這台美系三廂車賣得好 為啥還堅持推出兩廂車型

4T 雙離合先鋒天窗版指導價:13。99萬天窗版和炫鋒版(14。99萬)只相差1萬元,但在配置只是多了真皮方向盤、倒車視頻影像、真皮座椅、後排杯架、在差價上並不至一萬,而且天窗版的性價比更高。領鋒版的售價是16。99萬,在配置上可謂是一應俱全,但編者認為這個價格,全車落地都接近20萬,還不如買一台B級車,空間更大。

科魯茲也更新換代了,全新的車型也受到消費者的追捧,而且和上一代車型相比,也更加省油了,底盤的調校也偏舒適。至於剛上市的科魯茲就有不錯的銷量,10份就賣出了11191台,這也源自於上代車型給消費者帶來不錯的口碑。

全新科魯茲和上一代車型相比,感覺是完成了瘦身,凌厲的車身線條,更顯動感,前臉的設計也是沿用了雪佛蘭家族化的設計,就像是一頭溫柔的猛獸。整體視覺感官科魯茲更顯年輕時尚。

科魯茲在內飾採用“環抱式”的設計,有種被包裹着的安全感,中控設計雖然簡潔卻不簡單,空調面板加了大量鋼琴漆的點綴,在做工用料上面,中控台採用了皮質縫線的設計,營造出上檔次的氛圍,全黑的內飾更加年輕化。

上一代科魯茲,空間也一直是它的短板。全新科魯茲也改進了不少,在空間上並不會像上代車型這麼緊湊,對於應付日常出行毫無壓力。

科魯茲的售價為: 8.99-16.99萬。科魯茲共搭載4款發動機總成,分別是1.5L、1.6L自然吸氣發動機、1.4T、1.6T渦輪增壓發動機,傳動系統配備的是6擋手動、6擋手自一體、7擋雙離合,在動力的輸出表現上,自吸組合更平順、而渦輪組合會激進一些,只要是日常代步,動力可是綽綽有餘。在燃油經濟性上面,也比上代車型更加的省油。底盤的調校也更為舒適,更為居家了。那麼我們下面說說,那款車型更適合您。

1.4T推薦車型:

科魯茲 2017款 1.4T 雙離合先鋒天窗版

指導價:13.99萬

天窗版和炫鋒版(14.99萬)只相差1萬元,但在配置只是多了真皮方向盤、倒車視頻影像、真皮座椅、後排杯架、在差價上並不至一萬,而且天窗版的性價比更高。領鋒版的售價是16.99萬,在配置上可謂是一應俱全,但編者認為這個價格,全車落地都接近20萬,還不如買一台B級車,空間更大。具體見下錶:

1.5L推薦車型:

科魯茲 2017款 1.5L 手動先鋒版

指導價:10.99萬

購買手動車型,我更推薦乞丐版,因為手動先鋒版和手動炫鋒版(12.49萬)價格差價上相差了一萬五千,只是多了個幾個不實用的配置,畢竟是作為家用代步車,在配置主要以實用為主,而且性價比高!具體見下錶:

至於1.6L和1.6T兩款車型都是掀背版,想入手的朋友,編者認為暫且可以等一等,在廣州車展上,全新的科魯茲掀背版已亮相,但在價格上還未公布。

在外觀的設計上更具時尚年輕感,流暢的車身線條更加飄逸。在發動機總成上,傳承了三廂版的動力,1.4T渦輪增壓發動機,最大功率為150千瓦,最大扭矩240牛米,傳動系統也是匹配的7擋雙離合。1.5L自然吸氣發動機最大扭矩146牛米,最大功率114千瓦,傳動系統匹配的是6擋手動和6擋自動變速箱。

編者點評:

全新科魯茲可謂是全新換代,變得更居家更舒適,而且也擁有同級別中相對優秀的經濟油耗,在變速箱的調校上也不會像上代車型有嚴重的頓挫感。時尚動感、年輕,科魯茲還是非常值得購買!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

同張顯示卡,全新與經 18 個月挖礦的舊卡遊戲效能有差異嗎?這部實測告訴你

相信大家都曾聽過買二手顯卡時,最好避免挖礦卡的建議,不過很多人應該都不知道究竟差多少、是真的會降低效能很多嗎?最近國外 YouTube 頻道就有人實際測了 RTX 2080 Ti 這張顯卡,全新與 18 個月拿來採礦的舊卡效能比較,就結果來說確實有差,但嚴格來說也沒有想像中這麼多。

全新與經 18 個月採礦的舊卡遊戲效能有差異嗎?

最近國外 YouTube 頻道 Testing Games 使用 8 款 3A 遊戲測試 RTX 2080 Ti 新舊卡的效能,每一款基本上都是採用高品質設置,搭配的處理器都是 i9-10900K。

首先是《碧血狂殺 2》,左邊是新卡,右邊則是經過 18 個月挖礦的 RTX 2080Ti 舊卡,可以看到平均差異為 4FPS,舊卡運行時的溫度較高(74 度),時脈也降一些,變成 1815MHz:

《CYBERPUNK 2077》差異一樣是在 4FPS,舊卡運行時的溫度也是 70 度以上,1815MHz 的時脈:

《四海兄弟:決定版》差異多一點點,來到 5FPS,且運行時舊卡溫度提升到 86 度,時脈降到 1785MHz:

《地平線 黎明時分》也相差 5FPS,舊卡溫度與時脈表現就跟最前面兩款一樣:

《戰地風雲 5》差距再多一點,新卡比舊卡平均多了 6FPS,舊卡一樣運行時溫度較高、時脈較低:

《極限競速: 地平線 4》差異就比較明顯,新卡平均達到 105FPS,舊卡只到 94FPS,差距 11FPS:

《天國降臨:救贖》就還好,只有 3FPS 的差距,但舊卡運行時溫度高達 81 度:

最後是 《刺客教條:維京紀元》,平均相差 5FPS:

很明顯的,經過挖礦後的顯示卡跑遊戲時,溫度會比較高且時脈低一些,無疑會更縮減使用壽命,不過如果單純看 FPS 的話,基本上差距都在 5FPS 以內,非常小,遊玩時其實感覺不到差異。

所以說,打算買二手顯卡的人,如果那張挖礦卡價格真的非常便宜,你也打算只是頂著用幾年,還是可以考慮,但跟新卡、甚至是正常使用的二手顯卡價格差距不大,那就不太建議。

完整影片:

超神!國外硬體玩家成功複刻 3dfx Voodoo 5 經典顯卡,還真的可以運作

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

M1 Mac 不僅有黑科技的高效率,也讓安全真正「到位」

體驗過了搭載 Apple Silicon 晶片的新世代蘋果桌上電腦,的確讓人驚艷於其在效率堪稱黑科技的表現。然而我們沒有注意到,甚至在先前 M1 Mac 發表會也僅以「搭載新世代 Secure Enclave 安全隔離區」淡淡帶過的部分,其實深藏不露的是蘋果歷時十多年對於安全方面的努力。最終也讓 Mac 用戶得以在享受高效能體驗的同時,得以保有包括生物辨識、密碼與資料的最高「安全感」。繼續閱讀 M1 Mac 不僅有黑科技的高效率,也讓安全真正「到位」報導內文。 

▲圖片來源:Apple

M1 Mac 不僅有黑科技的高效率,也讓安全真正「到位」

農曆春節期間,Apple 釋出了新版滿滿 200 多頁的 Apple 平台安全性的完整指南。裡面在硬體與系統安全性都有提供全盤的說明,更針對資料加密、服務安全性乃至於行動世代開始令人無法忽視的 App 應用服務方面,提供了整個蘋果生態系的安全相關資訊。

從初代 iPhone 為企業用戶製作僅有「 1 頁」的說明啟程。這 10 多年來,蘋果為了行動世代乃至於到 M1 晶片的後電腦時代,的確都因為自主打造晶片為基礎所提供的優勢,而能從晶片出發紮紮實實打造生態系,以用戶所需的資訊安全角度出發來打造軟硬體功能,更因此獲得許多資安專家認可為最全面防護裝置,可說是蘋果生態系的隱形冠軍。

對應貼身行動裝置在安全上的更嚴苛要求(畢竟真的越來越多敏感資訊存在裡面了呢)。從 iPhone 開始,蘋果其實就已經利用自有晶片的優勢為用戶提供更安全的行動裝置使用體驗。有意思的是,即便一開始 iPhone 就提供了先進的檔案保護系統。但面對每人每天平均 80 次解鎖的需求,其實還是等到了 iPhone 5s 開始 Touch ID 指紋辨識加入後,才普遍讓人願意為手機加上這道防線,讓蘋果行動裝置資訊安全努力能更被善用。

畢竟無論家中的門有多強多安全,如果開鎖的方法麻煩到你都懶得鎖上也是白搭(笑)。至於隔離區的概念,大致上就像金庫了。它可以存放產生加密金鑰、保護資料與生物特徵識資訊等項目。提供相對於一般檔案系統更進階的防護。

確保急迫需要完整安全防護的行動裝置後,為了讓 Mac 也享有安全隔離區的保護,蘋果後續也在為電腦裝置裝上 Touch ID 的同時,導入可以確保系統開機、Apple Pay、FileVault 檔案加密及在本地端存進指紋以確保不會被盜用的 T 系列安全晶片 — 其實非 Touch ID 機種也有。

▲圖:在 M1 SoC 之前,Mac 需透過 T 系列晶片來滿足安全方面的需要。(來源:Apple)

現在,透過搭載 M1 晶片的 Mac 電腦,蘋果也終究讓所有源自 iOS / iPadOS 的完整安全功能,能夠在 Apple Silicon 電腦運行並完整保護 macOS 系統的使用者。以先前 T2 晶片所能提供的保護項目為基礎,再新增了包括逐檔案的資料保護、系統完整性與密碼防護機能。更讓機器學習的機能能夠在保護資料隱私與安全的狀態下於 M1 Mac 運行。

透過自有晶片的努力,Mac 終於在去年正式讓生態系統的安全性完全到位。不過為了對應持續不斷的環境變化,也能發現 Apple 還做了不少努力。包括時常提供能讓用戶願意盡快安裝的 OTA 更新。此外,他們其實還有提供優渥獎金的 Apple Security Bounty Program 賞金計畫。最近更在 iMessage 訊息服務導入讓駭客「越來越難」入侵,對流量進行隔離來將攻擊時間越拉越長的 BlastDoor 防護機制,也算是考量安全性與隱私的功能技術。

講起來,在資安方面的範疇,蘋果之所以能獲得不錯的評價。的確在軟硬體與服務方面的高度整合有很大的幫助。但最關鍵之處,應該還是願意與多方合作並不斷用新的視角審視安全原則的態度吧。真的要以快速反應對應,才能在這個瞬息萬變的時代永遠站在第一線為用戶提供充滿安全感的使用體驗。

延伸閱讀:

讓學習贏在起跑點,來看學霸分享 iPad 搭 Apple Pencil 的高效率讀書筆記法

Pixel「車禍偵測」助翻車意外昏迷者報警脫困!雖然他開的是山貓起重機(咦)

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

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