目录

服务外包——智能政策信息检索系统

    写一下这次服务外包经历,主要从我后端所用到的技术出发,而需求和具体实现不会细说。因为我们准备的时间有点晚,4月17号结束,我们是3月22号才开始的,所以说很多我想实现的东西最后也没有去做,这些会在博客中说明,用来督促一下自己,之后去做做完。

一. 技术选型

/program/policy/运行架构.png

微服务架构

技术选型主要是:Nacos + Gateway + Sentinel + OpenFeign

  • Nacos兼具注册中心和配置中心两大功能,相比Zuul来说功能强大了很多,不用重启项目就可以在线更改配置文件,纵享丝滑了。

    nacos配置动态刷新有两种方式:

    • @Value() + @RefreshScope
    • @ConfigurationProperties(Prefix)
  • Gateway主要负责路由,并结合JWT实现登录授权;结合Sentinel实现限流。

存储层

技术选型主要是:Elasticsearch + Redis + mysql + 七牛云对象存储

  • Elasticsearch:保存政策信息,分布式全文检索

    我在些写项目时,主要遇到了两个坑:

    • 版本问题。我服务器中部署的es集群是8.1.1版本的,项目中用的依赖还是7.x的,每次执行es查询请求时,都会有一条【warn】日志,导致了日志文件中基本塞满了这些无用的日志信息。试了很多种方法都没解决,后面也没去管这个问题。

      为什么我不升降es依赖的版本?主要还是时间问题,7.x版本的es用的还是High Level API (在官方文档中,已经显示为 deprecated),我用的比较熟;8.x版本的es用是Java client,有学习成本,而且这个项目的es查询方式挺多的,万一再报什么错,我怕进度上来不及。。

    • 相同查询条件,查询结果会变化。这种问题在es官网解释为“bouncing results 问题”,通俗来讲就是,es查询到的结果会根据评分来排序,评分相同时会根据时间戳字段来排序,时间戳也相同时,就会根据文档在分片中的顺序来排序。所以请求发到不同的分片时,查询到的结果可能是不同的,可以通过new SearchRequest(index).source(searchSourceBuilder).preference("sessionID")中的preference函数,传入一个字符串,es会根据该字符串的哈希值确定访问的分片。该字符串可以是sessionId、userId等。由于并不是每次查询时用户都登录了,以及分布式session问题,我偷懒直接传了用户查询DTO的toString()字符串。

  • Redis: 存放验证码,存放一些数据库查询到的信息,减少查询数据库的次数。注意修改了的信息要在redis中删掉。

  • 七牛云对象存储:存储多模态功能的图片。

工作流

    其实并没有配CI/CD,只是用docker-maven-plugin插件,能直接生成镜像并push到服务器的Register私服库中。然后写了个docker-compose.yml文件,能一键启动。

    后续的话,会将项目部署到k8s上,然后用Jekenis构建一个自动化部署的流水线,并实现金丝雀发布。

监控

    目前的监控靠的是druid和hystrix中的监控,我觉得还是很鸡肋的。我用jmeter进行测试的时候,仍旧感觉整个项目的运行情况我是不可见的。

   后续部署到k8s后,打算试试kubesphere,它内置了Prometheus实现项目的监控。也准备试试链路追踪的监控方案,如Skywalking。

二、Pom文件相关

这是我项目的总pom文件:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>site.koisecret</groupId>
    <artifactId>policy</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>policy-search</module>
        <module>policy-auth</module>
        <module>policy-gateway</module>
        <module>policy-commons</module>
        <module>es-operation</module>
    </modules>

    <name>${project.artifactId}</name>
    <packaging>pom</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
        <spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
        <maven.compiler.version>3.8.1</maven.compiler.version>
        <spring.checkstyle.version>0.0.28</spring.checkstyle.version>
        <docker.plugin.version>1.0.0</docker.plugin.version>
        <docker.registry>10.4.xx.xx:5000</docker.registry>
        <docker.namespace>policy</docker.namespace>
        <docker.host>http://10.4.xx.xx:2375</docker.host>
        <tag>1.0</tag>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.name}</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <pluginManagement>
            <plugins>
                <!--spring boot 默认插件-->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <!--maven  docker 打包插件 -->
                <plugin>
                    <groupId>com.spotify</groupId>
                    <artifactId>docker-maven-plugin</artifactId>
                    <version>${docker.plugin.version}</version>
                    <configuration>
                        <imageName>${docker.registry}/${docker.namespace}/${project.name}</imageName>
                        <dockerDirectory>${project.basedir}</dockerDirectory> <!-- 指定 Dockerfile 路径-->
                        <dockerHost>${docker.host}</dockerHost>
                        <imageTags>
                            <imageTag>${tag}</imageTag>
                            <imageTag>latest</imageTag>
                        </imageTags>
                        <forceTags>true</forceTags>
                    </configuration>
                    <executions>
                        <execution>
                            <id>push</id>
                            <phase>install</phase>
                            <goals>
                                <goal>build</goal>
                                <goal>push</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>

        <plugins>
            <!--代码格式插件,默认使用spring 规则-->
            <plugin>
                <groupId>io.spring.javaformat</groupId>
                <artifactId>spring-javaformat-maven-plugin</artifactId>
                <version>${spring.checkstyle.version}</version>
            </plugin>
            <!--代码编译指定版本插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.version}</version>
                <configuration>
                    <target>${maven.compiler.target}</target>
                    <source>${maven.compiler.source}</source>
                    <encoding>UTF-8</encoding>
                    <skip>true</skip>
                </configuration>
            </plugin>

        </plugins>
    </build>

    <profiles>
        <profile>
            <id>dev</id>
            <properties>
                <!-- 环境标识,需要与配置文件的名称相对应 -->
                <profiles.active>dev</profiles.active>
            </properties>
            <activation>
                <!-- 默认环境 -->
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>prod</id>
            <properties>
                <profiles.active>prod</profiles.active>
            </properties>
        </profile>
        <profile>
            <id>test</id>
            <properties>
                <profiles.active>test</profiles.active>
            </properties>
        </profile>
    </profiles>

    <repositories>
        <repository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>aliyun-plugin</id>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

