C#中await/async閑說

自從C#5.0增加異步編程之後,異步編程越來越簡單,async和await用的地方越來越多,越來越好用,只要用異步的地方都是一連串的異步,如果想要異步編程的時候,需要從底層開始編寫,這樣後邊使用的時候就是異步,那麼底層是如何實現??我們如何編寫高效率的異步方法??

#了解基於任務的異步模式(TAP)

基於任務的異步編程模型 (TAP) 提供了異步代碼的抽象化,你只需像往常一樣將代碼編寫為一連串語句即可,在開始調用的地方運行。例如:var task = method()①; await task②; 在①的時候開始運行可能還沒有運行完,在②程序掛起等待運行完,中間怎麼運行的你不需要知道,編譯器會做若干操作的。當開啟多個任務的時候,像要他們都執行完,在執行其他的時候,可以await Task.WhenAll(task1,task2 …..);

#了解async/await

await 運算符應用於異步方法,在方法的執行中插入掛起點,直到所等待任務完成。使用async 和await定義異步方法不一定會創建新線程,當編譯器看到await關鍵字時,線程會掛起等待運行結束。
await 僅可用於由 async 關鍵字修改的異步方法中,使用 async 修飾符定義的方法通常包含一個或多個 await 表達式,使用await運算符的任務通常是實現[基於任務的異步模式(TAP)]的方法調用返回,返回值包括 Task、Task<TResult>、ValueTask 和 ValueTask<TResult> 對象的方法。

# 調用 Task.Wait() 或者 Task.Result 立刻產生死鎖的充分條件

1. 調用 Wait() 或 Result 的代碼位於 UI 線程。
2. Task 的實際執行在其他線程,且需要返回 UI 線程。
死鎖的原因:UWP、WPF、Windows Forms 程序的 UI 線程都是單線程的。為了避免產生死鎖,你應該一條道走到黑, Async All the Way。或者.ConfigureAwait(false)

# ValueTask與Task的區別

7.0為async新增的ValueTask的作用(如果沒有在Nuget上下載System.Threading.Tasks.Extensions,ValueTask就在這個庫中),ValueTask用於值類型的異步;Task為引用類型的,每次需要分配空間。
例如:

public async Task<int> CalculateSum(int a, int b) {
    if (a == 0 && b == 0)
    {
        return 0;
    }

    return await Task.Run(() => a + b);
}

當a,b=0的時候不會運行到task里,這個時候返回task就造成了資源的浪費,修改為以下會效率更高

public async ValueTask<int> CalculateSum2(int a, int b)
{
    if (a == 0 && b == 0)
    {
        return 0;
    }

    return await Task.Run(() => a + b);
}

但是也不是說到處用ValueTask會好,當是引用類型的時候,用ValueTask,你需要關注更多的數據,這個時候用Task會更好。

# await/async原理分析

[AsyncStateMachine(typeof(Class1.<CalculateSum2>d__1))]
public ValueTask<int> CalculateSum2(int a, int b)
{
    Class1.<CalculateSum2>d__1 <CalculateSum2>d__;
    <CalculateSum2>d__.a = a;
    <CalculateSum2>d__.b = b;
    <CalculateSum2>d__.<>t__builder = AsyncValueTaskMethodBuilder<int>.Create();
    <CalculateSum2>d__.<>1__state = -1;
    AsyncValueTaskMethodBuilder<int> <>t__builder = <CalculateSum2>d__.<>t__builder;
    <>t__builder.Start<Class1.<CalculateSum2>d__1>(ref <CalculateSum2>d__);
    return <CalculateSum2>d__.<>t__builder.Task;
}

對CalculateSum2代碼解析,發現沒有await/async,原來又是編譯器提供的語法糖。

[__DynamicallyInvokable, DebuggerStepThrough, SecuritySafeCritical]
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
    if (stateMachine == null)
    {
        throw new ArgumentNullException("stateMachine");
    }
    ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    {
        ExecutionContext.EstablishCopyOnWriteScope(ref executionContextSwitcher);
        stateMachine.MoveNext();
    }
    finally
    {
        executionContextSwitcher.Undo();
    }
}

對Start方法進行分析,可以看出MoveNext,程序的運行其實還是一步一步進行的,那麼await/async會不會創建一個線程,這倒是不一定,這個由線程池決定,那麼異步了不創建一個線程,怎麼異步的,這裏的異步可能是運行在已經有的線程上。

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

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

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

HTTP認證之摘要認證——Digest(一)

導航

  • HTTP認證之基本認證——Basic(一)
  • HTTP認證之基本認證——Basic(二)
  • HTTP認證之摘要認證——Digest(一)
  • HTTP認證之摘要認證——Digest(二)

一、概述

Digest認證是為了修復基本認證協議的嚴重缺陷而設計的,秉承“絕不通過明文在網絡發送密碼”的原則,通過“密碼摘要”進行認證,大大提高了安全性。

相對於基本認證,主要有如下改進:

  • 絕不通過明文在網絡上發送密碼
  • 可以有效防止惡意用戶進行重放攻擊
  • 可以有選擇的防止對報文內容的篡改

需要注意的是,摘要認證除了能夠保護密碼之外,並不能保護其他內容,與HTTPS配合使用仍是一個良好的選擇。以下是摘要認證的具體流程圖:

看到上面出現了那麼多之前沒見過的參數,是不是有點慌(或是興奮)?別著急,這裏先給出一個概覽:

  • WWW-Authentication:用來定義使用何種方式(Basic、Digest、Bearer等)去進行認證以獲取受保護的資源
  • realm:表示Web服務器中受保護文檔的安全域(比如公司財務信息域和公司員工信息域),用來指示需要哪個域的用戶名和密碼
  • qop:保護質量,包含auth(默認的)和auth-int(增加了報文完整性檢測)兩種策略,(可以為空,但是)不推薦為空值
  • nonce:服務端向客戶端發送質詢時附帶的一個隨機數,這個數會經常發生變化。客戶端計算密碼摘要時將其附加上去,使得多次生成同一用戶的密碼摘要各不相同,用來防止重放攻擊
  • nc:nonce計數器,是一個16進制的數值,表示同一nonce下客戶端發送出請求的數量。例如,在響應的第一個請求中,客戶端將發送“nc=00000001”。這個指示值的目的是讓服務器保持這個計數器的一個副本,以便檢測重複的請求
  • cnonce:客戶端隨機數,這是一個不透明的字符串值,由客戶端提供,並且客戶端和服務器都會使用,以避免用明文文本。這使得雙方都可以查驗對方的身份,並對消息的完整性提供一些保護
  • response:這是由用戶代理軟件計算出的一個字符串,以證明用戶知道口令
  • Authorization-Info:用於返回一些與授權會話相關的附加信息
  • nextnonce:下一個服務端隨機數,使客戶端可以預先發送正確的摘要
  • rspauth:響應摘要,用於客戶端對服務端進行認證
  • stale:當密碼摘要使用的隨機數過期時,服務器可以返回一個附帶有新隨機數的401響應,並指定stale=true,表示服務器在告知客戶端用新的隨機數來重試,而不再要求用戶重新輸入用戶名和密碼了

二、剖析

1.當打開需要認證的頁面時,會彈出一個對話框,要求用戶輸入用戶名和密碼

2.使用Fidder監聽請求,可以看到在未進行認證或認證失敗的情況下,服務端會返回401 Unauthorized給客戶端,並附帶Challenge

3.輸入正確的用戶名和密碼后,瀏覽器會生成密碼摘要以及其他信息發送給服務端,服務端認證成功后,返回一些與授權會話相關的附加信息,放在Authorization-Info中。

其中,客戶端選擇的保護質量策略為authresponse就是通過計算得到的密碼摘要,具體計算方式如下(使用默認的MD5加密算法):

MD5(MD5(A1):<nonce>:<nc>:<cnonce>:<qop>:MD5(A2))

算法 A1
MD5(默認) <username>:<realm>:<password>
MD5-sess MD5(<username>:<realm>:<password>):<nonce>:<cnonce>
qop A2
auth(默認) <request-method>:<uri>
auth-int <request-method>:<uri>:MD5(<request-entity-body>)

另外,rspauth使得客戶端可以對服務器進行認證,稱為響應摘要。響應摘要的計算與請求摘要類似,但由於響應中沒有方法,而且報文實體數據有所不同,所有隻有報文主題信息A2不同。具體區別如下:

qop A2
auth(默認) :<uri>
auth-int :<uri>:MD5(<response-entity-body>)

4.當服務端隨機數過期時,再次請求認證,可以看到質詢中增加了stale=true,用戶無需再次輸入用戶名和密碼,瀏覽器會自動使用新的質詢參數進行密碼摘要的計算。

三、注意事項

1.預授權:服務端預先告知客戶端下一個隨機數是多少,使得客戶端可以直接生成正確的Authorization首部,避免了多次“請求/質詢”。常用的有一下三種方式:

  • 服務器預先在Authorization-Info成功首部中發送下一個隨機數nextnonce。雖然這種機制加快了事務處理的速度,但是它也破壞了對同一台服務器的多次請求進行管道化的功能,可能會造成很大的損失。
  • 服務器允許在一小段時間內使用同一個隨機數。這也就是我們上面剖析中使用的機制,在一定時間內使用同一個隨機數或限制某個隨機數的重用次數,當過期時,聲明stale=true。雖然這確實降低了安全性,但是重用的隨機數的生存周期是可控的,應該在安全和性能之間找到平衡。
  • 客戶端和服務器使用同步的、可預測的隨機數生成算法。

2.RFC 2617建議採用這個假想的隨機數公式:

BASE64(timestamp MD5(timestamp “:” ETag “:” private-key))

其中,timestamp是服務器產生隨機數的時間或其他不重複的值,ETag是與所請求實體有關的HTTP ETag首部的值,private-key是只有服務器知道的私鑰。

【精選推薦文章】

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

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

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

Python 裝飾器

裝飾器

什麼是裝飾器

裝飾器顧名思義就是一個有裝飾功能的工具,那麼裝飾器又是用來裝飾什麼的?為什麼要裝飾這個東西?裝飾的目的是什麼呢?本文會一一作答讓小弟一個一個說

開放封閉原則

談及裝飾器就要引申一個概念,那就是開放封閉原則,那麼問題又來了 什麼是開放封閉……好好好,直接說這個,開放封閉本來是兩個對立的概念,也就是說是一對反義詞,那麼為什麼要提出開放封閉原則呢?原因是在日常的開發工作中,一般最初上線的產品的功能是不盡完善的,就是說不夠完美但已經能夠支撐日常使用,其餘的功能可以日後擴展,舉例:N年前的QQ和現在的QQ(PS:雖然現在本人不怎麼用了)。在後期擴展功能時,因為函數已經是寫好的,而且存在大量調用,所以要直接去給函數增添新的功能顯然不現實,所以我們只能新建函數去給原來的函數擴展功能(其實這就是一個裝飾器啦)

那麼開放封閉原則到底是什麼? 答案是對源碼封閉,對新功能開放

  • 封閉原則:不要改變源代碼

  • 開放原則:能增加一些額外的功能

    如果還有客觀對開放封閉原則似知似解的話,沒關係接着往下看,不影響您食用本文,因為Python裝飾器本身就是對開放封閉原則完美的詮釋

裝飾器初識

不低調的說,裝飾器就是一個函數,名字本來很高大上,但本質就是一個函數,裝飾器函數的功能就是要裝飾一個函數,在不改變被裝飾函數的源代碼及調用方式的前提下,為其增加額外的功能。是不是有點門道了,是不是覺得這玩意也沒啥啦,真是優秀的同學。

