让 Java 做的系统 “更能打”(缓存 Redis + 日志 + 自启,企业级稳定)

搞定了接口文档,前端能顺畅对接了,但系统还藏着几个 “隐形问题”:

每次查图书列表都要查数据库,数据多了会变慢,像奶茶店每次做奶茶都要现煮珍珠,客人等得着急;日志文件一天天变大,占满服务器硬盘,像奶茶店的垃圾桶不清理,越堆越满;服务器重启后,项目要手动双击 jar 包启动,像奶茶店每天要手动开门、开机器,麻烦还容易忘。

今天这篇咱们聚焦 3 个企业级实用优化:Redis 缓存(查得快)、日志归档(不占空间)、服务化部署(自动启),再补个 SQL 索引小技巧,让你的系统从 “能用” 变成 “好用、稳定”,真正符合企业上线标准。

一、先搞懂:优化的核心是 “解决隐形痛点”(奶茶店的优化类比)

新手常觉得 “功能能跑就行,优化没必要”,但企业里 “稳定” 和 “速度” 比 “能用” 更重要,就像奶茶店:

没优化的奶茶店:客人点单后才煮珍珠,等 10 分钟;垃圾桶满了没人倒,卫生差;早上店员迟到,开门晚了;优化后的奶茶店:提前煮好珍珠(缓存),客人点单 1 分钟取餐;每天定时倒垃圾(日志归档),保持整洁;早上自动开门(服务化),不用等店员。

咱们的优化目标也一样,3 个核心痛点对应 3 个解决方案:

隐形痛点 优化方案 企业级价值 奶茶店类比
频繁查数据库导致接口慢 Redis 缓存常用数据 减少数据库压力,接口响应快 5-10 倍 提前煮好珍珠,不用现煮
日志文件太大占硬盘 Logback 日志按大小 + 日期归档 控制日志体积,避免硬盘满了崩系统 每天定时倒垃圾,不堆积水
服务器重启后要手动启动 服务化部署(Windows 服务 / Linux 自启) 无人值守,重启后自动恢复,减少人工操作 早上自动开门,不用等店员

二、优化 1:Redis 缓存 —— 给接口装 “预制珍珠”(查得快)

常用数据(比如图书列表、热门图书)每次都查数据库,就像奶茶店每次都现煮珍珠,效率低。用 Redis 把这些数据 “提前存起来”,查的时候先读 Redis,没有再查数据库,接口响应速度能提升好几倍。

步骤 1:装 Redis + 加依赖(准备 “预制珍珠的容器”)

装 Redis

Windows:下载 Redis 压缩包(Redis 官网),解压后双击
redis-server.exe
启动,黑窗口显示 “Ready to accept connections” 就是成功;Linux:用 Docker 装,一行命令搞定:
docker run -d -p 6379:6379 --name redis-book redis:6.2

加 Redis 依赖:打开
pom.xml
,添加 Spring Boot Redis 依赖:

xml



<!-- Spring Boot Redis依赖(操作Redis) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.7.10</version>
</dependency>
 
<!-- commons-pool2(Redis连接池,提升性能) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

步骤 2:配 Redis 连接(连接 “预制珍珠容器”)


application.yml
里加 Redis 配置,告诉项目怎么连 Redis:

yaml



spring:
  # 其他配置(数据源等)不变...
  redis:
    host: localhost  # Redis地址(服务器部署填服务器IP)
    port: 6379       # Redis默认端口
    password: ""     # Redis密码(默认没密码,生产环境要设)
    lettuce:         # 连接池配置(用lettuce客户端,比jedis好)
      pool:
        max-active: 8   # 最大连接数
        max-idle: 8     # 最大空闲连接
        min-idle: 2     # 最小空闲连接
        max-wait: 1000ms # 最大等待时间(超过报错)

步骤 3:写缓存逻辑 ——“预制珍珠 + 更新珍珠”

咱们给 “查询图书列表” 和 “按作者查图书” 加缓存,核心逻辑:查数据时先读 Redis,没有再查数据库,查完存 Redis;改数据时(加 / 删图书)更细 Redis,避免脏数据

1. 写 Redis 工具类(简化 Redis 操作)


common
包下新建
RedisUtil.java
,封装常用的 “存缓存、取缓存、删缓存” 方法:

java

运行



package com.example.bookmanage.common;
 
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import java.util.concurrent.TimeUnit;
 
@Component
public class RedisUtil {
    private final StringRedisTemplate redisTemplate;
 
