前言

WebAPi作为接口请求的一种服务,当我们请求该服务时我们目标是需要快速获取该服务的数据响应,这种情况在大型项目中尤为常见,此时迫切需要提高WebAPi的响应机制,当然也少不了前端需要作出的努力,这里我们只讲述在大小型项目中如何利用后台逻辑尽可能最大限度提高WebAPi性能,我们从以下几个方面来进行阐述。

性能提升一:JSON序列化器(Jil)

在.NET里面默认的序列化器是JavaScriptSrializer,都懂的,性能实在是差,后来出现了Json.NET,以至于在目前创建项目时默认用的序列化器是Json.NET,它被.NET开发者所广泛使用,它的强大和性能毋庸置疑,以至于现在Json.NET版本已经更新到9.0版本,但是在大型项目中一旦数据量巨大时,此时用Json.NET来序列化数据会略慢,这时我们就可以尝试用Jil,它里面的APi也足够我们用,我们讲述几个常用的APi并一起对比Json.NET来看看:

序列化对比

在Json.NET中是这样序列化的

  1. JsonConvert.SerializeObject(obj)

而在Jil中序列化数据是这样的

  1. JSON.Serialize(obj)

此时对于Jil序列化数据返回的字符串形式有两种

(1)直接接收

  1. object obj = new { Foo = , Bar = "abc" };
  2. string s = Jil.JSON.Serialize(obj)

(2)传递给StringWriter来接收

  1. var obj = new { Foo = , Bar = "abc" };
  2. var t = new StringWriter();
  3. JSON.SerializeDynamic(obj, t);

上述说到对于数据量巨大时用Jil其效率高于Json.NET,下来我们来验证序列化10000条数据

序列化类:

  1. public class Person
  2. {
  3. public int Id { get; set; }
  4. public string Name { get; set; }
  5. }

测试数据:

  1. var list = new List<Person>();
  2. for (int i = ; i < ; i++)
  3. {
  4. list.Add(new Person(){ Id = i });
  5. }
  6. var stop = new Stopwatch();
  7. stop.Start();
  8. var jil = SerializeList(list);
  9. Console.WriteLine(stop.ElapsedMilliseconds);
  10. stop.Stop();
  11. var stop1 = new Stopwatch();
  12. stop1.Start();
  13. var json = JsonConvert.SerializeObject(list);
  14. Console.WriteLine(stop1.ElapsedMilliseconds);
  15. stop1.Stop();

Jil序列化封装:

  1. private static string SerializeList(List<Person> list)
  2. {
  3. using (var output = new StringWriter())
  4. {
  5. JSON.Serialize(
  6. list,
  7. output
  8. );
  9. return output.ToString();
  10. }
  11. }

我们来看看测试用例:

此时利用Json.NET序列化数据明显优于Jil,但序列化数据为10万条数,Jil所耗时间会接近于Json.NET,当数据高于100万条数时这个时候就可以看出明显的效果,如下:

此时Jil序列化数据不到1秒,而利用Json.NET则需要足足接近3秒。

测试用例更新:

当将代码进行如下修改时,少量数据也是优于Json.NET,数据量越大性能越明显,感谢园友【calvinK】提醒:

  1. var list = new List<int>();
  2. for (int i = ; i < ; i++)
  3. {
  4. list.Add(i);
  5. }
  6.  
  7. var stop = new Stopwatch();
  8. stop.Start();
  9. for (var i = ; i < ; i++)
  10. {
  11. var jil = SerializeList(list);
  12.  
  13. }
  14.  
  15. Console.WriteLine(stop.ElapsedMilliseconds);
  16. stop.Stop();
  17. var stop1 = new Stopwatch();
  18. stop1.Start();
  19. for (var i = ; i < ; i++)
  20. {
  21. var json = JsonConvert.SerializeObject(list);
  22.  
  23. }
  24. Console.WriteLine(stop1.ElapsedMilliseconds);
  25. stop1.Stop();

结果如下:

  1. 关于Jil的序列化还有一种则是利用JSON.SerializeDynamic来序列化那些在编译时期无法预测的类型。 至于反序列化也是和其序列化一一对应。

下面我们继续来看看Jil的其他特性。若在视图上渲染那些我们需要的数据,而对于实体中不必要用到的字段我们就需要进行过滤,此时我们用到Jil中的忽略属性。

  1. [JilDirective(Ignore = true)]

