[WEB API]關於快取那件事,使用Strathweb.CacheOutput

[WEB API]關於快取那件事,使用Strathweb.CacheOutput

前言

其實要提升網站回應速度,有很多的環節,包括了db的選擇和硬體的配置,加上程式碼的寫法 etc...,但快取機制是非常重要的,此篇是針對只有一台ap的快取做法,如果有需要使用到load blanace的時候,就得使用其他做法了,或許redis會是您的好朋友,但是如果我們快取的機制並不到非常複雜,我們只希望程式碼簡單點,這種方式就非常的簡單和快速,這邊想要簡單記錄一下如何快速的實做web api的cache機制。

安裝Strathweb.CacheOutput

首先一樣使用nuget安裝吧,有興趣的人也可以直接去github看(https://github.com/filipw/Strathweb.CacheOutput)

開始使用web api cache

這裡使用之前oracle的例子來示例,程式碼如下

    public class OracleController : ApiController
    {
        public IDbConnection GetConnection //回傳OracleConnection
        {
            get
            {
                string connString = "Data source=localhost/book;User id=C##ANSON;Password=7154;";
                return new OracleConnection(connString);
            }
        }

        public async Task<IHttpActionResult> Get() //取得資料
        {
            using (var con = GetConnection)
            {
                var dao = con.QueryAsync<EmployeeDto>("SELECT id,name,address,phone_number as phoneNumber FROM  EMPLOYEE");
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(new { Employee = await dao, Company = await companyResult });
            }
        }

        public async Task<IHttpActionResult> Get(int Id)
        {
            using (var con = GetConnection)
            {
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(await companyResult);
            }
        }
    }

然後先測試一下swagger的部份,預設的狀況下,應該都可以看到是回傳200的狀態,不管我們重覆呼叫幾次

web api cache有分client cache和server cache,差異在於如果是client的話,想當然的就是儲存在使用者的電腦啦,這樣的好處是使用者在發送request的時候,不會再訪問到web api,當然就減輕了iis的壓力

        [CacheOutput(ClientTimeSpan =5)]//這邊指定的是秒數
        public async Task<IHttpActionResult> Get() //取得資料
        {
            using (var con = GetConnection)
            {
                var dao = con.QueryAsync<EmployeeDto>("SELECT id,name,address,phone_number as phoneNumber FROM  EMPLOYEE");
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(new { Employee = await dao, Company = await companyResult });
            }
        }

 

 從圖示可以明顯看到,除了第一個請求之外,其餘都是(from disk cache),但是client cache有幾個缺點,第一個是如果使用者把瀏覽器快取給強制取消掉,這招就行不通了,所以其實我們也可以使用server cache

        [CacheOutput(ServerTimeSpan = 5)] //這邊指定的是秒數
        public async Task<IHttpActionResult> Get() //取得資料
        {
            using (var con = GetConnection)
            {
                var dao = con.QueryAsync<EmployeeDto>("SELECT id,name,address,phone_number as phoneNumber FROM  EMPLOYEE");
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(new { Employee = await dao, Company = await companyResult });
            }
        }

從圖示可看到,除了第一個請求回應200之外,其餘都是304代表快取的意思,但是如果使用server cache的話,雖然能強迫快取,不會訪問到db,但是使用者訪問一樣會造成iis的負擔,所以實務上我們會選擇兩種方式都快取,這樣子首先快取的會是使用者的電腦,如果使用者把瀏覽器的快取清掉了,或者關閉預設快取,至少也只會訪問到iis而不會直接到db。

        [CacheOutput(ServerTimeSpan = 5,ClientTimeSpan =5)] //這邊指定的是秒數
        public async Task<IHttpActionResult> Get() //取得資料
        {
            using (var con = GetConnection)
            {
                var dao = con.QueryAsync<EmployeeDto>("SELECT id,name,address,phone_number as phoneNumber FROM  EMPLOYEE");
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(new { Employee = await dao, Company = await companyResult });
            }
        }

但是其實他提供了更多的方式

        //下面快取請擇其一,會全部列出來只是為了方便
        [CacheOutput(ServerTimeSpan =5,ClientTimeSpan =5)]
        [CacheOutputUntil(2017, 05, 25, 17, 00)] //快取直到2017/5/25 17:00
        [CacheOutputUntilToday(23, 00)]//直到今天23點
        [CacheOutputUntilThisMonth(28)]
        [CacheOutputUntilThisYear(7, 31)]
        public async Task<IHttpActionResult> Get() //取得資料
        {
            using (var con = GetConnection)
            {
                var dao = con.QueryAsync<EmployeeDto>("SELECT id,name,address,phone_number as phoneNumber FROM  EMPLOYEE");
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(new { Employee = await dao, Company = await companyResult });
            }
        }

當然我們也可以使用整個web api層級的快取,而不是只針對一個action

    [CacheOutput(ServerTimeSpan = 5, ClientTimeSpan = 5)]
    public class OracleController : ApiController
    {
        public IDbConnection GetConnection //回傳OracleConnection
        {
            get
            {
                string connString = "Data source=localhost/book;User id=C##ANSON;Password=7154;";
                return new OracleConnection(connString);
            }
        }
        
        public async Task<IHttpActionResult> Get() //取得資料
        {
            using (var con = GetConnection)
            {
                var dao = con.QueryAsync<EmployeeDto>("SELECT id,name,address,phone_number as phoneNumber FROM  EMPLOYEE");
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(new { Employee = await dao, Company = await companyResult });
            }
        }

        public async Task<IHttpActionResult> Get(int Id)
        {
            using (var con = GetConnection)
            {
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(await companyResult);
            }
        }
    }

可以從圖中看到不管我是直接call Get或有傳入ID的Get都會快取,如果我要忽略有傳入ID的,只要在該action加上IgnoreCacheOutput

        [IgnoreCacheOutput]
        public async Task<IHttpActionResult> Get(int Id)
        {
            using (var con = GetConnection)
            {
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(await companyResult);
            }
        }
    }

