分類
發燒車訊

三、netcore跨平台之 Linux配置nginx負載均衡

前面兩章講了netcore在linux上部署以及配置nginx,並讓nginx代理webapi。

這一章主要講如何配置負載均衡,有些步驟在前兩章講的很詳細了,所以這一章我就不會一個個截圖了。

因為本人只有一個服務器。所以我會在同一台服務器上部署兩套差不多的實例。

同樣的代碼,我們在Program.cs進行了修改,如圖所示:

這裏我把原來的端口6666改成了8888

 

 然後你可以改一改你的接口部分的代碼,便於讓你更好的看到效果。

這裏把value1和value2改成value3和value4,這裡是為了看到測試效果,在實際的開發中這裏不用改。

 

 然後發布和上傳到服務器,如何發布和上傳,我在第一章有講到:https://www.cnblogs.com/dengbo/p/11878766.html

注意的是你同樣的地方新建一個新的目錄保存你新上傳的程序,netcore是我第一章建立的,netcore1是新建的,

你把你新的發布包放在netcore即可。如圖:

上傳結束后,在這個目錄中運行你的程序,輸入下面的命令

dotnet WebApiTest.dll   --server.urls "http://*:8888"

如圖所示

 

 然後去看看你的接口是否正常

 

 

好了,這裏的準備工作完成了,下面我們進入到nginx的配置的目錄中

輸入下面的命令:

cd /usr/local/nginx/conf

然後對文件進行編輯

vim nginx.conf

 

 我們需要在這裏修改一下配置。

在如圖的server的平級添加如下的代碼

upstream NgWebApi {
                server localhost:6666;
                server localhost:8888;
    }

上面的 NgWebApi是隨意寫的名稱,不要糾結這裏。

然後在修改 proxy_pass後面的內容:

proxy_pass http://NgWebApi;

最終的結果如下:

 

 這樣你就修改完成,輸入:wq退出並保存即可。

最後檢查並重啟nginx

/usr/local/nginx/sbin/nginx -t
/usr/local/nginx/sbin/nginx -s reload

最後不要忘記把你的8888端口的webapi啟動一下。

這裏我務必要提醒你,請進入到你的程序的目錄中執行這段代碼,

cd /root/netcore1
dotnet WebApiTest.dll   --server.urls "http://*:8888"

啟動如下:

 

 

 好了,配置結束了,下面我們來測試下

 

還是昨天的那個網站進行測試   https://www.sojson.com/httpRequest/

 

 

 

多次發送請求會出現下面的響應

 

 

看到上面兩個請求,就說明你配置成功了,是不是很簡單。

上面這種配置,系統會採用默認的輪詢訪問不同的端口,nginx作為強大的反向代理,強大的遠遠不止這裏

下面簡單講講分發策略。

1)、輪詢 ——輪流處理請求(這是系統默認的)

      每個請求按時間順序逐一分配到不同的應用服務器,如果應用服務器down掉,自動剔除它,剩下的繼續輪詢,如果您的服務器都差不多,建議這個。 

2)、權重 ——誰的設置的大,誰就承擔大部分的請求

      通過配置權重,指定輪詢幾率,權重和訪問比率成正比,用於應用服務器性能不均的情況,有時候你買的服務器可能參差不齊,有的性能強大

    有的一般,你可以通過設置權重,把服務器性能強大權重設置大一點,這樣可以合理分配壓力。 

3)ip_哈希算法

      每一次的請求按訪問iphash結果分配,這樣每個訪客固定訪問一個應用服務器,可以解決session共享的問題。

 

 

關於權重的策略,如下圖示的 你只要加一個  weight=6 即可這裏不一定是6,是整數都行。

 

 

 然後保存即可

這裏不要忘記重啟nginx,以及運行8888端口的程序了,如果你不會,可以看前面的部分

最後我們看看效果

結果和上面的測試結果差不多,唯一不同的是出現下面這個結果的次數要大於另外一個的。

 

 

到這裏就結束了,感謝觀看。

 

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

分類
發燒車訊

數組與鏈表

前言

數組和鏈表是兩種數據結構,數組非常簡單易用但是它有兩個非常大的缺點,一個是數組一旦創建無法擴展,另一個則是數組的查找和刪除的速度很慢.

鏈表改善了一些數組的缺點,但是同樣的鏈表自身也存在一些自己的缺點.

本篇博客將為大家介紹一下這數組和鏈表特點及各自的優缺點.

閱讀前的準備工作

,一種粗略的評價計算機算法效率的方法.後面的內容會用到表示效率的方法.

1. 數組

我們按數組中的數組是否排序對數組進行劃分,將數組分為無序數組和有序數組.無序數組中的數組是無序的,而有序數組中的數據則是升序或者降序排序的.

1.1 無序數組

因為無序數組中的數據是無序的,往數組中添加數據時不用進行比較和移動數據,所以往無序數組裡面添加數據很快.無論是添加第一個數據還是第一萬個數據所需的時間是相同的,效率為O(1).

至於查找和刪除速度就沒有那麼快了,以數組中有一萬個數據項為例,最少需要比較1次,最多則需要比較一萬次,平均下來需要比較5000次,即N/2次比較,N代表數據量,大O表示法中常數可以忽略,所以效率為O(N).

結論:

  1. 插入很快,因為總是將數據插入到數組的空餘位置.
  2. 查找和刪除很慢,假設數組的長度為N,那麼平均的查找/刪除的比較次數為N/2,並且還需要移動數據.

1.2 有序數組

無序數組中存放的數據是無序的,有序數組裡面存放的數據則是有序的(有可能是升序有可能是降序).

因為有序數組中的數據是按升序/降序排列的,所以插入的時候需要進行排序並且移動數據項,所有有序數組的插入速度比無序數組慢. 效率為O(N).

刪除速度和無序數組一樣慢 效率為O(N).

有序數組的查找速度要比無序數組快,這是因為使用了一個叫做二分查找的算法.

二分查找: 二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須採用順序存儲結構,而且表中元素按關鍵字有序排列.

有一個關於二分查找的形象類比 -> 猜數遊戲

假設要在0-100之間猜一個數,那麼你第一個要猜的数字就是100的一半50的時候,你的朋友會告訴你這個数字比要猜的数字是大還是小,如果比数字大,你接下來要猜的数字就是50的一半25,你的朋友說比這個数字要大,那麼你下面要猜的数字就是25-50中間的那個數37,以此類推…

使用二分查找可極大的提高查找的效率,假設一個有序數組有十億個數據,那麼查找到所需的数字,最多只需比較30次.

有序數組使用二分查找的效率為O(logN).有序數組也可以通過二分查找來新增和刪除數據以提高效率,但是依然需要在新增/刪除后移動數據項,所以效率依然會有影響.

總結:

  1. 有序數組的查找速度比無序數組高,效率為O(logN)
  2. 有序數組的刪除和新增速度很慢,效率為O(N)

1.3 數組總結

數組雖然簡單易用,但是數組有兩個致命的缺點:

  1. 數組存儲的數量有限,創建的過大浪費資源,創建的過小溢出
  2. 數組的效率比其他數據結構低
  • 無序數組插入效率為O(1)時間,但是查找花費O(N)時間
  • 有序數組查找花費O(logN)時間,插入花費O(N)時間
  • 刪除需要移動平均半數的數據項,所以刪除都是O(N)的時間

2. 鏈表

數組一經創建大小就固定住了,無法修改,鏈表在這方面做出了改善,只要內存夠用就可以無限制的擴大.

鏈表是繼數組之後應用最廣泛的數據結構.

2.1 鏈表的特點

鏈表為什麼叫鏈表呢? 因為它保存數據的方式就像一條鎖鏈

鏈表保存數據的方式很像上面的這一條鎖鏈,每一塊鎖鏈就是一個鏈節點,鏈節點保存着自己的數據同時通過自己的next()方法指向下一個鏈節點. 鏈表通過鏈節點不斷地調用next()方法就可以遍歷鏈表中的所有數據.

在鏈表中,每個數據項都被包含在”鏈節點”(link)中,一個鏈結點是某個類的對象,這個類可以叫做Link.因為一個鏈表中有許多類似的鏈結點,所以有必要用一個不同於鏈表的類來表達鏈結點.

每個Link對象中都包含一個對下一個鏈結點引用的字段(通常叫做next).

鏈表本身的對象中有一個字段指向對第一個鏈結點的引用.

數據與鏈表查找數據的區別: 在數組中查找數據就像在一個大倉庫裏面一樣,一號房間沒有,我們去二號房間,二號房間沒有我們去三號房間,以此類推.. 按照地址找完所有房間就可以了.

而在鏈表中查找數據就像單線彙報的地下工作者,你是孤狼你想要彙報點情報給你的頂級上司毒蜂,但是你必須先報告給你的接頭人豬剛鬣,豬剛鬣在報告給它的單線接頭人土行孫,最後由土行孫報告給毒蜂.只能一個找一個,這樣最終完成任務.

2.2 Java代碼

鏈節點類:


/**
 * @author liuboren
 * @Title: 鏈節點
 * @Description:
 * @date 2019/11/20 19:30
 */
public class Link {
    //  保存的數據
    public int data;

    // 指向的下一個鏈節點
    public Link nextLink;

    public Link(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public Link getNextLink() {
        return nextLink;
    }

    public void setNextLink(Link nextLink) {
        this.nextLink = nextLink;
    }
}

鏈表類


/**
 * @author liuboren
 * @Title: 鏈表類
 * @Description:
 * @date 2019/11/20 19:31
 */
public class LinkList {
    private Link first;

    public LinkList() {
        first = null;
    }

    // 新增鏈節點方法
    public void insertFirst(int data) {
        Link link = new Link(data);
        link.setNextLink(first);
        first = link;
    }
}

在新增節點的時候,新增的link的next方法指向原來的first節點,並將鏈表類的first指向新增的節點.

2.4 其他鏈表

剛剛介紹的鏈表是單向鏈表,只能從后往前遍歷,其他的鏈表還有雙端鏈表、雙向鏈表、有序鏈表.

再簡單介紹一下雙端鏈表吧.

雙端鏈表就是在單向鏈表的基礎上,新增一個成員變量指向鏈表的最後一個對象.

雙端鏈表代碼:

/**
 * @author liuboren
 * @Title: 鏈表類
 * @Description:
 * @date 2019/11/20 19:31
 */
public class LinkList {
    private Link first;
    private Link last;