    // 构造注入(不用@Autowired,更规范)
    public RedisUtil(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
 
    /**
     * 存缓存(默认过期时间30分钟)
     * @param key 缓存键(比如"book:list")
     * @param value 缓存值(对象转JSON字符串)
     */
    public void setCache(String key, Object value) {
        setCache(key, value, 30, TimeUnit.MINUTES);
    }
 
    /**
     * 存缓存(自定义过期时间)
     */
    public void setCache(String key, Object value, long timeout, TimeUnit unit) {
        String valueStr = JSON.toJSONString(value); // 对象转JSON字符串
        redisTemplate.opsForValue().set(key, valueStr, timeout, unit);
    }
 
    /**
     * 取缓存(JSON字符串转对象)
     */
    public <T> T getCache(String key, Class<T> clazz) {
        String valueStr = redisTemplate.opsForValue().get(key);
        if (valueStr == null) {
            return null; // 缓存不存在,返回null
        }
        return JSON.parseObject(valueStr, clazz); // JSON转对象
    }
 
    /**
     * 删缓存(改数据时调用)
     */
    public void deleteCache(String key) {
        redisTemplate.delete(key);
    }
}
2. 改造 BookService—— 加缓存逻辑

修改
BookService
的查询和修改方法,整合缓存:

java

运行



package com.example.bookmanage.service;
 
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.bookmanage.common.RedisUtil;
import com.example.bookmanage.dao.BookMapper;
import com.example.bookmanage.entity.Book;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
 
@Slf4j
@Service
public class BookService extends ServiceImpl<BookMapper, Book> implements IService<Book> {
    private final RedisUtil redisUtil;
 
    // 注入RedisUtil
    public BookService(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }
 
    // 1. 查询所有图书(加缓存)
    public List<Book> getBookList() {
        String cacheKey = "book:list:all"; // 缓存键(唯一标识)
        // 先查缓存
        List<Book> bookList = redisUtil.getCache(cacheKey, List.class);
        if (bookList != null) {
            log.info("从Redis缓存获取图书列表");
            return bookList;
        }
        // 缓存不存在,查数据库
        bookList = baseMapper.selectList(null);
        log.info("从数据库获取图书列表,并存入Redis缓存");
        // 存入缓存(30分钟过期)
        redisUtil.setCache(cacheKey, bookList);
        return bookList;
    }
 
    // 2. 按作者查图书(加缓存)
    public List<Book> getBooksByAuthor(String author) {
        String cacheKey = "book:list:author:" + author; // 缓存键带作者名(唯一)
        // 先查缓存
        List<Book> bookList = redisUtil.getCache(cacheKey, List.class);
        if (bookList != null) {
            log.info("从Redis缓存获取作者{}的图书", author);
            return bookList;
        }
        // 查数据库
        com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Book> wrapper = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
        wrapper.eq("author", author);
        bookList = baseMapper.selectList(wrapper);
        log.info("从数据库获取作者{}的图书,并存入Redis缓存", author);
        // 存入缓存
        redisUtil.setCache(cacheKey, bookList);
        return bookList;
    }
 
    // 3. 添加图书(删缓存,避免脏数据)
    @Override
    public boolean save(Book book) {
        log.info("开始添加图书,书名:{}", book.getBookName());
        try {
            boolean result = super.save(book);
            // 添加成功后,删除图书列表缓存(下次查会重新从数据库读最新数据)
            redisUtil.deleteCache("book:list:all");
            // 如果知道作者,也删该作者的缓存(比如book:list:author:张三)
            if (book.getAuthor() != null) {
                redisUtil.deleteCache("book:list:author:" + book.getAuthor());
            }
            log.info("添加图书成功,已删除相关Redis缓存");
            return result;
        } catch (Exception e) {
            log.error("添加图书失败", e);
            throw e;
        }
    }
 
    // 4. 删除图书(同理,删缓存)
    @Override
    public boolean removeById(Serializable id) {
        // 先查要删除的图书,获取作者(用于删作者缓存)
        Book book = getById(id);
        boolean result = super.removeById(id);
        if (result && book != null) {
            // 删缓存
            redisUtil.deleteCache("book:list:all");
            redisUtil.deleteCache("book:list:author:" + book.getAuthor());
            log.info("删除图书成功,已删除相关Redis缓存");
        }
        return result;
    }
}

步骤 4:测试缓存效果 ——“是不是快了”

启动 Redis 和项目,两次访问
http://localhost:8080/book/list
,看日志:

第一次访问:日志显示 “从数据库获取图书列表,并存入 Redis 缓存”;第二次访问:日志显示 “从 Redis 缓存获取图书列表”,接口响应时间从 “几十毫秒” 降到 “几毫秒”,效果立竿见影!

🔍 图示 1:Redis 缓存效果对比示意图
访问次数 日志内容 响应时间 核心逻辑
第一次 从数据库获取图书列表,并存入 Redis 缓存 50ms 缓存空→查库→存缓存
第二次 从 Redis 缓存获取图书列表 3ms 缓存有→直接读缓存,不查库

三、优化 2:日志归档 —— 给日志 “定时倒垃圾”(不占空间)

之前的日志存在
D:/java-project-logs/book-manage/
,但只会按天分割,不会删旧日志,时间长了会堆几百个 G 的日志文件。咱们用 Logback 配置 “按大小 + 日期双分割”,并设置 “保留 30 天,超过自动删除”,像奶茶店 “每天倒垃圾,超过 30 天的旧垃圾直接清”。

步骤 1:修改 logback-spring.xml—— 配置日志归档

更新
src/main/resources/logback-spring.xml
,添加 “大小分割” 和 “过期删除” 规则:

xml



<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 日志格式和路径(不变) -->
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />
    <property name="LOG_PATH" value="D:/java-project-logs/book-manage" />
 
