关于Mybatis Generator,Nginx,Redis,Sharding-JDBC等的尝试与踩坑

简介

这个帖子记录《软件架构与中间件》课程实验5的实现过程中,用到的一些新技术以及一些踩坑
首先放一个实验要求:
在这里插入图片描述
其次这里提到的计算层,数据层,表示层,分别为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

项目架构

在这里插入图片描述
因为自己的笔记本性能有限,这里只开了三个虚拟机作为集群,模拟分布式环境。选用的技术有:Nginx(计算层),Sharding-JDBC和Redis(数据层),React(表示层)。
集群上部署的应用架构如上图。
首先一台主机运行Nginx,负责分发静态资源(前端使用React开发的页面),并对请求进行反向代理。然后三台主机都运行一个Tomcat后端(使用SpringBoot开发)。Ningx根据规则(比如ip_hash)将请求分别分发到这三个后端。在数据层,使用Redis作为数据的缓存,这里部署了单机的Redis在一台主机上,实际上Redis是可以搭集群的,但是这里因为精力有限,只使用了单机。然后使用Sharding-JDBC进行分库分表,将数据分别储存在三台主机的MySQL上。
下面介绍整个开发过程:

Mybatis Generator

实际上,在这个小项目里面直接使用Mybatis就可以了(毕竟只有一些增删改查,(虽然要求使用之前课程开发的系统上继续做,但是为了方便还是因为懒,直接写了一套简单的系统))。
我们知道MyBatis是一款优秀的Java持久层框架,但是其实自己写所有的实体类、Mapper 接口以及映射文件(XML)还是比较麻烦的。
MyBatis Generator是一个开源项目,它可以使用简单的 XML 配置文件或者 Java 注解来生成 MyBatis 的实体类、Mapper 接口以及映射文件(XML)。它可以根据数据库表结构自动生成代码,这样可以大大减轻开发人员的工作负担。

这部分主要参考解放双手!MyBatis官方代码生成工具给力!

引入依赖

pom.xml中引入Mybatis Generator的依赖。

        <!-- MyBatis 生成器 -->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.4.2</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.4.2</version>
        </dependency>

参数文件

resources下创建文件generator.properties
其中内容如下:

jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.connectionURL=jdbc:mysql://${host}:3306/${dbname}?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
jdbc.userId=${username}
jdbc.password=${password}

需要将其中的${}括起来的内容根据实际情况更换。

配置文件

resources下创建文件generatorConfig.xml
其中内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <properties resource="generator.properties"/>
    <context id="MySqlContext" targetRuntime="MyBatis3" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>
        <property name="javaFileEncoding" value="UTF-8"/>
        <!--生成mapper.xml时覆盖原文件-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />
        <!-- 为模型生成序列化方法-->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
        <!-- 为生成的Java模型创建一个toString方法 -->
<!--        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>-->

        <plugin type="org.mybatis.generator.plugins.LombokPlugin" />
        <!--可以自定义生成model的代码注释-->
<!--        <commentGenerator type="com.hit.lab.back.mbg.CommentGenerator">-->
        <commentGenerator >
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressDate" value="true"/><!-- 不生成日期 -->
            <property name="suppressAllComments" value="true"/>
            <property name="addRemarkComments" value="true"/>
        </commentGenerator>
        <!--配置数据库连接-->
        <jdbcConnection driverClass="${jdbc.driverClass}"
                        connectionURL="${jdbc.connectionURL}"
                        userId="${jdbc.userId}"
                        password="${jdbc.password}">
            <!--解决mysql驱动升级到8.0后不生成指定数据库代码的问题-->
            <property name="nullCatalogMeansCurrent" value="true" />
        </jdbcConnection>
        <!--指定生成model的路径-->
        <javaModelGenerator targetPackage="com.hit.lab.back.bean" targetProject="src\main\java"/>
        <!--指定生成mapper.xml的路径-->
        <sqlMapGenerator targetPackage="com.hit.lab.back.mbg.mapper" targetProject="src\main\resources"/>
        <!--指定生成mapper接口的的路径-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.hit.lab.back.mbg.mapper"
                             targetProject="src\main\java"/>
        <!--生成全部表tableName设为%-->
        <table tableName="t_customer" domainObjectName="Customer">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
        </table>
        <table tableName="t_order" domainObjectName="Order">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
        </table>
        <table tableName="t_product" domainObjectName="Product">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
        </table>
    </context>
