博客侧边栏性能优化实战记录

  Java   21分钟   106浏览   0评论
AI 智能摘要
AI正在分析文章内容

项目背景

最近为博客文章页(post.html)新增了"热门文章"和"最新文章"侧边栏功能,但发现从首页进入文章页时加载速度明显变慢。本文记录了从问题发现到完整优化的全过程。

需求分析

功能需求

  1. 热门文章区域:展示5篇文章,按浏览量从高到低排序
  2. 最新文章区域:展示3篇文章,按创建时间从新到旧排序
  3. 响应式设计:仅在PC端(≥1024px)显示,移动端自动隐藏
  4. 固定定位:随页面滚动保持固定位置

性能问题

  • 从首页进入文章页时加载缓慢
  • 每篇文章页都要查询所有文章数据
  • 查询包含大文本字段(article_content)

初始实现

1. 后端代码(IndexController.java)

@GetMapping(value = {"post/{articleUrl}", "post/{articleUrl}.html"})
public String post(Model model, @PathVariable String articleUrl, ...) {
    // ... 其他代码 ...

    // 获取热门文章(查询所有可见文章,按浏览量排序,前5篇)
    List<ArticleCustom> allArticles = articleService.findAllArticle(
        ArticleStatus.PUBLISH.getStatus(), 
        PostType.POST_TYPE_POST.getValue()
    );

    // 按浏览量排序并取前5篇
    List<ArticleCustom> sortedHotArticles = allArticles.stream()
        .sorted((a1, a2) -> {
            Long views1 = a1.getArticleViews() != null ? a1.getArticleViews() : 0L;
            Long views2 = a2.getArticleViews() != null ? a2.getArticleViews() : 0L;
            return views2.compareTo(views1);
        })
        .limit(5)
        .collect(Collectors.toList());
    model.addAttribute("hotArticles", sortedHotArticles);

    // 获取最新文章(按创建时间排序,前3篇)
    List<ArticleCustom> sortedLatestArticles = allArticles.stream()
        .sorted((a1, a2) -> {
            if (a1.getArticleNewstime() == null) return 1;
            if (a2.getArticleNewstime() == null) return -1;
            return a2.getArticleNewstime().compareTo(a1.getArticleNewstime());
        })
        .limit(3)
        .collect(Collectors.toList());
    model.addAttribute("latestArticles", sortedLatestArticles);

    return this.render("post");
}

2. 前端代码(post.html)

<!-- 页面整体容器 -->
<div class="post-page-wrapper">
    <!-- PC端左侧边栏 - 独立于文章容器 -->
    <aside class="post-sidebar-left">
        <div class="sidebar-left-inner">
            <!-- 热门文章区域 -->
            <div class="sidebar-section hot-articles">
                <h3 class="sidebar-title">
                    <i class="fa fa-fire"></i>
                    热门文章
                </h3>
                <ul class="sidebar-list">
                    <li class="sidebar-item" th:each="hotArticle, iterStat : ${hotArticles}">
                        <a th:href="@{'/post/'+${hotArticle.articleUrl}}" class="sidebar-link">
                            <span class="sidebar-rank" th:text="${iterStat.index + 1}"></span>
                            <span class="sidebar-text" th:text="${hotArticle.articleTitle}"></span>
                        </a>
                        <div class="sidebar-meta">
                            <i class="fa fa-eye"></i>
                            <span th:text="${hotArticle.articleViews}"></span>
                        </div>
                    </li>
                </ul>
            </div>
            <!-- 最新文章区域 -->
            <div class="sidebar-section latest-articles">
                <h3 class="sidebar-title">
                    <i class="fa fa-clock-o"></i>
                    最新文章
                </h3>
                <ul class="sidebar-list">
                    <li class="sidebar-item" th:each="latestArticle : ${latestArticles}">
                        <a th:href="@{'/post/'+${latestArticle.articleUrl}}" class="sidebar-link">
                            <span class="sidebar-dot"></span>
                            <span class="sidebar-text" th:text="${latestArticle.articleTitle}"></span>
                        </a>
                    </li>
                </ul>
            </div>
        </div>
    </aside>

    <!-- 文章主体区域 -->
    <article class="main-content page-page">
        <!-- 文章内容... -->
    </article>
</div>

3. CSS样式

/* 页面整体容器 */
.post-page-wrapper {
    display: flex;
    gap: 30px;
    max-width: 1400px;
    margin: 0 auto;
    padding: 0 20px;
}

/* 左侧边栏基础样式 - 默认隐藏 */
.post-sidebar-left {
    display: none;
}

/* PC端显示左侧边栏 */
@media (min-width: 1024px) {
    .post-sidebar-left {
        display: block;
        width: 260px;
        flex-shrink: 0;
        position: relative;
    }

    /* 左侧边栏内部容器 - 固定定位 */
    .sidebar-left-inner {
        position: sticky;
        top: 100px;
        max-height: calc(100vh - 120px);
        overflow-y: auto;
    }
}

性能优化

问题分析

  1. 查询字段过多findAllArticle查询返回所有字段,包括article_content(长文本)
  2. 无缓存机制:每次访问文章页都要查询数据库
  3. 内存排序:在Java内存中对所有文章进行排序
  4. 数据传输量大:大文本字段占用网络带宽

