这篇博文所列举的优化手段是针对比较传统项目,但是想提高系统的吞吐量现在时髦的技术还是那些前后端未分离, 使用nginx当成静态资源服务器去代理我们的静态资源 是谁限制了Throughput?# 当我们对一个传统的项目进行压力测试时,很容器就发现,系统的Throughput被数据库(mysql)限制的死死的,尽管代码看起来确实没毛病,逻辑也没有错误,但是过多的请求都被打向了数据库,数据库自个开启大量的IO操作,这样大的负载甚至会使Linux系统的整体负载骤然飙升,但是反观我们的系统的吞吐量,呵呵... 将目光投向缓存# 既然mysql的抗压能力限制了我们的系统,那就将数据缓存起来,尽一切可能减少用户和数据库之间的直接接触的次数,这样我们的系统的吞吐量,同一时间能处理器的请求数量自然会升上去 市面上的缓存技术很多, 比较火爆的是两款缓存数据库 Memcache 和 Redis , Redis 和 Memcahe的区别 Redis不仅仅支持key-value键值对类型的数据,同时还支持list,set,hash等数据结构 redis支持数据的备份,即master-slaver模式的集群备份 Redis是支持数据持久化的,它可以将内存中的数据保存在磁盘中,支持RDB和AOF两种持久化形式 对Redis进行压测# Copy # 挨个测试redis中的命令 # 每个数据包大小是3字节 # 100个并发, 发起10万次请求 redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000 [root@139 ~]# redis-benchmark -h 127.0.0.1 -p 9997 -c 100 -n 100000 ====== PING_INLINE ====== 100000 requests completed in 1.04 seconds 100 parallel clients 3 bytes payload keep alive: 1 98.68% <= 1 milliseconds // 百分之98.68的请求在1秒内完成了 99.98% <= 2 milliseconds 100.00% <= 2 milliseconds 96525.09 requests per second // 每秒完成的请求数在9万六左右 -d 指定数据包的大小,看下面redis的性能还是很强大的 -q 简化输出的参数 [root@139 ~]# redis-benchmark -h 127.0.0.1 -p 9997 -q -d 100 -c 100 -n 100000 PING_INLINE: 98619.32 requests per second PING_BULK: 95877.28 requests per second SET: 96153.85 requests per second GET: 95147.48 requests per second INCR: 95238.10 requests per second LPUSH: 95328.88 requests per second RPUSH: 95877.28 requests per second LPOP: 95328.88 requests per second RPOP: 97276.27 requests per second SADD: 96339.12 requests per second HSET: 98231.83 requests per second SPOP: 94607.38 requests per second LPUSH (needed to benchmark LRANGE): 92165.90 requests per second LRANGE_100 (first 100 elements): 97181.73 requests per second LRANGE_300 (first 300 elements): 96153.85 requests per second LRANGE_500 (first 450 elements): 94428.70 requests per second LRANGE_600 (first 600 elements): 95969.28 requests per second MSET (10 keys): 98231.83 requests per second 只测试 指定的命令 -t 跟多个命令参数 [root@139 ~]# redis-benchmark -p 9997 -t set,get -q -n 100000 -c 100 SET: 97276.27 requests per second GET: 98135.42 requests per second 从上面的压力测试中,可以看到,Redis的性能是绝对实力, 相当强悍,和mysql相比不是一个量级的, 所以结论很明显,如果我们在用户和mysql中键加一层redis做缓存,系统的吞吐量自然会上去 于是为了提高系统的抗压能力,我们将压力从mysql逐步转移到redis中 页面缓存技术# 在说页面缓存之前,我们先说一下在一个传统的项目中,一个请求的生命周期大概是这样的: 从浏览器发出到服务端, 服务端查询数据库获取结果, 再将结果数据传递给模板引擎将数据渲染进html页面 想提高这个过程的速度,我们可以这样搞, 页面缓存, 顾名思义就是将 html 页面缓存到缓存数据库中 示例如下: 一开始我们会先尝试从缓存中获取出已经渲染好的html源码响应给客户端, 响应的格式通过@ResponseBody和produces中的属性进行控制,告诉浏览器自己会返回给它html文本 优点: 将用户的请求的压力从mysql转移到redis, 这点强度对redis单机来说根本不是事 缺点: 很明显,将请求扩大到页面级别,数据一致性难免会受到影响, 这也是使用页面缓存不得不考虑的一点 特点1 : 严格控制缓存的时间, 一定别忘了添加过期时间... 特点2 : 原来都是让thymeleaf自动完成数据的渲染,现在的话,很明显是我们手动在渲染数据 举个例子 Copy @RequestMapping(value = "/to_list",produces = "text/html;charset=UTF-8") @ResponseBody public String toLogin(Model model, User user, HttpServletResponse response, HttpServletRequest request) { // 先从redis缓存中获取数据 String html = redisService.get(GoodsKey.goodsList, "", String.class); if (html != null) return html; // 查询商品列表 List goodsList = goodsService.getGoodsList(); model.addAttribute("goodsList", goodsList); // 使用Thymeleaf模板引擎手动渲染数据 WebContext springWebContext = new WebContext(request,response,request.getServletContext(),request.getLocale(),model.asMap()); String goods_list = thymeleafViewResolver.getTemplateEngine().process("goods_list", springWebContext); // 存入redis if (goods_list!=null){ redisService.set(GoodsKey.goodsList,"",goods_list); } return goods_list; } 既然都说到这里了, 就接着说还能怎么玩吧... 你看, 上面通过手动控制模板引擎的api竟然得到的已经渲染好的html源代码了, 什么叫做已经渲染好的? 说白了就是原来我在前端写:th ${user},这样的占位符,现在已经被thymeleaf替换成了 张三 ... (说的够直接吧) 拿到了已经渲染好的源代码,我们就能通过IO操作,将这个文件写到系统的某个目录上去,不知道大家有没有发现,去逛京东淘宝浏览某个商品页面时,就会发现url是类似这样的 www.jjdd.com/aguydg/ahdioa/1235345.html 这个后缀123145.html 大概率说明京东使用静态页的技术, 这太明智了,面对如此巨大数量的商品信息后缀用数字来表示也不错,而且速度还快不是? 怎么实现这种效果呢? 就是上面说的,通过IO将这些源码的数据写到Linux中的某一个目录下面, 文件名就是上面URL中的最后的数字, 通过Nginx做静态资源服务器将这些xxx.html代理起来, 用户再访问的话就走这个静态页, 同样不会接触数据库, 而且nginx还支持零拷贝,并发数5万不是事... 还有,后缀数组最好也别乱写,直接使用商品id会更好,毕竟是先点击商品获取到id,再进入到静态页 对象缓存技术# 缓存java中的对象, 比如将用户的信息持久化进redis, 每次用户查询自己的信息先从redis中查询,有的话直接返回,没有的话再去查询数据库, 这样同样实现了在用户和数据库之间多添加出一层缓存,也可以大大的提高系统的吞吐量 一般会怎么玩呢? 举个例子 用户的请求在查询数据库之前先尝试从redis中获取对象信息, redis中不存在的话就去数据库中查询, 查询完结果后将这个结果换存进redis Copy // todo 使用redis做缓存,减少和数据库的接触次数 public Label findById(Long labelId) { // 先尝试从缓存中查询当前对象 Label label = (Label) redisTemplate.opsForValue().get("label_id" + labelId); if (label==null){ Optional