</generatorConfiguration>

这里直接贴出了实验项目中使用的文件,请根据实际内容进行替换。
接下来对这个文件进行一定解读,更详细的内容请自行查阅官方的文档。
在这里插入图片描述
这里会将前面的generator.properties中的内容读入,所以前面的文件一定要写对。

在这里插入图片描述
这里配置生成的实体类,Mapper接口,XML映射文件的位置。
targetProject配置项目位置,一般就是当前位置,按照上面写就好。
targetPackage配置生成文件的包,会决定生成文件在项目的位置。
综上,对于这样一行来讲,

<javaModelGenerator targetPackage="com.hit.lab.back.bean" targetProject="src\main\java"/>

会将实体类生成到系统路径为${project_path}/src/main/java/com/hit/lab/back/bean这个目录下。
在这里插入图片描述
这三个标签对数据库中的table经行定义,tableName为数据库中的表名,domainObjectName为你需要生成的实体名,这也会影响到Mapper接口和XML的命名。

生成器

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class Generator {
    public static void main(String[] args) throws Exception {
        //MBG 执行过程中的警告信息
        List<String> warnings = new ArrayList<String>();
        //当生成的代码重复时,覆盖原代码
        boolean overwrite = true;
        //读取我们的 MBG 配置文件
        InputStream is = Generator.class.getResourceAsStream("/generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(is);
        is.close();

        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        //创建 MBG
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        //执行生成代码
        myBatisGenerator.generate(null);
        //输出警告信息
        for (String warning : warnings) {
            System.out.println(warning);
        }
    }
}

结合LomBok

Lombok是一种Java库,可以使Java开发更加简洁, 通过注解减少样板式代码。使用Lombok可以节省开发者大量的时间和精力,并提高代码可读性。

Lombok支持许多常见的类,如getter,setter,构造函数等,而不需要像手动编写Java Bean那样繁琐。在使用Lombok之前,JavaBean通常需要写很多的setter和getter方法和有参无参构造器。而使用 Lombok 就可以只用一个简单的注解来生成代码。

这里我希望使用lombok中的@Data等注解代替Mybatis Generator生成的实体类中的getter,setter,Constructor等等。
最开始询问GPT,得到的回答说要使用一个插件,(上面的配置文件中已经使用了),但是,经过排查,发现Mybatis Generator中并没有这个插件。
在这里插入图片描述
所以这个插件需要我们自己实现,这里参考了mybatis-generator 整合lombok
需要我们自己创建一个LombokPlugin,这里需要注意,它一定要在包package org.mybatis.generator.plugins;下,没有的话就自己在项目中创建一个。(这里猜测,其实不必一定在这个package,只要和前面引用的插件路径相同即可)
下面给出代码:

package org.mybatis.generator.plugins;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class LombokPlugin extends PluginAdapter {
    public LombokPlugin() {
    }

    public boolean validate(List<String> list) {
        return true;
    }

    public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        topLevelClass.addImportedType("lombok.Data");
        topLevelClass.addAnnotation("@Data");
        topLevelClass.addImportedType("lombok.Getter");
        topLevelClass.addImportedType("lombok.Setter");
        topLevelClass.addImportedType("lombok.ToString");
        topLevelClass.addAnnotation("@Getter");
        topLevelClass.addAnnotation("@Setter");
        topLevelClass.addAnnotation("@ToString");
        topLevelClass.addJavaDocLine("/**");
        topLevelClass.addJavaDocLine("* Created by Mybatis Generator " + this.date2Str(new Date()));
        topLevelClass.addJavaDocLine("*/");
        return true;
    }

    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        interfaze.addJavaDocLine("/**");
        interfaze.addJavaDocLine("* Created by Mybatis Generator " + this.date2Str(new Date()));
        interfaze.addJavaDocLine("*/");
        return true;
    }

    public boolean modelSetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        return false;
    }

    public boolean modelGetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        return false;
    }

    private String date2Str(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        return sdf.format(date);
    }
}

最终生成的实体类如图:
在这里插入图片描述
可以看到这样生成的实体类是非常干净的。

使用

