当你接了一笔十分抠门小老板的私活订单,他只有一台双核4G内存的服务器,仅此一台,十分抠门,不肯加多服务器。
还要满足以下要求:
-
后端服务用Java语言开发;(我猜想可能是他有个亲戚小孩大学刚刚毕业,他应该会一点点Java吧。不管啦,反正,将来都是他亲戚接手,我负责搞好第一个版本就行了。别到时候第三版又兜兜转转又回到我手上就行!) -
前后端分离; -
数据库和中间件都在这台可怜服务器; -
还有耗时任务要处理; -
监控与日志; -
要考虑将来能快速水平扩展。
这么多东西全部都挤到一台可怜巴巴的服务器上,也就是说我要最大限度地利用有限的可怜资源(双核CPU和4GB内存!!!),同时又要保持系统的稳定性和性能。
幸好钱给够了!!!我只能勉为其难帮忙搞搞吧。
下面是我提供的初步方案。
架构设计
-
后端服务:使用Spring Boot开发RESTful API。 -
前端应用:使用React.js构建单页应用(SPA)。 -
数据库:使用MySQL作为关系型数据库。 -
缓存层:使用Redis进行数据缓存。 -
消息队列:使用RabbitMQ处理异步任务(如发送邮件、短信验证码)。 -
负载均衡:由于只有一台服务器,暂时不需要真正的负载均衡器,但可以使用Nginx来代理请求并提供基本的反向代理功能。 -
CDN:使用外部CDN服务(如Cloudflare)来缓存静态资源。 -
监控与日志:使用Prometheus和Grafana进行监控,ELK Stack(Elasticsearch, Logstash, Kibana)进行日志管理。
架构图

