问答平台(7),优化网站的性能

缓存过程

缓存过程-图示

本地缓存

1
2
- 将数据缓存在应用服务器上,性能最好。
- 常用缓存工具:Ehcache、Guava、Caffeine等。

应用场景

  • 缓存数据要求与用户没有强关联,效率更高。

分布式缓存

1
2
- 将数据存在 NoSQL 数据库上,跨服务器。
- 常用缓存工具:MemCache、Redis等。

应用场景

  • 缓存数据没有局限性,适用范围更广。

多级缓存

1
2
- 一级缓存(本地缓存) > 二级缓存(分布式缓存) > DB
- 避免缓存雪崩(缓存失效,大量请求直达DB),提高系统的可用性。

Caffeine

  • 缓存帖子列表(优化热帖排行)

引入依赖

1
2
3
4
5
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.7.0</version>
</dependency>

配置文件

  • application-develop.properties: 增加内容
    1
    2
    3
    # caffeine
    caffeine.posts.max-size=15
    caffeine.posts.expire-seconds=180

业务层

  • DiscussPostService: 修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    private static final Logger logger = LoggerFactory.getLogger(DiscussPostService.class);

    @Value("${caffeine.posts.max-size}")
    private int maxSize;

    @Value("${caffeine.posts.expire-seconds}")
    private int expireSeconds;

    // Caffeine核心接口: Cache, LoadingCache, AsyncLoadingCache

    // 帖子列表缓存
    private LoadingCache<String, List<DiscussPost>> postListCache;

    // 帖子总数缓存
    private LoadingCache<Integer, Integer> postRowsCache;

    @PostConstruct
    public void init() {
    // 初始化帖子列表缓存
    postListCache = Caffeine.newBuilder()
    .maximumSize(maxSize)
    .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
    .build(new CacheLoader<String, List<DiscussPost>>() {
    @Nullable
    @Override
    public List<DiscussPost> load(@NonNull String key) throws Exception {
    if (key == null || key.length() == 0) {
    throw new IllegalArgumentException("参数错误!");
    }

    String[] params = key.split(":");
    if (params == null || params.length != 2) {
    throw new IllegalArgumentException("参数错误!");
    }

    int offset = Integer.valueOf(params[0]);
    int limit = Integer.valueOf(params[1]);

    // 二级缓存: Redis -> mysql

    logger.debug("load post list from DB.");
    return discussPostMapper.selectDiscussPosts(0, offset, limit, 1);
    }
    });

    // 初始化帖子总数缓存
    postRowsCache = Caffeine.newBuilder()
    .maximumSize(maxSize)
    .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
    .build(new CacheLoader<Integer, Integer>() {
    @Nullable
    @Override
    public Integer load(@NonNull Integer key) throws Exception {

    logger.debug("load post rows from DB.");
    return discussPostMapper.selectDiscussPostRows(key);
    }
    });
    }

    public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit, int orderMode) {
    if (userId == 0 && orderMode == 1) {
    return postListCache.get(offset + ":" + limit);
    }

    logger.debug("load post list from DB.");
    return discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);
    }

    public int findDiscussPostRows(int userId) {
    // 首页
    if (userId == 0) {
    return postRowsCache.get(userId);
    }

    logger.debug("load post rows from DB.");
    return discussPostMapper.selectDiscussPostRows(userId);
    }

测试类

  • CaffeineTests: 新增
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @ContextConfiguration(classes = CommunityApplication.class)
    public class CaffeineTests {

    @Autowired
    private DiscussPostService discussPostService;

    @Test
    public void initDataForTest() {
    for (int i = 0; i < 300000; i++) {
    DiscussPost post = new DiscussPost();
    post.setUserId(111);
    post.setTitle("互联网求职暖春计划");
    post.setContent("今年的就业形势,确实不容乐观。过了个年,仿佛跳水一般,整个讨论区哀鸿遍野!19届真的没人要了吗?!18届被优化真的没有出路了吗?!大家的“哀嚎”与“悲惨遭遇”牵动了每日潜伏于讨论区的牛客小哥哥小姐姐们的心,于是牛客决定:是时候为大家做点什么了!为了帮助大家度过“寒冬”,牛客网特别联合60+家企业,开启互联网求职暖春计划,面向18届&19届,拯救0 offer!");
    post.setCreateTime(new Date());
    post.setScore(Math.random() * 2000);
    discussPostService.addDiscussPost(post);
    }
    }

    @Test
    public void testCache() {
    // System.out.println(discussPostService.findDiscussPosts(0, 0, 10, 1));
    // System.out.println(discussPostService.findDiscussPosts(0, 0, 10, 1));
    // System.out.println(discussPostService.findDiscussPosts(0, 0, 10, 1));
    // System.out.println(discussPostService.findDiscussPosts(0, 0, 10, 0));
    System.out.println(discussPostService.findDiscussPostRows(0));
    System.out.println(discussPostService.findDiscussPostRows(0));
    System.out.println(discussPostService.findDiscussPostRows(0));
    System.out.println(discussPostService.findDiscussPostRows(1));
    }
    }

压力测试

  • jmeter

配置

线程组-图示
HTTP请求-图示
统一随机定时器-图示
聚合报告-图示

结果展示

优化前压力测试-图示
优化后压力测试-图示

结果说明

1
2
- 优化网站性能(本地缓存帖子列表,优化热帖排行)
- jmeter压力测试(未使用本地缓存的情况下,100个线程1分钟内访问热帖排行的吞吐量为5 req/s;使用本地缓存的情况下,100个线程1分钟内访问热帖排行的吞吐量接近150 req/s)

参考资料


问答平台(7),优化网站的性能
https://lcf163.github.io/2020/06/20/问答平台(7),优化网站的性能/
作者
乘风的小站
发布于
2020年6月20日
许可协议