    public LinkList() {
        first = null;
    }

    public boolean isEmpty() {
        return first == null;
    }

    // 新增鏈節點方法
    public void insertFirst(int data) {
        Link newLink = new Link(data);
        newLink.setNextLink(first);
        if (isEmpty()) {
            last = newLink;
        }
        first = newLink;

    }
}

雙向鏈表則是可以從first和last兩個方向進行遍歷,有序鏈表的數據都是按照關鍵字的順序排列的,本文不再展開了.

2.5 鏈表的效率

鏈表的效率:

  • 表頭插入和刪除速度都很快,花費O(1)的時間.
  • 平均起來,查找&刪除&插入在制定鏈節點後面都需要搜索一半的鏈節點需要O(N)次比較,雖然數組也需要O(N)次比較,但是鏈表讓然要快一些,因為不需要移動數據(只需要改變他們的引用)

3. 總結

鏈表解決了數組大小不能擴展的問題,但是鏈表自身依然存在一些問題(在鏈表的鏈節點後面查找&刪除&插入的效率不高),那麼有沒有一種數據結構即擁有二者的優點又改善了二者的缺點呢,答案是肯定的,下篇博客將為您介紹這種優秀的數據結構,敬請期待.

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

分類
發燒車訊

SpringBoot 源碼解析 (六)—– Spring Boot的核心能力 – 內置Servlet容器源碼分析(Tomcat)

Spring Boot默認使用Tomcat作為嵌入式的Servlet容器,只要引入了spring-boot-start-web依賴,則默認是用Tomcat作為Servlet容器:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Servlet容器的使用

默認servlet容器

我們看看spring-boot-starter-web這個starter中有什麼

核心就是引入了tomcat和SpringMvc,我們先來看tomcat

Spring Boot默認支持Tomcat,Jetty,和Undertow作為底層容器。如圖:

而Spring Boot默認使用Tomcat,一旦引入spring-boot-starter-web模塊,就默認使用Tomcat容器。

切換servlet容器

那如果我么想切換其他Servlet容器呢,只需如下兩步:

  • 將tomcat依賴移除掉
  • 引入其他Servlet容器依賴

引入jetty:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <!--移除spring-boot-starter-web中的tomcat-->
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <!--引入jetty-->
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

Servlet容器自動配置原理

EmbeddedServletContainerAutoConfiguration

其中
EmbeddedServletContainerAutoConfiguration是嵌入式Servlet容器的自動配置類,該類在
spring-boot-autoconfigure.jar中的web模塊可以找到。

我們可以看到EmbeddedServletContainerAutoConfiguration被配置在spring.factories中,看過我前面文章的朋友應該知道SpringBoot自動配置的原理,這裏將EmbeddedServletContainerAutoConfiguration配置類加入到IOC容器中,接着我們來具體看看這個配置類:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication// 在Web環境下才會起作用
@Import(BeanPostProcessorsRegistrar.class)// 會Import一個內部類BeanPostProcessorsRegistrar
public class EmbeddedServletContainerAutoConfiguration {

    @Configuration
    // Tomcat類和Servlet類必須在classloader中存在 // 文章開頭我們已經導入了web的starter,其中包含tomcat和SpringMvc // 那麼classPath下會存在Tomcat.class和Servlet.class
    @ConditionalOnClass({ Servlet.class, Tomcat.class }) // 當前Spring容器中不存在EmbeddedServletContainerFactory類型的實例
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat {

        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            // 上述條件註解成立的話就會構造TomcatEmbeddedServletContainerFactory這個EmbeddedServletContainerFactory
            return new TomcatEmbeddedServletContainerFactory();
        }
    }
    
    @Configuration
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
            WebAppContext.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedJetty {

        @Bean
        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
            return new JettyEmbeddedServletContainerFactory();
        }

    }
    
    @Configuration
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedUndertow {

        @Bean
        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
            return new UndertowEmbeddedServletContainerFactory();
        }

    }
    
    //other code...
}

在這個自動配置類中配置了三個容器工廠的Bean,分別是:

  • TomcatEmbeddedServletContainerFactory

  • JettyEmbeddedServletContainerFactory

  • UndertowEmbeddedServletContainerFactory

這裏以大家熟悉的Tomcat為例,首先Spring Boot會判斷當前環境中是否引入了Servlet和Tomcat依賴,並且當前容器中沒有自定義的
EmbeddedServletContainerFactory的情況下,則創建Tomcat容器工廠。其他Servlet容器工廠也是同樣的道理。

EmbeddedServletContainerFactory

  • 嵌入式Servlet容器工廠
public interface EmbeddedServletContainerFactory {

    EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers);
}

內部只有一個方法,用於獲取嵌入式的Servlet容器。

該工廠接口主要有三個實現類,分別對應三種嵌入式Servlet容器的工廠類,如圖所示:

TomcatEmbeddedServletContainerFactory

以Tomcat容器工廠TomcatEmbeddedServletContainerFactory類為例:

public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
    
    //other code...
    
    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) {
        //創建一個Tomcat
        Tomcat tomcat = new Tomcat(); //配置Tomcat的基本環節
        File baseDir = (this.baseDirectory != null ? this.baseDirectory: createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
       tomcat.getService().addConnector(connector);
        customizeConnector(connector);
      tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        
        //包裝tomcat對象,返回一個嵌入式Tomcat容器,內部會啟動該tomcat容器
        return getTomcatEmbeddedServletContainer(tomcat);
    }
}

首先會創建一個Tomcat的對象,並設置一些屬性配置,最後調用getTomcatEmbeddedServletContainer(tomcat)方法,內部會啟動tomcat,我們來看看:

protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
    Tomcat tomcat) {
    return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}

該函數很簡單,就是來創建Tomcat容器並返回。看看TomcatEmbeddedServletContainer類:

public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {

    public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        
        //初始化嵌入式Tomcat容器,並啟動Tomcat
 initialize();
    }
    
    private void initialize() throws EmbeddedServletContainerException {
        TomcatEmbeddedServletContainer.logger
                .info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();
                try {
                    final Context context = findContext();
                    context.addLifecycleListener(new LifecycleListener() {

                        @Override
                        public void lifecycleEvent(LifecycleEvent event) {
                            if (context.equals(event.getSource())
                                    && Lifecycle.START_EVENT.equals(event.getType())) {
                                // Remove service connectors so that protocol
                                // binding doesn't happen when the service is
                                // started.
                                removeServiceConnectors();
                            }
                        }

                    });

                    // Start the server to trigger initialization listeners
                    //啟動tomcat
                    this.tomcat.start(); // We can re-throw failure exception directly in the main thread
                    rethrowDeferredStartupExceptions();

                    try {
                        ContextBindings.bindClassLoader(context, getNamingToken(context),
                                getClass().getClassLoader());
                    }
                    catch (NamingException ex) {
                        // Naming is not enabled. Continue
                    }

                    // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                    // blocking non-daemon to stop immediate shutdown
                    startDaemonAwaitThread();
                }
                catch (Exception ex) {
                    containerCounter.decrementAndGet();
                    throw ex;
                }
            }
            catch (Exception ex) {
                stopSilently();
                throw new EmbeddedServletContainerException(
                        "Unable to start embedded Tomcat", ex);
            }
        }
    }
}

到這裏就啟動了嵌入式的Servlet容器,其他容器類似。

Servlet容器啟動原理

SpringBoot啟動過程

我們回顧一下前面講解的SpringBoot啟動過程,也就是run方法:

public ConfigurableApplicationContext run(String... args) {
    // 計時工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    configureHeadlessProperty();

    // 第一步:獲取並啟動監聽器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 第二步:根據SpringApplicationRunListeners以及參數來準備環境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
        configureIgnoreBeanInfo(environment);

        // 準備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術字體
        Banner printedBanner = printBanner(environment);

        // 第三步:創建Spring容器
        context = createApplicationContext();

        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // 第四步:Spring容器前置處理
        prepareContext(context, environment, listeners, applicationArguments,printedBanner);

        // 第五步:刷新容器
 refreshContext(context);

     // 第六步:Spring容器後置處理
        afterRefresh(context, applicationArguments);

      // 第七步:發出結束執行的事件
        listeners.started(context);
        // 第八步:執行Runners
        this.callRunners(context, applicationArguments);
        stopWatch.stop();
        // 返回容器
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, exceptionReporters, ex);
        throw new IllegalStateException(ex);
    }
}

我們回顧一下第三步:創建Spring容器

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            //根據應用環境,創建不同的IOC容器
            contextClass = Class.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

創建IOC容器,如果是web應用,則創建
AnnotationConfigEmbeddedWebApplicationContext的IOC容器;如果不是,則創建AnnotationConfigApplicationContext的IOC容器;很明顯我們創建的容器是AnnotationConfigEmbeddedWebApplicationContext
接着我們來看看
第五步,刷新容器
refreshContext(context);

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
}

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    //調用容器的refresh()方法刷新容器
 ((AbstractApplicationContext) applicationContext).refresh();
}

容器刷新過程

調用抽象父類AbstractApplicationContext的refresh()方法;

