If you have a good understanding of the microcomputer hardware system, we must be familiar with the term cache. This high-speed buffer memory (cache) technology is introduced in the CPU and motherboard chips. Because the cache access speed is faster than the memory, the introduction of cache can effectively solve the problem of mismatch between the CPU and memory speed. The hardware system can use the cache to store the data with high CPU access probability. When the CPU needs to access the data, it can directly read the data from the cache without having to access the memory with a relatively slow access speed, this improves the CPU efficiency. The Software Design draws on the cache mechanism introduced in the hardware design to improve the performance of the entire system. Especially for a database-driven web application, cache utilization is indispensable. After all, database query may be one of the most frequently called but slow operations on the entire web site. We cannot be slowed down by its old legs. The cache mechanism is the accelerator to solve this defect.
4.1 ASP. NET cache Overview
As the main product for developing Web applications under the. NET Framework, ASP. NET fully considers the cache mechanism. In some way, the Data Objects and web pages required by the system are stored in the memory, so that when the web site needs to obtain the data, without complex database connections, queries, and complex logic operations, you can easily and quickly get things at your fingertips, improving the performance of the entire web system.
ASP. NET provides two basic caching mechanisms to provide caching functions. One is the application cache, which allows developers to put the data or report business objects generated by the program into the cache. Another caching mechanism is the page output cache, which can be used to directly obtain pages stored in the cache without complicated re-processing of the page.
The implementation principle of application cache is not surprising. It only uses ASP. NET to manage the cache space in the memory. The Application Data Objects in the cache are stored as key/value pairs, which allows you to determine whether the data items in the cache exist based on the key value when accessing the data items in the cache.
The lifecycle of a data object in the cache is restricted. Even during the entire application lifecycle, this data object cannot be valid all the time. ASP. NET can manage application cache, such as removing data items when they are invalid, expired, or out of memory. In addition, the caller can use the cacheitemremovedcallback delegate to define a callback method so that the user can be notified when the data item is removed.
In. NET Framework, application caching is implemented through the system. Web. caching. cache class. It is a sealed class and cannot be inherited. For each application domain, a cache instance must be created, and its lifecycle must be consistent with that of the application domain. We can use the Add or insert method to add data items to the application cache, as shown below:
Cache ["first"] = "first item ";
Cache. insert ("second", "second item ");
We can also add dependencies to the application cache so that when the dependencies are changed, the data items can be removed from the cache:
String [] dependencies = {"Second "};
Cache. insert ("third", "Third item ",
New system. Web. caching. cachedependency (null, dependencies ));
This corresponds to the removal of data items in the cache. As mentioned above, ASP. NET can automatically manage the removal of items in the cache, but we can also explicitly remove related data items through code writing:
Cache. Remove ("first ");
Compared with the application cache, the page output cache is more widely used. It can process the processed ASP. when the client accesses the page again, it can save the page processing process, thus improving the page access performance and the Web server throughput. For example, in an e-commerce website, users often need to query product information. This process involves database access and matching of search conditions. When the data volume is large, such a search process is time-consuming. In this case, the page output cache can be used to store the query result page obtained by the first search in the cache. When you perform the second query, you can save the Data Query Process and reduce the page response time.
The page output cache is divided into the whole page cache and partial page cache. You can use the @ outputcache command to cache the output of the web page. It mainly includes two parameters: Duration and varybyparam. The duration parameter is used to set the cache time of a page or control, in seconds. The following settings indicate that the cache is valid within 60 seconds:
<% @ Outputcache duration = "60" varybyparam = "NONE" %>
As long as the duration value of the duration setting is not exceeded, users can directly obtain it in the cache when accessing the same page or control.
You can use the varybyparam parameter to create different caches Based on the set parameter values. For example, if you need to create a cache for a Textbox Control with the ID txtcity on the page that outputs the weather forecast results, the value of the cache will show the temperature of a city, then you can perform the following settings:
<% @ Outputcache duration = "60" varybyparam = "txtcity" %>
In this way, ASP. NET will judge the txtcity control value. Only when the input value is the same as the cache value will the corresponding value be retrieved from the cache. This effectively avoids data errors caused by different values.
The cache mechanism significantly improves performance. Through the Application Center Test test, we can find that the performance after cache is set is improved by more than three times than that when no cache is set.
The introduction of cache seems to be a perfect solution to improve performance. However, the cache mechanism also has disadvantages, that is, data expiration. Once the application data or page result value changes, the results you get within the cache validity period will be expired and inaccurate data. We can think about the disaster brought about by the stock system's use of cache. When you analyze the changing situation of the stock market with the data of incorrect expiration, you will find that the result is actually a "Failure, A thousand miles, "the seemingly good situation will be like a beautiful bubble, with a needle, the eyes will disappear without a trace.
So should we pursue high performance, regardless of the hidden dangers brought by the so-called "Data expiration? Apparently, in a specific scenario with frequent data updates like a stock system, the poor data expiration performance is even more unacceptable than inefficient Performance. Therefore, we need to make a balance between performance and data correctness. Fortunately,. NET Framework 2.0 introduces a new caching mechanism, which brings technical feasibility to our "fish and bear hands.
. The custom cache dependency introduced by NET 2.0, especially the sqlcachedependency Feature Based on the MS-SQL server, allows us to avoid the problem of "data expiration", it can be based on the corresponding data changes in the database, notification cache and remove expired data. In fact, in petshop 4.0, The sqlcachedependency feature is fully utilized.
4.2 sqlcachedependency
The sqlcachedependency feature is actually reflected by the system. Web. caching. sqlcachedependency class. This class allows you to monitor specific SQL Server database tables on all supported SQL Server versions (7.0,) and create cache items dependent on this table and data rows in the table. When the data of a data table or a specific row in a table is changed, the data items with dependencies will become invalid and the item will be automatically deleted from the cache to ensure that the expired data will not be retained in the cache.
Due to the version, SQL Server 2005 fully supports the sqlcachedependency feature, but it is not so lucky for SQL Server 7.0 and SQL Server 2000. After all, these products appear before. NET Framework 2.0, so it does not implement automatic monitoring of data changes in data tables and notifies ASP. NET. The solution is to use the polling mechanism to poll the SQL Server database at a specified interval through a thread in the ASP. NET process to track data changes.
To make SQL Server 7.0 or 2000 support the sqlcachedependency feature, you need to perform the relevant configuration steps on the database server. There are two ways to configure SQL SERVER: Use the aspnet_regsql command line tool or use the sqlcachedependencyadmin class.
4.2.1 use aspnet_regsql
The aspnet_regsql tool is located in the Windows/Microsoft. NET/framework/[version] folder. If you double-click the execution file of the tool, a wizard dialog box will pop up, prompting us to complete the corresponding operations:
Figure 4-1 aspnet_regsql Tool
The prompt information shown in 4-1 indicates that the Wizard is mainly used to configure the SQL Server database, such as membership and profiles. to configure sqlcachedependency, execute the command line. Take petshop 4.0 as an example. If the database name is mspetshop4, the command is:
Aspnet_regsql-s localhost-e-d mspetshop4-ed
The command parameters of the tool are described as follows:
-? Displays the help function of the tool;
-S is followed by the database server name or IP address;
-U is followed by the login username of the database;
-P is followed by the database login password;
-E this function is used for Windows integrated verification;
-D the following parameter indicates which database adopts the sqlcachedependency function;
-T followed by the sqlcachedependency function for which table the parameter is used;
-Ed allows sqlcachedependency for databases;
-Dd prohibits the use of sqlcachedependency for databases;
-Et allows sqlcachedependency for data tables;
-DT prohibits the use of sqlcachedependency for data tables;
-Lt lists the tables in the current database that have used the sqlcachedependency function.
The preceding command is used as an example to describe how to use the sqlcachedependency function for a database named mspetshop4, and the SQL server adopts the Windows integrated verification method. We can also execute the aspnet_regsql command on the relevant data table, such:
Aspnet_regsql-s localhost-e-d mspetshop4-T item-et
Aspnet_regsql-s localhost-e-d mspetshop4-t product-et
Aspnet_regsql-s localhost-e-d mspetshop4-T category-et
After executing the preceding four commands, the aspnet_regsql tool creates a new database table named aspnet_sqlcachetablesforchangenotification in the mspetshop4 database. The data table contains three fields. The tablename field records the name of the data table to be tracked. For example, in petshop 4.0, the data table to be logged includes category, item, and product. Icationicationcreated field records the start time of tracing. As an int field, changeid is used to record the number of times data changes in the data table. 4-2:
Figure 4-2 aspnet_sqlcachetablesforchangenotification data table
In addition, executing this command also adds a set of Stored Procedures for the mspetshop4 database, for ASP.. NET provides query and tracing of data tables. It also adds triggers for tables that use sqlcachedependency, which correspond to insert, update, delete, and other data change-related operations. For example, the trigger of the product data table:
Create trigger DBO. [product_aspnet_sqlcachenotification_trigger] on [product]
For insert, update, delete as begin
Set nocount on
Exec DBO. aspnet_sqlcacheupdatechangeidstoredprocedure n 'product'
End
Aspnet_sqlcacheupdatechangeidstoredprocedure is one of the stored procedures added by the tool. When you perform insert, update, or delete operations on the product data table, the trigger is activated and the aspnet_sqlcacheupdatechangeidstoredprocedure stored procedure is executed. The execution process is to modify the changeid field value of the aspnet_sqlcachetablesforchangenotification data table:
Create procedure DBO. aspnet_sqlcacheupdatechangeidstoredprocedure
@ Tablename nvarchar (450)
As
Begin
Update DBO. aspnet_sqlcachetablesforchangenotification with (rowlock) set changeid = changeid + 1
Where tablename = @ tablename
End
Go
4.2.2 use the sqlcachedependencyadmin class
We can also manage the use of sqlcachedependency features by programming. This class contains five important methods:
Disablenotifications
|
Notification of disabling sqlcachedependency object changes for a specific database
|
Disabletableforpolicications
|
Disable sqlcachedependency object change notification for a specific table in the database
|
Enablenotifications
|
Enable sqlcachedependency object change notification for a specific database
|
Enabletableforpolicications
|
Enable sqlcachedependency object change notification for a specific table in the database
|
Gettablesenabledforpolicications
|
Returns the list of all tables with sqlcachedependency enabled.
|
Table 4-1 main sqlcachedependencyadmin Methods
Suppose we have defined the following database connection string:
Const string connectionstr = "Server = localhost; database = mspetshop4 ″;
To enable the sqlcachedependency object change notification for the database mspetshop4, follow these steps:
Protected void page_load (Object sender, eventargs E)
{
If (! Ispostback)
{
Sqlcachedependencyadmin. enablenotifications (connectionstr );
}
}
Enable the sqlcachedependency object change notification for the data table product:
Sqlcachedependencyadmin. enabletablefornotifications (connectionstr, "product ");
If you want to call the related methods shown in Table 4-1, note that the account accessing the SQL Server database must have the permission to create tables and stored procedures. To call the enabletablefornotifications method, you must have the permission to create an SQL Server trigger on the table.
Although programming gives programmers more flexibility, the aspnet_regsql tool provides a simpler way to configure and manage sqlcachedependency. Petshop 4.0 adopts the aspnet_regsql tool, which writes a file named installdatabases. the batchcompute file of CMD contains the execution of the aspnet_regsql tool, and the file is called by the installer to configure SQL Server.
4.3 ASP. NET cache implementation in petshop 4.0
As a B2C pet online store, petshop needs to fully consider the visitor's user experience. If the response of the Web server is not timely due to a large amount of data, and the page and query data are delayed, this will damage the customer's mood to visit the website, and may lose this part of the customer after the patient wait. Undoubtedly, this is a very bad result. Therefore, when designing the system architecture, the performance of the entire system is very important. However, we cannot ignore data correctness because we focus on performance. In petshop 3.0 and earlier versions, this problem has not been well solved due to the limitations of ASP. NET cache. Petshop 4.0 introduces the sqlcachedependency feature, which greatly improves the system's processing of the cache.
4.3.1 cachedependency Interface
Petshop 4.0 introduces the sqlcachedependency feature and implements the SQL cache invalidation Technology for the cache corresponding to the category, product, and item data tables. When the corresponding data table changes, this technology can remove related items from the cache. The core of this technology is the sqlcachedependency class, which inherits the cachedependency class. However, to ensure the scalability of the entire architecture, we also allow designers to establish a custom cachedependency class to extend cache dependencies. Therefore, it is necessary to create an abstract interface for cachedependency and configure it in the web. config file.
In the namespace petshop. icachedependency of petshop 4.0, The ipetshopcachedependency interface is defined. It only contains one interface method:
Public interface ipetshopcachedependency
{
Aggregatecachedependency getdependency ();
}
Aggregatecachedependency is a new class added by. NET Framework 2.0. It monitors the collection of dependency objects. When any dependency object in this set changes, the cache object corresponding to the dependency object will be automatically removed.
The aggregatecachedependency class combines cachedependency objects. It can associate multiple cachedependency objects or even different types of cachedependency objects with cache items. Because petshop needs to create dependencies for the category, product, and item data tables, the ipetshopcachedependency interface method getdependency () is to return the aggregatecachedependency object with these dependencies established.
4.3.2 cachedependency implementation
The implementation of cachedependency is to create a corresponding sqlcachedependency type dependency for the category, product, and item data tables, as shown in the Code:
Public abstract class tabledependency: ipetshopcachedependency
{
// This is the separator that's used in Web. config
Protected char [] configurationseparator = new char [] {','};
Protected aggregatecachedependency dependency = new aggregatecachedependency ();
Protected tabledependency (string configkey)
{
String dbname = configurationmanager. etettings ["cachedatabasename"];
String tableconfig = configurationmanager. configurettings [configkey];
String [] tables = tableconfig. Split (configurationseparator );
Foreach (string tablename in tables)
Dependency. Add (New sqlcachedependency (dbname, tablename ));
}
Public aggregatecachedependency getdependency ()
{
Return dependency;
}
}
The databases and data tables for which dependencies need to be created are configured in the web. config file. The settings are as follows:
Depending on the dependencies between data tables, the dependencies required for different data tables are also different. The value in the configuration file can be seen. However, no matter how many dependencies are created, the created behavior logic is similar. Therefore, a common class tabledependency is abstracted during design and a constructor with parameters is created, establish the dependency. In the implementation of the interface method getdependency (), the returned object dependency is created in a protected constructor. Therefore, the implementation method here can be seen as the flexible use of the template method mode. For example, the child product of tabledependency builds the sqlcachedependency dependency of the product and category data tables by using the constructor of the parent class:
Public class product: tabledependency
{
Public Product (): Base ("producttabledependency "){}
}
If you need to customize cachedependency, you can create dependencies in different ways. However, whether you create a sqlcachedependency object or a custom cachedependency object, you can add these dependencies to the aggregatecachedependency class. Therefore, you can also create a special class for the custom cachedependency, you only need to implement the ipetshopcachedependency interface.
4.3.3 cachedependency Factory
The product, category, and item classes that inherit the abstract class tabledependency must create their own objects during the call. Because their parent class tabledependency implements the ipetshopcachedependency interface, they also indirectly implement the ipetshopcachedependency interface, which provides a prerequisite for implementing the factory mode.
In petshop 4.0, the configuration file and reflection technology are still used to implement the factory model. In the namespace petshop. cachedependencyfactory, dependencyaccess is the factory class for creating ipetshopcachedependency objects:
Public static class dependencyaccess
{
Public static ipetshopcachedependency createcategorydependency ()
{
Return loadinstance ("category ");
}
Public static ipetshopcachedependency createproductdependency ()
{
Return loadinstance ("product ");
}
Public static ipetshopcachedependency createitemdependency ()
{
Return loadinstance ("item ");
}
Private Static ipetshopcachedependency loadinstance (string classname)
{
String Path = configurationmanager. etettings ["cachedependencyassembly"];
String fullyqualifiedclass = path + "." + classname;
Return (ipetshopcachedependency) Assembly. Load (PATH). createinstance (fullyqualifiedclass );
}
}
The implementation of the entire factory model is 4-3:
Figure 4-3 cachedependency Factory
Although the dependencyaccess class creates categories, products, and items that implement the ipetshopcachedependency interface, the purpose of introducing the ipetshopcachedependency interface is to obtain the aggregatecachedependency type object of the dependency item. We can call the object's interface method getdependency (), as shown below:
Aggregatecachedependency dependency = dependencyaccess. createcategorydependency (). getdependency ();
To facilitate callers, it seems that we can improve the dependencyaccess class and change the original createcategorydependency () method to the method for creating an aggregatecachedependency object.
However, this method disrupts the dependencyaccess responsibilities of the factory class, and the behavior of creating an ipetshopcachedependency interface object may still be called by the caller. Therefore, it is necessary to retain the original dependencyaccess class.
In the design of petshop 4.0, the facade mode is introduced to facilitate callers to obtain aggregatecachedependency type objects more easily.
4.3.4 introduce the facade Mode
The facade mode can be used to encapsulate some complex logics to facilitate callers to call these complex logics. It seems that a uniform facade is provided, which encapsulates internal subsystems into a high-level interface. A typical facade mode is as follows:
Figure 4-4 facade Mode
The purpose of the facade mode is not to introduce a new function, but to provide a higher level of abstraction based on the existing function, so that the caller can directly call the function without worrying about the internal implementation method. Taking the cachedependency factory as an example, we need to provide the caller with a simple method to obtain the aggregatecachedependency object, so we have created the dependencyfacade class:
Public static class dependencyfacade
{
Private Static readonly string Path = configurationmanager. etettings ["cachedependencyassembly"];
Public static aggregatecachedependency getcategorydependency ()
{
If (! String. isnullorempty (PATH ))
Return dependencyaccess. createcategorydependency (). getdependency ();
Else
Return NULL;
}
Public static aggregatecachedependency getproductdependency ()
{
If (! String. isnullorempty (PATH ))
Return dependencyaccess. createproductdependency (). getdependency ();
Else
Return NULL;
}
Public static aggregatecachedependency getitemdependency ()
{
If (! String. isnullorempty (PATH ))
Return dependencyaccess. createitemdependency (). getdependency ();
Else
Return NULL;
}
}
The dependencyfacade class encapsulates the logic for obtaining the aggregatecachedependency type object. As a result, the caller can call relevant methods to obtain the aggregatecachedependency type object for creating the relevant dependency:
Aggregatecachedependency dependency = dependencyfacade. getcategorydependency ();
Compared to calling the getdependency () method of the dependencyaccess class directly, in addition to the simpler method, it also judges the cachedependencyassembly configuration section. If its value is null, a null object is returned.
In the app_code folder of petshop. Web, the dependencyfacade class is called by the getcategoryname () and getproductname () Methods of the static webutility class. For example, getcategoryname () method:
Public static string getcategoryname (string categoryid)
{
CATEGORY Category = new category ();
If (! Enablecaching)
Return category. getcategory (categoryid). Name;
String cachekey = string. Format (category_name_key, categoryid );
// Check whether the data item exists in the cache;
String data = (string) httpruntime. cache [cachekey];
If (Data = NULL)
{
// Obtain the duration value through the Web. config configuration;
Int cacheduration = int. parse (configurationmanager. receivettings ["categorycacheduration"]);
// If this data item does not exist in the cache, access the database through the business logic layer;
Data = category. getcategory (categoryid). Name;
// Create an aggregatecachedependency object through the facade class;
Aggregatecachedependency Cd = dependencyfacade. getcategorydependency ();
// Store data items and aggregatecachedependency objects in the cache;
Httpruntime. cache. Add (cachekey, Data, CD, datetime. Now. addhours (cacheduration), cache. nosdomainingexpiration, cacheitempriority. High, null );
}
Return data;
}
The getcategoryname () method first checks whether the categoryname data item already exists in the cache. If yes, the data is directly obtained through the cache; otherwise, the business logic layer calls the data access layer to access the database to obtain the categoryname. After obtaining the categoryname, the newly acquired data along with the aggregatecachedependency object created by the dependencyfacade class will be added to the cache.
The webutility static class is called by many pages of the presentation layer, such as the product page:
Public partial class products: system. Web. UI. Page
{
Protected void page_load (Object sender, eventargs E)
{
Page. Title = webutility. getcategoryname (request. querystring ["categoryid"]);
}
}
The logic for displaying the page title is put in the page_load event method. Therefore, each time you open the page, you must execute the method for obtaining the categoryname. If the caching mechanism is not used, when there is a large amount of category data, the page display will be very slow.
4.3.5 introduce proxy Mode
Business methods related to product, category, and item in bll in the business logic layer. The implementation logic is to call the data access layer (DAL) object to access the database to obtain relevant data. To improve system performance, we need to add the logic of the cache mechanism for these implementation methods. When we increase the cache mechanism for business objects, the caller should be consistent with the BLL business object call. That is to say, we need to introduce a new object to control the original BLL Business Object. This new object is the proxy object in proxy mode.
Taking petshop. BLL. product as an example, petshop creates a proxy object productdataproxy for it, and introduces a caching mechanism in methods such as getproductbycategory (). For example:
Public static class productdataproxy
{
Private Static readonly int producttimeout = int. parse (configurationmanager. deleettings ["productcacheduration"]);
Private Static readonly bool enablecaching = bool. parse (configurationmanager. deleettings ["enablecaching"]);
Public static ilist
Getproductsbycategory (string category)
{
Product = new product ();
If (! Enablecaching)
Return product. getproductsbycategory (category );
String key = "product_by_category _" + category;
Ilist DATA = (ilist) httpruntime. cache [Key];
// Check if the data exists in the data cache
If (Data = NULL)
{
Data = product. getproductsbycategory (category );
// Create a aggregatecachedependency object from the factory
Aggregatecachedependency Cd = dependencyfacade. getproductdependency ();
// Store the output in the data cache, and add the necessary aggregatecachedependency object
Httpruntime. cache. Add (Key, Data, CD, datetime. Now. addhours (producttimeout), cache. noslidingexpiration, cacheitempriority. High, null );
}
Return data;
}
}
Compared with the getproductsbycategory () method of the product object in the business logic layer, the cache mechanism is added. When no related data item exists in the cache, you can directly call the getproductsbycategory () method of the product in the business logic layer to obtain the data and store the data together with the corresponding aggregatecachedependency object in the cache.
The proxy mode is introduced to encapsulate business objects at the cache level and enhance control over business objects. Because the methods exposed outside the object are consistent, there is no substantive difference between the called proxy object and the real object for the caller.
From the perspective of separation of duties and hierarchical design, I prefer that these proxy objects are defined in the business logic layer, instead of being divided into the presentation layer UI as in the petshop design. In addition, if you need to consider the scalability and alternative of the program, we can also establish a unified interface or abstract class for the real object and the proxy object. However, from the perspective of the petshop presentation layer, it may be more reasonable to use static classes and static methods. We need to remember that "over-design" is a warning line for software design.
If you need to use a caching mechanism for the UI Layer to store application data in the cache, you can call these proxy objects. Take the productscontrol user control as an example. The call method is as follows:
Productslist. datasource = productdataproxy. getproductsbycategory (categorykey );
The productslist object belongs to the custom mlmlist type, which is a class derived from the system. Web. UI. webcontrols. datalist control. Its datasource attribute can accept the ilist set object.
However, in the design of petshop 4.0, for controls similar to the productscontrol type, the cache mechanism adopted is the page output cache. We can find clues from the source code on the productscontrol. ascx page:
<% @ Outputcache duration = "100000" varybyparam = "page; categoryid" %>
And ASP. net 1. different from the page output cache in ASP. NET 2.0, Asp.. Net user control introduces the cachepolicy attribute, which belongs to the controlcachepolicy class. It implements ASP programmatically.. Net user control output cache settings. You can set the dependencies related to the user control by setting the dependency attribute of the controlcachepolicy class. For example, in the productscontrol user control, set the following:
Protected void page_load (Object sender, eventargs E)
{
This. cachepolicy. Dependency = dependencyfacade. getproductdependency ();
}
The page output cache is used and the controlcachepolicy is used to set the output cache, so that business data and the entire page can be put into the cache. Compared with the application cache, this method greatly improves the performance. At the same time, it effectively avoids the disadvantages of "data expiration" through the introduced sqlcachedependency feature, so it is widely used in petshop 4.0. On the contrary, proxy objects previously created for product, category, and item business objects are "idle ", it is only used as a design method to demonstrate and "survive" with the source code of the entire system.