总结-17 优化服务器性能

一般来说我们优化服务器的性能会考虑从缓存入手,对于分布式服务器我们采用redis这样的分布式缓存,但是有些数据不用考虑分布式,此时我们可以引入本地缓存,这里我们用性能更好的Caffeine。
如图是缓存的一个基本结构,app得到一个请求时,会从本地缓存中查看,如果没有命中,就去DB获得数据同时同步本地缓存。但是对于类似用户Token这样的不区分服务器的数据时,我们就需要使用分布式缓存。总的来说,分布式缓存需要网络连接,因此性能上差一点。
本地缓存和分布式缓存
在我们的项目中我们对热门帖子列表做缓存,这是因为热门帖子是每五分钟计算一次分数,因此引入缓存是有必要的,而对于首页的帖子如果引入缓存,那么每次用户发布一个帖子是无法立刻看到自己的帖子的,但是如果缓存的更新速度太快,反而会影响效率。
对于什么数据采用什么缓存是需要思考的,不然可能会适得其反。

配置

对caffeine的配置我们只要配置一个缓存的最大值和过期时间即可,这是我们在项目中需要用到的。

1# caffeine 2caffeine.posts.max-size=15 3caffeine.posts.expire-seconds=180 4 5

service

这是一篇对caffeine进行简单但是详细介绍的博客
在我们的项目中我们主要使用LoadingCache,是一个同步缓存会阻塞的接口。
声明两个分别表示帖子列表的缓存和帖子总数的缓存列表的LoadingCache实例。

1// 帖子列表的缓存 2private LoadingCache<String, List<DiscussPost>> postListCache; 3 4// 帖子总数的缓存 5private LoadingCache<Integer, Integer> postRowsCache; 6 7

我们先不实例化这两个对象,先来看看怎么在原来的方法中使用,当是查询热门帖子列表的时候,直接使用postListCache.get()方法即可,该方法会得到该cache中的内容,如果过期或者没有内容的话会调用其load()方法得到数据返回并同步缓存。

1public List<DiscussPost> findDiscussPosts(int userId, int offset,int limit, int orderMode){ 2 // 只有当查询首页的热门帖子时才使用缓存 3 if (userId == 0 && orderMode == 1){ 4 // 如果没有查到会调用其load方法同步数据,这是阻塞的 5 return postListCache.get(offset + ":" + limit); 6 } 7 logger.debug("load post list from DB."); 8 return discussPostMapper.selectDiscussPosts(userId,offset,limit,orderMode); 9} 10 11

接下来,我们就来初始化帖子列表的缓存,我们可以在@PostConstruct注解标注的init()方法中初始化。我们使用Caffeine提供的newBuilder()方法来设置缓存的最大容量和过期方式和时间,然后在build()中匿名实例化一个CacheLoader对象即可。在该方法中我们首先检查key是否合法,合法的话就得到offset和limit,然后这里我们使用二级缓存的方式,caffeine->redis->DB这样的方式。
缓存访问顺序
因此,当本地缓存不存在的时候,我们先去redis里查,如果redis里没有就去数据库里查到需要的数据,然后存入redis并设置过期时间,然后返回该数据即可,caffeine会将该数据作为缓存进行同步。如果redis中有,首先查看是否过期,如果过期了也是从数据库里查询然后同步到redis和caffeine中,如果没有过期就将redis中的数据作为结果返回。
redis对于过期数据的处理一般是惰性删除,即过期之后不立刻删除,等到下次读/写的时候才会将数据删除,因此其实可以不用写过期之后的处理,同没有该key是一样的,但是为了逻辑的完善还是写上去了。

1// 初始化帖子列表缓存 2postListCache = Caffeine.newBuilder() 3 .maximumSize(maxSize) 4 .expireAfterWrite(expireSeconds, TimeUnit.SECONDS) 5 .build(new CacheLoader<String, List<DiscussPost>>() { 6 @Nullable 7 @Override 8 public List<DiscussPost> load(@NonNull String key) throws Exception { 9 if (key == null || key.length() == 0){ 10 throw new IllegalArgumentException("参数错误!"); 11 } 12 String[] params = key.split(":"); 13 if (params == null || params.length != 2){ 14 throw new IllegalArgumentException("参数错误!"); 15 } 16 int offset = Integer.valueOf(params[0]); 17 int limit = Integer.valueOf(params[1]); 18 19 // 二级缓存:Redis -> mysql 20 String redisKey = RedisKeyUtil.getHotPostListKey(offset,limit); 21 // 如果redis中不存在就去数据库里查,查到了就更新到redis中,然后返回作为本地缓存的同步 22 if (!redisTemplate.hasKey(redisKey)){ 23 logger.debug("redis中没有缓存"); 24 logger.debug("load post list from DB."); 25 List<DiscussPost> result = discussPostMapper.selectDiscussPosts(0,offset,limit,1); 26 for(DiscussPost post : result){ 27 redisTemplate.opsForZSet().add(redisKey,post,post.getScore()); 28 } 29 redisTemplate.expire(redisKey,15 ,TimeUnit.SECONDS); 30 return result; 31 } else { 32 logger.debug("redis中有缓存"); 33 // 要查看redis数据是否过期 34 System.out.println(redisTemplate.getExpire(redisKey)); 35 boolean isExpired = redisTemplate.getExpire(redisKey) < 0 ? true : false; 36 if (isExpired){ 37 // 如果过期的话,就删去key,就去数据库里查 38 logger.debug("redis expired!"); 39 redisTemplate.delete(redisKey); 40 logger.debug("load post list from DB."); 41 List<DiscussPost> result = discussPostMapper.selectDiscussPosts(0,offset,limit,1); 42 for(DiscussPost post : result){ 43 redisTemplate.opsForZSet().add(redisKey,post,post.getScore()); 44 } 45 redisTemplate.expire(redisKey,7*60 ,TimeUnit.SECONDS); 46 return result; 47 } else { 48 Set<DiscussPost> posts = redisTemplate.opsForZSet().reverseRange(redisKey,offset,limit); 49 logger.debug("redis len: " + redisTemplate.opsForZSet().zCard(redisKey)); 50 logger.debug("load post list from redis."); 51 List<DiscussPost> list = new ArrayList<>(); 52 for(DiscussPost post : posts){ 53 list.add(post); 54 } 55 return list; 56 } 57 } 58 } 59 }); 60 61

这样就实现了对热门帖子的二级缓存,然后我们需要对缓存的性能进行简单的测试,这里我们使用jmeter进行测试,我们使用120个线程组以随机时间对不使用缓存的热门页进行访问,查看其吞吐量,然后以同样的条件访问带缓存的热门帖子进行对比吞吐量。
有缓存
没有缓存的
我们可以看到有缓存的吞吐量有200+,而没有缓存的吞吐量只有14不到,吞吐量的差距是十分巨大的。

代码交流 2021