AbstractApplicationContext

 1 public void refresh() throws BeansException, IllegalStateException {
 2     synchronized (this.startupShutdownMonitor) {
 3         /**
 4          * 刷新上下文環境
 5          */
 6         prepareRefresh();
 7 
 8         /**
 9          * 初始化BeanFactory,解析XML,相當於之前的XmlBeanFactory的操作,
10          */
11         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
12 
13         /**
14          * 為上下文準備BeanFactory,即對BeanFactory的各種功能進行填充,如常用的註解@Autowired @Qualifier等
15          * 添加ApplicationContextAwareProcessor處理器
16          * 在依賴注入忽略實現*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
17          * 註冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的實例注入進去
18          */
19         prepareBeanFactory(beanFactory);
20 
21         try {
22             /**
23              * 提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
24              */
25             postProcessBeanFactory(beanFactory);
26 
27             /**
28              * 激活各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
29              * 執行對應的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
30              */
31             invokeBeanFactoryPostProcessors(beanFactory);
32 
33             /**
34              * 註冊攔截Bean創建的Bean處理器,即註冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意兩者的區別
35              * 注意,這裏僅僅是註冊,並不會執行對應的方法,將在bean的實例化時執行對應的方法
36              */
37             registerBeanPostProcessors(beanFactory);
38 
39             /**
40              * 初始化上下文中的資源文件,如國際化文件的處理等
41              */
42             initMessageSource();
43 
44             /**
45              * 初始化上下文事件廣播器,並放入applicatioEventMulticaster,如ApplicationEventPublisher
46              */
47             initApplicationEventMulticaster();
48 
49             /**
50  * 給子類擴展初始化其他Bean 51              */
52  onRefresh(); 53 
54             /**
55              * 在所有bean中查找listener bean,然後註冊到廣播器中
56              */
57             registerListeners();
58 
59             /**
60              * 設置轉換器
61              * 註冊一個默認的屬性值解析器
62              * 凍結所有的bean定義,說明註冊的bean定義將不能被修改或進一步的處理
63              * 初始化剩餘的非惰性的bean,即初始化非延遲加載的bean
64              */
65             finishBeanFactoryInitialization(beanFactory);
66 
67             /**
68              * 通過spring的事件發布機制發布ContextRefreshedEvent事件,以保證對應的監聽器做進一步的處理
69              * 即對那種在spring啟動后需要處理的一些類,這些類實現了ApplicationListener<ContextRefreshedEvent>,
70              * 這裏就是要觸發這些類的執行(執行onApplicationEvent方法)
71              * spring的內置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
72              * 完成初始化,通知生命周期處理器lifeCycleProcessor刷新過程,同時發出ContextRefreshEvent通知其他人
73              */
74             finishRefresh();
75         }
76 
77         finally {
78     
79             resetCommonCaches();
80         }
81     }
82 }

我們看第52行的方法:

protected void onRefresh() throws BeansException {

}

很明顯抽象父類AbstractApplicationContext中的onRefresh是一個空方法,並且使用protected修飾,也就是其子類可以重寫onRefresh方法,那我們看看其子類AnnotationConfigEmbeddedWebApplicationContext中的onRefresh方法是如何重寫的,AnnotationConfigEmbeddedWebApplicationContext又繼承EmbeddedWebApplicationContext,如下:

public class AnnotationConfigEmbeddedWebApplicationContext extends EmbeddedWebApplicationContext {

那我們看看其父類EmbeddedWebApplicationContext 是如何重寫onRefresh方法的:

EmbeddedWebApplicationContext

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        //核心方法:會獲取嵌入式的Servlet容器工廠,並通過工廠來獲取Servlet容器
 createEmbeddedServletContainer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start embedded container", ex);
    }
}

在createEmbeddedServletContainer方法中會獲取嵌入式的Servlet容器工廠,並通過工廠來獲取Servlet容器:

 1 private void createEmbeddedServletContainer() {
 2     EmbeddedServletContainer localContainer = this.embeddedServletContainer;
 3     ServletContext localServletContext = getServletContext();
 4     if (localContainer == null && localServletContext == null) {
 5         //先獲取嵌入式Servlet容器工廠
 6         EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();  7         //根據容器工廠來獲取對應的嵌入式Servlet容器
 8         this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());  9     }
10     else if (localServletContext != null) {
11         try {
12             getSelfInitializer().onStartup(localServletContext);
13         }
14         catch (ServletException ex) {
15             throw new ApplicationContextException("Cannot initialize servlet context",ex);
16         }
17     }
18     initPropertySources();
19 }

關鍵代碼在第6和第8行,先獲取Servlet容器工廠,然後根據容器工廠來獲取對應的嵌入式Servlet容器

獲取Servlet容器工廠

protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
    //從Spring的IOC容器中獲取EmbeddedServletContainerFactory.class類型的Bean
    String[] beanNames = getBeanFactory().getBeanNamesForType(EmbeddedServletContainerFactory.class); //調用getBean實例化EmbeddedServletContainerFactory.class
    return getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class);
}

我們看到先從Spring的IOC容器中獲取EmbeddedServletContainerFactory.class類型的Bean,然後調用getBean實例化EmbeddedServletContainerFactory.class,大家還記得我們第一節Servlet容器自動配置類EmbeddedServletContainerAutoConfiguration中注入Spring容器的對象是什麼嗎?當我們引入spring-boot-starter-web這個啟動器后,會注入TomcatEmbeddedServletContainerFactory這個對象到Spring容器中,所以這裏獲取到的Servlet容器工廠是TomcatEmbeddedServletContainerFactory,然後調用

TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer方法獲取Servlet容器,並且啟動Tomcat,大家可以看看文章開頭的getEmbeddedServletContainer方法。

大家看一下第8行代碼獲取Servlet容器方法的參數getSelfInitializer(),這是個啥?我們點進去看看

private ServletContextInitializer getSelfInitializer() {
    //創建一個ServletContextInitializer對象,並重寫onStartup方法,很明顯是一個回調方法
    return new ServletContextInitializer() { public void onStartup(ServletContext servletContext) throws ServletException { EmbeddedWebApplicationContext.this.selfInitialize(servletContext); } };
}

創建一個ServletContextInitializer對象,並重寫onStartup方法,很明顯是一個回調方法,這裏給大家留一點疑問:

  • ServletContextInitializer對象創建過程是怎樣的?
  • onStartup是何時調用的?
  • onStartup方法的作用是什麼?

ServletContextInitializer是 Servlet 容器初始化的時候,提供的初始化接口。這裏涉及到Servlet、Filter實例的註冊,我們留在下一篇具體講

 

 

 

 

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

分類
發燒車訊

QQ是怎樣創造出來的?——解密好友系統的設計

本篇介紹筆者接觸的第一個後台系統,從自身見聞出發,因此涉及的內容相對比較基礎,後台大牛請自覺略過。

什麼是好友系統?

簡單的說,好友系統是維護用戶好友關係的系統。我們最熟悉的好友系統案例當屬QQ,實際上QQ是一款即時通訊工具,憑着好友系統沉澱了海量的好友關係鏈,從而鑄就了一個堅不可摧的商業帝國。好友系統的重要性可見一斑。

熟悉互聯網產品的人都知道,當產品有了一定的用戶量,往往會開發一個好友系統。其主要目的是增加用戶粘性(有了好友就會常來)或者增加社區活躍度(有了好友就會多交流)。

而我的後台開發生涯就是從這樣一個系統開始的。

那時候,好友系統對於我們團隊大部分人來說,都是一個全新的事物,因為我們大部分人都是應屆生。整個系統的架構自然不是我們一群黃毛小孩所能創造。當年的架構圖已經找不到了,但是憑着一點記憶和多年來的經驗積累,還是可以把當年的架構勾勒出來。

 

如圖,好友系統的架構是常見的3層結構,包括接入層、邏輯層和數據層。

我們先從數據層講起。

因為我們對QQ太熟悉了,我們可以很容易地列出好友系統的數據主要包括用戶資料、好友關係鏈、消息(聊天消息和系統消息)、在線狀態等。

互聯網產品往往要面對海量的請求併發,傳統的關係型數據庫比較難滿足讀寫需求。在存儲中,一般是讀多寫少的數據才會使用MySQL等關係型數據庫,而且往往還需要增加緩存來保證性能;NoSQL(Not Only SQL)應該是目前的主流。

對於好友系統,用戶資料和好友關係鏈都使用了kv存儲,而消息使用公司自研的tlist(可以用redis的list替代),在線狀態下面再介紹。

接着是邏輯層

在這個系統中複雜度最高的應該是消息服務(而這個服務我並沒有參与開發[捂臉])。

消息服務中,消息按類型分為聊天消息和系統消息(系統消息包括加好友消息、全局tips推送等),按狀態分為在線消息和離線消息。在實現中,維護3種list:聊天消息、系統消息和離線消息。聊天消息是兩個用戶共享的,系統消息和離線消息每個用戶獨佔。當用戶在線時,聊天消息和系統消息是直接發送的;如果用戶離線,就把消息往離線消息list存入一份,等用戶再次登錄時拉取。

這樣看來,消息服務並不複雜?其實不然,系統設計中常規的流程設計往往是比較簡單的,但是對於互聯網產品,異常情況才是常態,當把各種異常情況都考慮進來時,系統就會非常複雜。

這個例子中,消息發送丟包是一種異常情況,怎麼保證在丟包情況下,還能正常運行就是一個不小的問題。

常見的解決方法是收包方回復確認包,發送方如果沒收到確認包就重發。但是確認包又可能丟包,那又可以給確認包增加一個確認包,這是一個永無止境的確認。

解決方法可以參考TCP的重傳機制。那問題來了,我們為什麼不用TCP呢?因為TCP還是比較慢的,聊天消息的可靠性沒有交易數據要求那麼高,丟幾條消息並不會造成嚴重後果,但是如果用戶每次發送消息后都要等很久才能被收到,那體驗是很差的。

一個比較折中的方案是,收包方回復確認包,如果發送方在一定時間內沒有收到確認就重發;如果收包方收到兩個相同的包(自定義seq一樣),去重即可。

一個面試題引發的討論:

面試時我常常會問候選人一個問題:在分佈式系統中怎樣實現一個用戶同時只能有一個終端在線(用戶在兩個地方先後登錄賬號,后一次登錄可以把前一次登錄踢下線)?這是互聯網產品中非常基礎的一個功能,考察的是候選人基本的架構設計能力。

設計要先從接入服務器(下稱接口機)說起。接口機是好友系統對外的窗口,主要功能是維護用戶連接、登錄鑒權、加解密數據和向後端服務透傳數據等。用戶連接好友系統,首先是連接到接口機,鑒權成功后,接口機會在內存中維護用戶session,後續的操作都是基於session進行。

如圖所示,用戶如果嘗試登錄兩次,接口機通過session就可以將第一次的登錄踢下線,從而保證只有一個終端在線。

問題解決了嗎?

沒有。因為實際系統肯定不會只有一台接口機,在多台接口的情況下,上面的方法就不可行了。因為每個接口機只能維護部分用戶的session,所以如果用戶先後連接到不同的接口機,就會造成用戶多處登錄的問題。

 

自然可以想到,解決的方法就是要維護一個用戶狀態的全局視圖。在我們的好友系統中,稱為在線狀態服務。

在線狀態服務,顧名思義就是維護用戶的在線狀態(登錄時間、接口機IP等)的服務。用戶登錄和退出會通過接口機觸發這裏的狀態變更。因為登錄包和退出包都可能丟包,所以心跳包也用作在線狀態維護(收到一次心跳標記為在線,收不到n次心跳標記為離線)。