Mybatis Generator生成的Example类主要用于条件查询和删除操作,以及分页查询的参数传递。下面简单介绍一下如何使用。

  1. 根据表结构生成对应的Example类
    例如,根据表名为user生成的Example类为UserExample,可以在mapper.xml文件中的SQL语句中引用。

  2. 设置查询条件
    在UserExample类中,可以设置查询条件,例如:

UserExample example = new UserExample();
example.createCriteria().andAgeGreaterThan(18);

//表示查询年龄大于18岁的用户。
  1. 执行查询操作
    调用对应的mapper接口方法进行查询操作,例如:
List<User> userList = userMapper.selectByExample(example);

//以上代码将返回满足查询条件的所有用户对象(List类型)。
  1. 分页查询
    在UserExample类中,还可以设置分页信息,例如:
UserExample example = new UserExample();
example.setLimit(10);
example.setOffset(0);
example.createCriteria().andAgeGreaterThan(18);
//以上代码表示返回满足条件的前10条记录。
  1. 删除操作
    在UserExample类中,除了可以查询,还可以删除符合条件的数据,例如:
UserExample example = new UserExample();
example.createCriteria().andAgeLessThan(18);
userMapper.deleteByExample(example);
//以上代码表示删除年龄小于18岁的用户记录。

需要注意的是,在使用Example类时,需要根据具体情况进行修改,例如表名、属性名、查询条件等等。

Sharding-JDBC

引入依赖

<!--sharding jdbc -->
        <!--&lt;!&ndash; https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-spring-boot-starter &ndash;&gt;-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-namespace</artifactId>
            <version>4.1.1</version>
        </dependency>
        <!--集成druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.16</version>
        </dependency>

配置文件

application.yml中添加以下配置

spring:
  shardingsphere:
    datasource:
      names: db1, db2, db3
      db1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test1
        username: test
        password: test
      db2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test2
        username: test
        password: test
      db3:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test3
        username: test
        password: test
    sharding:
      tables:
        t_customer:
          actual-data-nodes: db$->{1..3}.t_customer
          database-strategy:
            inline:
              sharding-column: id
              algorithm-expression: db$->{id % 3 + 1}
          key-generator:
            column: id
            type: SNOWFLAKE
        t_order:
          actual-data-nodes: db$->{1..3}.t_order
          database-strategy:
            inline:
              sharding-column: id
              algorithm-expression: db$->{id % 3 + 1}
          key-generator:
            column: id
            type: SNOWFLAKE
        t_product:
          actual-data-nodes: db$->{1..3}.t_product
          database-strategy:
            inline:
              sharding-column: id
              algorithm-expression: db$->{id % 3 + 1}
          key-generator:
            column: id
            type: SNOWFLAKE

这里对数据进行水平切片,经数据分散到不同的表中。
接下来按照我个人的理解对这些配置进行讲解。
在这里插入图片描述
这里定义数据源,type为数据源的类型,这里集成了druid连接池,所以选择com.alibaba.druid.pool.DruidDataSource。如果引入了druid(在未对这个项目改造时,使用Mybatis顺便引入的),但是直接跟着官网教程没有使用这个type,而是使用了org.apache.commons.dbcp2.BasicDataSource,会报错。一个坑
在这里插入图片描述
接下来是分片规则:
在这里插入图片描述
actual-data-nodes表示这个table在哪些数据源中有。
algorithm-expression表示按照id对3求余再加1,比如对于id为5的,对应db3,对于id为3的对应db1。
key-generator中的SNOWFLAKE表面按照雪花算法生成id。
接下里是第二个坑
在测试时发现insert的数据,不会将id回显到实体类中,实体类中的id为0,查看mybatis generator生成的xml代码,发现对应语句,如下:
在这里插入图片描述
按照这样的写法,应该是可以回显到实体类中的。
但是经过手动在mysql中执行

SELECT LAST_INSERT_ID();