接下來說明一下,其實web api cache會針對有參數的做快取,比如說我們取第一筆id的時候,他會針對傳入id為1的資料做快取,當我們傳2的時候會重取,然後針對2的部份也做一個快取。

        [CacheOutput(ServerTimeSpan = 50,ClientTimeSpan =50)]
        public async Task<IHttpActionResult> GetById(int Id)
        {
            using (var con = GetConnection)
            {
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(await companyResult);
            }
        }

然後此package還有提供了,如果我們觸發了某個action的時候,可以把預設的快取清掉,這種例子可以做在如果我們新增某筆資料的時候,我們希望取資料的時候,能即時的取到最新資料庫裡的資料,就可以這樣子做。

    public class OracleController : ApiController
    {
        public IDbConnection GetConnection //回傳OracleConnection
        {
            get
            {
                string connString = "Data source=localhost/book;User id=C##ANSON;Password=7154;";
                return new OracleConnection(connString);
            }
        }

        [CacheOutput(ServerTimeSpan = 50,ClientTimeSpan =50)]
        public async Task<IHttpActionResult> Get() //取得資料
        {
            using (var con = GetConnection)
            {
                var dao = con.QueryAsync<EmployeeDto>("SELECT id,name,address,phone_number as phoneNumber FROM  EMPLOYEE");
                var companyResult = con.QueryAsync<CompanyDto>("SELECT id,companyName FROM company");
                return Ok(new { Employee = await dao, Company = await companyResult });
            }
        }

        [InvalidateCacheOutput("Get")] //當觸發這個action時,會清除掉Get的快取
        public void Post(EmployeeDto employeeDto)
        {
            //add something
        }
    }

結論

其實這個pakcage提供了我們很多案例,所以如果我們沒有很複雜的快取條件或load blanace的狀況下,使用此package已經很夠用了,如果有興趣的讀者也可以去github看更詳細和進階的用法,如果有任何更好的建議,再請不吝告知。