一種常用的方法是,採用bitmap存儲在線狀態,具體是指在內存中分配一塊空間,32位機器上的自然數一共有4294967296個,如果用一個bit來表示一個用戶ID(例如QQ號),1代表在線,0代表離線,那麼把全部自然數存儲在內存只要4294967296 / (8 * 1024 * 1024) = 512MB(8bit = 1Byte)。當然,實現中也可以根據需要給每個用戶分配更多的bit。

於是,踢下線功能如圖所示。

 

用戶登錄的時候,接口機首先查找本機上是否有session,如果有則更新session,接着給在線狀態服務發送登錄包,在線狀態服務檢查用戶是否已經在線,如果在線則更新狀態信息,並向上次登錄的接口機IP發送踢下線包;接口機在收到踢下線包時會檢查包中的用戶ID是否存在session,如果存在則給客戶端發送踢下線包並刪除session。

在實際中,踢下線功能還有很多細節問題需要注意。

又回到用戶先後登錄同一台接口機的情況:

 

圖中踢下線流程是正確的,但是如果步驟10和13調換了順序(在UDP傳輸中是常見的)會發生什麼?大家可以自己推演一下,後到的踢下線包會把第二次登錄的A’踢下線了。這不是我們期望的。怎麼辦呢?

解決方法分幾個細節,①接口機在收到13號登錄成功包時,先將session A替換成session A’,然後給客戶端A發生踢下線包(避免多處存活導致互相踢下線);②踢下線包中必須包含除用戶ID外的其他標識信息,session的唯一標識應該是ID+XXX的形式(我最開始採用的是ID+LoginTime),XXX是為了區分某次的登錄;③接口機在收到踢下線包的時候只要判斷ID+XXX是否吻合來決定是否給客戶端發踢下線包。

現實情況,問題總是千奇百怪的,好在辦法總比問題多。

比如我在項目中遇到過接口機和在線狀態服務時間漂移(差幾秒)的情況。這樣踢下線的唯一標識就不能是用戶ID+LoginTime的形式了。可以為每次的登錄生成一個唯一的UUID解決。類似的問題還有很多,不再贅述。

總結一下,本篇主要介紹了好友系統的整體架構和部分模塊的實現方式。分佈式系統中各個模塊的實現其實並不難,難點主要在於應對複雜網絡環境帶來的問題(如丟包、時延等)和服務器異常帶來的問題(如為了應對服務器宕機會增加服務器冗餘度,進而又會引發其它問題)。

好友系統雖然簡單,但麻雀雖小五臟俱全,架構設計的各種技術基本都有涉及。例如分層結構、負載均衡、平行擴展、容災、服務發現、服務器開發框架等方面,後面我會在各個不同的項目中介紹這些技術,敬請期待。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

分類
發燒車訊

詳解JavaScript錯誤捕獲和上報流程

 

 

 

 

怎麼捕獲錯誤並且處理,是一門語言必備的知識。在JavaScript中也是如此。

那怎麼捕獲錯誤呢?初看好像很簡單,try-catch就可以了嘛!但是有的時候我們發現情況卻繁多複雜。

  • Q1: 同步可以try-catch,但一個異步回調,比如setTimeOut里的函數還可以try-catch嗎?

  • Q2: Promise的錯誤捕獲怎麼做?

  • Q3: async/await怎麼捕獲錯誤?

  • Q4: 我能夠在全局環境下捕獲錯誤並且處理嗎?

  • Q5: React16有什麼新的錯誤捕獲方式嗎?

  • Q6: 捕獲之後怎麼上報和處理?

 

問題有點多,我們一個一個來。

 

Q1. 同步代碼里的錯誤捕獲方式

在同步代碼里,我們是最簡單的,只要try-catch就完了 

function test1 () {
  try {
    throw Error ('callback err');
  } catch (error) {
    console.log ('test1:catch err successfully');
  }
}
test1();

輸出結果如下,顯然是正常的

Q2. 普通的異步回調里的錯誤捕獲方式(Promise時代以前)

上面的問題來了,我們還能通過直接的try-catch在異步回調外部捕獲錯誤嗎?我們試一試 

// 嘗試在異步回調外部捕獲錯誤的結果
function test2 () {
  try {
    setTimeout (function () {
      throw Error ('callback err');
    });
  } catch (error) {
    console.log ('test2:catch err successfully');
  }
}
test2(); 

輸出

注意這裏的Uncaught Error的文本,它告訴我們錯誤沒有被成功捕捉。

為什麼呢? 因為try-catch的是屬於同步代碼,它執行的時候,setTimeOut內部的的匿名函數還沒有執行呢。而內部的那個匿名函數執行的時候,try-catch早就執行完了。( error的內心想法:哈哈,只要我跑的夠慢,try-catch還是追不上我!)

但是我們簡單想一想,誒我們把try-catch寫到函數裏面不就完事了嘛!

 

 

function test2_1 () {
  setTimeout (function () {
    try {
      throw Error ('callback err');
    } catch (error) {
      console.log ('test2_1:catch err successfully');
    }
  });
}
test2_1();

輸出結果如下,告訴我們這方法可行

 

總結下Promise時代以前,異步回調中捕獲和處理錯誤的方法

  • 在異步回調內部編寫try-catch去捕獲和處理,不要在外部哦

  • 很多異步操作會開放error事件,我們根據事件去操作就可以了

Q3. Promise里的錯誤捕獲方式

可通過Promise.catch方法捕獲

function test3 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  }).catch (err => {
    console.log ('promise error');
  });
}

輸出結果

>> reject方法調用和throw Error都可以通過Promise.catch方法捕獲

function test4 () {
  new Promise ((resolve, reject) => {
    reject ('promise reject error');
  }).catch (err => {
    console.log (err);
  });
} 

輸出結果

 

>> then方法中的失敗回調和Promise.catch的關係

  • 如果前面的then方法沒寫失敗回調,失敗時後面的catch是會被調用的

  • 如果前面的then方法寫了失敗回調,又沒拋出,那麼後面的catch就不會被調用了

// then方法沒寫失敗回調
function test5 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  })
    .then (success => {})
    .catch (err => {
      console.log ('the error has not been swallowed up');
    });
}
// then方法寫了失敗回調
function test5 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  })
    .then (success => {},err => {})
    .catch (err => {
      console.log ('the error has not been swallowed up');
    });
}

輸出分別為

1.the error has not been swallowed up
2.無輸出

Q4.async/await里的錯誤捕獲方式

對於async/await這種類型的異步,我們可以通過try-catch去解決

async function test6 () {
  try {
    await getErrorP ();
  } catch (error) {
    console.log ('async/await error with throw error');
  }
}
 
function getErrorP () {
  return new Promise ((resolve, reject) => {
    throw Error ('promise error');
  });
}
test6();

輸出結果如下

 

>> 如果被await修飾的Promise因為reject調用而變化,它也是能被try-catch的

(我已經證明了這一點,但是這裏位置不夠,我寫不下了)

Q5.在全局環境下如何監聽錯誤

window.onerror可以監聽全局錯誤,但是很顯然錯誤還是會拋出

window.onerror = function (err) {
  console.log ('global error');
};
throw Error ('global error');

 

輸出如下

 

Q6.在React16以上如何監聽錯誤

>> componentDidCatch和getDerivedStateFromError鈎子函數

class Bar extends React.Component {
  // 監聽組件錯誤
  componentDidCatch(error, info) {
    this.setState({ error, info });
  }
  // 更新 state 使下一次渲染能夠显示降級后的 UI
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  render() {
  }
}

 

 

有錯誤,那肯定要上報啊!不上報就發現不了Bug這個樣子。Sentry這位老哥就是個人才,日誌記錄又好看,每次見面就像回家一樣

 

 

Sentry簡單介紹

Sentry provides open-source and hosted error monitoring that helps all software
teams discover, triage, and prioritize errors in real-time.
One million developers at over fifty thousand companies already ship
better software faster with Sentry. Won’t you join them?
—— Sentry官網

 

Sentry是一個日誌上報系統,Sentry 是一個實時的日誌記錄和匯總處理的平台。專註於錯誤監控,發現和數據處理,可以讓我們不再依賴於用戶反饋才能發現和解決線上bug。讓我們簡單看一下Sentry支持哪些語言和平台吧

 

在JavaScript領域,Sentry的支持也可以說是面面俱到

 

參考鏈接
https://docs.sentry.io/platforms/ 

Sentry的功能簡單說就是,你在代碼中catch錯誤,然後調用Sentry的方法,然後Sentry就會自動幫你分析和整理錯誤日誌,例如下面這張圖截取自Sentry的網站中

 

在JavaScript中使用Sentry 

1.首先呢,你當然要註冊Sentry的賬號

這個時候Sentry會自動給你分配一個唯一標示,這個標示在Sentry里叫做 dsn

2. 安卓模塊並使用基礎功能

安裝@sentry/browser 

npm install @sentry/browser

 

在項目中初始化並使用

import * as Sentry from '@sentry/browser';
 
Sentry.init ({
  dsn: 'xxxx',
});
 
try {
  throw Error ('我是一個error');
} catch (err) {
    // 捕捉錯誤
  Sentry.captureException (err);
}

3.上傳sourceMap以方便在線上平台閱讀出錯的源碼

 

// 安裝
$ npm install --save-dev @sentry/webpack-plugin
$ yarn add --dev @sentry/webpack-plugin
 
// 配置webpack
const SentryWebpackPlugin = require('@sentry/webpack-plugin');
module.exports = {
  // other configuration
  plugins: [
    new SentryWebpackPlugin({
      include: '.',
      ignoreFile: '.sentrycliignore',
      ignore: ['node_modules', 'webpack.config.js'],
      configFile: 'sentry.properties'
    })
  ]
}; 

4. 為什麼不是raven.js?

 

// 已經廢棄,雖然你還是可以用
var Raven = require('raven-js');
Raven
  .config('xxxxxxxxxxx_dsn')
  .install();

 

Sentry的核心功能總結

捕獲錯誤

try { aFunctionThatMightFail(); } catch (err) { Sentry.captureException(err); }

 

設置該錯誤發生的用戶信息

下面每個選項都是可選的,但必須 存在一個選項 才能使Sentry SDK捕獲用戶: id 

Sentry.setUser({
    id:"penghuwan12314"
  email: "penghuwan@example.com",
  username:"penghuwan",
  ip_addressZ:'xxx.xxx.xxx.xxx'
  });

 

設置額外數據