发现其返回也一直是0,即使对于设置了自增主键的表也是这样。
个人分析,是因为,Sharding-JDBC的key-generator是在程序中生成一个id,再将数据整个插入到数据库中,这个id并不是通过MySQL自己生成的,所以通过这个函数获取到的一直是0。
修改方法如下:
SELECT LAST_INSERT_ID();修改为SELECT MAX( id ) FROM t_customer即可

  <insert id="insert" parameterType="com.hit.lab.back.bean.Customer">
    <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long">
      SELECT MAX( id ) FROM `t_customer`
    </selectKey>
    insert into t_customer (type, name)
    values (#{type,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
  </insert>

Redis

关于Redis的基础配置以及连接池的设置,参考:
SpringBoot教程(十四) | SpringBoot集成Redis(全网最全)

引入依赖

        <!-- 集成redis依赖  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.32</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2-extension-spring5 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2-extension-spring5</artifactId>
            <version>2.0.32</version>
        </dependency>

这里不仅引入了redis的依赖,还有连接池的依赖,以及fastjson2的依赖。连接池和fastjson2都是为了优化系统性能。连接池的作用不必多说,fastjson2作为Redis序列化的工具可以更快的进行序列化。

配置文件

application.yml中添加以下配置

spring:
  redis:
    host: localhost
    port: 6379
#    password: 123456
    database: 0
#    cluster:
#      nodes: 10.255.144.115:7001,10.255.144.115:7002,10.255.144.115:7003,10.255.144.115:7004,10.255.144.115:7005,10.255.144.115:7006
#      max-redirects: 3
    lettuce:
      pool:
        max-idle: 16
        max-active: 32
        min-idle: 8

自定义配置



import com.alibaba.fastjson2.support.spring.data.redis.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * Description: 设置redis配置
 */
@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private int redisPort;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(redisHost, redisPort);
        return new LettuceConnectionFactory(configuration);
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 默认缓存时间
                .entryTtl(Duration.ofSeconds(600))
                // 设置key的序列化方式
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
                // 设置value的序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer));
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        // 使用fastJson序列化
        // value值的序列化采用fastJsonRedisSerializer
        redisTemplate.setValueSerializer(new FastJsonRedisSerializer<>(Object.class));
        redisTemplate.setHashValueSerializer(new FastJsonRedisSerializer<>(Object.class));
        // key的序列化采用StringRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }

}

redisTemplate这个方法中,指定序列化使用import com.alibaba.fastjson2.support.spring.data.redis.FastJsonRedisSerializer;这个序列化器。
又一个坑
这里配置一开始写成了这样:
在这里插入图片描述
这样在本地运行不会出什么问题,但是当把它放在集群上时,发现即使修改了配置文件中的spring.redis.host,但是Lettuce一直都尝试连接localhost:6739。所以需要通过以上的方式读入这些配置,也许还有其他的改法,有知道的话可以留言。

使用

关于redis的使用,一般是封装成工具类再使用,这里直接采用了SpringBoot整合Redis,封装RedisUtils里面的工具类,由于篇幅比较长,这里就不粘贴了。
使用时只要将其注入到自己的Service或者Controller中即可。
在这里插入图片描述

Nginx

因为Nginx在这个项目中用来分发静态资源,以及反向代理,另外还可以解决跨域问题,这里没有代码层面的开发。
这里先给出在Windows上的配置文件:


#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       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  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    upstream backend{
        server      127.0.0.1:8080;
    }
    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html; # 配置根路径请求时默认为加载/index.html
        }
		location /favicon.ico {  
		}  
        location /goods {
            try_files $uri $uri/ /index.html; # 将所有请求重定向到index.html
        }
        location /customers {
            try_files $uri $uri/ /index.html; # 将所有请求重定向到index.html
        }
        location /sales {
            try_files $uri $uri/ /index.html; # 将所有请求重定向到index.html
        }
        location /api {
            proxy_pass  http://backend;
            proxy_set_header    Host    $host;
            proxy_set_header    X-Real-IP    $remote_addr;
            add_header 'Access-Control-Allow-Origin' '*' always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
            add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
            if ($request_method = 'OPTIONS') {
                return 204;
            }
        }
    }
}

因为在这个配置中,并没有遇到太大的问题,另外其中注释也写的比较明白,这里不再赘述。

集群环境搭建

修改hosts

首先使用ifconfig查询三台虚拟机的ip:
在这里插入图片描述
然后修改三台虚拟机的/etc/hosts文件,方便后面从主机名到ip的映射。
在这里插入图片描述

JDK

  1. 下载jdk二进制文件,这里使用jdk-8u351-linux-x64.tar.gz
    在这里插入图片描述

  2. 解压下载后的文件到某个目录。
    在这里插入图片描述

  3. 修改/etc/profile文件,配置JDK信息。然后执行