    <!-- 1. 控制台输出(不变) -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
 
    <!-- 2. 文件输出(按日期+大小分割,过期删除) -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 当前日志文件路径 -->
        <file>${LOG_PATH}/book-manage.log</file>
        
        <!-- 分割规则:按日期(天)+ 大小(500MB)分割 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 分割后的日志文件名:book-manage-2025-10-20.0.log(0表示当天的第1个500MB文件) -->
            <fileNamePattern>${LOG_PATH}/book-manage-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>500MB</maxFileSize>         <!-- 单个文件最大500MB,超过分割 -->
            <maxHistory>30</maxHistory>             <!-- 保留30天的日志,超过自动删除 -->
            <totalSizeCap>10GB</totalSizeCap>        <!-- 日志总大小不超过10GB,避免占满硬盘 -->
        </rollingPolicy>
        
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
 
    <!-- 3. 全局日志级别(不变) -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

步骤 2:看日志归档效果 ——“垃圾清干净了吗”

启动项目后,日志文件会按以下规则生成:

当天日志先存在
book-manage.log
;当文件达到 500MB,自动分割成
book-manage-2025-10-20.1.log

book-manage-2025-10-20.2.log
;超过 30 天的日志(比如 10 月 1 日的)会自动删除,总大小不会超过 10GB。

🔍 图示 2:日志归档后的文件结构示意图

plaintext



D:/java-project-logs/book-manage/
├─ book-manage.log                # 当前日志文件(当天)
├─ book-manage-2025-10-20.1.log   # 10月20日第1个500MB文件
├─ book-manage-2025-10-20.2.log   # 10月20日第2个500MB文件
├─ book-manage-2025-10-19.1.log   # 10月19日的日志(保留)
└─ (10月1日及之前的日志已自动删除)

四、优化 3:服务化部署 —— 让项目 “自动开门”(不用手动启)

之前服务器重启后,要手动双击 jar 包或输命令启动项目,麻烦还容易漏。咱们把项目做成 “系统服务”:Windows 用 WinSW 做成服务,Linux 用 systemd 设置自启,像奶茶店 “早上自动开门、开机器”,不用人工干预。

场景 1:Windows 服务器 —— 用 WinSW 做成系统服务

步骤 1:下载 WinSW 工具

进 WinSW 官网(WinSW GitHub),下载
WinSW-x64.exe
(64 位系统),重命名为
book-service.exe
,放到
D:/java-projects/
(和 jar 包同目录)。

步骤 2:写服务配置文件

在同目录新建
book-service.xml
(文件名要和 exe 一致),配置服务信息:

xml



<service>
    <!-- 服务名称(在Windows服务里显示) -->
    <id>BookManageService</id>
    <!-- 服务显示名称(易读) -->
    <name>图书管理系统服务</name>
    <!-- 服务描述 -->
    <description>Java图书管理系统的系统服务,重启后自动启动</description>
    
    <!-- 要执行的命令(java -jar 你的jar包) -->
    <executable>java</executable>
    <arguments>-jar "D:/java-projects/book-manage-1.0-SNAPSHOT.jar"</arguments>
    
    <!-- 日志配置(服务启动/停止日志) -->
    <logpath>D:/java-projects/service-logs/</logpath>
    <logmode>rotate</logmode> <!-- 日志按大小分割 -->
    