Sentry.setExtra("character_name", "Mighty Fighter");
設置作用域 
Sentry.withScope(function(scope) {
    // 下面的set的效果只存在於函數的作用域內
  scope.setFingerprint(['Database Connection Error']);
  scope.setUser(someUser);
  Sentry.captureException(err);
});
// 在這裏,上面的setUser的設置效果會消失

 

設置錯誤的分組

整理日誌信息,避免過度冗餘 

Sentry.configureScope(function(scope) {
  scope.setFingerprint(['my-view-function']);
});

 

設置錯誤的級別

在閱讀日誌時可以確定各個bug的緊急度,確定排查的優先書序

Sentry.captureMessage('this is a debug message', 'debug');
//fatal,error,warning,info,debug五個值
// fatal最嚴重,debug最輕

 

自動記錄某些事件

例如下面的方法,會在每次屏幕調整時完成上報 

window.addEventListener('resize', function(event){
  Sentry.addBreadcrumb({
    category: 'ui',
    message: 'New window size:' + window.innerWidth + 'x' + window.innerHeight,
    level: 'info'
  });
})

Sentry實踐的運用

根據環境設置不同的dsn

let dsn;
  if (env === 'test') {
    dsn = '測試環境的dsn';
  } else {
    dsn =
      '正式環境的dsn';
  }
 
Sentry.init ({
  dsn
});

 

 

 

 

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

分類
發燒車訊

Java編程思想——第14章 類型信息(二)反射

六、反射:運行時的類信息

  我們已經知道了,在編譯時,編譯器必須知道所有要通過RTTI來處理的類。而反射提供了一種機制——用來檢查可用的方法,並返回方法名。區別就在於RTTI是處理已知類的,而反射用於處理未知類。Class類與java.lang.reflect類庫一起對反射概念進行支持,該類庫包含Field、Method以及Constructor(每個類都實現了Member接口)。這些類型是由JVM運行時創建的,用來表示未知類種對應的成員。使用Constructor(構造函數)創建新的對象,用get(),set()方法讀取和修改與Field對象(字段)關聯的字段,用invoke()方法調用與Method對象(方法)關聯的方法。這樣,匿名對象的類信息就能在運行時被完全確定下來,而在編譯時不需要知道任何事情。

  其實,當反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,在做其他事情之前必須先加載這個類的Class對象。因此,那個類的.class文件對於JVM來說必須時可獲取的(在本地或網絡上)所以反射與RTTI的區別只在於:對於RTTI來說,編譯器在編譯時打開和檢查.class文件,而對於反射來說,.class文件在編譯時是不可獲得的,所以是運行時打開和檢查.class文件。反射在需要創建更動態的代碼時很有用。

七、動態代理

  代理是基本的設計模式:為其他對象提供一種代理,以便控制對象,而在對象前或后加上自己想加的東西。

interface Interface {
    void doSomething();

    void doSomeOtherThing(String args);
}

class RealObject implements Interface {

    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void doSomeOtherThing(String args) {
        System.out.println("doSomeOtherThing" + args);
    }
}

class SimpleProxy implements Interface {

    private Interface proxyId;

    public SimpleProxy(Interface proxyId) {
        this.proxyId = proxyId;
    }

    @Override
    public void doSomething() {
        //將原有的doSomething 方法添加上了一個輸出 這就是代理之後新增的東西
        //就好比某公司代理遊戲后加的內購
        System.out.println("SimpleProxy doSomething");
        proxyId.doSomething();
    }

    @Override
    public void doSomeOtherThing(String args) {
        proxyId.doSomeOtherThing(args);
        //新增的東西可以在原有之前或之後都行
        System.out.println("SimpleProxy doSomeOtherThing" + args);
    }
}

public class SimpleProxyDemo {
    static void consumer(Interface i) {
        i.doSomething();
        i.doSomeOtherThing(" yi gi woli giao");
    }

    public static void main(String[] args) {
        consumer(new RealObject());
        System.out.println("-----  -----  -----");
        consumer(new SimpleProxy(new RealObject()));
    }
}

結果:

doSomething
doSomeOtherThing yi gi woli giao
-----  -----  -----
SimpleProxy doSomething
doSomething
doSomeOtherThing yi gi woli giao
SimpleProxy doSomeOtherThing yi gi woli giao

  因為consumer()接受的Interface,所以無論是RealObject還是SimpleProxy,都可以作為參數,而SimpleProxy插了一腳 代理了RealObject加了不少自己的東西。

  java的動態代理更前進一步,因為它可以動態創建代理並動態地處理對所代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理器上,它的工作是揭示調用的類型並確定相應的對策。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Interface {
    void doSomething();

    void doSomeOtherThing(String args);
}

class RealObject implements Interface {

    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void doSomeOtherThing(String args) {
        System.out.println("doSomeOtherThing" + args);
    }
}

class DynamicProxyHandler implements InvocationHandler {
    private Object proxyId;

    public DynamicProxyHandler(Object proxyId) {
        this.proxyId = proxyId;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**** proxy:" + proxy.getClass() + ", method" + method + ", args:" + args);
        if (args != null) {
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }
        return method.invoke(proxyId, args);
    }
}

public class SimpleProxyDemo {
    static void consumer(Interface i) {
        i.doSomething();
        i.doSomeOtherThing(" yi gi woli giao");
    }

    public static void main(String[] args) {
        RealObject realObject = new RealObject();
        consumer(realObject);
        System.out.println("-----  -----  -----");
     //動態代理 可以代理任何東西 Interface proxy
= (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(realObject)); consumer(proxy); } }

結果:

doSomething
doSomeOtherThing yi gi woli giao
-----  -----  -----
**** proxy:class $Proxy0, methodpublic abstract void Interface.doSomething(), args:null
doSomething
**** proxy:class $Proxy0, methodpublic abstract void Interface.doSomeOtherThing(java.lang.String), 
args:[Ljava.lang.Object;@7ea987ac  yi gi woli giao
doSomeOtherThing yi gi woli giao

通過Proxy.newProxyInstance()可以創建動態代理,這個方法需要三個參數:

1. 類加載器:可以從已經被加載的對象中獲取其類加載器;

2. 你希望該代理實現的接口列表(不可以是類或抽象類,只能是接口);

3. InvocationHandler接口的一個實現;

在 invoke 實現中還可以根據方法名處對不同的方法進行處理,比如:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**** proxy:" + proxy.getClass() + ", method" + method + ", args:" + args);
        if (args != null) {
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }
        if (method.getName().equals("doSomething")) { System.out.println("this is the proxy for doSomething"); } return method.invoke(proxyId, args);
    }

還可以對參數或方法進行更多的操作因為 你已經得到了他們 盡情的使用你的代理權吧 ~~ 先加它十個內購。

九、接口與類型信息

  interface關鍵字的一種重要目標就是允許程序員隔離構件,進而降低耦合。反射,可以調用所有方法,甚至是private。唯獨final是無法被修改的,運行時系統會在不拋任何異常的情況接受任何修改嘗試,但是實際上不會發生任何修改。

    void callMethod(Object a, String methodName) throws Exception {
        Method method = a.getClass().getDeclaredMethod(methodName);
        method.setAccessible(true);
        method.invoke(a);
    }

 

 

  

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

分類
發燒車訊

在開發框架中擴展微軟企業庫,支持使用ODP.NET(Oracle.ManagedDataAccess.dll)訪問Oracle數據庫,基於Enterprise Library的Winform開發框架實現支持國產達夢數據庫的擴展操作

在前面隨筆《》中介紹了在代碼生成工具中使用ODP.NET(Oracle.ManagedDataAccess.dll)訪問Oracle數據庫,如果我們在框架應用中需要使用這個如何處理了?由於我們開發框架底層主要使用微軟企業庫(目前用的版本是4.1),如果是使用它官方的Oracle擴展,那麼就是使用EntLibContrib.Data.OdpNet(這個企業庫擴展類庫使用了Oracle.DataAccess.dll),不過這種方式還是受限於32位和64位的問題;假如我們使用ODP.NET(Oracle.ManagedDataAccess.dll)方式,可以使用自己擴展企業庫支持即可,類似於我們支持國產數據庫–達夢數據庫一樣的原理,使用Oracle.ManagedDataAccess類庫可以避免32位和64位衝突問題,實現統一兼容。

1、擴展支持ODP.NET(Oracle.ManagedDataAccess.dll)訪問

為了實現自定義的擴展支持,我們需要對企業庫的擴展類庫進行處理,類似我們之前編寫達夢數據庫的自定義擴展類庫一樣,這方面可以了解下之前的隨筆《》,我們現在增加對ODP.NET(Oracle.ManagedDataAccess.dll)方式的擴展支持。

首先我們創建一個項目,並通過Nugget的方式獲得對應的Oracle.ManagedDataAccess.dll類庫,參考企業庫對於Mysql的擴展或者其他的擴展,稍作調整即可。

 OracleDatabase類似下面代碼

using System;
using System.Data;
using System.Data.Common;

using Microsoft.Practices.EnterpriseLibrary.Common;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Data.Configuration;
using Oracle.ManagedDataAccess.Client;

namespace EntLibContrib.Data.OracleManaged
{
    /// <summary>
    /// <para>Oracle數據庫對象(使用ODP驅動)</para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// Internally uses OracleProvider from Oracle to connect to the database.
    /// </para>
    /// </remarks>
    [DatabaseAssembler(typeof(OracleDatabaseAssembler))]
    public class OracleDatabase : Database
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="OracleDatabase"/> class
        /// with a connection string.
        /// </summary>
        /// <param name="connectionString">The connection string.</param>
        public OracleDatabase(string connectionString) : base(connectionString, OracleClientFactory.Instance)
        {
        }
        
        /// <summary>
        /// <para>
        /// Gets the parameter token used to delimit parameters for the
        /// Oracle database.</para>
        /// </summary>
        /// <value>
        /// <para>The '?' symbol.</para>
        /// </value>
        protected char ParameterToken
        {
            get
            {
                return ':';
            }
        }

        .........

主要就是把對應的類型修改為Oracle的即可,如Oracle的名稱,以及參數的符號為 :等地方,其他的一一調整即可,不在贅述。

完成后,修改程序集名稱,編譯為 EntLibContrib.Data.OracleManaged.dll 即可。

 

2、框架應用的數據庫配置項設置

完成上面的步驟,我們就可以在配置文件中增加配置信息如下所示,它就能正常的解析並處理了。

 