优化方案

1. SQL查询优化

新增Mapper方法(ArticleMapperCustom.java)

/**
 * 查询侧边栏文章列表(只查询必要字段,不包含content)
 */
List<ArticleCustom> findSidebarArticles(@Param(value = "status") int status, 
                                        @Param(value = "post") String post);

优化SQL(ArticleMapperCustom.xml)

<!-- 查询侧边栏文章列表(只查询必要字段,不包含content,提高查询速度) -->
<select id="findSidebarArticles" resultType="com.zou.blog.model.domain.ArticleCustom">
    SELECT
        id,
        article_title,
        article_url,
        article_views,
        article_newstime,
        article_thumbnail
    FROM mayday_article
    WHERE article_status = #{status}
    AND article_post = #{post}
    ORDER BY article_newstime DESC
</select>

优化点

  • 只查询必要字段(不包含content大文本)
  • SQL层面按时间排序,减少Java内存排序

2. Redis缓存优化

Service层添加缓存(ArticleServiceImpl.java)

/**
 * 查询侧边栏文章列表(带缓存,只查询必要字段)
 */
@Override
@Cacheable(value = "articles", key = "'sidebarArticles'+#status+#post")
public List<ArticleCustom> findSidebarArticles(int status, String post) {
    return articleMapperCustom.findSidebarArticles(status, post);
}

缓存策略

  • 缓存键:sidebarArticles0post
  • 缓存清除:文章保存、更新、删除时自动清除
@Override
@CacheEvict(value = "articles", allEntries = true, beforeInvocation = true)
public void save(Article article, Long[] tags, Long[] categorys) throws Exception {
    // 保存文章...
}

@Override
@CacheEvict(value = "articles", allEntries = true, beforeInvocation = true)
public void update(Article article, Long[] tags, Long[] categorys) throws Exception {
    // 更新文章...
}

3. Controller优化

// 获取侧边栏文章列表(带缓存,只查询必要字段)
List<ArticleCustom> sidebarArticles = articleService.findSidebarArticles(
    ArticleStatus.PUBLISH.getStatus(), 
    PostType.POST_TYPE_POST.getValue()
);

// 获取热门文章(按浏览量排序,前5篇)
List<ArticleCustom> sortedHotArticles = sidebarArticles.stream()
    .sorted((a1, a2) -> {
        Long views1 = a1.getArticleViews() != null ? a1.getArticleViews() : 0L;
        Long views2 = a2.getArticleViews() != null ? a2.getArticleViews() : 0L;
        return views2.compareTo(views1);
    })
    .limit(5)
    .collect(Collectors.toList());
model.addAttribute("hotArticles", sortedHotArticles);

// 获取最新文章(按创建时间排序,前3篇)- 数据已按时间排序,直接取前3条
List<ArticleCustom> sortedLatestArticles = sidebarArticles.stream()
    .limit(3)
    .collect(Collectors.toList());
model.addAttribute("latestArticles", sortedLatestArticles);

优化点

  • 使用带缓存的新方法
  • 最新文章直接取前3条(SQL已排序)

4. 数据库索引优化

建议添加复合索引:

-- 侧边栏查询优化索引(状态+类型+时间)
ALTER TABLE mayday_article 
ADD INDEX idx_sidebar_query (article_status, article_post, article_newstime);

-- 热门文章排序优化索引
ALTER TABLE mayday_article 
ADD INDEX idx_hot_articles (article_status, article_post, article_views);

优化效果对比

指标 优化前 优化后
查询字段 所有字段(含content大文本) 只查询必要字段
数据库查询 每次访问都查询 缓存命中时不查询
内存排序 对所有文章排序两次 只排序热门文章
数据传输 传输大文本 不传输content
响应时间 较慢 首次正常,后续毫秒级

关键技术点

1. Sticky定位实现侧边栏固定

.sidebar-left-inner {
    position: sticky;
    top: 100px;  /* 距离视口顶部距离 */
    max-height: calc(100vh - 120px);
    overflow-y: auto;
}

2. Spring Cache使用

  • @Cacheable:缓存方法返回值
  • @CacheEvict(allEntries = true):清除所有缓存
  • 缓存命名空间:articles

3. MyBatis字段选择

避免使用SELECT *,明确指定需要的字段,特别是排除大文本字段。

总结

本次优化从三个层面提升了性能:

  1. SQL层:只查询必要字段,减少数据传输
  2. 缓存层:使用Redis缓存,减少数据库查询
  3. 应用层:优化排序逻辑,减少内存计算

优化后从首页进入文章页的速度明显提升,用户体验得到改善。

后续优化建议

  1. 数据库索引:根据实际查询情况添加合适的复合索引
  2. 缓存过期:考虑设置缓存过期时间,平衡实时性和性能
  3. 异步加载:侧边栏数据可以异步加载,进一步提升首屏速度
  4. CDN加速:静态资源使用CDN,减少服务器压力
如果你觉得文章对你有帮助,那就请作者喝杯咖啡吧☕
微信
支付宝
  0 条评论