本文转载自微信公众号“老王加”,老王加作者老王。转载本文请联系老王Plus公众号。服务器性能问题在数据量小的时候一般不会表现出来,不需要太在意。但是一旦数据量大了,就会变成一件必须处理的麻烦事。通常,性能问题可能有许多不同的原因。内存问题、缓慢的数据库请求和太少的机器只是问题的一部分。在手项目日量10亿。在最近的一段时间里,我填补了很多坑,也学到了很多东西。在今天的文章中,我会把这一段的经验总结成几类问题。当然,分类不必非常严格。重要的是给你一些建议。到用的时候,省点坑就够了。另外,顺序不重要,想到什么去,不是说前面的内容比后面的内容更需要注意。1.数据库调用数据库调用的性能会严重影响系统的整体性能。在大多数情况下,与数据库的快速交互是获得良好性能的最重要因素。需要重点关注以下几点:索引策略索引对数据库交互的影响无需解释。重要的是检查,检查每一个索引,每一个查询。很多时候,你想的不一定是你想的。通过查询语句和条件检查索引的使用情况,检查索引的结构。确保每个查询都能正确使用你要使用的索引。表结构设计表结构设计最重要的是对业务的理解。对数据之间关系的理解越深,表结构就越合理。同样的工作尽量在数据库上做,避免在服务器上做不容易理解。以代码为例://好的方法vargirls=dbContext.Users.Where(user=>user.gender==female);varcount=girls.Count();//不好的方法vargirls=dbContext.Users.Where(user=>user.gender==female).ToList();varcount=girls.Count;下面这样,第一行以ToList()结束。当实体执行查询时,整个数据从数据库中检索和提取,然后在服务器中计数。上面的方法会直接在数据库中统计。显然,统计是在数据库中进行的,网络传输的开销会少一些。使数据库尽可能靠近应用服务器。数据库和应用服务器之间的距离无非就是网络。“更近”的网络会带来更少的延迟。这个“近”是指网络拓扑结构上的接近程度,而不是位置和距离。对于多机房的分布式应用,最低要求是在同一个数据中心有一个或多个完整的副本集和应用服务。数据库数据库的使用方式有很多种,你想使用数据库的方式,关系型、NoSQL、内存数据库等等。并非所有数据库都是生而平等的。有的适合Key-Value键值对,有的适合事务处理,有的适合存储日志。开发中不要拘泥于数据库类型,要根据业务类型和数据库特性来使用。比如MongoDB本身就是一个文档型数据库,其结构不适合JOIN操作。但它非常适合存储包含大量业务数据的文档。所以在使用的时候,尽量避免使用JOIN操作业务。当然,这只是一个例子。其实MongoDB对于JOIN类的内容有更好的处理方式,大家可以自行了解。确保数据库有足够的硬件资源。服务器的扩展性一般比较高,但其实数据库的扩展性也是需要注意的。对于数据库服务器,需要注意存储空间、内存、网络和CPU。经验上,当接近限额时,服务器可能不会给你明确的报警;并且当有报警时,可能已经达到了极限,出现了故障,处理起来非常困难。所以,当你发现某些任务开始变慢时,就意味着你需要进行全面的检查。承认存在一些低效的查询并不是所有的查询都是高效的。特别是查询基于一些实体框架,如EF或Hibernate。在技??术和时间可能的情况下,谨慎使用DataFrame是一种很好的做法。使用连接池而不是单个连接。如果每次查询都需要重新建立连接,那是非常可怕的,从性能到应用可靠性。要使用数据库,首先要学习如何使用连接池。谨慎使用存储过程当您有需要花费大量时间来处理的复杂查询时,存储过程就是解决方案。但是一定要小心,一定要小心,一定要小心,重要的事情说三遍。在我的团队中,存储过程是被禁止的。相对而言,这里的安全需求超过了性能。但是,在本文中,尤其是在讨论数据库操作的性能时,我们不能忘记存储过程。数据库分片策略分布式数据库性能的核心在于分片。分片基于一个原则:让业务的每一次查询操作对应尽可能少的分片。上面写的其实是一些原则。实际上,最难的部分是识别这些问题。因此,您需要熟悉各种工具。通常,数据库本身也可以提供相关的内容,比如慢查询、扩展问题、网络瓶颈等。对于数据库,不要局限于使用它们。一定的深入了解,对成??长会有很大的帮助。2、内存压力对于一些高吞吐量的应用,服务器内存压力是最常见的问题。当吞吐量很高时,垃圾回收(GC)跟不上内存的分配和释放。而这种压力的表现就是服务器花在垃圾回收上的时间变多了,执行代码的时间变少了。这种状态可以在多种情况下发生。最常见的情况是内存容量耗尽。当你达到内存限制时,垃圾收集器会恐慌并开始更频繁的整体垃圾收集,这种收集模式非常昂贵。但问题是,为什么会这样?为什么你越来越接近内存使用的限制?原因通常是错误或不是很好的缓存管理或内存泄漏。通过拍摄内存快照并检查占用所有字节的内容,可以使用内存分析器轻松发现这一点。首先意识到您有记忆问题很重要。最简单的方法是使用性能计数器。3.缓存数据缓存是一种非常好的、非常有效的优化技术。一个典型的例子是,当客户端发送请求时,服务器可以将结果存储在缓存中。当客户端再次发送同一个请求时(不一定是同一个客户端),服务器不需要再次查询数据库或者做任何计算得到结果,它只是从缓存中获取。考虑一下搜索引擎的作用。如果这是一个常见的搜索,它可能一天会被询问多次。如果不做缓存,每次都用算力生成同一个页面不可怕吗?当然,在某些程序中使用缓存会增加应用程序的复杂性。首先,每隔一段时间缓存就需要失效和刷新,对吧?我们不能总是返回相同的结果。另一个问题是,如果使用不当,缓存会膨胀并导致内存问题。幸运的是,ASP.Net有许多优秀的缓存库已经实现,可以帮助解决大部分工作。4.垃圾回收优化在应用服务器性能优化中,垃圾回收是必须要考虑的问题。我们知道Dotnet垃圾回收有两种不同的模式:工作站模式和服务器模式。前者针对资源使用最少的快速响应进行了优化,而后者则用于高吞吐量。Dotnet运行时,桌面应用程序中的GC模式默认设置为工作站模式,服务器中的GC模式设置为服务器模式。这个默认值几乎总是最好的。在服务器中,GC会使用更多的机器资源,但可以处理更大的吞吐量。换句话说,该进程将有更多线程专用于垃圾收集,并且每秒能够释放更多字节。与系统自动默认为GC模式相比,手动设置应用程序的垃圾收集模式会是一种更安全的做法。服务器并不总是正确地知道需要什么回收模式。5.减少不必要的客户端请求客户端请求的数量在很大程度上可以决定服务器的数量或服务器的负载。因此,通过一些技巧减少服务器请求也是优化的一部分。该内容需要在申请中具体讨论或体验。我举几个实际的例子:自动补全机制通常是这样应用的,即当我们在前端输入时,客户端从输入的第一个字符开始调用API。比如我们输入“Dotnet”,那么我们就会向服务器发送6个请求---“D”、“Do”、“Dot”、“Dotn”等等。但实际上,考虑到输入的连续性,我们可以在调用前做一个短暂的延迟,比如停止输入500ms再向服务器发送请求。你可能不相信,但我们实际应用中的实测结果可以减少93%的调用。客户端缓存还是上面的例子。对于同一个应用,很多位置的输入是相同或相似的。如果我们将自动完成结果缓存在客户端,而不是每次都发送这些请求,我们也可以减少很多不必要的请求。在批处理应用程序中,页面与服务器之间通常有很多交互。通常最无脑的做法就是发送事件请求。这种方式无形中会给服务器带来不小的压力。如果可能的话,将这些事件合并到一个请求中会更有效且对服务器更友好。6、妥善处理挂起的请求客户端对服务器的请求可能会被挂起。也就是客户端发出了请求,但是没有收到响应,或者准确的说,经过了比较长的时间后,收到了一个超时响应。虽然我们不希望这种情况发生,但它一直在发生:处理请求时间过长,或者代码死锁,或者代码出错而没有正确捕获错误,当然还有等待一些应该已经出现但没有出现的东西,例如来自队列的消息、较长的数据库响应或对另一个服务的调用。本质上,当一个请求被挂起时,一个或多个线程也被挂起。但是应用程序并没有停止并继续处理新的请求。如果这个挂起在其他请求上重现,随着时间的推移,越来越多的线程挂起,最终影响服务器或系统的响应。所以requesthang对服务器性能的影响是非常大的。要解决这个问题,就需要对核心部分,也就是挂起的部分进行调试,保证程序处理各种可能的情况,不会造成意外的挂起。7.服务器崩溃服务器崩溃也是一个可能的性能问题。一般而言,客户端请求过程中出现正常异常时,应用程序不会崩溃。但是总是会出现一些问题,比如上下文外的异常,或者一些灾难性的异常,比如OutOfMemoryException、ExecutionEngineException、StackOverflowException,当这些出现的时候,不管加多少catch,都无法阻止崩溃。通常,如果应用程序托管在WebServer上,例如IIS、Nginx或Jexus上的ASP.Net应用程序,当应用程序崩溃时,WebServer会自动回收资源并重启应用程序。客户端感知是暂时的缓慢响应或503错误。但是,如果是直接启动的ASP.Net应用程序,程序会永久关闭,需要手动重启。这将是一个问题。因此,一方面,使用WebServer将是一个好习惯。另一方面还是要检查代码,从根本上解决问题。8.永远记住应用的规模这个问题说起来容易,但在实际开发中,应用的规模往往被遗忘或忽略。使用缓存时,你会忘记分布式缓存和同步问题,直接使用单机内存缓存;写入数据库时??,你会忘记并发下的数据一致性问题;..太多了,解决方案就不一一写了。就是从一开始就对代码进行扩容---从开发到测试,都采用双向扩容,即水平扩容(向外扩容)和垂直扩容(向上扩容)。垂直扩展意味着向服务器机器添加更多功能,例如更多CPU和RAM,而水平扩展意味着添加更多机器。请记住,从开发和测试开始,就必须使用与生产环境相同规模的环境。9、同步和异步应用服务不同于桌面应用或终端应用。当一个服务在执行过程中需要等待响应,比如数据库操作或者调用其他服务,这个服务本身就开始存在一定的风险。如果数据库或其他服务忙于处理其他请求,或者出现性能问题,性能问题将不可避免地传递给调用者。该怎么办?要解决的基本模式是异步调用。异步调用有两层意思:代码的异步调用就是我们常说的async和await。对框架的异步调用。这通常是通过使用像Kafka或RabbitMQ这样的队列服务来完成的。将消息发送到队列而不等待响应。这些消息由另一个服务拾取并处理。这种方法通常是一种不需要回复的服务。如果您需要回复,您还可以使用SignalR等推送通知。重要的是,通过这种方式,系统组件不需要主动等待服务。一切都是异步处理的。服务之间的耦合可以松散得多。当然,这会使代码变得更加复杂。权衡是对代码的控制。10.一个小总结。出差期间,断断续续写下了这个东西。好像有点乱,不过就是这样,:P在实际项目中,很多方面如果不注意,就会把服务器的性能搞得一团糟,出错的地方也很多。至于解决办法,没有捷径和窍门。它需要周密的计划、经验丰富的工程师和大量的缓冲时间来处理可能出现的问题。后面会写一些工具的应用。在很多方面,仍然有很好的工具可以帮助解决或至少快速找到问题。总之,这是一个亲身经历的演讲,希望能给大家一个很好的示范。