 上面使用了兩種方式,一種是官方擴展的EntLibContrib.Data.OdpNet方式,一種是我們這裏剛剛出爐的 EntLibContrib.Data.OracleManaged方式,完整的數據庫支持文件信息如下所示。

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data"/>
    <section name="oracleConnectionSettings" type="EntLibContrib.Data.OdpNet.Configuration.OracleConnectionSettings, EntLibContrib.Data.OdpNet" />
  </configSections>
  <connectionStrings>
    <!--SQLServer數據庫的連接字符串-->
    <add name="sqlserver" providerName="System.Data.SqlClient" connectionString="Persist Security Info=False;Data Source=(local);Initial Catalog=WinFramework;Integrated Security=SSPI"/>
    
    <!--Oracle數據庫的連接字符串-->
    <add name="oracle" providerName="System.Data.OracleClient" connectionString="Data Source=orcl;User ID=whc;Password=whc"/>
    
    <!--MySQL數據庫的連接字符串-->
    <add name="mysql" providerName="MySql.Data.MySqlClient" connectionString="Server=localhost;Database=WinFramework;Uid=root;Pwd=123456;"/>
    
    <!--PostgreSQL數據庫的連接字符串-->
    <add name="npgsql" providerName="Npgsql" connectionString="Server=localhost;Port=5432;Database=postgres;User Id=postgres;Password=123456"/>
    
    <!--路徑符號|DataDirectory|代表當前運行目錄-->    
    <!--SQLite數據庫的連接字符串-->
    <add name="sqlite"  providerName="System.Data.SQLite" connectionString="Data Source=|DataDirectory|\WinFramework.db;Version=3;" />
    <!--Microsoft Access數據庫的連接字符串-->
    <add name="access" providerName="System.Data.OleDb" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\WinFramework.mdb;User ID=Admin;Jet OLEDB:Database Password=;" />   
    
    <!--IBM DB2數據庫的連接字符串-->
    <add    name="db2" providerName="IBM.Data.DB2"    connectionString="database=whc;uid=whc;pwd=123456"/>
    
    <!--採用OdpNet方式的Oracle數據庫的連接字符串-->
    <add    name="oracle2"    providerName="Oracle.DataAccess.Client"    connectionString="Data Source=orcl;User id=win;Password=win;" />
    <add    name="oracle3"    providerName="OracleManaged"    connectionString="Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl.mshome.net)));User ID=win;Password=win" />
  </connectionStrings>
  <dataConfiguration defaultDatabase="oracle3">
    <providerMappings>
      <add databaseType="EntLibContrib.Data.MySql.MySqlDatabase, EntLibContrib.Data.MySql" name="MySql.Data.MySqlClient" />
      <add databaseType="EntLibContrib.Data.SQLite.SQLiteDatabase, EntLibContrib.Data.SqLite" name="System.Data.SQLite" />
      <add databaseType="EntLibContrib.Data.PostgreSql.NpgsqlDatabase, EntLibContrib.Data.PostgreSql" name="Npgsql" />      
      <add databaseType="EntLibContrib.Data.DB2.DB2Database, EntLibContrib.Data.DB2" name="IBM.Data.DB2" />
      <add databaseType="EntLibContrib.Data.OdpNet.OracleDatabase, EntLibContrib.Data.OdpNet" name="Oracle.DataAccess.Client" />
      <add databaseType="EntLibContrib.Data.Dm.DmDatabase, EntLibContrib.Data.Dm" name="Dm" />
      <!--增加ODP.NET(Oracle.ManagedDataAccess.dll)方式的擴展支持-->
      <add databaseType="EntLibContrib.Data.OracleManaged.OracleDatabase, EntLibContrib.Data.OracleManaged" name="OracleManaged" />
    </providerMappings>
  </dataConfiguration>
  
  <appSettings>

  </appSettings>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
    <supportedRuntime version="v2.0.50727"/>
  </startup>
</configuration>

這樣我們底層就可以實現多種數據庫的兼容訪問了。

採用不同的數據庫,我們需要為不同數據庫的訪問層進行生成處理,如為SQLServer數據的表生成相關的數據訪問層DALSQL,裏面放置各個表對象的內容,不過由於採用了相關的繼承類處理和基於數據庫的代碼生成,需要調整的代碼很少。

我們來編寫一段簡單的程序代碼來測試支持這種ODP.net方式,測試代碼如下所示。

private void btnGetData_Click(object sender, EventArgs e)
{
    string sql = "select * from T_Customer";// + " Where Name = :name";
    Database db = DatabaseFactory.CreateDatabase();
    DbCommand command = db.GetSqlStringCommand(sql);
    //command.Parameters.Add(new OracleParameter("name", "張三"));

    using (var ds = db.ExecuteDataSet(command))
    {
        this.dataGridView1.DataSource = ds.Tables[0];   
    }
}

測試界面效果如下所示。

以上這些處理,可以適用於Web框架、Bootstrap開發框架、Winform開發框架、混合式開發框架中的應用,也就是CS、BS都可以使用。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

人機對話技術研究進展與思考

嘉賓:袁彩霞 博士 北京郵電大學 副教授

整理:Hoh Xil

來源:阿里小蜜 & DataFun AI Talk

出品:DataFun

注:歡迎轉載,轉載請在留言區內留言。

導讀:本次分享的主題為人機對話技術研究進展與思考。主要梳理了我們團隊近兩年的工作,渴望可以通過這樣的介紹,能給大家一個關於人機對話 ( 包括它的科學問題和應用技術 ) 方面的啟示,幫助我們進行更深入的研究和討論。主要包括:

  1. Spoken dialogue system:a bird view ( 首先我們來看什麼是人機對話,尤其是 Spoken dialogue。其實說 Spoken 的時候,有兩層含義:第一個 spoken 就是 speech,第二個我們處理的語言本身具有 spoken 的特性。但是,稍後會講的 spoken 是指我們已經進行語音識別之後,轉換為文本的一個特殊的自然語言,後面討論的口語對話不過多地討論它的口語特性,主要是講人和機器之間的自然語言對話。)

  2. X-driven dialogue system:緊接着來講解我們近些年的研究主線 X-driven dialogue syatem,X 指構建一個對話系統時,所採用的數據是什麼,從最早的 dialogue -> FAQ -> KB -> KG -> document 以及我們一直在嘗試的圖文多模態數據。

  3. Concluding remarks ( 結束語 )

01

Spoken dialogue system:a bird view

學術界關於對話系統有着不同的劃分,這種劃分目前看來不是非常準確,也不是特別標準的劃分了。但是,接下來的內容,主要是圍繞着這兩個主線:

限定領域,專門指任務型對話 ( 圍繞某一特定用戶對話目標而展開的 )。對於任務型對話,對話系統的優化目標就是如何以一個特別高的回報、特別少的對話輪次、特別高的成功率來達成用戶的對話目標。所以即便是限定領域,我們這裏討論的也是特別限定的、專門有明確的用戶對話目標的一種對話。

開放領域,not purely task-oriented, 已經不再是純粹的對話目標驅動的對話,包括:閑聊、推薦、信息服務等等,後面逐步展開介紹。

我們在研究一個問題或者做論文答辯和開題報告時,經常討論研究對象的意義在哪裡。圖中,前面講的是應用意義,後面是理論意義。我們實驗室在北京郵電大學叫智能科學與技術實驗室,其實她的前身叫人工智能實驗室。所以從名字來看,我們做了非常多的 AI 基礎理論的研究,我們在研究這些理論的時候,也會講 AI 的終極目的是研製一種能夠從事人類思維活動的計算機系統。人類思維活動建立在獲取到的信號的基礎上。人類獲取信號的方式大體有五類,包括視覺、聽覺、觸覺、味覺、嗅覺等,其中視覺和聽覺是兩個比較高級的傳感器通道,尤其是視覺通道,佔據了人類獲得信息的80%以上。所以我們從這兩個角度,設立了兩個研究對象:第一個是語言,第二個是圖像。而我們在研究語言的時候,發現語言有一個重要的屬性,叫交互性,交互性最典型的一個體現就是對話;同時,語言不是一個獨立的模態,語言的處理離不開跟它相關的另一個通道,就是視覺通道。所以我們早期更多是為了把交互和多模態這樣的屬性納入到語言建模的範圍,以其提升其它自然語言處理系統的性能,這就是我們研究的一個動機。

  1. Block diagram

上圖為 CMU 等在1997年提出來的人機對話框架,基於這個框架人們開發出了非常多優秀的應用系統,比如應用天氣領域的 “Jupiter”。這個框架從提出到商業化應用,一直到今天,我們都還沿着這樣的一個系統架構在進行開發,尤其是任務驅動的對話。

這就是具體的對話系統的技術架構。

  1. Specific domain

這個架構發展到現在,在功能模塊上,已經有了一個很清晰的劃分:

首先進行語音識別,然後自然語言理解,緊接着做對話管理,將對話管理的輸出交給自然語言生成模塊,最後形成自然語言應答返回給用戶。這就是一個最典型的 specific domain 的架構。早期 task 限定的 dialogue,基本上都是按照這個架構來做的。這個架構雖然是一個 Pipeline,但是從研究的角度來講,每一個模塊和其它模塊之間都會存在依賴關係。因此,我們試圖從研究的角度把不同的功能模塊進行統一建模。在這個建模過程中,又會產生新的學術性問題,我們旨在在這樣的問題上可以產生驅動性的技術。

  1. Open domain

Open domain,也就是“閑聊”,實現上主要分為途徑:

第一個是基於匹配/規則的閑聊系統;第二個是基於檢索的閑聊系統;第三個是基於編解碼結構的端到端對話系統。當然,實際情境中,這幾個途徑往往結合在一起使用。

02

X-Driven dialogue system

目前無論是任務型對話還是閑聊式對話,都採用數據驅動的方法,因此依據在構建人機對話系統時所用到的數據不同,建模技術和系統特性也就體現出巨大的不同。我們把使用的數據記為 X,於是就有了不同的 X 驅動的對話。

  1. Our roadmap

如果想讓機器學會像人一樣對話,我們可以提供的最自然的數據就是 dialogue。我們從2003年開始做對話驅動的對話;2012年開始做 FAQ 驅動的對話;2015年開始做知識庫 ( KB ) 驅動的對話;2016年開始做知識圖譜 ( KG ) 驅動的對話,相比於 KB,KG 中的知識點產生了關聯,有了這種關聯人們就可以在大規模的圖譜上做知識推理;2017年開始做文檔驅動的對話。這就是我們研究的大致脈絡。