我们来看看:

  1. public class Person
  2. {
  3. [JilDirective(Ignore = true)]
  4. public int Id { get; set; }
  5. public int Name { get; set; }
  6. }
  1. var jil = SerializeList(new Person() { Id = , Name = } );
  2. Console.WriteLine(jil);

另外在Jil中最重要的属性则是Options,该属性用来配置返回的日期格式以及其他配置,若未用其属性默认利用Json.NET返回如【\/Date(143546676)\/】,我们来看下:

  1. var jil = SerializeList(new Person() { Id = , Name = "", Time = DateTime.Now });

进行如下设置:

  1. JSON.Serialize(
  2. p,
  3. output,
  4. new Options(dateFormat: DateTimeFormat.ISO8601)
  5. );

有关序列化继承类时我们同样需要进行如下设置,否则无法进行序列化

  1. new Options(dateFormat: DateTimeFormat.ISO8601, includeInherited: true)

Jil的性能绝对优于Json.NET,Jil一直在追求序列化的速度所以在更多可用的APi可能少于Json.NET或者说没有Json.NET灵活,但是足以满足我们的要求。

性能提升二:压缩(Compress)

压缩方式(1) 【IIS设置】

启动IIS动态内容压缩

压缩方式(2)【DotNetZip】

利用现成的轮子,下载程序包【DotNetZip】即可,此时我们则需要在执行方法完毕后来进行内容的压缩即可,所以我们需要重写【 ActionFilterAttribute 】过滤器,在此基础上进行我们的压缩操作。如下:

  1. public class DeflateCompressionAttribute : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuted(HttpActionExecutedContext actionContext)
  4. {
  5. var content = actionContext.Response.Content;
  6. var bytes = content == null ? null : content.ReadAsByteArrayAsync().Result;
  7. var compressContent = bytes == null ? new byte[] : CompressionHelper.DeflateByte(bytes);
  8. actionContext.Response.Content = new ByteArrayContent(compressContent);
  9. actionContext.Response.Content.Headers.Remove("Content-Type");
  10. if (string.Equals(actionContext.Request.Headers.AcceptEncoding.First().Value, "deflate"))
  11. actionContext.Response.Content.Headers.Add("Content-encoding", "deflate");
  12. else
  13. actionContext.Response.Content.Headers.Add("Content-encoding", "gzip");
  14. actionContext.Response.Content.Headers.Add("Content-Type", "application/json;charset=utf-8");
  15. base.OnActionExecuted(actionContext);
  16. }
  17.  
  18. }

利用DotNetZip进行快速压缩:

  1. public class CompressionHelper
  2. {
  3. public static byte[] DeflateByte(byte[] str)
  4. {
  5. if (str == null)
  6. {
  7.  
  8. return null;
  9.  
  10. }
  11. using (var output = new MemoryStream())
  12. {
  13.  
  14. using (var compressor = new Ionic.Zlib.GZipStream(
  15.  
  16. output, Ionic.Zlib.CompressionMode.Compress,
  17.  
  18. Ionic.Zlib.CompressionLevel.BestSpeed))
  19. {
  20.  
  21. compressor.Write(str, , str.Length);
  22.  
  23. }
  24.  
  25. return output.ToArray();
  26.  
  27. }
  28.  
  29. }
  30.  
  31. }

我们来对比看一下未进行内容压缩前后结果响应的时间以及内容长度,给出如下测试类:

  1. [HttpGet]
  2. [DeflateCompression]
  3. public async Task<IHttpActionResult> GetZipData()
  4. {
  5. Dictionary<object, object> dict = new Dictionary<object, object>();
  6. List<Employee> li = new List<Employee>();
  7. li.Add(new Employee { Id = "", Name = "xpy0928", Email = "a@gmail.com" });
  8. li.Add(new Employee { Id = "", Name = "tom", Email = "b@mail.com" });
  9. li.Add(new Employee { Id = "", Name = "jim", Email = "c@mail.com" });
  10. li.Add(new Employee { Id = "", Name = "tony",Email = "d@mail.com" });
  11. dict.Add("Details", li);return Ok(dict);
  12.  
  13. }

结果运行错误:

这里应该是序列化出现问题,在有些浏览器返回的XML数据,我用的是搜狗浏览器,之前学习WebAPi时其返回的就是XML数据,我们试着将其返回为Json数据看看。

  1. var formatters = config.Formatters.Where(formatter =>
  2. formatter.SupportedMediaTypes.Where(media =>
  3. media.MediaType.ToString() == "application/xml" || media.MediaType.ToString() == "text/html").Count() > ) //找到请求头信息中的介质类型
  4. .ToList();
  5.  
  6. foreach (var match in formatters)
  7. {
  8. config.Formatters.Remove(match);
  9. }

我们未将其压缩后响应的长度如下所示:

压缩后结果明显得到提升

接下来我们自定义用.NET内置的压缩模式来实现看看

压缩方式(3)【自定义实现】

既然响应的内容是通过HttpContent,我们则需要在重写过滤器ActionFilterAttribute的基础上来实现重写HttpContent,最终根据获取到浏览器支持的压缩格式对数据进行压缩并写入到响应流中即可。

  1. public class CompressContent : HttpContent
  2. {
  3. private readonly string _encodingType;
  4. private readonly HttpContent _originalContent;
  5. public CompressContent(HttpContent content, string encodingType = "gzip")
  6. {
  7. _originalContent = content;
  8. _encodingType = encodingType.ToLowerInvariant();
  9. Headers.ContentEncoding.Add(encodingType);
  10. }
  11. protected override bool TryComputeLength(out long length)
  12. {
  13. length = -;
  14. return false;
  15. }
  16. protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
  17. {
  18. Stream compressStream = null;
  19. switch (_encodingType)
  20. {
  21. case "gzip":
  22. compressStream = new GZipStream(stream, CompressionMode.Compress, true);
  23. break;
  24. case "deflate":
  25. compressStream = new DeflateStream(stream, CompressionMode.Compress, true);
  26. break;
  27. default:
  28. compressStream = stream;
  29. break;
  30. }
  31. return _originalContent.CopyToAsync(compressStream).ContinueWith(tsk =>
  32. {
  33. if (compressStream != null)
  34. {
  35. compressStream.Dispose();
  36. }
  37. });
  38. }
  39. }

重写过滤器特性

  1. public class CompressContentAttribute : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuted(HttpActionExecutedContext context)
  4. {
  5. var acceptedEncoding = context.Response.RequestMessage.Headers.AcceptEncoding.First().Value;
  6. if (!acceptedEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)
  7. && !acceptedEncoding.Equals("deflate", StringComparison.InvariantCultureIgnoreCase))
  8. {
  9. return;
  10. }
  11. context.Response.Content = new CompressContent(context.Response.Content, acceptedEncoding);
  12. }
  13.  
  14. }

关于其响应结果对比则不再叙述,和上述利用DotNetZip结果一致。

当写压缩内容时,我发现一个问题,产生了疑问, context.Response.Content.Headers 和 context.Response.Headers 为何响应中有两个头Headers呢?,没有去细究这个问题,大概说说个人想法。

context.Response.Content.Headers和context.Response.Headers有什么不同呢?

我们看看context.Response.Headers中的定义,其摘要如下:

  1. // 摘要:
  2. // Gets a value that indicates if the HTTP response was successful.
  3. //
  4. // 返回结果:
  5. // Returns System.Boolean.A value that indicates if the HTTP response was successful.
  6. // true if System.Net.Http.HttpResponseMessage.StatusCode was in the range 200-299;
  7. // otherwise false.

而context.Response.Content.Headers中的定义,其摘要如下:

  1. // 摘要:
  2. // Gets the HTTP content headers as defined in RFC 2616.
  3. //
  4. // 返回结果:
  5. // Returns System.Net.Http.Headers.HttpContentHeaders.The content headers as
  6. // defined in RFC 2616.

对于Content.Headers中的Headers的定义是基于RFC 2616即Http规范,想必这么做的目的是将Http规范隔离开来,我们能够方便我们实现自定义代码或者设置有关响应头信息最终直接写入到Http的响应流中。我们更多的是操作Content.Headers所以将其区别开来,或许是出于此目的吧,有知道的园友可以给出合理的解释,这里只是我的个人揣测。

性能提升三:缓存(Cache:粒度比较大)

缓存大概是谈的最多的话题,当然也有大量的缓存组件供我们使用,这里只是就比较大的粒度来谈论这个问题,对于一些小的项目还是有一点作用,大的则另当别论。

当我们进行请求可以查看响应头中会有这样一个字段【Cache-Control】,如果我们未做任何处理当然则是其值为【no-cache】。在任何时期都不会进行缓存,都会重新进行请求数据。这个属性里面对应的值还有private/public、must-revalidate,当我们未指定max-age的值时且设置值为private、no-cache、must-revalidate此时的请求都会去服务器获取数据。这里我们首先了解下关于Http协议的基本知识。

