简单的web服务高可用架构

1.架构图

简单的web服务高可用架构_第1张图片

2.nginx

2.1 nginx支持htpps

  • openssl生成证书私钥(这里需要输入两次密码,请谨记)
    简单的web服务高可用架构_第2张图片
openssl genrsa -des3 -out 《证书名称》.key 2048
  • openssl生成不需要密码的密钥
    在这里插入图片描述
openssl rsa -in test.key -out 《要生成的无密码私钥名称》.key
  • openssl创建证书签名文件(根据要求填写信息)
    简单的web服务高可用架构_第3张图片
openssl req -new -key 《私钥名称》.key -out 《要生成的签名文件名称》.csr
  • 生成证书
    简单的web服务高可用架构_第4张图片
openssl x509 -req -days 365 -in 《签名文件名称》.csr -signkey 《私钥名称》.key -out 《要生成的证书名称》.crt

2.2 nginx https分流配置

server {
     
        #端口后边+ ssl 意思是开启ssl支持https
        listen       10086 ssl;
        server_name  <项目名称要和生成私钥的compony对应上才可>;
		
		#SSL配置
		ssl_certificate      /ssl/test.crt; # 配置证书地址
		ssl_certificate_key  /ssl/test_nopass.key; # 配置证书私钥地址
		ssl_protocols        TLSv1 TLSv1.1 TLSv1.2; # 配置SSL协议版本
		ssl_ciphers          ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; # 配置SSL加密算法
		ssl_prefer_server_ciphers  on; # 优先采取服务器算法
		ssl_session_cache    shared:SSL:10m; # 配置共享会话缓存大小
		ssl_session_timeout  10m; # 配置会话超时时间


        charset utf-8;

		location / {
     
			proxy_pass  https://webgroup;
           # 如下的配置很重要不然会发现有些前后端未分离的项目页面请求路径前缀会变成proxy_pass
			proxy_redirect off;
			proxy_set_header Host $http_host;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
     
            root   html;
        }
}

2.3 完整的nginx conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
     
    worker_connections  1024;
}