source /etc/profile

命令生效。

在这里插入图片描述
这里建议先重启一下,让这些环境变量生效(固化?)。
5. 验证一下:

在这里插入图片描述

MySQL

安装

sudo apt install mysql-server

验证

一般安装后会自动运行,使用下面指令验证:

sudo systemctl status mysql

在这里插入图片描述
这代表mysql在正常运行。

修改配置

接下来需要对mysql进行一些修改,使其可以远程连接。主要参考:MySQL8 设置远程访问授权

  1. 确定服务器上的防火墙没有阻止 3306 端口
    由于我的虚拟机环境,防火墙没有开启,这里省略。
  2. MySQL配置文件中设置本地IP/localhost绑定
    在这里插入图片描述
    /etc/mysql/mysql.conf.d/mysqld.cnf文件中找到bind-address这一行,这里原本是127.0.0.1,代表只监听从本机来的连接,将其改为0.0.0.0,监听所有来源。
  3. 创建一个远程账户,并授权
CREATE USER 'remote'@'%' IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON *.* TO 'remote'@'%' WITH GRANT OPTION; 
FLUSH PRIVILEGES;

在这里插入图片描述

  1. 验证一下
    在这里插入图片描述
    这里在宿主机使用Navicat来测试,确保可以远程连接。

Nginx

安装

sudo apt install nginx

验证

和mysql一样一般安装后会自动运行,使用下面指令验证:

sudo systemctl status nginx

在这里插入图片描述

修改配置

通过apt安装的nginx的配置文件都在/etc/nginx中。
在这里插入图片描述
conf.d中可以存放自定义的配置文件,nginx.conf中为全局的默认配置,建议不要乱改。
但是要有以下修改:

  1. userwww-data改为root
    否则会报500,查看/var/log/nginx/error.log,如下图:
    在这里插入图片描述