【1】若设置为private,则其不能共享缓存意思则是不会在本地缓存页面即对于代理服务器而言不会复制一份,而如果对于用户而言其缓存更加是私有的,只是对于个人而言,用户之间的缓存相互独立,互不共享。若为public则说明每个用户都可以共享这一块缓存。对于这二者打个比方对于博客园的推送的新闻是公开的,则可以设置为public共享缓存,充分利用缓存。

【2】max-age则是缓存的过期时间,在某一段时间内不会去重新请求从服务器获取数据,直接在本地浏览器缓存中获取。

【3】must-revalidate从字面意思来看则是必须重新验证,也就是对于过期的数据进行重新获取新的数据,那么它到底什么时候用呢?归根结底一句话:must-revalidate主要与max-age有关,当设置了max-age时,同时也设置了must-revalidate,等缓存过期后,此时must-revalidate则会告诉服务器来获取最新的数据。也就是说当设置max-age = 0,must-revalidate = true时可以说是与no-cache = true等同。

下面我们来进行缓存控制:

  1. public class CacheFilterAttribute : ActionFilterAttribute
  2. {
  3. public int CacheTimeDuration { get; set; }
  4. public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
  5. {
  6. actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue
  7. {
  8. MaxAge = TimeSpan.FromSeconds(CacheTimeDuration),
  9. MustRevalidate = true,
  10. Public = true
  11. };
  12. }
  13. }

添加缓存过滤特性:

  1. [HttpGet]
  2. [CompressContent]
  3. [CacheFilter(CacheTimeDuration = 100)]
  4. public async Task<IHttpActionResult> GetZipData()
  5. {
  6. var sw = new Stopwatch();
  7. sw.Start();
  8. Dictionary<object, object> dict = new Dictionary<object, object>();
  9. List<Employee> li = new List<Employee>();
  10. li.Add(new Employee { Id = "", Name = "xpy0928", Email = "a@gmail.com" });
  11. li.Add(new Employee { Id = "", Name = "tom", Email = "b@mail.com" });
  12. li.Add(new Employee { Id = "", Name = "jim", Email = "c@mail.com" });
  13. li.Add(new Employee { Id = "", Name = "tony", Email = "d@mail.com" });
  14.  
  15. sw.Stop();
  16.  
  17. dict.Add("Details", li);
  18. dict.Add("Time", sw.Elapsed.Milliseconds);
  19.  
  20. return Ok(dict);
  21.  
  22. }

结果如下:

性能提升四:async/await(异步方法)

当在大型项目中会出现并发现象,常见的情况例如注册,此时有若干个用户同时在注册时,则会导致当前请求阻塞并且页面一直无响应最终导致服务器崩溃,为了解决这样的问题我们需要用到异步方法,让多个请求过来时,线程池分配足够的线程来处理多个请求,提高线程池的利用率 !如下:

  1. public async Task<IHttpActionResult> Register(Employee model)
  2. {
  3. var result = await UserManager.CreateAsync(model);
  4. return Ok(result);
  5. }

总结

本节我们从以上几方面讲述了在大小项目中如何尽可能最大限度来提高WebAPi的性能,使数据响应更加迅速,或许还有其他更好的解决方案,至少以上所述也可以作为一种参考,WebAPi一个很轻量的框架,你值得拥有,see u。