  1. Dialogue-driven dialogue

早期在做 Dialogue driven 的時候,多依賴人工採集數據,但是,從2013年以來,逐步開放了豐富的涵蓋多領域多場景的公開數據集。比如最近的 MultiWOZ,從 task specific 角度講,數據質量足夠好、數據規模足夠大,同時涵蓋的對話情景也非常豐富。但是,目前公開的中文數據集還不是很多。

這個是和任務型對話無關的數據集,也就是採集的人與人對話的數據集。尤其以 Ubuntu 為例,從15年更新至今,已經積累了非常大規模的數據。

以 Dialogue 為輸入,我們開展了任務型和非任務型兩個方向的工作。先來看下任務型對話:

2.1 NLU

當一個用戶輸入過來,第一個要做的就是自然語言理解 ( NLU ),NLU 要做的三件事為:Domain 識別;Intent 識別;信息槽識別或叫槽填充。這三個任務可以分別獨立地或採用管道式方法做,也可以聯合起來進行建模。在聯合建模以外,我們還做了一些特別的研究。比如我們在槽識別的時候,總是有新槽,再比如有些槽值非常奇怪,例如 “XX手機可以一邊打電話一邊視頻嗎?”,對應着槽值 “視頻電話”,採用序列標註的方式,很難識別它,因為這個槽值非常不規範。用戶輸入可能像這樣語義非常鬆散,不連續,也可能存在非常多噪音,在進行聯合建模時,傳統的序列標註或分類為思想,在實際應用中已經不足以解決問題了。

我們針對這個問題做了比較多的探討,右圖為我們2015年的一個工作:在這三個任務聯合建模的同時,在槽填充這個任務上將序列標註和分類進行同時建模,來更好地完成 NLU。

在 NLU 領域還有一個非常重要的問題,隨着開發的業務領域越來越多,我們發現多領域對話產生了諸多非常重要的問題,例如在數據層有些 domain 數據可能很多,有些 domain 數據可能很少,甚至沒有,於是就遇到冷啟動的問題。因此,我們做了非常多的 domain transfer 的工作。上圖為我們2016年的一個工作,我們會把數據比較多的看成源領域,數據比較少的看成目標領域。於是,嘗試了基於多種遷移學習的 NLU,有的是在特徵層進行遷移,有的是在數據層遷移,有的是在模型層進行遷移。圖中是兩個典型的在特徵層進行遷移的例子,不僅關注領域一般特徵,而且關注領域專門特徵,同時採用了對抗網絡來生成一個虛擬的特徵集的模型。

2.2 NLU+DM

緊接着,我們研究了 NLU 和對話管理 ( DM ) 進行聯合建模,因為我們發現人人對話的時候,不見得是聽完對方說完話,理解了對方的意圖,然後才形成對話策略,有可能這兩個過程是同時發生的。甚或 DM 還可以反作用於 NLU。早期我們基於的一個假設是, NLU 可能不需要一個顯式的過程,甚至不需要一個顯式的 NLU 的功能,我們認為 NLU 最終是服務於對話管理 ( DM ),甚至就是對話管理 ( DM ) 的一部分。所以,2013年的時候,我們開始了探索,有兩位特別優秀的畢業生在這兩個方面做了特別多的工作。比如,如何更好地聯合建模語言理解的輸出和對話管理的策略優化。這是我們在 NLU 和 DM 聯合建模的工作,同時提升了 NLU 和 DM 的性能。

在聯合模型中,我們發現,DM 的建模涉及到非常多的 DRL ( 深度強化學習 ) 的工作,訓練起來非常困難,比如如何設計一個好的用戶模擬器,基於規則的,基於統計的,基於語言模型的,基於 RL 的等等我們嘗試了非常多的辦法,也取得了一系列有趣的發現。2018年時我們研究一種不依賴於規則的用戶模擬器,業界管這個問題叫做 “Self”-play,雖然我們和 “Self”-play 在網絡結構上差異挺大的,但是我們還是借鑒了 “Self”-play 訓練的特性,把我們自己的系統叫做 “Self”-play。在這樣的機制引導下,我們研究了不依賴於規則,不依賴於有標記數據的用戶模擬器,使得這個用戶模擬器可以像 Agent 一樣,和我們所構造的對話的 Agent 進行交互,在交互的過程中完成對用戶的模擬。

在訓練過程中還有一個重要的問題,就是 reward 怎麼來,我們知道在 task oriented 時,reward 通常是人類專家根據業務邏輯/規範制定出來的。事實上,當我們在和環境交互的時候不知道 reward 有多大,但是環境會隱式地告訴我們 reward 是多大,所以我們做了非常多的臨接對和 reward reshaping 的工作。

2.3 小結

Dialogue-driven dialogue 這種形式的對話系統,總結來看:

優點:

定義非常好,邏輯清晰,每一個模塊的輸入輸出也非常清晰,同時有特別堅實的數學模型可以對它進行建模。

缺點:

由於非常依賴數據,同時,不論是在 NLU 還是 NLG 時,我們都是採用有監督的模型來做的,所以它依賴於大量的、精細的標註數據。

而 DM 往往採用 DRL 來做。NIPS2018 時的一個 talk,直接指出:任何一個 RL 都存在的問題,就是糟糕的重現性、復用性、魯棒性。

  1. FAQ-driven dialogue

FAQ 是工業界非常常見的一種情景:有大量的標準問,以及這個標準問的答案是什麼。基於這個標準問,一個用戶的問題來了,如何找到和它相似的問題,進而把答案返回給用戶,於是這個 Service 就結束了。

實際中,我們如何建 FAQ?更多的時候,我會把這個問題和我們庫的標準問題做一個相似度的計算或者做一個分類。

我們在做這個工作的時候發現一個特別大的問題,就是 Unbalanced Data 問題。比如,我們有5000個問題,每個問題都有標準答案,有些問題可能對應的用戶問題特別多,比如 “屏幕碎了” 可能會有1000多種不同的問法,還有些問題,可能在幾年的時間里都沒有人問到過。所以,面對數據不均衡的問題,我們從2016年開始做了 Data transfer 的工作。

大致的思路是:我有一個標準問題,但是很糟糕,這個標準問題沒有用戶問題,也就是沒有訓練語料。接下來發現另外一個和這個標準問很相似的其它標準問有很多的訓練語料,於是藉助這個標準問,來生成虛擬樣本,進而削弱了 Unbalance。

具體的方法:我們把目標領域的標準問看成 Query,把和它相似的標準問題及其對應的用戶問題看成 Context,採用了 MRC 機器閱讀理解的架構來生成一個答案,作為目標問題的虛擬的用戶問題,取得了非常好的效果,並且嘗試了三種不同的生成用戶問題的方法。

實際項目中,FAQ 中的 Q 可能有非常多的問題,例如3000多個類,需要做極限分類,這就導致性能低下,且非常耗時,不能快速響應用戶的問題。於是我們做了一個匹配和分類進行交互的 model,取得了不錯的效果。

目前,大部分人都認為 FAQ 驅動的 dialogue 不叫 dialogue,因為我們通常說的 dialogue 輪次是大於兩輪的。而 FAQ 就是一個 QA 系統,沒有交互性。有時候帶來的用戶體驗非常不友好,比如當答案非常長的時候,系統要把長長的答案返回,就會一直講,導致用戶比較差的體驗。於是,我們基於 FAQ 發展出了一個多輪對話的數據,如右圖,這是我們正在開展的一個工作。

  1. KB-driven dialogue

KB 最早人們認為它就是一個結構化的數據庫,通常存儲在關係型數據庫中。比如要訂一個酒店,這個酒店有各種屬性,如位置、名稱、戶型、價格、面積等等。早期 CMU 的對話系統,所有的模塊都要和 Hub 進行交互,最後 Hub 和後端的數據庫進行交互。數據庫的價值非常大,但是早期人們在建模人機對話的時候,都忽視了數據庫。這裏就會存在一個問題:機器和用戶交互了很久,而在檢索數據庫時發現沒有答案,或者答案非常多,造成用戶體驗非常糟糕。

從2012年開始,我們開始把 KB 引入我們的對話系統。圖中的對話系統叫做 “teach-and-learn” bot,這是一個多模態的對話,但是每個涉及到的 object,我們都會把它放到 DB 中。和用戶交互過程中,不光看用戶的對話狀態,還要看數據庫狀態。這個想法把工作往前推進了一些。

直到2016年,MSR 提出的 KB-InfoBot,第一次提出了進行數據庫操作時,要考慮它的可導性,否則,就沒辦法在 RL 網絡中像其它的 Agent action 一樣進行求導。具體的思路:把數據庫的查詢和 Belief State 一起總結起來做同一個 belief,進而在這樣的 belief 基礎上做各種對話策略的優化。

在上述方法的基礎上,我們做了有效的改良,包括 entropy regularities 工作。是每次和用戶進行交互時,數據庫的 entropy 會發生變化。比如當機器問 “你想訂哪裡的酒店?”,用戶答 “阿里中心附近的。”,於是數據庫立刻進行了一次 entropy 計算進行更新,接着繼續問 “你想訂哪一天的?”,用戶答 “訂7月28號的”,於是又進行了一次 entropy 計算進行更新。這樣在和用戶進行交互的時候,不光看用戶的 dialogue 輸入,還看 DB 的 entropy 輸入,以這兩項共同驅動 Agent action 進行優化。

這裏我們做了特別多的工作,信息槽從1個到5個,數據庫的規模從大到小,都做了特別多的嘗試,這樣在和用戶交互的時候,agent 可以自主的查詢檢索,甚至可以填充和修改數據庫。

這是我們2017發布的一個工作,KB driven-dialogue,其優點:

控制萬能高頻回復 ( 提高答應包含的有用信息 )

賦予 agent 對話主動性

