With the further promotion of the company's projects and the increase in the number of users, it is already faced with the problem that a single server cannot load.
Due to the time relationship, the optimization mainly involves two steps. First, the application layer code is optimized to improve the load and throughput of a single server. Then perform table sharding and introduce distributed applications such as queue and memcached.
Project Background: This is an online competition project (http://race.gwy.591up.com), during which the database writing pressure is high.
Current problem: 1. server bandwidth pressure. 2. Database pressure.
Is a web server CPU usage report.
In general, the CPU usage of the application layer server is not high.
Is a web server bandwidth report.
From this report, we can see that each competition has a high peak bandwidth usage.
Let's look at the database server bandwidth report.
Similarly, the traffic of the database server during the competition is too large. Obviously, this is abnormal, and the query is definitely faulty.
In the face of such problems, the first phase is determined to mainly optimize the following.
1. Use flash storage for resumable recording. (Do question breakpoint: similar to program breakpoint, when the user answers the nth question, he will still find the nth question when the next time he enters .) It was originally stored in a database, but every time you make a question, you will execute an update statement. The database is the MyISAM engine of MySQL, and the update operation is often blocked.
2. Change the system submission behavior. After the user submits the competition, the system executes an update statement to update the time when the user submits the exam. The same update is also executed within the same period of time. After communicating with the product manager, confirm that the update can be executed at the last minute, the user's answer time is allowed to have an error of 1 minute.
3. Change the database update statement to an insert statement. Due to time issues, this optimization point is postponed to the second optimization stage for further processing.
4. Adjust the application server to support the LVS cluster. After analyzing the current system, you do not need to adjust the code to directly deploy the cluster. The problem is that the same process cache exists in multiple servers. This situation is acceptable for the time being, later, you need to change to memcached to centrally manage the cache.
5. Pressure issues when the score page jumps at the same time. The online competition submission time is very concentrated. After the user answers the question, the user will jump to a page to wait for the answer (at this time, the background Windows Service is conducting competition statistics ), here, the concurrency and bandwidth pressure on the server are very high. Therefore, optimization does not jump here, but waits on the current page, and different users are automatically allocated different waiting periods to avoid occupying the server bandwidth.
6. for each complete civil servant examination paper, the question resource is 150 K-200 K, because the answer and view analysis are on different pages, the previous implementation will cause the question to be loaded multiple times, A serious waste of Bandwidth Resources. Therefore, it is optimized to input static resources by handler. After loading the resources from the server once, all subsequent calls to the question can be loaded from the local cache of the browser to save the server bandwidth. At the same time, the server only enables gzip compression on the static resource server. compressing dynamic files will waste the server's CPU resources, and only perform gzip compression on the questions output by handler, on the one hand, it saves the CPU, and on the other hand, it compresses the-K question resources to about 50 K.
7. database performance optimization. The position of each condition in the Code query is adjusted, so that the query statement can be used more to the index. At the same time, the original insert operation is modified to insert multiple records at a time and other database queries are optimized.
Any optimization should address existing problems. From the server monitoring report, we can see that the bandwidth pressure on our website's application server and the bandwidth pressure on the database server are high, the CPU usage of the application server is not high. Therefore, the main optimization is to optimize the bandwidth of the application server and the write pressure on the database server, because the purpose is clear and the effect is obvious.
The article mentioned that handler is used to output static resources to cache the browser, and this Code is attached. Other optimizations are highly targeted and won't be too long, it mainly records the work methods and methods of this optimization.
The static resources output by handler use. Net stream compression. Therefore, we declare an interface of the compression tool.
Using system. io; namespace nd. race. compressor {/// <summary> /// compressed machine interface // </Summary> Public interface icompressor {string encodingname {Get;} bool canhandle (string acceptencoding ); void compress (string content, stream targetstream) ;}} the gzip compressed to implement this interface. 1234567891011121314151617181920212223242526272829 using system. io; using system. io. compression; using system. text; namespace nd. race. compressor {public sealed class gzipcompressor: icompressor {Public String encodingname {get {return "gzip" ;}} public bool canhandle (string acceptencoding) {return! String. isnullorempty (acceptencoding) & acceptencoding. contains ("gzip");} public void compress (string content, stream targetstream) {using (VAR writer = new gzipstream (targetstream, compressionmode. compress) {var bytes = encoding. utf8.getbytes (content); writer. write (bytes, 0, bytes. length );}}}}
The same deflate compressors also implement this interface.
using System.IO;using System.IO.Compression;using System.Text; namespace ND.Race.Compressor{ public sealed class DeflateCompressor : ICompressor { public string EncodingName { get { return "deflate"; } } public bool CanHandle(string acceptEncoding) { return !string.IsNullOrEmpty(acceptEncoding) && acceptEncoding.Contains("deflate"); } public void Compress(string content, Stream targetStream) { using (var writer = new DeflateStream(targetStream, CompressionMode.Compress)) { var bytes = Encoding.UTF8.GetBytes(content); writer.Write(bytes, 0, bytes.Length); } } }}
If the browser does not support stream compression, we can only directly output the content, so we need a processing class that does not compress.
using System.IO;using System.Text; namespace ND.Race.Compressor{ public sealed class NullCompressor : ICompressor { public string EncodingName { get { return "utf-8"; } } public bool CanHandle(string acceptEncoding) { return true; } public void Compress(string content, Stream targetStream) { using (targetStream) { var bytes = Encoding.UTF8.GetBytes(content); targetStream.Write(bytes, 0, bytes.Length); } } }}
Now we can start coding our handler.
Public class questioncachehandler: ihttphandler {# region static variable // <summary> // resource expiration time /// </Summary> Private Static readonly int durationindays = 30; /// <summary> /// stream compression interface /// </Summary> Private Static readonly icompressor [] compressors = {New gzipcompressor (), new deflatecompressor (), new nullcompressor () };# endregion # region private variable /// <summary> // memory stream compression class /// </Summary> private icompressor compressor; /// <summary> /// etag /// </Summary> private string etagcachekey; /// <summary> /// competition session ID /// </Summary> private long raceid; # endregion public void processrequest (httpcontext context) {If (context = NULL) return; long. tryparse (context. request. querystring ["raceid"], out raceid); If (raceid = 0) return; var acceptencoding = context. request. headers ["Accept-encoding"]; compressor = compressors. first (O => O. canhandle (acceptencoding); etagcachekey = string. concat (raceid, "/etag"); If (isinbrowsercache (context, etagcachekey) return; sendoutputtoclient (context, true, etagcachekey );} /// <summary> /// send content to the client /// </Summary> /// <Param name = "context"> </param> /// <Param name = "insertcacheheaders"> </param> // <Param name = "etag"> </param> private void sendoutputtoclient (httpcontext context, bool insertcacheheaders, string etag) {string content = ""; memorystream = new memorystream (); compressor. compress (content, memorystream); byte [] bytes = memorystream. toarray (); httpresponse response = context. response; If (insertcacheheaders) {httpcachepolicy cache = context. response. cache; cache. setetag (etag); cache. setomitvarystar (true); cache. setmaxage (timespan. fromdays (durationindays); cache. setlastmodified (datetime. now); cache. setexpires (datetime. now. adddays (durationindays); // use the expiration time cache in the HTTP 1.0 browser. setvaliduntilexpires (true); cache. setcacheability (httpcacheability. public); cache. setrevalidation (httpcacherevalidation. allcaches); cache. varybyheaders ["Accept-encoding"] = true;} response. appendheader ("Content-Length", bytes. length. tostring (system. globalization. cultureinfo. invariantculture); response. contenttype = "text/plain"; response. contenttype = "application/X-JavaScript"; response. appendheader ("content-encoding", compressor. encodingname); If (bytes. length> 0) response. outputstream. write (bytes, 0, bytes. length); If (response. isclientconnected) response. flush ();} /// <summary> /// whether the browser has been cached /// </Summary> /// <Param name = "context"> </param> /// <Param name = "etag"> </param> // <returns> </returns> private bool isinbrowsercache (httpcontext context, string etag) {string incomingetag = context. request. headers ["If-None-match"]; If (string. equals (incomingetag, etag, stringcomparison. ordinal) {context. response. cache. setetag (etag); context. response. appendheader ("Content-Length", "0"); context. response. statuscode = (INT) system. net. httpstatuscode. notmodified; context. response. end (); Return true;} return false;} public bool isreusable {get {return false ;}}}
The server code uses the HTTP Request Header's accept-encoding to determine whether stream compression is supported, and then uses the header's etag to determine whether a cached copy exists in the browser.
From: http://blog.moozi.net/archives/web-performance-optimization-practice-application-optimization.html