那么,设置什么样的线程数最合理呢?其实很多多线程的场景都有一个默认配置,即“线程数 = 2 * CPU 核数 + 1”。除去这个配置,还有一个根据最佳实践得出来的公式:
线程数 = [(线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间] × CPU 数量
当然,最好的办法是通过性能测试来发现最佳的线程数。
换句话说,要提升性能我们就要减少 CPU 的执行时间,另外就是要设置一个合理的并发线程数,通过这两方面来显著提升服务器的性能。
现在,你知道了如何来快速提升性能,那接下来你估计会问,我应该怎么发现系统哪里最消耗 CPU 资源呢?
就服务器而言,会出现瓶颈的地方有很多,例如 CPU、内存、磁盘以及网络等都可能会导致瓶颈。此外,不同的系统对瓶颈的关注度也不一样,例如对缓存系统而言,制约它的是内存,而对存储型系统来说 I/O 更容易是瓶颈。
我们定位的场景是秒杀,它的瓶颈更多地发生在 CPU 上。
怎样简单地判断 CPU 是不是瓶颈呢?一个办法就是看当 QPS 达到极限时,你的服务器的 CPU 使用率是不是超过了 95%,如果没有超过,那么表示 CPU 还有提升的空间,要么是有锁限制,要么是有过多的本地 I/O 等待发生。
如何优化系统对 Java 系统来说,可以优化的地方很多,这里我重点说一下比较有效的几种手段,供你参考,它们是:减少编码、减少序列化、Java 极致优化、并发读优化。接下来,我们分别来看一下。
1. 减少编码Java 的编码运行比较慢,这是 Java 的一大硬伤。在很多场景下,只要涉及字符串的操作(如输入输出操作、I/O 操作)都比较耗 CPU 资源,不管它是磁盘 I/O 还是网络 I/O,因为都需要将字符转换成字节,而这个转换必须编码。每个字符的编码都需要查表,而这种查表的操作非常耗资源,所以减少字符到字节或者相反的转换、减少字符编码会非常有成效。减少编码就可以大大提升性能
2. 减少序列化序列化也是 Java 性能的一大天敌,减少 Java 中的序列化操作也能大大提升性能。又因为序列化往往是和编码同时发生的,所以减少序列化也就减少了编码。
序列化大部分是在 RPC 中发生的,因此避免或者减少 RPC 就可以减少序列化,当然当前的序列化协议也已经做了很多优化来提升性能。有一种新的方案,就是可以将多个关联性比较强的应用进行“合并部署”,而减少不同应用之间的 RPC 也可以减少序列化的消耗。
所谓“合并部署”,就是把两个原本在不同机器上的不同应用合并部署到一台机器上,当然不仅仅是部署在一台机器上,还要在同一个 Tomcat 容器中,且不能走本机的 Socket,这样才能避免序列化的产生。
另外针对秒杀场景,我们还可以做得更极致一些,接下来我们来看第 3 点:Java 极致优化。
3. Java 极致优化Java 和通用的 Web 服务器(如 Nginx 或 Apache 服务器)相比,在处理大并发的 HTTP 请求时要弱一点,所以一般我们都会对大流量的 Web 系统做静态化改造,让大部分请求和数据直接在 Nginx 服务器或者 Web 代理服务器(如 Varnish、Squid 等)上直接返回(这样可以减少数据的序列化与反序列化),而 Java 层只需处理少量数据的动态请求。针对这些请求,我们可以使用以下手段进行优化:
1、直接使用 Servlet 处理请求。避免使用传统的 MVC 框架,这样可以绕过一大堆复杂且用处不大的处理逻辑,节省 1ms 时间(具体取决于你对 MVC 框架的依赖程度)
2、直接输出流数据。使用 resp.getOutputStream() 而不是 resp.getWriter() 函数,可以省掉一些不变字符数据的编码,从而提升性能;数据输出时推荐使用 JSON 而不是模板引擎(一般都是解释执行)来输出页面。
也许有读者会觉得这个问题很容易解决,无非就是放到 Tair 缓存里面。集中式缓存为了保证命中率一般都会采用一致性 Hash,所以同一个 key 会落到同一台机器上。虽然单台缓存机器也能支撑 30w/s 的请求,但还是远不足以应对像“大秒”这种级别的热点商品。那么,该如何彻底解决单点的瓶颈呢?
答案是采用应用层的 LocalCache,即在秒杀系统的单机上缓存商品相关的数据。
那么,又如何缓存(Cache)数据呢?你需要划分成动态数据和静态数据分别进行处理:
1、像商品中的“标题”和“描述”这些本身不变的数据,会在秒杀开始之前全量推送到秒杀机器上,并一直缓存到秒杀结束;