在这里插入图片描述
解决方法参考Nginx转发请求,报13:Permission denied错误
其分析如下:
在这里插入图片描述

  1. 将最后的include /etc/nginx/sites-enabled/*;注释掉,它会将你自己定义的location / {}覆盖掉,去到欢迎页面。
    这里也能看出会将/etc/nginx/conf.d/*.conf这个路径下的配置文件导入进来。
    在这里插入图片描述

在这里插入图片描述

自定义配置

/etc/nginx/conf.d路径下新建一个以conf为扩展名的文件。
这里我直接贴出配置文件,具体其中内容不再赘述。

upstream backend{
	ip_hash;
	server	lab1:8080;
	server	lab2:8080;
	server	lab3:8080;
}
server {
	listen		80;
	server_name	localhost;

	location / {
		root	/home/lsxuan/site;
		index  index.html index.htm;
		try_files $uri $uri/ /index.html; # 配置根路径请求时默认为加载/index.html
	}
	location /favicon.ico {
	}
	location /goods {
		try_files $uri $uri/ /index.html; # 将所有请求重定向到index.html
	}
	location /customers {
		try_files $uri $uri/ /index.html; # 将所有请求重定向到index.html
	}
	location /sales {
		try_files $uri $uri/ /index.html; # 将所有请求重定向到index.html
	}
	location /api {
		proxy_pass  http://backend;
		proxy_set_header    Host    $host;
		proxy_set_header    X-Real-IP    $remote_addr;
		add_header 'Access-Control-Allow-Origin' '*' always;
		add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
		add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
		if ($request_method = 'OPTIONS') {
			return 204;
		}
	}
}

防火墙

一般80端口应该都是开的吧,所以一般不用操作。当然我的虚拟机环境防火墙完全没开,更加不用操作了。
坑again
这里一开始我将nginx.conf这个文件清空,自己写了一些server块,upstream块之类的,没有以下配置,导致浏览器一直报错,且css无法正常加载。
在这里插入图片描述
而且,因为浏览器默认会开启缓存,这种静态文件不会重新加载,所以,即使将这个文件修改正确,浏览器还是会一直报错。血泪啊
坑again的again

  1. 在同一个局域网内,ip_hash不生效(虚拟机当然也有这个问题)
    详细分析参考nginx通过ip-hash算法负载不均或不起效问题
  2. 轮询时不起作用,所有请求都发往同一后端
    参考 Nginx配置了默认轮询方式,但是轮询失效,刷新后只能访问一个tomcat服务器
    解决办法:
    在配置文件中添加
   	location /favicon.ico {
   	}

验证

在这里插入图片描述
在宿主机通过ip访问虚拟机,看看nginx是否正常。

Redis

参考Ubuntu 20.04 安装和配置 Redis

安装

sudo apt install redis-server

验证

同前两者一样一般安装后会自动运行,使用下面指令验证:

sudo systemctl status redis-server

在这里插入图片描述

编辑配置文件

/etc/redis/redis.conf找到bind,将其从127.0.0.1 ::1修改为0.0.0.0 ::1,原理同mysql的修改。
在这里插入图片描述

防火墙

虚拟机环境防火墙没开

验证

在这里插入图片描述
在宿主机使用以下指令验证其是否可以远程访问:

redis-cli -h ${host/ip} ping

部署

前端

前端使用react在webstorm中开发。
在webstorm的命令行中使用以下命令打包。

npm run build

运行完成后会生成以下文件。
在这里插入图片描述
在windows上将这些文件拷贝到以下目录:
在这里插入图片描述
在ubuntu上将这些文件拷贝到以下目录:

在这里插入图片描述
这个路径都是在配置文件中可以自定义的。
比如,在windows中:
在这里插入图片描述
在ubuntu上:
在这里插入图片描述

后端打包

在这里插入图片描述
pom.xml中这一行注释掉,否则打出来的包运行时会报错,没有主类。(以前的某次打包的经验,这次没有验证)

在这里插入图片描述
使用maven的package打包。
在这里插入图片描述
将这个生成的jar放到ubuntu的任意一个目录即可。

运行

前端放到nginx设置的目录下即可。
后端使用java运行。

java -jar ${jar_name}

这样运行会使用jar包内部的配置文件,如果要指定springboot的配置文件可以使用-Dspring.config.location参数。比如

java -jar back-0.0.1-SNAPSHOT.jar -Dspring.config.location=./appication.yml

另外这里贴出appication.yml的内容:

logging:
  level:
    root: INFO
mybatis:
  mapper-locations: classpath*:dao/*.xml, classpath*:com/**/mapper/*.xml
server:
  port: 8080
spring:
  redis:
    host: lab1
    port: 6379

    database: 0
    lettuce:
      pool:
        max-idle: 16
        max-active: 32
        min-idle: 8
  shardingsphere:
    datasource:
      names: db1, db2, db3
      db1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://lab1:3306/test
        username: remote
        password: 123456
      db2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://lab2:3306/test
        username: remote
        password: 123456
      db3:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://lab3:3306/test
        username: remote
        password: 123456
    sharding:
      tables:
        t_customer:
          actual-data-nodes: db$->{1..3}.t_customer
          database-strategy:
            inline:
              sharding-column: id
              algorithm-expression: db$->{id % 3 + 1}
          key-generator:
            column: id
            type: SNOWFLAKE
        t_order:
          actual-data-nodes: db$->{1..3}.t_order
          database-strategy:
            inline:
              sharding-column: id
              algorithm-expression: db$->{id % 3 + 1}
          key-generator:
            column: id
            type: SNOWFLAKE
        t_product:
          actual-data-nodes: db$->{1..3}.t_product
          database-strategy:
            inline:
              sharding-column: id
              algorithm-expression: db$->{id % 3 + 1}
          key-generator:
            column: id
            type: SNOWFLAKE

效果

Sharding-JDBC

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
插入若干条数据,可以看到数据被分散到多个数据源中。

Redis

这里先介绍一下,代码中如何使用redis
在这里插入图片描述
这里只是一个简单的处理,从redis中取出所有的product,如果没有再从数据库中取出并存到redis中,若redis中已经缓存,则直接返回给客户端。
以下是redis中缓存的内容:
在这里插入图片描述

Nginx

因为ip_hash对同一局域网中的请求都会转发到同一后端服务器(这是nginx的实现算法决定的),所以这里先将ip_hash注释掉,采用默认的轮询策略。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到nginx将请求平均分配到了各个后端服务器。

源代码

有机会放到github上,先鸽了。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值