代碼show(技術博客不寫代碼干白話說出去丟人)

def warpper(f):  #定義一個函數(裝飾器),傳入的參數是被修飾的函數的函數名

def inner(*args,**kwargs): #嵌套一個內存函數,這個函數主題才是執行被裝飾函數源碼的關鍵

    '''這塊可以加要在被裝飾函數執行之前的操作哈'''

    ret = f(*args,**kwargs) #這裏的形參我會在下面說明

    '''這裏可以寫被裝飾函數之後的,兄嘚,別客氣,想加啥方法加什麼'''

    return ret #這裏的返回值如果我一會兒不忘的話也會在下面說明

return inner

@warpper #這個叫語法糖,嗯……可以吃(可能老外命名的時候就是這麼想的),結構是@加函數名,作用下面會說明
def func():
  print('我就是那個被裝飾的函數')
簡單說明

因為本人比較懶,所以原諒我直接把代碼甩上去了,後面有註釋,看懂了的大佬可以say goodbye啦,想打我的接着聽我白話,那我就把備註再重複一遍,哈哈,你也看到了,裝飾器用到了函數的嵌套(再具體點就是閉包,要問什麼是閉包,百度吧,哈哈),首先外層函數接收到一個函數名,然後返回值是內層函數的函數名;再來內層函數可以接收參數,其中ret = f(*args,**kwargs)有兩個作用,一是執行了傳入的函數,也就是執行了被修飾的函數,二是將返回值賦給了一個變量,此處要說明一下,對被修飾的函數功能的擴展要寫在這裏哦,最後 ret作為內層函數的返回值返回給函數執行者。

語法糖

@warpper這東西和 func = wrapper(func)是一樣的,也就是說最後三行代碼可以這樣寫

#@warpper 
def func():
  print('我就是那個被裝飾的函數')
func = wrapper(func)   #注意奧,這玩意要寫在被裝飾函數的下邊,語法糖才寫上邊

至於為什麼要寫着東西,或者為什麼要用語法糖?請聽下回分解~~~,收起你滴拳頭,是這樣,我剛開始的時候談到了,裝飾器是要在不改變源碼和其調用方式的前提下給其增加新的功能,注意到了么 調用方式 嗯……沒錯就是調用方式,如果我不這樣寫那我是不是要wrapper(func)()這樣去調用啊,是不是有點繞了,但是我把wrapper(func)賦值給了一個和被裝飾函數同名的變量,那我此時要怎麼調用,是不是就是func(),這樣就滿足了開放封閉原則,完美!其實本質就是要把裝飾器“偽裝”成原函數,包括調用方式、參數、返回值,裝就要裝的像一點,對吧。

裝飾帶參數的函數
def wrapper(f):
def inner(*args,**kwargs):
f(*args,**kwargs)
return inner
@wrapper
def func(a,b):
print(a,b)

函數func中有兩個形參a和b,說一下這兩個參數在裝飾器中的旅程,函數inner的萬能參數接收到a和b打包,然後inner函數充當中間商將打包后的元組給了f,在f中打散又成了變量a和b,在裝飾器時就不會影響傳參了,這就是裝飾帶參數的函數

裝飾有返回值的函數

def wrapper(f):
def inner():
ret = f()
return ret
return inner
@wrapper
def func():
return('我不管,我最帥')

哈哈,這段代碼中有我的心聲,你們都懂的,很簡單,裝飾有返回值的參數,在inner函數中將f()賦值給ret,這樣ret就接收到了返回值,再將ret返回給inner(),以此來達到“模擬”被裝飾函數的返回值,也可以說是通過這種方法來拿到被裝飾函數的返回值。

裝飾器帶參數

先舉個例子,當我們需要寫一個簡單的登陸認證功能的時候,我們的目的是要用裝飾器給調用的函數增加一個認證是否登陸過此網站的功能,也就是要驗證用戶名和密碼,但比如不同公司的網站數據庫肯定並非同一個,這個時候就需要帶參數的裝飾器啦,它可以實現我們要用一個裝飾器裝飾多個類似函數的目的

def wrapper_out(n):
   def wrapper(f):
       def inner(*args,**kwargs):
           with open(n,encoding='utf-8') as f1:
           '''此部分省略認證的詳細功能'''
           f(*args,**kwargs)
       return inner
   return wrapper

@wrapper_out('webpage1')
def wangzhi1():
   print('歡迎訪問網址1')
   
@wrapper_out('webpage2')
def wangzhi1():
   print('歡迎訪問網址2')

如果使用標準的裝飾器函數的話只能裝飾其中的一個函數,當裝飾另一個函數時會因為訪問不到正確的數據庫而報錯。

@wrapper_out('webpage1')這段代碼先執行wrapper_out('webpage1')這個函數先把參數webpage傳給n,並且返回一個wrapper,此時@和wrapper結合在一塊有沒有很眼熟,沒錯,這就是我們所熟悉的標準的裝飾器了,餘下的流程就和標準的裝飾器完全相同了。

多個裝飾器裝飾一個函數

這是一種特殊的情況,下面重點分析這種情況的結果是如何產生的,會有點繞。

def wrapper1(func1):
    def inner1():
        print('wrapper1 ,before func')
        func1()
        print('wrapper1 ,after func')
    return inner1

def wrapper2(func2): # func2 == inner1
    def inner2():
        print('wrapper2 ,before func')
        func2() # inner1
        print('wrapper2 ,after func')
    return inner2

@wrapper2  
@wrapper1  
def f():
print('in f') # 3

f()  

結果:

wrapper2,before func
wrapper1,before func
in f
wrapper1,after func
wrapper2,after func

怎麼說?是不是和你預期的結果有所不同,下面來按步驟說一下為什麼會產生這樣的結果(我盡量表達清楚哈)

1. 函數定義不調用不執行直接pass
2.   @wrapper2
@wrapper1  
def f():
單獨看下面這兩行,標準裝飾器哈 @wrapper1 等價於 f = wrapper1(f) = inner1(返回值哈)
3. @wrapper2
  @wrapper1   #注意哈 看步驟2 這裏現在是inner1咯、
  @wrapper2 等價於 inner1 = wrapper2(inner1) = inner2(返回值)
4. 此時的 f = inner2 執行f() 就從inner2()開始執行
5. 執行inner2 首先打印'wrapper2 ,before func'
6. 然後執行func2,由步驟3可知當前wrapper2中的參數是inner1 也就是執行inner1 所以打印wrapper1 ,before func
7. 然後執行func1 參考步驟2 可知這裏的func1()執行的是f()也就是真正的原函數,打印in f
8. 順序執行,打印wrapper1 ,after func
9. inner1執行完(其實就是func2執行完)還是順序執行 打印wrapper2 ,after func
10. 結果出來了、哈哈

這部分本人能力也只能寫成這樣了,各位看官看不懂那一定是小弟沒表述清楚,不過沒關係,在下再支一招,看圖

 

結合結果分析,相信各位老闆也能推導出正確的結果了,嗯、真帥!

好,到此對Python裝飾器應該有那麼一丟丟的認識了哈,我不管,就得有認識。下面的內容和文章關係不大哈

第一篇正經寫的文章,可能文章內容表達不盡如人意的正經哈,但初心是好的,就是分享知識,分享心得嘛,嗯,不管怎麼說,我還是很欣慰的對自己,哈哈,能有人從中有收穫就更perfect嘍

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

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

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

ElasticSearch簡介

目錄

  • 1. 定義
  • 2. 與 Lucene 的關係
  • 3. 優點
  • 4. 缺點
  • 5. 解決的問題
  • 6. 應用場景
  • 7. 倒排索引(摘自Elasticsearch權威指南)

1. 定義

Elasticsearch 是一個高度可擴展的開源全文搜索和分析引擎。它允許您快速,近實時地存儲,搜索和分析大量數據。它通常用作底層引擎、技術,為具有複雜搜索功能和要求的應用程序提供支持。

Elasticsearch 也使用 Java 開發並使用 Lucene 作為其核心來實現所有索引和搜索的功能,但是它的目的是通過簡單的 RESTful API 來隱藏 Lucene 的複雜性,從而讓全文搜索變得簡單。

ES 是基於Lucene這個非常成熟的索引方案,另加上一些分佈式的實現:集群,分片,複製等。

2. 與 Lucene 的關係

Lucene 是一套用於全文檢索和搜尋的開源程式庫,由 Apache 軟件基金會支持和提供。Lucene 提供了一個簡單卻強大的應用程式接口,能夠做全文索引和搜尋。但是 Lucene 操作複雜,一般不直接利用 Lucene 作為搜索引擎,ElasticSearch 就是利用 Java 簡化了 Lucene 的使用。

3. 優點

  1. 具備橫向可擴展性:只需要增加一台服務器,做些配置,啟動 ES 進程就可以快速併入集群。’
  2. 分片機制:同一個索引分成多個分片(sharding),類似於 redis 中的分片,採取分而治之的思想來更好地解決問題。
  3. 高可用:提供複製機制,一個分片可以設置多個複製,使得某台服務器宕機的話,集群依舊可以正常運行,並會把丟失的複製恢復到其它可用節點上’

4. 缺點

  1. 節點數據的一致性問題:其默認的機制是通過多播機制,同步元數據信息,但是在比較繁忙的集群中,可能會由於網絡的阻塞,或者節點處理能力達到飽和導致各節點元數據不一致——也就是所謂的腦裂問題,這樣會使集群處於不一致狀態。目前並沒有一個徹底的解決方案來解決這個問題,但是可以通過將工作節點與元數據節點分開的部署方案來緩解這種情況。
  2. 沒有細粒度的權限管理,沒有像MySQL那樣的分各種用戶,每個用戶又有不同的權限。

5. 解決的問題

  • 更快的在大量數據中檢索相關數據,性能遠優於傳統數據庫
  • 結合分詞器,根據關鍵詞返回統計結果

6. 應用場景

  • 全文檢索:例如淘寶 app 搜索 17寸電腦關鍵詞,搜索系統將依據關鍵詞分詞查詢,按照指定的匹配度返回對應的商品。這是 ES 最核心也是最常用的功能。
  • 記錄和日誌分析:圍繞Elasticsearch構建的生態系統使其成為最容易實施和擴展日誌記錄解決方案之一。結合Logstash,ElasticSearch 和Kibana 三個組件,可以搭建一套高效的日誌收集和分析系統,也就是我們常見的ELK系統。
  • 數據可視化:Kibana 是一款功能強大且易於使用的可視化工具,可以結合 ES 對大量數據提供圖表選項、地理數據等可視化組件。

7. 倒排索引(摘自Elasticsearch權威指南)

Elasticsearch 是通過 Lucene 的倒排索引技術實現比關係型數據庫更快的過濾。特別是它對多條件的過濾支持非常好。

Elasticsearch 使用一種稱為 倒排索引 的結構,它適用於快速的全文搜索。一個倒排索引由文檔中所有不重複詞的列表構成,對於其中每個詞,有一個包含它的文檔列表。

例如,假設我們有兩個文檔,每個文檔的 content 域包含如下內容:

  1. The quick brown fox jumped over the lazy dog
  2. Quick brown foxes leap over lazy dogs in summer

為了創建倒排索引,我們首先將每個文檔的 content 域拆分成單獨的 詞(我們稱它為 詞條tokens),創建一個包含所有不重複詞條的排序列表,然後列出每個詞條出現在哪個文檔。結果如下所示:

Term      Doc_1  Doc_2
-------------------------
Quick   |       |  X
The     |   X   |
brown   |   X   |  X
dog     |   X   |
dogs    |       |  X
fox     |   X   |
foxes   |       |  X
in      |       |  X
jumped  |   X   |
lazy    |   X   |  X
leap    |       |  X
over    |   X   |  X
quick   |   X   |
summer  |       |  X
the     |   X   |
------------------------

現在,如果我們想搜索 quick brown ,我們只需要查找包含每個詞條的文檔:

Term      Doc_1  Doc_2
-------------------------
brown   |   X   |  X
quick   |   X   |
------------------------
Total   |   2   |  1

兩個文檔都匹配,但是第一個文檔比第二個匹配度更高。如果我們使用僅計算匹配詞條數量的簡單 相似性算法 ,那麼,我們可以說,對於我們查詢的相關性來講,第一個文檔比第二個文檔更佳。

但是,我們目前的倒排索引有一些問題:

  • Quickquick 以獨立的詞條出現,然而用戶可能認為它們是相同的詞。
  • foxfoxes 非常相似, 就像 dogdogs ;他們有相同的詞根。
  • jumpedleap, 儘管沒有相同的詞根,但他們的意思很相近。他們是同義詞。

使用前面的索引搜索 +Quick +fox 不會得到任何匹配文檔。(記住,+ 前綴表明這個詞必須存在。)只有同時出現 Quickfox 的文檔才滿足這個查詢條件,但是第一個文檔包含 quick fox ,第二個文檔包含 Quick foxes

我們的用戶可以合理的期望兩個文檔與查詢匹配。我們可以做的更好。

如果我們將詞條規範為標準模式,那麼我們可以找到與用戶搜索的詞條不完全一致,但具有足夠相關性的文檔。例如:

  • Quick 可以小寫化為 quick
  • foxes 可以 詞幹提取 –變為詞根的格式– 為 fox 。類似的, dogs 可以為提取為 dog
  • jumpedleap 是同義詞,可以索引為相同的單詞 jump

現在索引看上去像這樣:

Term      Doc_1  Doc_2
-------------------------
brown   |   X   |  X
dog     |   X   |  X
fox     |   X   |  X
in      |       |  X
jump    |   X   |  X
lazy    |   X   |  X
over    |   X   |  X
quick   |   X   |  X
summer  |       |  X
the     |   X   |  X
------------------------

這還遠遠不夠。我們搜索 +Quick +fox 仍然 會失敗,因為在我們的索引中,已經沒有 Quick 了。但是,如果我們對搜索的字符串使用與 content 域相同的標準化規則,會變成查詢 +quick +fox ,這樣兩個文檔都會匹配!

github地址

【精選推薦文章】

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

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

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

Java NIO學習系列一:Buffer

  前面三篇文章中分別總結了標準Java IO系統中的File、RandomAccessFile、I/O流系統,對於I/O系統從其繼承體系入手,力求對類數量繁多的的I/O系統有一個清晰的認識,然後結合一些I/O的常規用法來加深對標準I/O系統的掌握,感興趣的同學可以看一下:

  <<Java I/O系統學習系列一:File和RandomAccessFile>>

  <<Java I/O系統學習系列二:輸入和輸出>>

  <<Java I/O系統學習系列三:I/O流的典型使用方式>>

  從本文開始我會開始總結NIO部分,Java NIO(注意,這裏的NIO其實叫New IO)是用來替換標準Java IO以及Java 網絡API的,其提供了一系列不同與標準IO API的方式來處理IO,從JDK1.4開始引入,其目的在於提高速度。

  之所以能夠提高速度是因為其所使用的結構更接近於操作系統執行I/O的方式:通道和緩衝器。我們可以把它想象成一個煤礦,通道是一個包含煤層(數據)的礦藏,而緩衝器則是派送到礦藏的卡車。卡車滿載煤炭而歸,我們再從卡車上獲得煤炭。也就是說,我們並沒有直接和通道交互,而是和緩衝器交互,並把緩衝器派送到通道。通道要麼從緩衝器獲得數據,要麼向緩衝器發送數據。

   在標準IO的API中,使用字節流和字符流。而在Java NIO中是使用Channel(通道)和Buffer(緩衝區),數據從channel中讀取到buffer中,或從buffer寫入到channel中。Java NIO類庫中的核心組件為:

  • Buffer
  • Channel
  • Selector

  本文中我們會着重總結Buffer相關的知識點(後面的文章中會繼續介紹Channel即Selector),本文主要會圍繞如下幾個方面展開:

  Buffer簡介

  Buffer的內部結構  

  Buffer的主要API

  ByteBuffer

  Buffer類型

  總結

 

1. Buffer簡介

  Java NIO中的Buffer一般和Channel配對使用。可以從Channel中讀取數據到Buffer,或者寫數據到Channel中。一個Buffer其實就是代表一個內存塊,你可以往裡面寫數據或者從中讀取數據。這個內存塊被包裝成一個Buffer對象,並且提供了一系列方法使得操作內存塊更便捷。

  通過Buffer來讀寫數據通常包括如下4步:

  1. 寫數據到Buffer中;
  2. 調用buffer.flip();
  3. 從Buffer讀取數據;
  4. 調用buffer.clear()或buffer.compact();

  當往Buffer中寫數據時,Buffer能夠記錄寫了多少數據。當要從Buffer中讀取數據時,就需要通過調用flip()方法將Buffer從寫模式切換到讀模式。一旦讀完所有數據,需要清空Buffer,讓它再次處於寫狀態。可以通過調用clear()或compact()方法來完成這一步:

  • clear()方法會清空整個Buffer;
  • compact()方法僅僅清空你已經從Buffer中讀取的數據,未讀數據會被移動到Buffer起始位置,可以緊接着未讀的數據寫入新的數據;

  如下是一個簡單的使用例子,通過FileChannel和ByteBuffer讀取pom.xml文件,並逐字節輸出:

public class BufferDemo {

    public static void main(String[] args) {
        try {
            RandomAccessFile raf = new RandomAccessFile("pom.xml","r");
            FileChannel channel = raf.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(48);
            int byteReaded = channel.read(buffer);
            while(byteReaded != -1) {
                buffer.flip();
                while(buffer.hasRemaining()) {
                    System.out.print((char)buffer.get());
                }
                buffer.clear();
                byteReaded = channel.read(buffer);
            }
            raf.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }    
}

 

2. Buffer的內部結構

  上面說到Buffer封裝了一塊內存塊,並提供了一系列的方法使得可以方便地操縱內存中的數據。至於如何操縱?Buffer提供了4個索引。要理解Buffer的工作原理,就需要從這些索引說起:

  • capacity(容量);
  • position(位置);
  • limit(界限);
  • mark(標記);

   其中position和limit的含義取決於Buffer是處於什麼模式(讀或者寫模式),capacity的含義則和模式無關,而mark則只是一個標記,可以通過mark()方法進行設置。下圖描述了讀寫模式下三種屬性分別代表的含義,詳細解釋見下文:

2.1 Capacity

  Buffer代表一個內存塊,所以其是有確定大小的,也叫“容量”。可以往buffer中寫入各種數據如byte、long、chars等,當Buffer被寫滿了則需要將其清空(可以通過讀取數據或者清空數據)之後才能繼續寫入數據。

2.2 Position

  當往Buffer中寫數據時,寫入的地方就是所謂的position,其初始值為0,最大值為capacity-1。當往Buffer中寫入一個byte或者long的數據時,position會前移以指向下一個即將被插入的位置。

  當從Buffer中讀取數據時,讀取數據的地方就是所謂的position。當執行flip將Buffer從寫模式切換到讀模式時,position會被重置為0。隨着不斷從Buffer讀取數據,position也會不斷後移指向下一個將被讀取的數據。

2.3 Limit

  在寫模式下,Buffer的limit是指能夠往Buffer中寫入多少數據,其值等於Buffer的capacity。

  在讀模式下,Buffer的limit是指能夠從Buffer讀取多少數據出來。因此當從寫模式切換到讀模式下時,limit就被設置為寫模式下的position的值(這很好理解,寫了多少才能讀到多少)。

 2.4 Mark

  mark其實就是一個標記,可以通過mark()方法設置,設置值為當前的position。

 

  下面是用於設置和複位索引以及查詢它們值的方法:

 

  capacity()      返回緩衝區容量
  clear()      清空緩衝區,將position設置為0,limit設置為容量。我們可以調用此方法覆寫緩衝區
  flip()       將limit設置為position,position設置為0。此方法用於準備從緩衝區讀取已經寫入的數據
  limit()        返回limit值
  limit(int lim)    設置limit值
  mark()       將mark設置為position
  position()     返回position值
  position(int pos)  設置position值
  remaining()    返回(limit – position)
  hasRemaining()  若有介於position和limit之間的元素,則返回true

 

3. Buffer的主要API

  除了如上和索引相關的方法之外,Buffer還提供了一些其他的方法用於寫入、讀取等操作。

3.1 給Buffer分配空間

  要獲得一個Buffer對象就可以通過Buffer類的allocate()方法來實現,如下分別是分配一個48字節的ByteBuffer和1024字符的CharBuffer:

ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);

3.2 往Buffer中寫數據

  有兩種方式往Buffer中寫入數據:

  • 從Channel中往Buffer寫數據;
  • 通過Buffer的put()方法寫入數據;
int bytesRead = inChannel.read(buf); // read into buffer
buf.put(127);

  put()方法有多個重載版本,比如從指定位置寫入數據,或寫入字節數組等。

3.3 flip()

  flip()方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設為0,limit設為position之前的值。

3.4 從Buffer讀數據

  也有兩種方法從Buffer讀取數據:

  • 從Buffer中讀數據到Channel中;
  • 調用Buffer的get()方法讀取數據;
int bytesWritten = inChannel.write(buf); // read from buffer into channel
byte aByte = buf.get();

3.5 rewind()

  rewind()方法將position設置為0,可以從頭開始讀數據。

3.6 clear()和compact()

  當從Buffer讀取數據結束之後要將其切換回寫模式,可以調用clear()、compact()這兩個方法,兩者之間的區別如下:

  調用clear(),會將position設為0,limit設為capacity,也就是說Buffer被清空了,但是裏面的數據仍然存在,只是這時沒有標記可以告訴你哪些數據是已讀,哪些是未讀。

  如果讀取到一半需要寫入數據,但是未讀的數據稍後還需要讀取,這時可以使用compact(),其會將所有未讀取的數據複製到Buffer的前面,將position設置到這些數據後面,limit設置為capacity,所以此時是從未讀的數據後面開始寫入新的數據。

3.7 mark()和reset()

  調用mark()方法可以標誌一個指定的位置(即設置mark值),之後調用reset()方法時position又會回到之前標記的位置。

 

4. ByteBuffer

   ByteBuffer是一個比較基礎的緩衝器,繼承自Buffer,是可以存儲未加工字節的緩衝器,並且也是唯一直接與通道交互的緩衝器。可以通過ByteBuffer的allocate()方法來分配一個固定大小的ByteBuffer,並且其還有一個方法選擇集,用於以原始的字節形式或基本類型輸出和讀取數據。但是,沒辦法輸出或讀取對象,即使是字符串對象也不行。這種處理雖然很低級,但卻正好,因為這是大多數操作系統中更有效的映射方式。

  ByteBuffer也分為直接和非直接緩衝器,通過allocate()創建的就是非直接緩衝器,而通過allocateDirect()方法就可以創建出一個緩衝器直接緩衝器,這是一個與操作系統有更高耦合性的緩衝器,也就意味着它能夠帶來更高的速度,但是分配的開支也會更大。

  儘管ByteBuffer只能保存字節類型的數據,但是它具有可以從其所容納的字節中產生出各種不同基本類型值的方法。下面的例子展示怎樣使用這些方法來插入和抽取各種數值:

public class GetData {    
    private static final int BSIZE = 1024;
    public static void main(String[] args){
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        int i = 0;
        while(i++ < bb.limit())
            if(bb.get() != 0)
                System.out.println("nonzero");
        System.out.println("i = " + i);
        bb.rewind();
        // store and read a char array:
        bb.asCharBuffer().put("Howdy!");
        char c;
        while((c = bb.getChar()) != 0)
            System.out.print(c + " ");
        System.out.println();
        bb.rewind();
        // store and read a short:
        bb.asShortBuffer().put((short)471142);
        System.out.println(bb.getShort());
        bb.rewind();
        // sotre and read an int:
        bb.asIntBuffer().put(99471142);
        System.out.println(bb.getInt());
        bb.rewind();
        // store and read a long:
        bb.asLongBuffer().put(99471142);
        System.out.println(bb.getLong());
        bb.rewind();
        // store and read a float:
        bb.asFloatBuffer().put(99471142);
        System.out.println(bb.getFloat());
        bb.rewind();
        // store and read a double:
        bb.asDoubleBuffer().put(99471142);
        System.out.println(bb.getDouble());
        bb.rewind();
    }
}

 

5. Buffer類型

  Java NIO中包含了如下幾種Buffer:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

  這些Buffer類型代表着不同的數據類型,使得可以通過Buffer直接操作如char、short等類型的數據而不是字節數據。其中MappedByteBuffer略有不同,後面會專門總結。

  通過ByteBuffer我們只能往Buffer直接寫入或者讀取字節數組,但是通過對應類型的Buffer比如CharBuffer、DoubleBuffer等我們可以直接往Buffer寫入char、double等類型的數據。或者利用ByteBuffer的asCharBuffer()、asShorBuffer()等方法獲取其視圖,然後再使用其put()方法即可直接寫入基本數據類型,就像上面的例子。

  這就是視圖緩衝器(view buffer)可以讓我們通過某個特定的基本數據類型的視窗查看其底層的ByteBuffer。ByteBuffer依然是實際存儲數據的地方,“支持”着前面的視圖,因此對視圖的任何修改都會映射成為對ByteBuffer中數據的修改。這使得我們可以很方便地向ByteBuffer插入數據。視圖還允許我們從ByteBuffer一次一個地(與ByteBuffer所支持的方式相同)或者成批地(通過放入數組中)讀取基本類型值。在下面的例子中,通過IntBuffer操縱ByteBuffer中的int型數據:

public class IntBufferDemo {    
    private static final int BSIZE = 1024;
    public static void main(String[] args){
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        IntBuffer ib = bb.asIntBuffer();
        // store an array of int:
        ib.put(new int[]{11,42,47,99,143,811,1016});
        // absolute location read and write:
        System.out.println(ib.get(3));
        ib.put(3,1811);
        // setting a new limit before rewinding the buffer.
        ib.flip();
        while(ib.hasRemaining()){
            int i = ib.get();
            System.out.println(i);
        }
    }
}

  上例中先用重載后的put()方法存儲一個整數數組。接着get()和put()方法調用直接訪問底層ByteBuffer中的某個整數位置。這些通過直接與ByteBuffer對話訪問絕對位置的方式也同樣適用於基本類型。

 

6. 總結

  本文簡單總結了Java NIO(Java New IO),其目的在於提高速度。Java NIO類庫中主要包括Buffer、Channel、Selector,本文主要總結了Buffer相關的知識點:

  • Buffer叫緩衝器,她是和Channel(通道)交互的,可以從channel中讀數據到buffer中,或者從buffer往channel中寫數據;
  • Buffer內部封裝了一塊內存,提供了一系列API使得可以方便地操作內存中的數據。其內部是通過capacity、position、limit、mark等變量來跟蹤標記封裝的數據的;
  • ByteBuffer是最基本的Buffer,是唯一可以直接與通道交互的緩衝器,其可以直接操縱字節數據或字節數組;
  • 除了ByteBuffer之外,Buffer還有許多別的類型如:MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer;
  • 雖然只有ByteBuffer能夠直接和通道交互,但是可以從ByteBuffer獲取多種不同的視圖緩衝器,進而同時具備了直接操作基本數據類型和與通道交互的能力;

  基礎知識的總結也許是比較枯燥的,但是如果你已經看到這裏說明你很有耐心,如果覺得對你有幫助的話,不妨點個贊關注一下吧^_^

 

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

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

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

SAP中的數據庫表索引

數據庫表中的索引可以加快查詢的速度。索引是數據庫表字段的有序副本。附加的字段包含指向真實數據庫錶行的指針。排序可以使訪問錶行的速度變快,例如,可以使用二分搜索。數據庫表至少有一個主索引,由它的key字段定義。它也可以有一到多個二級索引。

本文鏈接:https://www.cnblogs.com/hhelibeb/p/11061879.html 

英文原文:https://help.sap.com/doc/abapdocu_753_index_htm/7.53/en-US/abenddic_database_tables_index.htm

主索引

主索引是由主鍵的key字段構造的唯一索引,AS ABAP總會自動創建它。對於每個索引字段的組合,表中最多只能有一條記錄。 如果無法使用主索引識別記錄集,比如說,沒有使用主索引查詢字段,就會發生全表掃描,或者數據庫系統會嘗試使用合適的二級索引(如果有的話)。

二級索引

除了由主鍵定義的主索引,也可以為數據庫表定義唯一或不唯一的二級索引。創建二級索引通常會提高數據庫的讀性能,前提是讀取的時候使用到了二級索引。

二級索引包含一系列數據庫表字段,有一個最大3位長度的文本数字組成的ID。0是一個保留ID,用來表示主索引。string和rawstring類型的字段無法成為索引字段(全文索引除外)。也不建議使用數據類型FLTP的字段作為索引字段。

數據庫表在數據庫中被創建的時候,二級索引也會被定義。此外,可以晚些在相同的系統中創建新的二級索引。如果如果在其他系統增加新的二級索引而不作修改的話,它們會被創建為擴展索引。以下是建議的索引的命名空間:

  • 客戶為標準表添加的索引ID前綴為’Y’或者’Z’。
  • 合作夥伴為標準表添加的索引ID前綴為’J’,不同合作夥伴創建的索引的名稱可能衝突。
  • 其他表可以有任意名字的索引,不過不應以’Y’,’Z’或’J’開頭。

數據庫中的索引名字通常是DBTAB~ID,DBTAB是數據庫表的名字,ID是3位字符的ID。也可能有其它名字,比如空格或下劃線。

二級索引可以是唯一的,但是(不像主索引)沒必要。對唯一索引而言,數據庫表不能含有同樣索引值的多行數據。試圖插入重複的行,會取消數據庫操作,並在ABAP中觸發相應的異常。在指定了client的表中,唯一索引必須包含client字段。

訪問數據庫時,數據庫系統的優化器會檢查是否有合適的索引,並使用它。索引的選擇取決於平台,意味着可以在ABAP字典中定義非唯一索引在不同的數據庫系統中是否可用。有幾種選項,

  • Index in all database systems:這個索引會在每個數據庫中創建。
  • In selected database systems:可以使用選擇列表或排除列表來定義數據庫系統,每個列表最多有4個條目。
  • No database index:不在任何數據庫中創建索引,這個選項可以用於刪除二級索引。

這些選項對錶緩存的二級索引無效。如果表緩存有相關設置,那麼系統就會根據表緩存的設置決定是否使用二級索引。

唯一二級索引總是會被創建,而且無法從數據庫刪除。可以使用事務代碼ST05中的SQL跟蹤功能來判斷訪問數據時系統使用的索引。

索引對於查詢數據的提升效果取決於索引代表結果數據集的能力。只有索引中可以對結果集進行有效約束的字段才是有用的。這種情況下,索引中的字段順序是一個對於數據的訪問速度十分重要的因素。第一個字段必須是那些有着大量不同可選值的字段。在查詢中,要在查詢條件中指定索引的第一個字段,這樣索引才有用。另外,只有一個索引字段前面的全部索引字段都在查詢條件內時,這個索引字段才生效。字段的訪問速度和索引是否為唯一索引無關。

對於以下情況,創建二級索引可以帶來好處:

  • 如果需要查詢的表記錄不包含在現有索引內,響應時間很久,應該創建二級索引。
  • 這個字段的選擇性很強,每個值可以用於區分少於5%的表記錄。
  • 數據庫主要用於讀取。因為更改表時也需要更新索引,會降低寫入性能。
  • 如果讀取的字段也在索引里,那麼在訪問索引后不需要再次從索引之外讀取它們。如果只有少量字段經常被選擇,把它們全部包含在索引里的做法可以大大提高性能。