http {
     
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
	
	upstream webgroup{
		ip_hash;
		server 10.36.6.20:81 weight=1;
		server 10.36.6.20:82 weight=1;
	}
	
	 #端口后边+ ssl 意思是开启ssl支持https
        listen       10086 ssl;
        server_name  <项目名称要和生成私钥的compony对应上才可>;
		
		#SSL配置
		ssl_certificate      /ssl/test.crt; # 配置证书地址
		ssl_certificate_key  /ssl/test_nopass.key; # 配置证书私钥地址
		ssl_protocols        TLSv1 TLSv1.1 TLSv1.2; # 配置SSL协议版本
		ssl_ciphers          ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; # 配置SSL加密算法
		ssl_prefer_server_ciphers  on; # 优先采取服务器算法
		ssl_session_cache    shared:SSL:10m; # 配置共享会话缓存大小
		ssl_session_timeout  10m; # 配置会话超时时间


        charset utf-8;

		location / {
			proxy_pass  https://webgroup;
           # 如下的配置很重要不然会发现有些前后端未分离的项目页面请求路径前缀会变成proxy_pass
			proxy_redirect off;
			proxy_set_header Host $http_host;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

2.4 nginx docker-componse

 # nginx ---------------------------------------------
  nginx:
    labels:
      service: nginx
    logging:
      options:
        labels: "service"
    restart: always
    image: nginx
    container_name: nginx
    ports:
      - 8080:80
      - 80:1080
    volumes:
      - /nginx/nginx.conf:/etc/nginx/nginx.conf
      - /nginx/log:/var/log/nginx
      - /ssl:/ssl
    networks:
      - soil-net
    environment:
      - TZ=Asia/Shanghai

3.web服务自动切换数据源配置

前言: 这里使用的阿里的数据库连接池druid,灵感来自于他的主从分离,我一想你可以主从分离那肯定是哪里可以该数据库的url地址啊,然后我一行一行debug终于找到了一个办法可以改,但是这样只是个临时凑合的办法会慢,我的服务切换会有五六秒的时间才可以切换过来,再此建议增加中间件keepalive+双主备份的情况让服务和mysql解耦

3.1拦截异常

@ControllerAdvice(value = "要拦截的目录例如com.test.controller")
@Slf4j
public class DefaultGlobalExceptionHandlerAdvice {
     
    /**
     * 数据库连接超时
     *
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(RecoverableDataAccessException.class)
    public ReturnMsg dataAccessResourceFailureException(RecoverableDataAccessException ex) {
     
        //发生异常切换数据源
        CustomizeDruidDataSource.switchDbType();
        return "系统异常!";
    }
}

3.2切换url的工具类

前言: 我还做了读写分离的一些工作,但没必要展示,这里的urls可以自己往里边传,我是有个配置文件自己拼装了一个数组放进来的

import com.alibaba.druid.pool.DruidDataSource;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @explain 替换掉alibaba数据源类,动态切换数据库,一旦数据库挂掉切换到另一台
 * @Classname CustomizeDruidDataSource
 * @Date 2021/10/11 12:20
 * @Created by hanzhao
 */
public class CustomizeDruidDataSource extends DruidDataSource {
     
    private boolean lastInited;

    private static boolean dbStatus = true; //true代表主库运行正常,false代表主库运行异常


    private String[] urls;

    public static void switchDbType() {
     
        if(dbStatus){
     
            dbStatus = false;
        }else{
     
            dbStatus = true;
        }
    }

    public String[] getUrls() {
     
        return urls;
    }

    public void setUrls(String[] urls) {
     
        this.urls = urls;
    }

    @Override
    public void init() throws SQLException {
     
        lastInited = inited;
        super.init();
        if (!lastInited && inited) {
     
            new Thread(new ValidateUrlTask()).start();
        }
    }

    class ValidateUrlTask implements Runnable {
     

        @Override
        public void run() {
     
            while (true) {
     
                // 如果这个数据源被关闭了,就结束这个定时任务
                if (isClosed()) {
     
                    break;
                }
                //如果这个数据源已经被初始化了,同时连接异常才进行处理
                if (isInited() && !dbStatus) {
     
                    for (String thisUrl : urls) {
     
                        Connection connection = null;
                        try {
     
                            connection = DriverManager.getConnection(thisUrl, username, password);
                            jdbcUrl = thisUrl;
                            CustomizeDruidDataSource.switchDbType();
                        } catch (Exception ignored) {
     
                        } finally {
     
                            try {
     
                                connection.close();
                            } catch (Exception ignored) {
     
                            }
                        }
                    }
                }
                try {
     
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
     
                }
            }
        }
    }
}

3.3springboot配置

spring:
  #数据源地址
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 主库数据源
      master:
        url: jdbc:mysql://url?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
        username: root
        password: 密码
      # 从库数据源
      slave:
        #从数据源开关/默认关闭
        enabled: true
        url: jdbc:mysql://url?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
        username: root
        password: 密码
      # 初始连接数
      initialSize: 5
      # 最小连接池数量
      minIdle: 10
      # 最大连接池数量
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # 设置白名单,不填则允许所有访问
        allow:
        url-pattern: /monitor/druid/*
      filter:
        stat:
          enabled: true
          # 慢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true

4.mysql主从备份

4.1 mysql配置文件

[mysqld]
# [必须]服务器唯一ID,默认是1,一般取IP最后一段
server-id=1

# [必须]启用二进制日志
log-bin=mysql-bin 

# 复制过滤:也就是指定哪个数据库不用同步(mysql库一般不同步)
binlog-ignore-db=mysql
# 数据目录
datadir		= /var/lib/mysql
# 错误日志位置
log-error	= /var/log/mysql/error.log

# 设置需要同步的数据库 binlog_do_db = 数据库名; 
# 如果是多个同步库,就以此格式另写几行即可。
# 如果不指明对某个具体库同步,表示同步所有库。除了binlog-ignore-db设置的忽略的库
# binlog_do_db = test #需要同步test数据库。

# 确保binlog日志写入后与硬盘同步
sync_binlog = 1

# 跳过所有的错误,继续执行复制操作
slave-skip-errors = all    

sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION 

4.2 docker-componse文件

soilmysql_master:
    labels:
      service: soilmysql_master
    logging:
      options:
        labels: "service"
    user: "root"
    restart: always
    image: mysql:5.7
    container_name: "mysql_master"
    ports:
      - "10086:3306"
    volumes:
      - 《将数据库日志数据等信息存储到宿主的磁盘中》:/var/lib/mysql
      - 《宿主mysql配置文件路径》:/etc/mysql/mysql.conf.d/mysqld.cnf
    environment:
      MYSQL_ROOT_PASSWORD: 初始root账户密码
    networks:
      - soil-net
soilmysql_slave:
    labels:
      service: mysql_slave
    logging:
      options:
        labels: "service"
    user: "root"
    restart: always
    image: mysql:5.7
    container_name: "soilmysql_slave"
    ports:
      - "10010:3306"
    volumes:
      - 《将数据库日志数据等信息存储到宿主的磁盘中》:/var/lib/mysql
      - 《宿主mysql配置文件路径》:/etc/mysql/mysql.conf.d/mysqld.cnf
    environment:
      MYSQL_ROOT_PASSWORD: 初始root账户密码
    networks:
      - soil-net

4.3 mysql双主命令

  1. 将my.cnf配置文件拷贝到docker-componse挂载的目录下
  2. 记得修改server-id
  3. 分别进入容器中执行以下命令
    mysql -uroot -p<密码>
    CREATE USER ‘《同步账户》’@’%’ IDENTIFIED BY ‘《同步账户的密码》’;
    GRANT ALTER, SHOW VIEW, SHOW DATABASES, SELECT, PROCESS, EXECUTE, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TABLESPACE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, INDEX, INSERT, REFERENCES, TRIGGER, UPDATE, CREATE USER, FILE, LOCK TABLES, RELOAD, REPLICATION CLIENT, REPLICATION SLAVE, SHUTDOWN, SUPER ON . TO ‘《同步账户》’@’%’ WITH GRANT OPTION;
    FLUSH PRIVILEGES;
    CHANGE MASTER TO master_host=’<另一台机器的ip地址>’, master_port=<另一台机器的端口号>, master_user=’《同步账户》’, master_password=’《同步账户的密码》’;
    START SLAVE;
  4. 上述步骤在两台机器上全部执行完成后,查看状态
    SHOW SLAVE STATUS;
    Slave_IO_Running | Slave_SQL_Running都为yes 才算成功

5.参考文献

5.1 nginx+ssl

http://nginx.org/en/
https://www.nginx.cn/doc/
https://www.jianshu.com/p/06952c316f0c
https://www.runoob.com/w3cnote/nginx-setup-intro.html
https://blog.csdn.net/u011659193/article/details/85778764
https://blog.csdn.net/zhenghongcs/article/details/109302331

5.2 mysql双主备份

https://www.cnblogs.com/a1304908180/p/10351930.html
https://blog.csdn.net/zhangguanghui002/article/details/78959816
https://www.jianshu.com/p/70ca1ef79cd4
https://help.aliyun.com/knowledge_detail/41106.html?spm=a2c6h.13066369.0.0.5ae84055BKlrw9#XACzT

你可能感兴趣的