基于Mina的配置中心(二)

896 阅读6分钟

基于Mina的配置中心(二)

首先来看看配置中心数据库的设计。

表结构:

CREATE TABLE `message` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '消息体id',
  `project_name` varchar(200) DEFAULT NULL COMMENT '项目名称',
  `env_value` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'local' COMMENT '环境,dev日常,gray灰度,online线上,local本地',
  `property_value` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'preperties 中的value',
  `config_value` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '配置中心配置的需要注入的值',
  `remote_address` varchar(100) DEFAULT NULL COMMENT '客户端 session key',
  `creator` bigint(20) DEFAULT NULL COMMENT '创建人',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modifier` bigint(20) DEFAULT NULL COMMENT '修改人',
  `gmt_modify` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `config_version` int(11) NOT NULL DEFAULT '1' COMMENT '乐观锁,版本',
  `is_deleted` int(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除,0未删除,1已删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='消息体';
  1. project_name:项目名称,不同的项目可能有名字相同的配置,所以用来区分。
  2. env_value:当前环境,我觉得既然是配置中心,就应该接管所有配置,不需要用户再写三个application.properties文件来区分。这个极大的简化了多环境项目。
  3. property_value:这个东西其实就是application.properties里面配置的值,举个栗子🌰,比如你在application.properties写了一个这个配置mina.config.name=data1,那么data1就是property_value
  4. config_value:就是你真正要配置的值,就是用来替换data1的值。
  5. remote_address:这个是客户端的地址,主要是当服务端更改了配置项,来主动向客户端推送的。是的,这个配置中心既有推的模式,也有拉的模式。

然后用我们非常熟悉的Mybatis-Plus代码生成器,生成一下代码。
生成后的效果:

首先要把 Message 这个类移动到Base模块中,因为客户端发送消息,也需要使用这个类。
最后效果:

MinaServerProperty

因为使用了SpringBoot,接下来我们使用配置类的方式来整合Mina

因为有很多配置属性,我不想用户全都写到application.properties中,而且如果用户在application.properties中写配置属性的时候,能出现提示就更好了。

所以首先要创建一个类:MinaServerProperty

package com.lww.mina.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author lww
 * @date  2020-07-05 16:13
 */
@ConfigurationProperties(prefix = "mina.server")
public class MinaServerProperty {

    /**
     * 服务器监听端口,默认 9123
     */
    private Integer port = 9123;

    /**
     * 服务器ip地址,默认 127.0.0.1
     */
    private String address = "127.0.0.1";

    /**
     * 缓冲区大小,默认2048
     */
    private Integer readBufferSize = 2048;

    /**
     * 空闲时间,单位秒 默认 5 秒没操作就进入空闲状态
     */
    private Integer idelTimeOut = 5;

    /**
     * 初始化线程池大小,默认10
     */
    private Integer corePoolSize = 10;

    /**
     * 最大线程数,默认20
     */
    private Integer maximumPoolSize = 20;

    /**
     * 初始化用户名
     */
    private String username;

    /**
     * 初始化密码
     */
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Integer getReadBufferSize() {
        return readBufferSize;
    }

    public void setReadBufferSize(Integer readBufferSize) {
        this.readBufferSize = readBufferSize;
    }

    public Integer getIdelTimeOut() {
        return idelTimeOut;
    }

    public void setIdelTimeOut(Integer idelTimeOut) {
        this.idelTimeOut = idelTimeOut;
    }

    public Integer getCorePoolSize() {
        return corePoolSize;
    }

    public void setCorePoolSize(Integer corePoolSize) {
        this.corePoolSize = corePoolSize;
    }

    public Integer getMaximumPoolSize() {
        return maximumPoolSize;
    }

    public void setMaximumPoolSize(Integer maximumPoolSize) {
        this.maximumPoolSize = maximumPoolSize;
    }
}

@ConfigurationProperties

这个注解配合@EnableConfigurationProperties这个一起使用,可以在项目里生成如下的文件:
/META-INF/spring-configuration-metadata.json

注意:要使用Maven的编译命令,或者打包命令才会在target或者jar包中创建/META-INF/spring-configuration-metadata.json这个文件,还有这里不要用lombok,老老实实的写gettersetter,不然是没有提示的。

{
  "groups": [
    {
      "name": "mina.server",
      "type": "com.lww.mina.config.MinaServerProperty",
      "sourceType": "com.lww.mina.config.MinaServerProperty"
    }
  ],
  "properties": [
    {
      "name": "mina.server.address",
      "type": "java.lang.String",
      "description": "服务器ip地址,默认 127.0.0.1",
      "sourceType": "com.lww.mina.config.MinaServerProperty",
      "defaultValue": "127.0.0.1"
    },
    {
      "name": "mina.server.core-pool-size",
      "type": "java.lang.Integer",
      "description": "初始化线程池大小,默认10",
      "sourceType": "com.lww.mina.config.MinaServerProperty",
      "defaultValue": 10
    },
    {
      "name": "mina.server.idel-time-out",
      "type": "java.lang.Integer",
      "description": "空闲时间,单位秒 默认 5 秒没操作就进入空闲状态",
      "sourceType": "com.lww.mina.config.MinaServerProperty",
      "defaultValue": 5
    },
    {
      "name": "mina.server.maximum-pool-size",
      "type": "java.lang.Integer",
      "description": "最大线程数,默认20",
      "sourceType": "com.lww.mina.config.MinaServerProperty",
      "defaultValue": 20
    },
    {
      "name": "mina.server.password",
      "type": "java.lang.String",
      "description": "初始化密码",
      "sourceType": "com.lww.mina.config.MinaServerProperty"
    },
    {
      "name": "mina.server.port",
      "type": "java.lang.Integer",
      "description": "服务器监听端口,默认 9123",
      "sourceType": "com.lww.mina.config.MinaServerProperty",
      "defaultValue": 9123
    },
    {
      "name": "mina.server.read-buffer-size",
      "type": "java.lang.Integer",
      "description": "缓冲区大小,默认2048",
      "sourceType": "com.lww.mina.config.MinaServerProperty",
      "defaultValue": 2048
    },
    {
      "name": "mina.server.username",
      "type": "java.lang.String",
      "description": "初始化用户名",
      "sourceType": "com.lww.mina.config.MinaServerProperty"
    }
  ],
  "hints": []
}

当用户在application.properties里配置属性时,会有提示:

  1. application.properties里名称是 mina.server前缀加上属性的名称,如果是驼峰命名的,则用-分隔开,如mina.server.read-buffer-size
  2. 属性的默认值就是,我们声明时创建的值。
  3. 描述就是javadoc,在application.properties里的提示就是这些东西。

MinaServerConfig

package com.lww.mina.config;

import javax.annotation.Resource;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author lww
 * @date 2020-07-05 16:13
 */
@Configuration
@EnableConfigurationProperties(MinaServerProperty.class)
public class MinaServerConfig {

    @Resource
    private MinaServerProperty config;

    /**
     * 配置mina的多线程过滤器
     */
    @Bean
    public ExecutorFilter executorFilter() {
        //设置初始化线程数,最大线程数
        return new ExecutorFilter(config.getCorePoolSize(), config.getMaximumPoolSize());
    }

    /**
     * 配置mina的日志过滤器
     */
    @Bean
    public LoggingFilter loggingFilter() {
        return new LoggingFilter();
    }
}

当然配置类不会这么简单,我们现在能配的东西不多。现在有很多东西没有写,等写好之后再把配置补上。

自定义协议

在客户端与服务端的通讯中,在底层其实是二进制数据,Mina提供了TextLineCodecFactory,根据换行符的编码器,虽然一般场景够用,但是还是不满足我们的需求,为了更好的解决半包和粘包,我们需要自定义协议。

  • 半包:客户端发送的数据,在服务器端读取到的其实是二进制数据,服务器不知道读取多少是完整的。所以读取到的可能是不完整的数据包。
  • 粘包:同上,客户端一次发送了好几个数据包,服务器一次读取了两个或多个包的数据。

解决半包粘包的方式有很多种,我们采用添加消息头的方式,消息头中包含消息的长度,还有类型。

package com.lww.mina.protocol;

import lombok.Data;
import org.apache.commons.lang3.StringUtils;

/**
 * @author lww
 * @date 2020-07-05 17:47
 */
@Data
public class MessagePack {

    /**
     * 数据总长度
     */
    private int len;

    /**
     * 模块代码
     */
    private int module;

    /**
     * 包体  Message json格式
     */
    private String body;

    /**
     * 包头长度
     */
    public static final int PACK_HEAD_LEN = 8;

    /**
     * 最大长度
     */
    public static final int MAX_LEN = 9999;

    public MessagePack(int module, String body) {
        this.module = module;
        this.body = body;
        // 总长度
        this.len = PACK_HEAD_LEN + (StringUtils.isBlank(body) ? 0 : body.getBytes().length);
    }
}

一个 int 类型占4个字节,所以长度加上模块就占8个byte,包头长度就是8。而Message则转为Json存储在body中。

总结

第二章先写到这里,我们自定义了消息协议和Mina的配置类,用户在application.properties中配置时,会有提示。

项目源码

第三章会创建自定义的编码解码器,还有心跳检测,在第三章我们基本可以完成Mina的配置类,敬请期待。

对啦,欢迎大家关注我的公众号,共同学习,一起进步。加油🤣

本文使用 mdnice 排版