总pom文件解析

  • <modules>

    本项目包含的模块信息。

  • <packaging>pom</packaging>

    打包方式,总pom文件、包含多个模块的目录中的pom文件,它的打包方式就是pom。单独模块下的打包方式默认是jar

  • <properties>

    进行版本管理,方便后续修改版本

  • <dependencyManagement>

    依赖管理,这里的依赖不会导入到各个模块之中,只是作为版本管理。当模块的pom文件导入依赖时,可以不指定版本,版本由<dependencyManagement>中指定。

  • <dependencies>

    这里的依赖会导入到各个模块中,通常放一些通用的依赖。

  • <build>

    放一些构建项目的的插件,最基本的就是 spring-boot-maven-plugin maven打包插件。

  • <profiles>

    用于分隔多套环境,配合maven打包命令、application.yml使用。可以把<build>中的内容放到各个<profile>中,则可以实现选择不同环境就用不同的打包方式。

  • <repositories>和<pluginRepositories>

    配置依赖和插件的下载地址。

插件解析

spring-boot-maven-plugin

spring boot 默认插件,用于maven打包。

docker-maven-plugin

docker打包镜像插件,配置好Dockerfle位置、docker服务器所在地址、私服库地址、镜像名称等信息后,可以通过命令行代码直接生成镜像并push到服务器。

maven-assembly-plugin

这个插件本项目中没用到,是用来生成zip包的,很多开源项目会有这种构建方式,可以配合shell脚本启动。

三、 异步

为了加快响应速度,采用多线程的方式异步处理是很有必要的。我还没学过消息队列,目前仅用线程池进行异步处理。

  • 为什么用线程池?

    • 1.减少系统频繁开辟线程关闭线程的开销
    • 2.防止用户过多时,系统开辟太多线程而资源耗尽
    • 3.线程池有阻塞队列,应该可以缓解数据库压力
  • 使用范例:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    @Configuration
    public class ThreadConfig {
        @Bean
        public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties threadPoolConfigProperties) {
            return new ThreadPoolExecutor(
                    threadPoolConfigProperties.getCorePoolSize(),
                    threadPoolConfigProperties.getMaximumPoolSize(),
                    threadPoolConfigProperties.getKeepAliveTime(),
                    TimeUnit.SECONDS,
                    new LinkedBlockingDeque<>(threadPoolConfigProperties.getDequeCapacity()),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.CallerRunsPolicy()
            );
        }
    }
    
    ------------------------------------------------------------
    
    @Component
    @ConfigurationProperties(prefix = "thread")
    @Data
    @RefreshScope   
    public class ThreadPoolConfigProperties {
        private int corePoolSize;
        private int maximumPoolSize;
        private int keepAliveTime;
        private int dequeCapacity;
    
    }
    

    这么写可以在nacos中动态调整线程池的配置。corePoolSize可以参考服务器的cpu核数,dequeCapacity可以参考服务器内存大小。

四、 部署

后端部署

主要是docker + docker-compose的方式。

policy-search模块的bootstrap.yml文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
server:
  port: 8001

spring:
  application:
    name:  @artifactId@
  profiles:
    active: @profiles.active@
  cloud:
    nacos:
      discovery:
        server-addr: ${NACOS_HOST:policy-register}:${NACOS_PORT:8848}
        namespace: 45735c42-3ca3-4f2c-b7a1-64axxx
      config:
        server-addr: ${NACOS_HOST:policy-register}:${NACOS_PORT:8848}
        file-extension: yml
        namespace: 45735c42-3ca3-4f2c-b7a1-64axxx

有关policy-search模块的docker-compose.yml文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
version: "3"

services:
  policy-search:
    restart: always
    container_name: policy-search
    image: 10.4.xx.xx:5000/policy/policy-search
    environment:
      NACOS_HOST: 10.4.xx.xx
      NACOS_PORT: 8848
      spring.cloud.nacos.discovery.namespace: cfeacfb9-325b-494e-aa2c-axxxxxx
      spring.cloud.nacos.discovery.config: cfeacfb9-325b-494e-aa2c-a35xxxxxx
#      spring.cloud.nacos.ip: ###
    ports:
      - 8001:8001
  • ${NACOS_HOST:policy-register}:这个写法的意思是,如果项目启动时传了变量NACOS_HOST,则使用传来的值;如果没有传值,则用policy-register作为值传入。policy-registerhost文件中做了映射,可以理解为localhost.
  • environment中,可以传入变量,用于覆盖项目配置文件中的值
  • 当用docker启动时,服务注册入nacos的ip地址是172.x.x.x,其他服务器上的服务是找不到该服务的,可以通过spring.cloud.nacos.ip指定暴露的ip地址。

前端部署

   目前没有使用docker镜像,直接通过ngnix反向代理静态文件的方式,访问前端。但是有个问题,有些页面刷新就无法访问,如“ http://koisecret.site/mine/info ”,而直接从首页“ http://koisecret.site ”点击头像就可以访问。不知道是部署的问题还是前端打包的问题。

   后续还会个项目部署一下证书。

五、 测试

   目前我只用了Jmeter对几个接口进行了压力测试,观察了一下Jmeter给出的测试报告;用htop命令观察了一下服务器cpu和内存的使用情况。

   后续会用jvisualvm,学习一下jvm调优过程,并结合链路追踪、Prometheus等,观察服务运行情况。