关于大小型项目如何最大限度提高WebAPi性能的更多相关文章

  1. WebAPi性能

    提高WebAPi性能   前言 WebAPi作为接口请求的一种服务,当我们请求该服务时我们目标是需要快速获取该服务的数据响应,这种情况在大型项目中尤为常见,此时迫切需要提高WebAPi的响应机制,当然 ...

  2. 从 github 执行 git clone 一个大的项目时提示 error: RPC failed

    目前克隆一个比较大的项目,出现RPC failed的错误 Cloning into 'bigfiles'... remote: Counting objects: 190, done. remote: ...

  3. discuz论坛apache日志hadoop大数据分析项目:清洗数据核心功能解说及代码实现

    discuz论坛apache日志hadoop大数据分析项目:清洗数据核心功能解说及代码实现http://www.aboutyun.com/thread-8637-1-1.html(出处: about云 ...

  4. 【转】百亿级实时大数据分析项目,为什么不用Hadoop?

    百亿数量级的大数据项目,软硬件总体预算只有30万左右,需求是进行复杂分析查询,性能要求多数分析请求达到秒级响应.        遇到这样的项目需求,预算不多的情况,似乎只能考虑基于Hadoop来实施. ...

  5. 腾讯两大开源项目Tars、TSeer

    6月25日,在LC3(LinuxCon + ContainerCon + CloudOpen)中国2018大会上,腾讯宣布其两大开源项目——RPC开发框架Tars.轻量化名字服务方案TSeer,加入L ...

  6. PyTorch大更新!谷歌出手帮助开发,正式支持TensorBoard | 附5大开源项目

    大家又少了一个用TensorFlow的理由. 在一年一度的开发者大会F8上,Facebook放出PyTorch的1.1版本,直指TensorFlow"腹地". 不仅宣布支持Tens ...

  7. 利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

    利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果 前言 近日公司接到一个轨道系统的需求,需要将地铁线路及列车实时位置展示在大屏上.既然是大屏项目,那视觉效果当然是第一重点,咱们可以先来看看项 ...

  8. 如何最大限度提高.NET的性能

    优化 .NET的性能 1)避免使用ArrayList.     因为任何对象添加到ArrayList都要封箱为System.Object类型,从ArrayList取出数据时,要拆箱回实际的类型.建议使 ...

  9. 使用 libevent 和 libev 提高网络应用性能

    使用 libevent 和 libev 提高网络应用性能 Martin C. Brown, 作家, Freelance 简介: 构建现代的服务器应用程序需要以某种方法同时接收数百.数千甚至数万个事件, ...

随机推荐

  1. es6 数组的工具类

    根据Es6中map和Set的特性,实现了对array的分组和转换操作. exports.mapToObj = function (strMap) { let obj = Object.create(n ...

  2. MongoDB和Redis-NoSQL数据库-文档型-内存型

    1NoSQL简述 CAP(Consistency,Availabiity,Partitiontolerance)理论告诉我们,一个分布式系统不可能满足一致性,可用性和分区容错性这三个需求,最多只能同时 ...

  3. Unity小游戏制作 - 暗影随行

    用Unity制作小游戏 - 暗影惊吓 最近玩了一个小游戏,叫做暗影惊吓,虽然是一个十分简单的小游戏,但是感觉还是十分有趣的.这里就用Unity来实现一个类似的游戏. 项目源码:DarkFollow 主 ...

  4. 字典NSDictionary以及NSMutableDictionary的用法总结

    做过Java语言 或者 C语言 开发的朋友应该很清楚 关键字map 吧,它可以将数据以键值对儿的形式储存起来,取值的时候通过KEY就可以直接拿到对应的值,非常方便.在Objective-C语言中 词典 ...

  5. ex2-注释和井号

    代码: print("I could have code like this.") # and the commnt after is ignored.# You can also ...

  6. ASP.NET vNext 概述

    兼容Mono的下一代云环境Web开发框架ASP.NET vNext 我们知道了ASP.NET vNext是一个全新的框架,是一个与时俱进的框架.这篇文章将深入讨论在整体架构更多的细节,文档参照 ASP ...

  7. Java NIO1:I/O模型概述

    I/O模型 在开始NIO的学习之前,先对I/O的模型有一个理解,这对NIO的学习是绝对有好处的.我画一张图,简单表示一下数据从外部磁盘向运行中进程的内存区域移动的过程: 这张图片明显忽略了很多细节,只 ...

  8. Some warning were found during validation

    前几天做一个iOS下的App更新,到上传的时候出了问题,一直传了大半个小时,结果还是没传完,再试依然不行,于是只好关机,把电脑带回家弄. 回家后出现了更奇怪的事,经过漫长等待后,竟然出现这个提示: 我 ...

  9. [.net 面向对象程序设计进阶] (27) 团队开发利器(六)分布式版本控制系统Git——在Visual Studio 2015中使用Git

    [.net 面向对象程序设计进阶] (26) 团队开发利器(六)分布式版本控制系统Git——在Visual Studio 2015中使用Git 本篇导读: 接上两篇,继续Git之旅 分布式版本控制系统 ...

  10. iOS block种类和切换

    block 分为三种 NSGlobalBlock,NSStackBlock, NSMallocBlock. NSGlobalBlock:类似函数,位于text段: NSStackBlock:位于栈内存 ...