具体配置参数
Spring Boot
-
server.tomcat.max-threads=100
: -
考虑因素: 每个线程大约占用约1MB堆栈空间。为了平衡并发能力和内存使用,我们需要为其他组件留出足够的内存。 -
计算: 假设每个线程占用1MB堆栈空间,4GB内存中留出2.5GB用于其他服务(包括MySQL、Redis、RabbitMQ等),剩下1.5GB可用于Tomcat线程池。因此,最多支持约1500个线程。然而,设置为100是为了平衡并发能力和内存使用,并确保系统稳定性。 -
server.connection-timeout=10s
: -
考虑因素: 设置较短的连接超时时间可以减少长时间挂起的无效连接,提高资源利用率。 -
计算: 10秒是一个合理的默认值,既不会太短导致正常请求被中断,也不会太长导致资源浪费。
MySQL
-
max_connections=50
: -
考虑因素: 每个连接需要一定的内存开销。在4GB内存中,留出大部分内存给应用程序和其他服务。 -
计算: 根据经验,每个MySQL连接大约占用几MB内存。假设每个连接占用8MB内存,50个连接大约占用400MB内存,留出足够内存给其他组件。 -
innodb_buffer_pool_size=768M
: -
考虑因素: InnoDB缓冲池是MySQL性能的关键,用于缓存数据和索引。 -
计算: 在4GB内存中,分配768MB给InnoDB缓冲池是一个合理的选择,这可以帮助提高数据库查询性能。 -
禁用查询缓存:
-
原因: 查询缓存在MySQL 8.0中已被移除,因此不需要额外配置。
Redis
-
maxmemory=512M
: -
考虑因素: Redis主要用于缓存数据,分配足够的内存以存储常用数据。 -
计算: 在4GB内存中,分配512MB给Redis是一个合理的选择,这有助于提高应用的响应速度。 -
maxmemory-policy=allkeys-lru
: -
原因: LRU(Least Recently Used)策略会在内存达到上限时移除最近最少使用的键,确保缓存的有效性。
RabbitMQ
-
vm_memory_high_watermark.relative=0.3
: -
考虑因素: 控制RabbitMQ的内存使用,防止内存不足影响其他服务。 -
计算: 相对内存水位设置为0.3意味着当可用内存低于30%时,RabbitMQ会开始拒绝新的消息发布,确保有足够的内存供其他组件使用。
Nginx
-
worker_processes=2
: -
考虑因素: 双核CPU适合运行两个工作进程。 -
计算: 每个工作进程可以处理大量并发请求,两个进程可以充分利用双核CPU的能力。 -
worker_connections=1024
: -
考虑因素: 每个工作进程可以处理的最大连接数。 -
计算: 1024是一个常见的默认值,可以根据实际情况调整。在4GB内存中,这个设置是合理的。
Prometheus & Grafana
-
scrape_interval=15s
: -
考虑因素: 抓取指标的时间间隔,过短会影响性能,过长会导致监控延迟。 -
计算: 15秒是一个合理的折衷值,既能及时获取指标,又不会过于频繁地消耗资源。 -
Grafana用于可视化监控数据
ELK Stack
-
Elasticsearch堆内存 (
Xms=256m Xmx=256m
): -
考虑因素: Elasticsearch需要足够的堆内存来高效地处理数据。 -
计算: 分配256MB堆内存给Elasticsearch,留出足够的内存给其他组件。 -
Logstash批处理大小 (
pipeline.batch.size=125
): -
考虑因素: Logstash批处理大小决定了每次处理的日志数量。 -
计算: 125是一个合理的批量大小,可以在不显著增加内存使用的情况下提高处理效率。 -
Kibana监听端口 (
server.port=5601
): -
考虑因素: Kibana的默认监听端口。 -
计算: 5601是一个标准的端口号,用于访问Kibana界面。
预估内存
从上面的配置预估一下内存是否超过了4GB?
计算如下:
Spring Boot Tomcat Threads: 100 threads * 1MB/thread = 100MB
MySQL Connections: 50 connections * 8MB/connection = 400MB
MySQL InnoDB Buffer Pool: 768MB
Redis: 512MB
RabbitMQ: 100MB (大概估一下而已)
Nginx: 4MB (用于2个工作进程,每个进程有1024个连接)
Prometheus: 内存使用率极低,相比之下可以忽略不计
Elasticsearch: 256MB (初始堆大小和最大堆大小)
Logstash: 内存使用率极低,相比之下可以忽略不计
Kibana: 内存使用率极低,相比之下可以忽略不计
总计约为:
100MB+400MB+768MB+512MB+100MB+4MB+256MB=2140MB ≈ 2.14GB
仍然在 4GB 的内存限制范围内,确保系统有足够的内存来运行所有组件。
后端
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- Actuator for Monitoring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Prometheus Integration -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- Spring Boot DevTools for Development -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
src/main/resources/application.properties
# 服务器配置
server.port=8080 # 设置服务器端口为8080
server.tomcat.max-threads=100 # 每个线程大约占用约1MB堆栈空间,4GB内存中留出2.5GB用于其他服务,剩下1.5GB可用于Tomcat线程池
server.connection-timeout=10s # 设置较短的连接超时时间可以减少长时间挂起的无效连接,提高资源利用率
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?useSSL=false&serverTimezone=UTC # 数据库连接URL
spring.datasource.username=root # 数据库用户名
spring.datasource.password=password # 数据库密码
spring.jpa.hibernate.ddl-auto=update # 自动更新数据库模式
spring.jpa.show-sql=true# 显示SQL语句
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect # 使用MySQL InnoDB方言
# Redis配置
spring.redis.host=localhost # Redis主机地址
spring.redis.port=6379 # Redis端口号
# RabbitMQ配置
spring.rabbitmq.host=localhost # RabbitMQ主机地址
spring.rabbitmq.port=5672 # RabbitMQ端口号
spring.rabbitmq.username=guest # RabbitMQ用户名
spring.rabbitmq.password=guest # RabbitMQ密码
# Actuator和Prometheus配置
management.endpoints.web.exposure.include=* # 暴露所有Actuator端点
management.endpoint.health.show-details=always # 始终显示健康检查详情
management.metrics.export.prometheus.enabled=true# 启用Prometheus指标导出
Java代码
简单搞搞一个Spring Boot应用类、控制器类以及消息生产者和消费者,能起到教育分享作用就行了。大家想象一下就行啦。
package com.example.demo;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class DemoApplication {
@Autowired
private RabbitTemplate rabbitTemplate;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public Queue queue() {
return new Queue("tasks", false); // 创建一个名为"tasks"的队列
}
@RestController
class HelloController {
@GetMapping("/hello")
public String sayHello(@RequestParam String name) {
rabbitTemplate.convertAndSend("tasks", "Hello, " + name + "!"); // 发送消息到队列
return"Message sent to queue!";
}
}
// Example Consumer
@Component
class MessageConsumer {
@RabbitListener(queues = "tasks")
public void receiveMessage(String message) {
System.out.println("Received message: " + message); // 打印接收到的消息
// Process the message here (e.g., send email, SMS)
}
}
}
前端 (React.js)
简单例子作用是展示前端调用后端API。
import React, { useEffect, useState } from 'react';
import axios from 'axios';
functionApp() {
const [message, setMessage] = useState('');
useEffect(() => {
axios.get('/api/hello?name=World') // 调用后端API
.then(response => {
setMessage(response.data); // 更新状态
})
.catch(error => {
console.error('There was an error fetching the message!', error); // 错误处理
});
}, []);
return (
<div className="App">
<header className="App-header">
<h1>{message}</h1> {/* 显示消息 */}
</header>
</div>
);
}
export default App;
Nginx配置
用于代理请求到Spring Boot应用和提供静态文件服务。
user nginx; # 使用nginx用户运行Nginx
worker_processes 2; # 双核CPU适合运行两个工作进程
pid /run/nginx.pid; # PID文件路径
events {
worker_connections 1024; # 每个工作进程可以处理的最大连接数
}
http {
include /etc/nginx/mime.types; # 包含MIME类型定义
default_type application/octet-stream; # 默认MIME类型
sendfile on; # 开启sendfile以提高性能
keepalive_timeout 65; # 保持连接超时时间
server {
listen 80; # 监听80端口
server_name yourdomain.com; # 服务器名称
location /api/ {
proxy_pass http://localhost:8080/; # 将/api/路径的请求代理到Spring Boot应用
proxy_set_header Host $host; # 设置转发请求头
proxy_set_header X-Real-IP $remote_addr; # 设置真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 设置转发链路信息
proxy_set_header X-Forwarded-Proto $scheme; # 设置协议信息
}
location / {
root /var/www/html; # 静态文件根目录
index index.html; # 默认索引文件
try_files $uri$uri/ /index.html; # 尝试匹配文件或重定向到index.html
}
}
}
MySQL配置 (my.cnf
)
[mysqld]
bind-address = 127.0.0.1 # 绑定到本地地址
port = 3306 # MySQL端口号
default-storage-engine = InnoDB # 默认存储引擎为InnoDB
character-set-server = utf8mb4 # 字符集设置为utf8mb4
collation-server = utf8mb4_unicode_ci # 排序规则设置为utf8mb4_unicode_ci
max_connections = 50 # 最大连接数,每个连接大约占用几MB内存
innodb_buffer_pool_size = 768M # InnoDB缓冲池大小,用于缓存数据和索引
thread_cache_size = 8 # 线程缓存大小,减少线程创建开销
query_cache_type = 0 # 禁用查询缓存(MySQL 8.0中已被移除)
query_cache_size = 0 # 查询缓存大小设置为0
log_error = /var/log/mysql/error.log # 错误日志路径
slow_query_log = 1 # 启用慢查询日志
slow_query_log_file = /var/log/mysql/slow-query.log # 慢查询日志文件路径
long_query_time = 2 # 慢查询阈值,超过2秒的查询会被记录
Redis配置 (redis.conf
)
bind 127.0.0.1 # 绑定到本地地址
port 6379 # Redis端口号
daemonize yes # 以后台守护进程方式运行
supervised systemd # 使用systemd管理
pidfile /var/run/redis_6379.pid # PID文件路径
logfile /var/log/redis/redis-server.log # 日志文件路径
databases 16 # 数据库数量
save 900 1 # 每900秒至少保存1次更改
save 300 10 # 每300秒至少保存10次更改
save 60 10000 # 每60秒至少保存10000次更改
stop-writes-on-bgsave-error no # 后台保存错误时不停止写操作
rdbcompression yes # 启用RDB快照压缩
dir /var/lib/redis # 数据目录
maxmemory 512M # 最大内存使用量
maxmemory-policy allkeys-lru # 内存达到上限时使用LRU策略移除键
RabbitMQ配置
[
{rabbit, [
{vm_memory_high_watermark_relative, 0.3}, % 相对内存水位设置为0.3,当可用内存低于30%时开始拒绝新消息发布
{disk_free_limit, {mem_relative, 0.2}}, % 磁盘空间限制为内存的20%
{tcp_listen_options, [binary, {packet, raw}, {reuseaddr, true}, {backlog, 128}, {nodelay, true}]} % TCP监听选项
]}
].
Prometheus配置 (prometheus.yml
)
global:
scrape_interval: 15s # 抓取指标的时间间隔为15秒
scrape_configs:
- job_name: 'spring-boot'
metrics_path: '/actuator/prometheus' # 指标路径
static_configs:
- targets: ['localhost:8080'] # 目标地址
ELK Stack配置
Elasticsearch (elasticsearch.yml
)
cluster.name: my-application# 集群名称
node.name:node-1# 节点名称
network.host:127.0.0.1# 绑定到本地地址
http.port:9200# HTTP端口号
discovery.type:single-node# 单节点集群
bootstrap.memory_lock:true# 锁定JVM内存
xpack.security.enabled:false# 关闭安全特性
path.logs:/var/log/elasticsearch# 日志路径
path.data:/var/lib/elasticsearch# 数据路径
indices.query.bool.max_clause_count:1024# 最大布尔子句数
jvm.options:
-Xms256m# JVM初始堆内存
-Xmx256m# JVM最大堆内存
Logstash (logstash.conf
)
input {
file {
path => "/var/log/application/*.log" # 输入日志文件路径
start_position => "beginning" # 从文件开头读取
}
}
output {
elasticsearch {
hosts => ["http://localhost:9200"] # Elasticsearch主机地址
index => "application-logs-%{+YYYY.MM.dd}" # 索引模板
}
}
Kibana (kibana.yml
)
server.host: "0.0.0.0" # 监听所有网络接口
server.port: 5601 # 监听端口号
elasticsearch.hosts: ["http://localhost:9200"] # Elasticsearch主机地址
monitoring.ui.container.elasticsearch.enabled: false # 关闭容器监控
logging.verbose: false # 不启用详细日志
关注我,送Java福利
/**
* 这段代码只有Java开发者才能看得懂!
* 关注我微信公众号之后,
* 发送:"666",
* 即可获得一本由Java大神一手面试经验诚意出品
* 《Java开发者面试百宝书》Pdf电子书
* 福利截止日期为2025年01月30日止
* 手快有手慢没!!!
*/
System.out.println("请关注我的微信公众号:");
System.out.println("Java知识日历");
原文始发于微信公众号(Java知识日历):当数据库和中间件都挤在一台双核4G服务器,该如何设计架构保证系统的稳定性和性能?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/310615.html