  1. KG-driven dialogue

剛剛講的基於 KB 的 dialogue 任務,基本都認為對話任務就是在進行槽填充的任務,如果一個 agent 是主動性的,通過不停的和用戶進行交互來採集槽信息,所以叫槽填充,當槽填完了,就相當於對話任務成功了。但是,當我們在定義槽的時候,我們認為槽是互相獨立的,並且是扁平的。然而,實際中許多任務的槽之間存在相關性,有的是上下位關係,有的是約束關係,有的是遞進關係等等。這樣自然的就引出了知識圖譜,知識圖譜可以較好地描述上述的相關性。於是,產生了兩個新問題:

知識圖譜驅動的對話理解:實體鏈接

知識圖譜驅動的對話管理:圖路徑規劃

這裏主要講下第二個問題。

舉個例子,我們在辦理電信業務,開通一個家庭寬帶,需要提供相關的證件,是自己去辦,還是委託人去辦,是房東還是租戶等等,需要提供各種不同的材料。於是這個情景就產生了條件的約束,某一個 node 和其它 node 是上下位的關係,比如證件可以是身份證,也可以是護照或者戶口簿等等。所以我們可以通過知識圖譜來進行處理。

當一個用戶的對話過來,首先會鏈接到不同的 node,再基於 node 和對話歷史構造一個對話的 state,我們會維持一個全局的 state 和一個活躍的 state,同時活躍的 state 會定義三種不同的操作動作,一個是祖先節點,一個是兄弟節點,還有一個是孩子節點。所以,在這樣的知識圖譜上如何尋優,比如當通過某種計算得到,它應該在某個節點上進行交互的時候,我們就應該輸出一個 action,這個 action 要和用戶確認他是一個租戶,還是自有住房等等。所以,這個 action 是有區別於此前所提到的在特定的、扁平的 slot 槽上和用戶進行信息的確認、修改等還是有很大不同的。解決這樣的問題,一個非常巨大的挑戰就是狀態空間非常大。比如圖中的節點大概有120個,每個節點有3個不同的狀態,知識從節點的狀態來看就有3的120次冪種可能。這也是我們正在開展的待解決的一個問題。

在端到端的對話模型 ( 閑聊 ) 中,也開始逐步地引入知識圖譜。下面介紹兩個比較具有代表性的引入知識圖譜后的人機對話。其中右邊是2018年 IJCAI 的傑出論文,清華大學黃民烈老師團隊的工作,引入了通過 KG 來表示的 Commonsense,同時到底在編碼器端還是在解碼器端引入知識,以及如何排序,排序的時候如何結合對話的 history 做知識的推理等等,都做了特別全面的研究。

另一個比較有代表性的工作是在 ICLR2019 提出的,在架構中引入了 Local Memory 和 Global Memory 相融合的技術,通過這種融合,在編碼器端和解碼器端同時加入了知識的推理。

總結下 KB/KG-driven dialogue:

優點:

已經有大規模公開的數據 ( e.g.,InCar Assistant,MMD,M2M )。

訓練過程可控&穩定,因為這裏多數都是有監督學習。

缺點:

因為採用有監督的方式進行訓練,所以存在如下問題:

① 環境確定性假設
② 缺少對動作的建模
③ 缺少全局的動作規劃
Agent 被動,完全依賴於訓練數據,所以模型是不賦予 Agent 主動性的。

構建 KB 和 KG 成本昂貴!

  1. Document-driven dialogue

Document 驅動的對話,具有如下優點:

① 應用場景真實豐富:

情景對話 ( conversation ),比如針對某個熱門事件在微博會產生很多對話,這就是一個情景式的對話。

電信業務辦理 ( service ),比如10086有非常多的套餐,如何從中選擇一個用戶心儀的套餐?每個套餐都有說明書,我們可以圍繞套餐的說明書和用戶進行交互,如 “您希望流量多、還是語音多”,如果用戶回答 “流量多”,就可以基於文本知道給用戶推薦流量多的套餐,如果有三個候選,機器就可以基於這三個候選和用戶繼續進行交互,縮小候選套餐範圍,直到用戶選出心儀的套餐。

電商產品推薦 ( recommendation ),可以根據商品的描述,進行各種各樣的對話。這裏的輸入不是一個 dialogue,也不是一個 KB,甚至結構化的內容非常少,完全是一個 free document,如何基於這些 document 進行推薦,是一個很好的應用場景。

交互式信息查詢 ( retrieval ),很多時候,一次查詢的結果可能不能用戶的需求,如何基於非結構化的查詢結果和用戶進行交互,來更好地達成用戶的查詢目的。

……

② 數據獲取直接便捷:

相比於 dialogue、FAQ、KB、KQ 等,Document 充斥着互聯網的各種各樣的文本,都可以看成是文本的數據,獲取方便,成本非常低。

③ 領域移植性強:

基於文本,不再基於專家定義的 slot,也不再基於受限的 KB/KG,從技術上講,所構造的 model 本身是和領域無關的,所以它賦予了 model 很強的領域移植性。

這是我們正在進行的工作,情景對話偏向於閑聊,沒有一個用戶目標。這裏需要解決的問題有兩個:

如何引入文檔:編碼端引入文檔、解碼端引入文檔

如何編碼文檔:文檔過長、冗餘信息過多

數據:

我們在 DoG 數據的基礎上模擬了一些數據,形成了如上圖所示的數據集,分 Blind 和 Non-blind 兩種情形構造了不同的數據集。

我們發現,基於文本的端到端的聊天,有些是基於內容的閑聊,有些還需要回答特定的問題。所以,評價的時候,可以直接用 F1 評價回答特定問題,用閑聊通用的評價來評價基於內容的聊天。

剛剛講的是偏閑聊式的對話,接下來講下任務型對話。

這裏的動機分為兩種情況:單文檔和多文檔,我們目前在挑戰多文檔的情況,單文檔則採用簡單的多輪閱讀理解來做。

多文檔要解決的問題:

如何定義對話動作:因為是基於Document進行交互,而不再是基於slot進行交互,所以需要重新定義對話動作。

如何建模文檔差異:以剛剛10086的例子為例,總共有10個業務,通過一輪對話,挑選出3個,這3個業務每個業務可能都有10種屬性,那麼其中一些屬性值相同的屬性,沒必要再和用戶進行交互,只需要交互它們之間不同的點,所以交互的角度需要隨對話進行動態地變化。

數據:

這裏採用的數據是 WikiMovies 和模擬數據,具體見上圖。

  1. A very simple sketch of dialogue

上圖為任務型對話和非任務型對話的幾個重要節點,大家可以了解下。

03

Concluding remarks

任務型對話具有最豐富的落地場景。

純閑聊型對話系統的學術價值尚不清楚。

任務型和非任務型邊界愈加模糊,一個典型的人人對話既包括閑聊,又包括信息獲取、推薦、服務。

引入外部知識十分必要,外部知識形態各異,建模方法也因而千差萬別。

Uncertainty 和 few-shot 問題,是幾乎所有的對話系統最大的 “卡脖子” 問題。

X-driven dialogue 中的 X 還有哪些可能?剛剛提到的 X 都還是基於文本的。事實上,從2005年開始,我們已經開始做 image 和文本數據融合的對話;從2013年開始做 Visual QA/Dialogue,挑戰了 GuessWhat 和 GuessWhich 任務。

X=multi media ( MM Dialogue ),X 還可以很寬泛的就是指多媒體,不光有 image 還可能有 text,2018年已經有了相關的任務,並且開源了非常多的電商業務。這是一個非常有挑戰性的工作,也使人機對話本身更加擬人化。

04

Reference

這是部分的參考文獻,有些是我們自己的工作,有些是特別傑出的別人的工作。

今天的分享就到這裏,感謝大家的聆聽,也感謝我們的團隊。

歡迎關注DataFunTalk同名公眾號,收看第一手原創技術文章。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

中國發改委協調汽車充電設施建設 加快政策落實

從中國發改委網站獲悉,停車場充電設施建設協調會7月18日下午召開。會議表示將針對大城市、特大城市存在的停車位資源緊張、社會停車場投資主體多、充電設施企業盈利模式相對單一等問題,進一步推進相關工作。  
  停車場充電設施建設協調會由中國國家發改委基礎產業司副司長鄭劍主持,就2016年第二批城市停車場項目配建充電基礎設施問題,與安徽、江蘇、江西、陝西、浙江、湖北、上海、大連、廈門等地方發展改革委、充電基礎設施服務企業和國家電網公司進行交流座談。   據國家能源局電力司初步統計,截至今年6月底,中國全國已建成公共充電樁8.1萬個,比去年底增長65%;隨車建成私人充電樁超過5萬個,比去年底增長約12%。1-6月全國新能源汽車充電量超過6億kWh,替代燃油約20萬噸,電動汽車的發展對能源結構調整和城市環境提升貢獻明顯。   為新能源汽車的推廣和應用創造良好的環境,國家能源局相關部門加快了推動充電樁政策規劃的落實,組織起草加快居民區充電基礎設施建設的檔。該文件有望7月份出臺,將有效推進居民區和工作場所建樁工作,合理優化公共充電樁佈局,提高公共充電樁利用率。   文章來源:中國發展網

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

特斯拉Gigafactory趕工,可望提前量產

美國電動車商特斯拉(Tesla Motors Inc.)的平價車款「Model 3」預定2018年問世,為了趕上需求,特斯拉造價50億美元的「Gigafactory」超級電池廠正在加緊趕工,將比原定時程提前數年、明(2017)年初就可以開始量產車用鋰電池。

華爾街日報、Electrek 24日報導,坐落於內華達州雷諾市的Gigafactory目前占地超過3,000英畝,特斯拉已加倍聘請建築工人,共計1,000名人員一週兩班每天輪流趕工,預計明年初就可大功告成。特斯拉技術長兼共同創辦人JB Straubel表示,在汽車量產前,電池和電池組的組裝廠房一定要事先完工,因此無論是建廠計畫或是電池的擴產時間表都會加快進度。

特斯拉電池供應夥伴Panasonic Corp.已承諾要為該廠提供16億美元的資金,目前則因為找不到合適的人才而傷透腦筋。特斯拉執行長Elon Musk預估,Gigafactory完工之後,2020年將可年產105GW的電池,足以供應120萬台Model S豪華電動轎車所需,但其中有1/3將用於定置型電池儲存裝置(stationary battery storage product)。

假如這座廠房能如期完工、擴產,則其產能將是全球現有電池廠的10倍之多,這也使得北美的鋰礦開採活動大增。

OilPrice.com 21日報導,電池過去幾年來的需求倍數成長,鋰已成為今(2016)年來最夯的金屬、擊敗黃金,雖然最近幾個月的價格漲勢稍緩,但強勁的基本面顯示其長期前景依舊看俏。另外,在戴姆勒(Daimler)、日產汽車等業者的推波助瀾下,預估到了2016年插電式電動車銷售量(plug-in electric vehicle,簡稱PEV)有望年增62%,2017年、2018年更有望成長60%、100%。這相當於2018年會賣出60萬輛PEV。

(本文內容由授權提供)

 

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!