    <!-- 启动模式(自动启动) -->
    <startmode>Automatic</startmode>
    <!-- 停止命令(优雅停止) -->
    <stopexecutable>taskkill</stopexecutable>
    <stoparguments>/f /t /im java.exe /fi "windowtitle eq BookManageService"</stoparguments>
</service>
步骤 3:安装并启动服务

以 “管理员身份” 打开 CMD,进入
D:/java-projects/
,执行命令:

bash



# 安装服务
book-service.exe install
# 启动服务
book-service.exe start

然后打开 “Windows 服务”(Win+R 输入
services.msc
),能看到 “图书管理系统服务”,状态是 “正在运行”,启动类型是 “自动”—— 重启服务器后,服务会自动启动,不用再手动点 jar 包!

🔍 图示 3:Windows 服务安装效果示意图
操作命令 服务状态 核心效果

book-service.exe install
服务列表新增 “图书管理系统服务” 服务安装成功

book-service.exe start
服务状态变为 “正在运行” 服务启动,项目开始运行
重启服务器 服务自动恢复 “正在运行” 状态 无需手动干预,自动启动

场景 2:Linux 服务器 —— 用 systemd 设置自启

步骤 1:写 systemd 服务文件

新建
/etc/systemd/system/book-manage.service
文件,内容如下:

ini



[Unit]
# 服务描述
Description=Book Manage System Service
# 依赖:网络和Redis启动后再启动该服务
After=network.target redis.service
 
[Service]
# 启动用户(用普通用户,避免root权限风险)
User=root
# 工作目录(jar包所在目录)
WorkingDirectory=/home/java-projects/
# 启动命令
ExecStart=/usr/bin/java -jar /home/java-projects/book-manage-1.0-SNAPSHOT.jar
# 停止命令(优雅停止)
ExecStop=/bin/kill -15 $MAINPID
# 重启策略:异常退出后自动重启
Restart=on-failure
# 重启等待时间:2秒
RestartSec=2s
 
[Install]
# 开机自启:关联到multi-user.target(多用户模式)
WantedBy=multi-user.target
步骤 2:启用并启动服务

执行命令:

bash



# 重新加载systemd配置(让系统识别新服务)
systemctl daemon-reload
# 启动服务
systemctl start book-manage.service
# 设置开机自启
systemctl enable book-manage.service
# 查看服务状态(确认是否在运行)
systemctl status book-manage.service

看到 “active (running)” 就说明服务启动成功,重启 Linux 服务器后,执行
systemctl status book-manage.service
,服务会自动恢复运行。

🔍 图示 4:Linux 服务状态示意图

bash



[root@localhost ~]# systemctl status book-manage.service
● book-manage.service - Book Manage System Service
   Loaded: loaded (/etc/systemd/system/book-manage.service; enabled; vendor preset: disabled)
   Active: active (running) since  Tue 2025-10-21 10:00:00 CST; 5min ago
 Main PID: 1234 (java)
    Tasks: 20 (limit: 2345)
   Memory: 256.0M
   CGroup: /system.slice/book-manage.service
           └─1234 /usr/bin/java -jar /home/java-projects/book-manage-1.0-SNAPSHOT.jar

五、小优化:SQL 索引 —— 给数据库 “装快速查找器”

查询 “按作者查图书” 时,如果 book 表数据多(比如 10 万条),会扫全表,慢。给
author
字段加索引,像给数据库装 “快速查找器”,能秒定位到该作者的所有图书。

步骤 1:给 book 表加索引

在 Navicat 执行 SQL:

sql



-- 给book表的author字段加普通索引(适合频繁查询的字段)
CREATE INDEX idx_book_author ON book(author);

步骤 2:测试索引效果

加索引前,执行
EXPLAIN SELECT * FROM book WHERE author = '张三';
,看 “type” 列是 “ALL”(全表扫描);加索引后,再执行同样的 SQL,“type” 列变成 “ref”(走索引查询),查询时间从 “几百毫秒” 降到 “几毫秒”。

六、优化时的 5 个小心注意

Redis 缓存脏数据:添加 / 删除图书后忘了删缓存,导致查的是旧数据 —— 记住 “改数据必删缓存”,或用 “缓存过期时间” 兜底;日志路径不存在:Logback 配置的
LOG_PATH
(比如
D:/java-project-logs/book-manage
)没手动创建,会报 “找不到路径” 错误 —— 先创建目录再启动项目;Windows 服务权限不足:WinSW 没以管理员身份运行,安装服务时会报 “拒绝访问”—— 右键 CMD→“以管理员身份运行”;Linux 服务 jar 包路径错
ExecStart
里的 java 路径或 jar 包路径写错,服务启动失败 —— 用
which java
查 java 实际路径,用
ls
确认 jar 包是否存在;索引加太多:给 book 表的所有字段都加索引,导致添加 / 删除图书变慢 —— 只给 “频繁查询的字段”(如 author)加索引,索引是 “查询快、写慢”,平衡取舍。

© 版权声明

相关文章

暂无评论

none
暂无评论...