云计算百科
云计算领域专业知识百科平台

当数据库和中间件都挤在一台双核4G服务器,该如何设计架构保证系统的稳定性和性能?...

当你接了一笔十分抠门小老板的私活订单,他只有一台双核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)进行日志管理。

  • 架构图

    3a98191418f9812e144209db6514ca66.png

    具体配置参数

    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知识日历");

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 当数据库和中间件都挤在一台双核4G服务器,该如何设计架构保证系统的稳定性和性能?...
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!