注:選擇性(Selectivity),是指不重複的索引值(也叫基數,Cardinality)與表記錄數(#T)的比值, Index Selectivity = Cardinality / #T

二級索引也會增加系統負載,因為每次表內容被修改時,二級索引都要做相應調整。表的每個額外的索引都會降低插入行的性能。如果需要頻繁在表中插入數據,那麼應該只建立很少的索引。太多索引也會導致數據庫的優化器找不到正確的索引。為了避免這點,表中的索引最好不相交(沒有相同的字段)。

索引應該只包含幾個字段,比如,原則上不超過4個。這是因為索引字段在被更新的時候,索引也要被更新。適合作為索引的字段是:

  • 經常被查詢,並且選擇性高。需要把選擇性最高的字段放在索引的開始位置。
  • 如果一個字段在大部分表記錄中的值都是初始值,那麼它不應成為索引字段。
  • 如果一個數據庫表有不止一個索引,那麼索引間不應該重疊。

不應該為一個表創建超過5個索引,因為,

  • 每個索引都會增加更新開銷。
  • 數據量會增加。
  • 數據庫優化器會因為可選擇的索引過多變得更加容易出錯。

索引只支持明確的條件值,比如=或者LIKE。如果條件中包含某些不確定因素,比如<>,那麼索引將無法改善性能。條件中包含OR時,優化器通常停止工作。換句話說,使用索引時,OR條件的字段是不生效的。一個例外是OR關係互相獨立。因此,對於包含OR和索引字段結合的條件,有時需要修改條件的形式。(可以看下面的例子)

注意

  • 某些數據庫的索引會忽略0,意味着查詢0值時,沒有索引可用。
  • 如有必要,可以在ABAP SQL(Open SQL)中使用附加項%_HINTS為database hints來調整系統優化器,以決定使用哪個二級索引。

例子

下面這個句子會導致優化器無法使用索引,因為遇到了OR:

SELECT * FROM spfli 
         WHERE carrid = 'LH' AND 
              ( CITYFROM = 'FRANKFURT' OR  cityfrom = 'NEW YORK' ).

替換成下面這樣的一個相等的句子,可以根據現有索引對整個條件進行優化(原因見前文):

SELECT * 
       FROM spfli 
       WHERE ( carrid = 'LH' AND cityfrom = 'FRANKFURT' ) OR 
             ( carrid = 'LH' AND cityfrom = 'NEW YORK' ).

全文索引

SAP HANA數據庫支持全文索引,全文索引可以作為二級索引。全文索引會在數據庫中被創建為一個額外的可見的列。全文索引的列的內容會被保存在這個額外的列中,以某種格式存儲,在相關數據被訪問的時候會發揮作用。

以下是全文索引的使用條件:

  • 只有對SAP HANA數據庫中的列存儲類型的表,才可以創建全文索引。
  • 只能為數據類型為指定的幾種內建數據類型的列(CHAR, SHORTSTRING, STRING, or RAWSTRING)創建全文索引,一個全文索引只能對應一個列。
  • 數據庫表必須包含一個文本語言列。

全文索引總是非唯一索引。使用全文索引的訪問基於數據庫中的WHERE CONTAINS元素。目前這個元素在ABAP SQL中還不可用,需要使用Native SQL或者AMDP。

注意

更多有關全文索引的信息,參看:SAP HANA Developer Guide.

 

參考閱讀:MySQL索引入門簡述

 

【精選推薦文章】

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

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

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

【機器學習之數學】03 有約束的非線性優化問題——拉格朗日乘子法、KKT條件、投影法

目錄

  • 1 將有約束問題轉化為無約束問題
    • 1.1 拉格朗日法
      • 1.1.1 KKT條件
      • 1.1.2 拉格朗日法更新方程
      • 1.1.3 凸優化問題下的拉格朗日法
    • 1.2 罰函數法
  • 2 對梯度算法進行修改,使其運用在有約束條件下
    • 2.1 投影法
      • 2.1.1 梯度下降法 to 投影梯度法
      • 2.1.2 正交投影算子
  • References
  • 相關博客

梯度下降法、最速下降法、牛頓法等迭代求解方法,都是在無約束的條件下使用的,而在有約束的問題中,直接使用這些梯度方法會有問題,如更新后的值不滿足約束條件。

那麼問題來了,如何處理有約束的優化問題?大致可以分為以下兩種方式:

  1. 將有約束的問題轉化為無約束的問題,如拉格朗日乘子法和KKT條件;
  2. 對無約束問題下的求解算法進行修改,使其能夠運用在有約束的問題中,如對梯度下降法進行投影,使得更新后的值都滿足約束條件。

1 將有約束問題轉化為無約束問題

1.1 拉格朗日法

僅含等式約束的優化問題
\[ \begin{array}{cl}{\text { minimize }} & {f(\boldsymbol{x})} \\ {\text { subject to }} & {\boldsymbol{h}(\boldsymbol{x})=\mathbf{0}}\end{array} \]

其中,\(x \in \mathbb{R}^n\)\(f : \mathbb{R}^{n} \rightarrow \mathbb{R}\)\(\boldsymbol{h} : \mathbb{R}^{n} \rightarrow \mathbb{R}^{m}, \boldsymbol{h}=\left[h_{1}, \ldots, h_{m}\right]^{\top}, \text { and } m \leq n\)

該問題的拉格朗日函數為:
\[ l(\boldsymbol{x}, \boldsymbol{\lambda})=f(\boldsymbol{x})+\boldsymbol{\lambda}^{\top} \boldsymbol{h}(\boldsymbol{x}) \]

FONC:對拉格朗日函數 \(l(\boldsymbol{x}, \boldsymbol{\lambda})\) 求偏導數,令偏導數都等於 0,求得的解必然滿足原問題的等式約束,可以從這些解裏面尋找是否有局部最優解。這是求得局部最優解的一階必要條件。

拉格朗日條件:(分別對 \(\bm x\)\(\bm \lambda\) 求偏導)
\[ \begin{array}{l}{D_{x} l\left(\boldsymbol{x}^{*}, \boldsymbol{\lambda}^{*}\right)=\mathbf{0}^{\top}} \\ {D_{\lambda} l\left(\boldsymbol{x}^{*}, \boldsymbol{\lambda}^{*}\right)=\mathbf{0}^{\top}}\end{array} \]

上式中,對 \(\lambda\) 求偏導數得到的就是等式約束。

拉格朗日條件是必要而非充分條件,即滿足上述方程的點 \(\boldsymbol x^{*}\) 不一定是極值點。

1.1.1 KKT條件

既含等式約束又含不等式約束的優化問題:
\[ \begin{array}{rl}{\operatorname{minimize}} & {f(\boldsymbol{x})} \\ {\text { subject to }} & {\boldsymbol{h}(\boldsymbol{x})=\mathbf{0}} \\ {} & {\boldsymbol{g}(\boldsymbol{x}) \leq \mathbf{0}}\end{array} \]

其中,\(f : \mathbb{R}^{n} \rightarrow \mathbb{R}\)\(\boldsymbol{h} : \mathbb{R}^{n} \rightarrow \mathbb{R}^{m}, m \leq n\),並且 \(\boldsymbol{g} : \mathbb{R}^{n} \rightarrow \mathbb{R}^{p}\)

將該問題轉化為拉格朗日形式:
\[ l(\boldsymbol{x}, \boldsymbol{\lambda})=f(\boldsymbol{x})+\boldsymbol{\lambda}^{\top} \boldsymbol{h}(\boldsymbol{x}) +\boldsymbol{\mu}^{\top} \boldsymbol{g}(\boldsymbol{x}) \]

\(\bm x^{*}\) 是原問題的一個局部極小點,則必然存在 \(\bm{\lambda}^{* \top} \in \mathbb{R}^m\)\(\bm{\mu}^{* \top} \in \mathbb{R}^p\),使得下列KKT條件成立:

  1. \(\bm {\mu}^{*} \geq 0\)
  2. \(D f\left(\boldsymbol{x}^{*}\right)+\boldsymbol{\lambda}^{* \top} D \boldsymbol{h}\left(\boldsymbol{x}^{*}\right)+\boldsymbol{\mu}^{* \top} D \boldsymbol{g}\left(\boldsymbol{x}^{*}\right)=\mathbf{0}^{\top}\)
  3. \(\boldsymbol{\mu}^{* \top} \boldsymbol{g}\left(\boldsymbol{x}^{*}\right)=0\)
  4. \({\boldsymbol{h}(\boldsymbol{x}^*)=\mathbf{0}}\)
  5. \({\boldsymbol{g}(\boldsymbol{x}^*) \leq \mathbf{0}}\)

KKT條件中,\(\bm{\lambda}^{*}\) 是拉格朗日乘子向量,\(\bm{\mu}^{*}\) 是KKT乘子向量,\(\bm{\lambda}^{*}\)\(\bm{\mu}^{*}\) 的元素分別稱為拉格朗日乘子和KKT乘子。

1.1.2 拉格朗日法更新方程

將含約束的優化問題轉化為拉格朗日形式后,我們可以用更新方程對該問題進行迭代求解。

這也是一種梯度算法,但拉格朗日乘子、KKT 乘子的更新和自變量 \(\bm x\) 的更新不同,自變量 \(\bm x\) 繼續採用梯度下降法更新,而拉格朗日乘子、KKT 乘子的更新方程如下:
\[ \boldsymbol{\lambda}^{(k+1)}=\boldsymbol{\lambda}^{(k)}+\beta_{k} \boldsymbol{h}\left(\boldsymbol{x}^{(k)}\right), \\ \boldsymbol{\mu}^{(k+1)}=\left[\boldsymbol{\mu}^{(k)}+\beta_{k} \boldsymbol{g}\left(\boldsymbol{x}^{(k)}\right)\right]_{+} \]

其中,\([\cdot]_{+}=\max \{\cdot, 0\}\)

1.1.3 凸優化問題下的拉格朗日法

拉格朗日乘子法和KKT條件在一般的含約束條件的優化問題中,都只是一階必要條件,而在凸優化問題中,則變成了充分條件。

凸優化問題指的是目標函數是凸函數,約束集是凸集的優化問題。線性規劃、二次規劃(目標函數為二次型函數、約束方程為線性方程)都可以歸為凸優化問題。

凸優化問題中,局部極小點就是全局極小點。極小點的一階必要條件就是凸優化問題的充分條件。

1.2 罰函數法

考慮一般形式的有約束優化問題:
\[ \begin{array}{cl}{\operatorname{minimize}} & {f(\boldsymbol{x})} \\ {\text { subject to }} & {\boldsymbol{x} \in \Omega}\end{array} \]

將問題變為如下無約束的形式:
\[ \operatorname{minimize} f(\boldsymbol{x})+\gamma P(\boldsymbol{x}) \]

其中,\(\gamma\) 是懲罰因子,\(P : \mathbb{R}^{n} \rightarrow \mathbb{R}\) 是罰函數。求解該無約束優化問題,把得到的解近似作為原問題的極小點。

罰函數需要滿足以下 3 個條件:

  1. \(\bm P\) 是連續的;
  2. 對所有 \(\bm x \in \mathbb{R}^n\)\(P(\boldsymbol{x}) \ge 0\) 成立;
  3. \(P(\boldsymbol{x})=0\),當且僅當 \(\bm x\) 是可行點(即 \({\bm{x} \in \Omega}\))。

2 對梯度算法進行修改,使其運用在有約束條件下

2.1 投影法

梯度下降法、最速下降法、牛頓法等優化算法都有通用的迭代公式:
\[ \boldsymbol{x}^{(k+1)}=\boldsymbol{x}^{(k)}+\alpha_{k} \boldsymbol{d}^{(k)} \]

其中,\(\boldsymbol{d}^{(k)}\) 是關於梯度 \(\nabla f(\bm x^{(k)})\) 的函數,如在梯度下降法中,\(\boldsymbol{d}^{(k)} = -\nabla f(\bm x^{(k)})\)

考慮優化問題:
\[ \begin{array}{cl}{\operatorname{minimize}} & {f(\boldsymbol{x})} \\ {\text { subject to }} & {\boldsymbol{x} \in \Omega}\end{array} \]

在上述有約束的優化問題中,\(\boldsymbol{x}^{(k)}+\alpha_{k} \boldsymbol{d}^{(k)}\) 可能不在約束集 \(\Omega\) 內,這是梯度下降等方法無法使用的原因。

而投影法做的是,如果 \(\boldsymbol{x}^{(k)}+\alpha_{k} \boldsymbol{d}^{(k)}\) 跑到約束集 \(\Omega\) 外面去了,那麼將它投影到約束集內“最接近”的點;如果 \(\boldsymbol{x}^{(k)}+\alpha_{k} \boldsymbol{d}^{(k)} \in \Omega\),那麼正常更新即可。

投影法的更新公式為:
\[ \boldsymbol{x}^{(k+1)}=\boldsymbol{\Pi}\left[\boldsymbol{x}^{(k)}+\alpha_{k} \boldsymbol{d}^{(k)}\right] \]

其中 \(\bm \Pi\) 為投影算子,\(\bm \Pi[\bm x]\) 稱為 \(\bm x\)\(\Omega\) 上的投影。

2.1.1 梯度下降法 to 投影梯度法

梯度下降法的迭代公式為:
\[ \boldsymbol{x}^{(k+1)}=\boldsymbol{x}^{(k)}-\alpha_{k} \nabla f\left(\boldsymbol{x}^{(k)}\right) \]

將投影算法引入梯度下降法,可得投影梯度法,迭代公式如下:
\[ \boldsymbol{x}^{(k+1)}=\boldsymbol{\Pi}\left[\boldsymbol{x}^{(k)}-\alpha_{k} \nabla f\left(\boldsymbol{x}^{(k)}\right)\right] \]

2.1.2 正交投影算子

含線性約束優化問題的投影梯度法可以利用正交投影算子來更新 \(\bm x^{(k)}\)

含線性約束的優化問題如下所示:
\[ \begin{array}{cl}{\operatorname{minimize}} & {f(\boldsymbol{x})} \\ {\text { subject to }} & {\boldsymbol{A x}=\boldsymbol{b}}\end{array} \]

其中,\(f : \mathbb{R}^{n} \rightarrow \mathbb{R}\)\(\boldsymbol{A} \in \mathbb{R}^{m \times n}, m<n\)\(\operatorname{rank} \boldsymbol{A}=m, \boldsymbol{b} \in \mathbb{R}^{m}\),約束集 \(\Omega=\{\boldsymbol{x} :\boldsymbol{A} \boldsymbol{x}=\boldsymbol{b} \}\)

這種情況下,正交投影算子矩陣 \(\bm P\) 為:
\[ \boldsymbol{P}=\boldsymbol{I}_{n}-\boldsymbol{A}^{\top}\left(\boldsymbol{A} \boldsymbol{A}^{\top}\right)^{-1} \boldsymbol{A} \]

正交投影算子 \(\bm P\) 有兩個重要性質:

  1. \(P=P^{\top}\).
  2. \(P^{2}=P\).

在投影梯度算法中,可以按照如下公式更新 \(\bm x^{(k)}\)
\[ \boldsymbol{x}^{(k+1)}=\boldsymbol{x}^{(k)}-\alpha_{k} \boldsymbol{P} \nabla \boldsymbol{f}(\boldsymbol{x}^{(k)}) \]

References

Edwin K. P. Chong, Stanislaw H. Zak-An Introduction to Optimization, 4th Edition

相關博客

【機器學習之數學】01 導數、偏導數、方嚮導數、梯度
【機器學習之數學】02 梯度下降法、最速下降法、牛頓法、共軛方向法、擬牛頓法
【機器學習之數學】03 有約束的非線性優化問題——拉格朗日乘子法、KKT條件、投影法

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

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

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

量子邏輯門

量子態的演化

在前面量子糾纏1中我們已經提到了量子比特的線性代數表示,即,對於一個量子態 \(\alpha_0 | 0\rangle +\alpha_1 | 1\rangle\)我們可以化簡成$ \left[ \begin{array}{}{\alpha_0} \ {\alpha_1}\end{array}\right]$ 。

量子態不是一成不變的,就像高電平會變成低電平,一個量子態也能演化成另一個量子態,量子態的演化就是在Hilbert空間中的旋轉,如圖(a)所示。

通過一個U操作,我們就將 \(| 0\rangle\) 變成了 \(U| 0\rangle\)\(| 1\rangle\) 變成了 \(U| 1\rangle\)\(| u\rangle\) 變成了 \(U| u\rangle\) ,如圖(b)所示。需要注意的是,我添加一個U操作,沒有改變 \(| 0\rangle\)\(| 1\rangle\)\(| u\rangle\) 之間的關係, \(U| 0\rangle\)\(U| 1\rangle\)\(| 0\rangle\)\(| 1\rangle\) 一樣,他們之間的關係依舊是垂直。

$ (| 0\rangle, | 1\rangle)=0$

$ (U| 0\rangle, U| 1\rangle)=0$

\((,)\) 是內積的意思, $ (| 0\rangle, | 1\rangle)= \left[ \begin{array}{}{1}&{0}\end{array}\right]\left[ \begin{array}{}{0} \ {1}\end{array}\right]$ ,同樣,也可以簡寫成 \(\langle0| 1\rangle\)\(\langle0|\) 表明是 \(| 0\rangle\) 的共軛轉置。

對於這種兩個向量之間夾角不會變的旋轉稱為剛性旋轉 rigid rotation。

而這種U操作被成為酉操作,也是unitary transformation

Unitary Transformation

量子比特我們用向量來表示,因為我們量子比特的演化是線性的,所以在量子比特上的操作,可以用矩陣來表示。

單量子比特是 \(2*1\) 的向量,則單量子比特門是 \(2*2\) 的矩陣。

\(| 0\rangle\) 變到 \(U| 0\rangle\) 在線性代數上就是 $ \left[ \begin{array}{}{1} \ {0}\end{array}\right]$ 變到 $ \left[ \begin{array}{}{\frac{1}{\sqrt2}} \ {\frac{1}{\sqrt2}}\end{array}\right]$

\[ \left[ \begin{array}{}{\frac{1}{\sqrt2}} \\ {\frac{1}{\sqrt2}}\end{array}\right]=U\left[ \begin{array}{}{1} \\ {0}\end{array}\right]\]

\[U= \left[ \begin{array}{}{\frac{1}{\sqrt2}} &{-\frac{1}{\sqrt2}} \\ {\frac{1}{\sqrt2}}&{\frac{1}{\sqrt2}} \end{array}\right]\]

對於旋轉了 \(\theta\) 角度的操作,都可以用 \(U_{\theta}= \left[ \begin{array}{}{cos\theta} &{-sin\theta} \\ {sin\theta}&{cos\theta} \end{array}\right]\) 表達。

如果要做相反操作,就是將順時針轉 \(\theta\) 角度, \(U_{-\theta}= \left[ \begin{array}{}{cos\theta} &{sin\theta} \\ {-sin\theta}&{cos\theta} \end{array}\right]\)

很巧的是, \(U_\theta^\dagger=U_{-\theta}\)\(\dagger\) 是共軛轉置的意思。

\(U_\theta U_{\theta}^\dagger=I\) ,意思也很好理解,因為順時針 \(\theta\)\(-\theta\) ,正好就回到原位。

事實上所有的量子操作都是可逆的,所有的量子操作都酉操作。

那麼什麼是酉操作呢?

U is unitary iff \(U^\dagger U =I\)

對於酉矩陣的更多特徵會在線性代數的章節提到,這裏主要提一個,酉矩陣是保內積的。

保內積又是什麼意思?

兩個向量在乘以相同的U后,他們的內積不變。

\[(U| a\rangle, U| b\rangle)=\langle a|U^\dagger U|b\rangle=\langle a|I|b\rangle=\langle a|b\rangle\]

單量子邏輯門

量子邏輯門和經典邏輯門一個巨大的不同是——量子邏輯門可逆。

經過了經典的邏輯門與門或者非門,我們的信息會丟失,告訴你與門后的輸出結果是0,你知道與門前的輸入嗎?(0,0)、(0,1)、(1,0)都有可能。

而對於量子邏輯門來說,我經過U變換后的結果是 \(|a\rangle\) ,那麼 \(U^\dagger |a\rangle\) 就是變換前的輸入了。

舉例幾個常用的單量子邏輯門:

\(X=\left[ \begin{array}{}{0} &{1} \\ {1}&{0} \end{array}\right]\),X門又稱為比特翻轉,他可以把 \(|0\rangle\) 變成 \(|1\rangle\) ,把 \(|1\rangle\) 變成 \(|0\rangle\)

\(Y=\left[ \begin{array}{}{0} &{-i} \\ {i}&{0} \end{array}\right]\)

\(Z=\left[ \begin{array}{}{1} &{0} \\ {0}&{-1} \end{array}\right]\),Z門又稱為相位翻轉門,可以把 \(|+\rangle\) 變成 \(|-\rangle\)\(-|1\rangle\) 變成 \(|1\rangle\)

以及一個特別有用的門,Hadamard門:
\(H=\left[ \begin{array}{}{\frac{1}{\sqrt2}} &{\frac{1}{\sqrt2}} \\ {\frac{1}{\sqrt2}}&{-\frac{1}{\sqrt2}} \end{array}\right]\) ,他的作用是把 \(|1\rangle\) 變成 \(|-\rangle\)\(|0\rangle\) 變成 \(|+\rangle\)

兩量子邏輯門

對於兩量子比特來說,他們的狀態是 \(\alpha_{00} | 00\rangle+\alpha_{01} | 01\rangle+\alpha_{10} | 10\rangle+\alpha_{11} | 11\rangle\) ,需要用 \(4*1\) 的向量來描述,也就是 $ \left[ \begin{array}{}{\alpha_{00}} \ {\alpha_{01}} \ {\alpha_{10}} \ {\alpha_{11}} \end{array} \right]$ ,對應操作兩比特的邏輯門,也就是 \(4*4\) 的矩陣了。

兩比特的量子門有各自管各自的,如圖(c),也有一個控制另一個的,如圖(d)。

對於圖c來說, \(U=u_1\otimes u_2\) ,如果 \(u_1=\left[ \begin{array}{}{a} &{c} \\ {b}&{d} \end{array}\right],u_2=\left[ \begin{array}{}{e} &{g} \\ {f}&{h} \end{array}\right]\) ,那麼, \(U=\left[ \begin{array}{}{a\left[ \begin{array}{}{e} &{g} \\ {f}&{h} \end{array}\right]} &{c\left[ \begin{array}{}{e} &{g} \\ {f}&{h} \end{array}\right]} \\ {b\left[ \begin{array}{}{e} &{g} \\ {f}&{h} \end{array}\right]}&{d\left[ \begin{array}{}{e} &{g} \\ {f}&{h} \end{array}\right]} \end{array}\right]\) ,這也就是張量積的算法。

對於圖d來說,這是一個受控非門CNOT門,他的意思是,如果a是0,那麼b保持不變,如果a是1,那麼b就是變成相反的,比如 \(|0\rangle\) 變成 \(|1\rangle\) ,或者把 \(|1\rangle\) 變成 \(|0\rangle\)

\(|00\rangle to|00\rangle,|01\rangle to|01\rangle,|10\rangle to|11\rangle,|11\rangle to|10\rangle\)

用矩陣來描述就是 \(\left[\begin{array}{cccc}{1} & {0} & {0} & {0} \\ {0} & {1} & {0} & {0} \\ {0} & {0} & {0} & {1} \\ {0} & {0} & {1} & {0}\end{array}\right]\)

至此,主要的量子邏輯門就介紹完畢,如果想要動手實踐的話,有阿里的量子計算雲平台、華為的hiQ、IMB的IBM Q

參考資料

Quantume Mechanics & Quantume Computation Lecture 5

【精選推薦文章】

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

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

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

Windows性能計數器監控實踐

Windows性能計數器(Performance Counter)是Windows提供的一種系統功能,它能實時採集、分析系統內的應用程序、服務、驅動程序等的性能數據,以此來分析系統的瓶頸、監控組件的表現,最終幫助用戶對系統進行合理調優。市面上採集Windows性能計數器指標的產品參差不齊,尤其在處理某類應用程序有多個進程實例時,採集的數據更是差強人意。所幸微軟為碼農精心準備了獲得性能計數器指標的接口,用於靈活獲得相關性能計數器指標值,但進程級別Windows性能計數器指標的採集監控,並沒有想象的那麼美好。因此本文結合筆者應用實踐,探討進程級別Windows性能計數器指標統一採集監控方案,以及在應用實踐中遇到的坑,作為避坑指南,供感興趣的同行參考。

進程級別Windows性能計數器指標作為特來電監控平台的一部分,對深入掌握系統進程級別運行狀態,定位系統存在的問題,以便更快、更準的發現潛在的線上問題,起到了舉足輕重的作用。

針對Windows性能計數器的監控,統一的採集監控方案如下所示:

 

 性能計數器指標統一採集監控方案

本文重點關注指標管理與指標採集,對指標存儲及指標展現只做概要闡述。

一、        指標管理

Windows性能計數器指標類別比較多,因此我們需要對關注的指標進行分類管理。針對進程級別監控,我們主要關注CLR以及進程相關類別指標:.NET CLR Memory、.NET CLR Exception、.NET CLR Jit、.NET CLR Loading、Process等。

一個Windows性能計數器主要由3個屬性來標識:指標類別(Category Name)、指標名稱(Counter Name)、指標實例(Instance Name)。為了能對某類應用程序的多個進程實例進行統一採集,我們不對指標實例進行管理,而對指標實例對應的進程名稱進行管理,同時支持一個性能計數器指標關聯多個進程名稱,並且在運行時動態計算出每個進程名稱對應的多個進程實例,從而大幅降低指標管理的工作量。

二、        指標採集

指標採集主要解決採集插件運行時的空間(採集範圍)與時間(採集頻率)問題。並不是所有機器都部署了我們關注的應用程序,因此需要通過採集範圍,確定需要對哪些機器上的性能計數器指標進行採集,同時需要確定採集頻率,比如10秒、1分鐘、5分鐘等。

雖然微軟提供了性能計數器接口用於採集對應的指標值,但當一個應用程序有多個進程實例時(比如一個機器上部署了多個IIS站點,進程名稱都是w3wp,在性能計數器中的實例名稱是w3wp、w3wp#1、…、w3wp#n),進行指標採集的坑會比較多,這裏介紹幾個比較典型的問題。

由於性能計數器默認不显示進程ID,所以無法直接建立進程實例和性能計數器指標實例的關聯關係,相同的性能計數器指標實例名稱,可能屬於一個或多個不同的進程實例。

 

 進程實例與性能計數器實例關聯關係

比如在.NET CLR Memory和Process中實例名稱同為w3wp#1的性能計數器,可能對應同一個進程實例,也可能對應不同的進程實例,這是最詭異的坑!市面上一些監控產品無法準確採集同一應用程序對應多個進程實例的性能計數器指標值,可能與此有關。

為了能建立進程實例與性能計數器實例的關聯關係,需要在显示性能計數器實例時帶上進程ID。

方案一:修改註冊表。但潛在的坑也很明顯:只適用於.NET CLR Memory以及Process類別的性能計數器,同時可能會導致第三方監控工具失效,並且修改生產環境的註冊表風險不可控,不是首選方案。

方案二:動態設置環境變量。針對.NET CLR相關的性能計數器,在調用性能計數器接口之前,進行如下環境變量設置:

Environment.SetEnvironmentVariable(“COMPlus_ProcessNameFormat”, “1”);

該方案是進程級別的,設置后得到的性能計數器實例會自動帶上進程ID,並且不會影響到全局設置或者其它應用程序,是推薦方案。

採集進程級別指標時,有時需要根據IIS站點進程ID獲得對應的應用程序池以及物理路徑:

 

 通過進程ID獲得應用程序池以及物理路徑

方案一:調用WMI(Windows Management Instrumentation)接口獲得應用程序池。

Select * from Win32_Process WHERE processID=PID

該方案存在的坑:頻繁調用會導致機器CPU飆升,不是首選方案。

方案二:調用Appcmd.exe命令獲得應用程序池。

appcmd.exe list wp

該方案通過命令獲得結果后,只需要進行字符串解析,即可獲得進程ID與應用程序池的關聯關係,是推薦方案。

三、        指標存儲

指標存儲在時序數據庫中,每個性能計數器類別(Category Name)+性能計數器名稱(Counter Name)對應一個指標表,表中按進程名稱進行分類,每一行表示一個進程實例對應性能計數器實例的指標值。

四、        指標展現

指標展現可以按進程名稱、進程實例、機器等維度進行分類聚合展現,相比登錄到每個機器設置性能計數器,指標集中展現大幅提升了工作效率。

五、        總結

本文探討了Windows性能計數器監控實踐,主要涉及指標管理、指標採集、指標存儲、指標展現四個方面,同時介紹了同一應用程序對應多個進程實例時,指標採集中遇到的坑。

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

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

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

[NewLife.XCode]角色權限

NewLife.XCode是一個有10多年歷史的開源數據中間件,支持nfx/netcore,由新生命團隊(2002~2019)開發完成並維護至今,以下簡稱XCode。

整個系列教程會大量結合示例代碼和運行日誌來進行深入分析,蘊含多年開發經驗於其中,代表作有百億級大數據實時計算項目。

開源地址:https://github.com/NewLifeX/X (求star, 864+)

 

前面講解了XCode的各種用法,這一章我們來講講內置的Membership,同時也是XCode的第一標準示例!

 

設計背景

現代管理信息系統絕大部分採用BS架構,無一例外需要用戶角色權限的支持!

結合團隊諸多兄弟姐妹的經驗,設計了一個大小適中的用戶權限系統Membership,目標是滿足80%的使用場景,並具備一定的擴展性。

 

Membership剛開始就採用了角色授權體系,每個用戶只有一種角色,角色擁有菜單資源權限集。

隨着Membership實用性日益增加,2015年初正式合併進入XCode,作為一個模塊存在。

 

2016年第二代魔方NewLife.Cube採用ASP.Net MVC5重構,讓Membership的榮譽達到了鼎峰!

在MVC中,每個Controller就是一個菜單資源,其下的Search/Detail/Insert/Update/Delete等Action作為角色在該菜單資源下的權限子項,保存在角色屬性數據中。

 

2018年為了增強魔方功能,在某些場景下支持單用戶多角色,且兼容已有系統,用戶表增加RoleIDs字段,保存擴展角色,原來的RoleID作為主角色。

 

管理提供者

管理提供者接口 IManageProvider ,提供了Membership基本操作實現。

  1. 當前登錄用戶 GetCurrent、SetCurrent,靜態訪問 ManageProvider.User
  2. 查找用戶 FindByID、FindByName
  3. 註冊登錄註銷 Register、Login、Logout
  4. 當前用戶主機(訪問者IP)ManageProvider.UserHost
  5. IManageProvider 默認由XCode.Membership中的UserX/Role/Menu支持,如若用戶使用自己的用戶權限表,可重新實現該接口

 

用戶權限

用戶 UserX

用戶數據模型:

  <Table Name="User" Description="用戶" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名稱。登錄用戶名" />
      <Column Name="Password" DataType="String" Description="密碼" />
      <Column Name="DisplayName" DataType="String" Description="昵稱" />
      <Column Name="Sex" DataType="Int32" Description="性別。未知、男、女" Type="SexKinds" />
      <Column Name="Mail" DataType="String" Description="郵件" />
      <Column Name="Mobile" DataType="String" Description="手機" />
      <Column Name="Code" DataType="String" Description="代碼。身份證、員工編號等" />
      <Column Name="Avatar" DataType="String" Length="200" Description="頭像" />
      <Column Name="RoleID" DataType="Int32" Description="角色。主要角色" />
      <Column Name="RoleIDs" DataType="String" Length="200" Description="角色組。次要角色集合" />
      <Column Name="DepartmentID" DataType="Int32" Description="部門。組織機構" />
      <Column Name="Online" DataType="Boolean" Description="在線" />
      <Column Name="Enable" DataType="Boolean" Description="啟用" />
      <Column Name="Logins" DataType="Int32" Description="登錄次數" />
      <Column Name="LastLogin" DataType="DateTime" Description="最後登錄" />
      <Column Name="LastLoginIP" DataType="String" Description="最後登錄IP" />
      <Column Name="RegisterTime" DataType="DateTime" Description="註冊時間" />
      <Column Name="RegisterIP" DataType="String" Description="註冊IP" />
      <Column Name="Ex1" DataType="Int32" Description="擴展1" />
      <Column Name="Ex2" DataType="Int32" Description="擴展2" />
      <Column Name="Ex3" DataType="Double" Description="擴展3" />
      <Column Name="Ex4" DataType="String" Description="擴展4" />
      <Column Name="Ex5" DataType="String" Description="擴展5" />
      <Column Name="Ex6" DataType="String" Description="擴展6" />
      <Column Name="UpdateUser" DataType="String" Description="更新用戶" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新用戶" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
      <Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新時間" />
      <Column Name="Remark" DataType="String" Length="200" Description="備註" />
    </Columns>
    <Indexes>
      <Index Columns="Name" Unique="True" />
      <Index Columns="RoleID" />
      <Index Columns="UpdateTime" />
    </Indexes>
  </Table>

常用字段有ID、用戶名和密碼,登錄註冊相關信息;

角色RoleID、RoleIDs用於實現權限集控制;

部分場景需要郵箱Mail、手機Mobile或者工號Code登錄;

如果仍然不能滿足要求,可以考慮使用Ex1~Ex6等擴展字段。

 

常用功能點:

  1. 初始化時,如果數據表為空,自動插入admin/admin用戶賬號,角色是“管理員”
  2. 支持註冊登錄,使用MD5保存密碼
  3. 支持編號查詢FindByID和名稱查詢FindByName,分別採用了對象緩存和對象從鍵,輕鬆實現百萬級賬號快速查詢
  4. 支持IIdentity接口

 

角色 Role

角色數據模型:

  <Table Name="Role" Description="角色" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名稱" />
      <Column Name="Enable" DataType="Boolean" Description="啟用" />
      <Column Name="IsSystem" DataType="Boolean" Description="系統。用於業務系統開發使用,不受數據權限約束,禁止修改名稱或刪除" />
      <Column Name="Permission" DataType="String" Length="500" Description="權限。對不同資源的權限,逗號分隔,每個資源的權限子項豎線分隔" />
      <Column Name="Ex1" DataType="Int32" Description="擴展1" />
      <Column Name="Ex2" DataType="Int32" Description="擴展2" />
      <Column Name="Ex3" DataType="Double" Description="擴展3" />
      <Column Name="Ex4" DataType="String" Description="擴展4" />
      <Column Name="Ex5" DataType="String" Description="擴展5" />
      <Column Name="Ex6" DataType="String" Description="擴展6" />
      <Column Name="CreateUser" DataType="String" Description="創建用戶" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建用戶" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="創建時間" />
      <Column Name="UpdateUser" DataType="String" Description="更新用戶" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新用戶" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
      <Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新時間" />
      <Column Name="Remark" DataType="String" Length="200" Description="備註" />
    </Columns>
    <Indexes>
      <Index Columns="Name" Unique="True" />
    </Indexes>
  </Table>

角色表比較簡單主要是名稱和啟用,以及保存菜單權限數據的Permission

角色支持的操作權限:

    /// <summary>操作權限</summary>
    [Flags]
    [Description("操作權限")]
    public enum PermissionFlags
    {
        /// <summary>無權限</summary>
        [Description("無權限")]
        None = 0,

        /// <summary>查看權限</summary>
        [Description("查看")]
        Detail = 1,

        /// <summary>添加權限</summary>
        [Description("添加")]
        Insert = 2,

        /// <summary>修改權限</summary>
        [Description("修改")]
        Update = 4,

        /// <summary>刪除權限</summary>
        [Description("刪除")]
        Delete = 8,

        /// <summary>所有權限</summary>
        [Description("所有")]
        All = 0xFF,
    }

主要功能點:

  1. 數據表為空時初始化4個基本角色:管理員、高級用戶、普通用戶、遊客
  2. 啟動時角色權限校驗,清理角色中無效的權限項(可能菜單已刪除),以及授權管理員訪問所有角色都無權訪問的新菜單
  3. 支持編號查詢FindByID和名稱查詢FindByID,採用實體緩存,目標系統不會超過1000個角色
  4. 支持權限判斷與設置 Has/Get/Set/Reset 等
  5. 重載實體類 Delete/Save/Update/OnLoad/OnPropertyChanged,加載實體對象時展開權限,保存時合併

 

菜單 Menu

菜單數據模型:

  <Table Name="Menu" Description="菜單" BaseType="EntityTree" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名稱" />
      <Column Name="DisplayName" DataType="String" Description="显示名" />
      <Column Name="FullName" DataType="String" Length="200" Description="全名" />
      <Column Name="ParentID" DataType="Int32" Description="父編號" />
      <Column Name="Url" DataType="String" Length="200" Description="鏈接" />
      <Column Name="Sort" DataType="Int32" Description="排序" />
      <Column Name="Icon" DataType="String" Description="圖標" />
      <Column Name="Visible" DataType="Boolean" Description="可見" />
      <Column Name="Necessary" DataType="Boolean" Description="必要。必要的菜單,必須至少有角色擁有這些權限,如果沒有則自動授權給系統角色" />
      <Column Name="Permission" DataType="String" Length="200" Description="權限子項。逗號分隔,每個權限子項名值豎線分隔" />
      <Column Name="Ex1" DataType="Int32" Description="擴展1" />
      <Column Name="Ex2" DataType="Int32" Description="擴展2" />
      <Column Name="Ex3" DataType="Double" Description="擴展3" />
      <Column Name="Ex4" DataType="String" Description="擴展4" />
      <Column Name="Ex5" DataType="String" Description="擴展5" />
      <Column Name="Ex6" DataType="String" Description="擴展6" />
      <Column Name="CreateUser" DataType="String" Description="創建用戶" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建用戶" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="創建時間" />
      <Column Name="UpdateUser" DataType="String" Description="更新用戶" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新用戶" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
      <Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新時間" />
      <Column Name="Remark" DataType="String" Length="200" Description="備註" />
    </Columns>
    <Indexes>
      <Index Columns="Name" />
      <Index Columns="ParentID,Name" Unique="True" />
    </Indexes>
  </Table>

菜單實體類採用樹形實體基類 EntityTree ,通過 ParentID 實現上下級關聯,同級 ParentID+Name 唯一

 

主要功能點:

  1. 支持自動掃描Controller作為菜單,因此魔方只需要增加Controller,即可在菜單表看到新頁面
  2. 實體樹適用於1000行以內樹形數據表,一次性加載數據到內存,在內存中根據ParentID構造實體對象樹,最常用樹形是Parent/Childs

 

日誌統計

日誌 Log

數據模型:

  <Table Name="Log" Description="日誌" ConnName="Log" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Category" DataType="String" Description="類別" />
      <Column Name="Action" DataType="String" Description="操作" />
      <Column Name="LinkID" DataType="Int32" Description="鏈接" />
      <Column Name="UserName" DataType="String" Description="用戶名" />
      <Column Name="Ex1" DataType="Int32" Description="擴展1" />
      <Column Name="Ex2" DataType="Int32" Description="擴展2" />
      <Column Name="Ex3" DataType="Double" Description="擴展3" />
      <Column Name="Ex4" DataType="String" Description="擴展4" />
      <Column Name="Ex5" DataType="String" Description="擴展5" />
      <Column Name="Ex6" DataType="String" Description="擴展6" />
      <Column Name="CreateUser" DataType="String" Description="創建用戶" />
      <Column Name="CreateUserID" DataType="Int32" Description="用戶編號" />
      <Column Name="CreateIP" DataType="String" Description="IP地址" />
      <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="時間" />
      <Column Name="Remark" DataType="String" Length="500" Description="詳細信息" />
    </Columns>
    <Indexes>
      <Index Columns="Category" />
      <Index Columns="CreateUserID" />
      <Index Columns="CreateTime" />
    </Indexes>
  </Table>

日誌表記錄分類、操作和日誌內容。

主要功能點:

  1. 日誌提供者LogProvider,提供了唯一核心方法 WriteLog,默認實現就是寫該日誌表。可從對象容器取得日誌提供者 ObjectContainer.Resolve<LogProvider>()
  2. 從IManageProvider接口獲取當前登錄用戶以及遠程訪問IP寫入日誌相應字段

 

在線 UserOnline

數據模型:

  <Table Name="UserOnline" Description="用戶在線" ConnName="Log">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="UserID" DataType="Int32" Description="用戶" />
      <Column Name="Name" DataType="String" Master="True" Description="名稱" />
      <Column Name="SessionID" DataType="String" Description="會話。Web的SessionID或Server的會話編號" />
      <Column Name="Times" DataType="Int32" Description="次數" />
      <Column Name="Page" DataType="String" Description="頁面" />
      <Column Name="Status" DataType="String" Length="200" Description="狀態" />
      <Column Name="OnlineTime" DataType="Int32" Description="在線時間。本次在線總時間,秒" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="創建時間" />
      <Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="修改時間" />
    </Columns>
    <Indexes>
      <Index Columns="UserID" />
      <Index Columns="SessionID" />
      <Index Columns="CreateTime" />
    </Indexes>
  </Table>

藉助用戶行為模塊 UserBehaviorModule , 維護用戶在線記錄,持久化在 UserOnline 表

 

訪問統計 VisitStat

  <Table Name="VisitStat" Description="訪問統計" ConnName="Log">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Level" DataType="Int32" Description="層級" Type="XCode.Statistics.StatLevels" />
      <Column Name="Time" DataType="DateTime" Description="時間" />
      <Column Name="Page" DataType="String" Nullable="False" Description="頁面" />
      <Column Name="Title" DataType="String" Master="True" Description="標題" />
      <Column Name="Times" DataType="Int32" Description="次數" />
      <Column Name="Users" DataType="Int32" Description="用戶" />
      <Column Name="IPs" DataType="Int32" Description="IP" />
      <Column Name="Error" DataType="Int32" Description="錯誤" />
      <Column Name="Cost" DataType="Int32" Description="耗時。毫秒" />
      <Column Name="MaxCost" DataType="Int32" Description="最大耗時。毫秒" />
      <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="創建時間" />
      <Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新時間" />
      <Column Name="Remark" DataType="String" Length="5000" Description="詳細信息" />
    </Columns>
    <Indexes>
      <Index Columns="Page,Level,Time" Unique="True" />
      <Index Columns="Level,Time" />
    </Indexes>
  </Table>

藉助用戶行為模塊 UserBehaviorModule , 維護用戶訪問記錄,寫入日誌表,並寫入訪問統計表。

主要功能要點:

  1. 記錄頁面訪問統計,簡單支持IP數和用戶數
  2. 支持年月日三級統計,作為XCode日期統計表的標準示例

 

其它

部門 Department

數據模型:

  <Table Name="Department" Description="部門。組織機構,多級樹狀結構" BaseType="EntityTree" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Code" DataType="String" Description="代碼" />
      <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名稱" />
      <Column Name="FullName" DataType="String" Length="200" Description="全名" />
      <Column Name="ParentID" DataType="Int32" Description="父級" />
      <Column Name="Level" DataType="Int32" Description="層級。樹狀結構的層級" />
      <Column Name="Sort" DataType="Int32" Description="排序。同級內排序" />
      <Column Name="Enable" DataType="Boolean" Description="啟用" />
      <Column Name="Visible" DataType="Boolean" Description="可見" />
      <Column Name="Ex1" DataType="Int32" Description="擴展1" />
      <Column Name="Ex2" DataType="Int32" Description="擴展2" />
      <Column Name="Ex3" DataType="Double" Description="擴展3" />
      <Column Name="Ex4" DataType="String" Description="擴展4" />
      <Column Name="Ex5" DataType="String" Description="擴展5" />
      <Column Name="Ex6" DataType="String" Description="擴展6" />
      <Column Name="CreateUser" DataType="String" Description="創建用戶" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建用戶" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="創建時間" />
      <Column Name="UpdateUser" DataType="String" Description="更新用戶" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新用戶" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
      <Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新時間" />
      <Column Name="Remark" DataType="String" Length="200" Description="備註" />
    </Columns>
    <Indexes>
      <Index Columns="Name" />
      <Index Columns="ParentID,Name" Unique="True" />
      <Index Columns="Code" />
      <Index Columns="UpdateTime" />
    </Indexes>
  </Table>

 

 

字典參數 Parameter

數據模型:

  <Table Name="Parameter" Description="字典參數">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="編號" />
      <Column Name="Category" DataType="String" Description="類別" />
      <Column Name="Name" DataType="String" Master="True" Description="名稱" />
      <Column Name="Value" DataType="String" Length="200" Description="數值" />
      <Column Name="LongValue" DataType="String" Length="2000" Description="長數值" />
      <Column Name="Kind" DataType="Int32" Description="種類。0普通,21列表,22名值" Type="XCode.Membership.ParameterKinds" />
      <Column Name="Enable" DataType="Boolean" Description="啟用" />
      <Column Name="Ex1" DataType="Int32" Description="擴展1" />
      <Column Name="Ex2" DataType="Int32" Description="擴展2" />
      <Column Name="Ex3" DataType="Double" Description="擴展3" />
      <Column Name="Ex4" DataType="String" Description="擴展4" />
      <Column Name="Ex5" DataType="String" Description="擴展5" />
      <Column Name="Ex6" DataType="String" Description="擴展6" />
      <Column Name="CreateUser" DataType="String" Description="創建用戶" />
      <Column Name="CreateUserID" DataType="Int32" Description="創建用戶" />
      <Column Name="CreateIP" DataType="String" Description="創建地址" />
      <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="創建時間" />
      <Column Name="UpdateUser" DataType="String" Description="更新用戶" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新用戶" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
      <Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新時間" />
      <Column Name="Remark" DataType="String" Length="200" Description="備註" />
    </Columns>
    <Indexes>
      <Index Columns="Category,Name" Unique="True" />
      <Index Columns="Name" />
      <Index Columns="UpdateTime" />
    </Indexes>
  </Table>

 

 

系列教程

NewLife.XCode教程系列[2019版]

  1. 增刪改查入門。快速展現用法,代碼配置連接字符串
  2. 數據模型文件。建立表格字段和索引,名字以及數據類型規範,推薦字段(時間,用戶,IP)
  3. 實體類詳解。數據類業務類,泛型基類,接口
  4. 功能設置。連接字符串,調試開關,SQL日誌,慢日誌,參數化,執行超時。代碼與配置文件設置,連接字符串局部設置
  5. 反向工程。自動建立數據庫數據表
  6. 數據初始化。InitData寫入初始化數據
  7. 高級增刪改。重載攔截,自增字段,Valid驗證,實體模型(時間,用戶,IP)
  8. 臟數據。如何產生,怎麼利用
  9. 增量累加。高併發統計
  10. 事務處理。單表和多表,不同連接,多種寫法
  11. 擴展屬性。多表關聯,Map映射
  12. 高級查詢。複雜條件,分頁,自定義擴展FieldItem,查總記錄數,查匯總統計
  13. 數據層緩存。Sql緩存,更新機制
  14. 實體緩存。全表整理緩存,更新機制
  15. 對象緩存。字典緩存,適用用戶等數據較多場景。
  16. 百億級性能。字段精鍊,索引完備,合理查詢,充分利用緩存
  17. 實體工廠。元數據,通用處理程序
  18. 角色權限。Membership
  19. 導入導出。Xml,Json,二進制,網絡或文件
  20. 分表分庫。常見拆分邏輯
  21. 高級統計。聚合統計,分組統計
  22. 批量寫入。批量插入,批量Upsert,異步保存
  23. 實體隊列。寫入級緩存,提升性能。
  24. 備份同步。備份數據,恢複數據,同步數據
  25. 數據服務。提供RPC接口服務,遠程執行查詢,例如SQLite網絡版
  26. 大數據分析。ETL抽取,調度計算處理,結果持久化

【精選推薦文章】

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

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

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"