一,概述
一样平常来说,提到负载均衡,大家一样平常很轻易想到欣赏器 -> NGINX -> 反向代理多个Tomcat这样的架构图——业界管这种负载均衡模式叫“服务器端负载均衡”,因为此种模式下,负载均衡算法是NGINX提供的,而NGINX部署在服务器端。
本文所讲的Ribbon则是一个客户端侧负载均衡组件——通俗地说,就是集成在客户端(服务消耗者一侧),并提供负载均衡算法的一个组件。Ribbon是Netflix发布的负载均衡器,它可以帮我们控制HTTP和TCP客户端的行为。只需为Ribbon配置服务提供者地址列表,Ribbon就可基于负载均衡算法计算出要请求的目的服务地址。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机、响应时间加权等。
二,实现过程
一样平常环境下,负载均衡组件Ribbon和微服务注册中心Eureka是配合使用的。
为了在非springCloud微服务项目中,使用Ribbon的客户端负载均衡能力,我们可以按如下步骤实现:
- 定义服务的被调用方Client,编写webApi接口
- 定义服务的调用方Cousumer,调用webApi接口
- 定义网关模块Gateway,通过定义路由的方式重新定义webApi接口路径,并引入Ribbon客户端负载均衡
- 将步骤2中的WebApi地址切换为网关模块路由接口地址,从而使原来的webApi地址具有了客户端负载均衡功能
- 使用docker部署Client(多实例)、Gateway,并在编排文件中使用服务名代替客户端列表地址,解耦Cousumer与Client之间的代码接口。
三,项目源码
1. 源码放送:
https://gitee.com/00fly/microservice-all-in-one/tree/master/ribbon-demo
或者使用下面的备份文件恢复成原始的项目代码
怎样恢复,请移步查阅:神奇代码恢复工具
- //goto docker-auto-ip\docker-compose.yml
- version: '3.8'
- services:
- #gateway
- ribbon-gateway:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-gateway:0.0.1
- container_name: ribbon-gateway
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- ports:
- - 8085:8080
- environment:
- USER_SERVERS: ribbon-user-0:8081,ribbon-user-1:8081
- MOVIE_SERVERS: ribbon-movie:8082
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
-
- #client01
- ribbon-user-0:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1
- container_name: ribbon-user-0
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- #client02
- ribbon-user-1:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1
- container_name: ribbon-user-1
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- ribbon-movie:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-movie:0.0.1
- container_name: ribbon-movie
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- environment:
- USER_API_URL: ribbon-gateway:8080/user/
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- //goto docker-auto-ip\restart.sh
- #!/bin/bash
- docker-compose down && \
- docker-compose --compatibility up -d && \
- docker stats
- //goto docker-auto-ip\stop.sh
- #!/bin/bash
- docker-compose down
- //goto docker-fix-ip\docker-compose.yml
- version: '3.8'
- networks:
- default:
- name: devops
- driver: bridge
- ipam:
- config:
- - subnet: 172.88.88.0/24
- services:
- #gateway
- ribbon-gateway:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-gateway:0.0.1
- container_name: ribbon-gateway
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- ports:
- - 8085:8080
- environment:
- MOVIE_SERVERS: 172.88.88.101:8082
- USER_SERVERS: 172.88.88.200:8081,172.88.88.201:8081
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- networks:
- default:
- ipv4_address: 172.88.88.100
-
- #client01
- ribbon-user-0:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1
- container_name: ribbon-user-0
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- networks:
- default:
- ipv4_address: 172.88.88.200
- #client02
- ribbon-user-1:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1
- container_name: ribbon-user-1
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- networks:
- default:
- ipv4_address: 172.88.88.201
- ribbon-movie:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-movie:0.0.1
- container_name: ribbon-movie
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- environment:
- USER_API_URL: http://172.88.88.100:8080/user/
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- networks:
- default:
- ipv4_address: 172.88.88.101
- //goto docker-fix-ip\restart.sh
- #!/bin/bash
- docker-compose down && \
- docker-compose --compatibility up -d && \
- docker stats
- //goto docker-fix-ip\stop.sh
- #!/bin/bash
- docker-compose down
- //goto docker-scale\docker-compose.yml
- version: '3.8'
- services:
- #gateway
- ribbon-gateway:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-gateway:0.0.1
- container_name: ribbon-gateway
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- ports:
- - 8085:8080
- environment:
- USER_SERVERS: ribbon-user:8081
- MOVIE_SERVERS: ribbon-movie:8082
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
-
- #client
- ribbon-user:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- ribbon-movie:
- image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-movie:0.0.1
- deploy:
- resources:
- limits:
- cpus: '1'
- memory: 300M
- reservations:
- memory: 200M
- environment:
- USER_API_URL: ribbon-gateway:8080/user/
- restart: on-failure
- logging:
- driver: json-file
- options:
- max-size: 5m
- max-file: '1'
- //goto docker-scale\restart.sh
- #!/bin/bash
- docker-compose down && \
- docker-compose --compatibility up -d --scale ribbon-user=2 --scale ribbon-movie=2 && \
- docker stats
- //goto docker-scale\stop.sh
- #!/bin/bash
- docker-compose down
- //goto 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.itmuch.cloud</groupId>
- <artifactId>ribbon-all-in-one</artifactId>
- <version>0.0.1</version>
- <packaging>pom</packaging>
- <!-- 引入spring boot的依赖 -->
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.2.6.RELEASE</version>
- <relativePath />
- </parent>
- <modules>
- <module>ribbon-gateway</module>
- <module>ribbon-movie</module>
- <module>ribbon-user</module>
- </modules>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <maven.build.timestamp.format>yyyyMMdd-HH</maven.build.timestamp.format>
- <docker.hub>registry.cn-shanghai.aliyuncs.com</docker.hub>
- <java.version>1.8</java.version>
- <docker.plugin.version>1.2.2</docker.plugin.version>
- <docker.image.prefix>00fly</docker.image.prefix>
- <spring.cloud.version>Hoxton.SR6</spring.cloud.version>
- <knife4j.version>2.0.8</knife4j.version>
- <skipTests>true</skipTests>
- </properties>
- <dependencyManagement>
- <dependencies>
- <!-- 引入spring cloud的依赖 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>${spring.cloud.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>knife4j-spring-boot-starter</artifactId>
- <version>${knife4j.version}</version>
- </dependency>
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>knife4j-micro-spring-boot-starter</artifactId>
- <version>${knife4j.version}</version>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>repackage</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <!-- 添加docker-maven插件 -->
- <plugin>
- <groupId>io.fabric8</groupId>
- <artifactId>docker-maven-plugin</artifactId>
- <version>0.40.3</version>
- <executions>
- <execution>
- <phase>package</phase>
- <goals>
- <goal>build</goal>
- <!--<goal>push</goal>-->
- <!--<goal>remove</goal>-->
- </goals>
- </execution>
- </executions>
- <configuration>
- <!-- 连接到带docker环境的linux服务器编译image -->
- <!--<dockerHost>http://192.168.182.10:2375</dockerHost>-->
- <!-- Docker 推送镜像仓库地址 -->
- <pushRegistry>${docker.hub}</pushRegistry>
- <images>
- <image>
- <!--推送到私有镜像仓库,镜像名需要添加仓库地址 -->
- <name>${docker.hub}/00fly/${project.artifactId}:${project.version}</name>
- <!--定义镜像构建行为 -->
- <build>
- <dockerFileDir>${project.basedir}</dockerFileDir>
- </build>
- </image>
- </images>
- <authConfig>
- <!--认证配置,用于私有镜像仓库registry认证 -->
- <username>${docker.username}</username>
- <password>${docker.password}</password>
- </authConfig>
- </configuration>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
- </project>
- //goto ribbon-gateway\Dockerfile
- #基础镜像
- FROM adoptopenjdk/openjdk8-openj9:alpine-slim
- COPY wait-for.sh /
- RUN chmod +x /wait-for.sh && \
- ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
- #引入运行包
- COPY target/*.jar /app.jar
- #指定交互端口
- EXPOSE 8080
- CMD ["--server.port=8080"]
- #项目的启动方式
- ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-Xshareclasses", "-Xquickstart", "-jar", "/app.jar"]
- //goto ribbon-gateway\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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>com.itmuch.cloud</groupId>
- <artifactId>ribbon-all-in-one</artifactId>
- <version>0.0.1</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>ribbon-gateway</artifactId>
- <packaging>jar</packaging>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- <exclusions>
- <exclusion>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-log4j2</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
- </dependency>
- <!-- 集成 knife4j -->
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>knife4j-spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
- </dependencies>
- <build>
- <finalName>${project.artifactId}</finalName>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>io.fabric8</groupId>
- <artifactId>docker-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
- //goto ribbon-gateway\src\main\java\com\fly\gateway\config\SwaggerHeaderFilter.java
- package com.fly.gateway.config;
- import org.springframework.cloud.gateway.filter.GatewayFilter;
- import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
- import org.springframework.http.server.reactive.ServerHttpRequest;
- import org.springframework.stereotype.Component;
- import org.springframework.util.StringUtils;
- import org.springframework.web.server.ServerWebExchange;
- /**
- * @author fsl
- * @description: SwaggerHeaderFilter
- * @date 2019-06-0310:47
- */
- @Component
- public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory<Object>
- {
- private static final String HEADER_NAME = "X-Forwarded-Prefix";
-
- private static final String URI = "/v2/api-docs";
-
- /**
- * 网关过滤器
- */
- @Override
- public GatewayFilter apply(Object config)
- {
- return (exchange, chain) -> {
- ServerHttpRequest request = exchange.getRequest();
- String path = request.getURI().getPath();
- if (!StringUtils.endsWithIgnoreCase(path, URI))
- {
- return chain.filter(exchange);
- }
- String basePath = path.substring(0, path.lastIndexOf(URI));
- ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
- ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
- return chain.filter(newExchange);
- };
- }
- }
- //goto ribbon-gateway\src\main\java\com\fly\gateway\config\SwaggerResourceConfig.java
- package com.fly.gateway.config;
- import java.util.ArrayList;
- import java.util.List;
- import org.springframework.cloud.gateway.config.GatewayProperties;
- import org.springframework.cloud.gateway.route.RouteLocator;
- import org.springframework.cloud.gateway.support.NameUtils;
- import org.springframework.context.annotation.Primary;
- import org.springframework.stereotype.Component;
- import lombok.AllArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import springfox.documentation.swagger.web.SwaggerResource;
- import springfox.documentation.swagger.web.SwaggerResourcesProvider;
- /***
- * 聚合各个服务的swagger接口
- */
- @Slf4j
- @Component
- @Primary
- @AllArgsConstructor
- public class SwaggerResourceConfig implements SwaggerResourcesProvider
- {
- /**
- * 网关路由
- */
- private final RouteLocator routeLocator;
-
- private final GatewayProperties gatewayProperties;
-
- @Override
- public List<SwaggerResource> get()
- {
- List<SwaggerResource> resources = new ArrayList<>();
- List<String> routes = new ArrayList<>();
- routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
- gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
- route.getPredicates()
- .stream()
- .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
- .forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(), predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("**", "v2/api-docs"))));
- });
- return resources;
- }
-
- private SwaggerResource swaggerResource(String name, String location)
- {
- log.info("name:{},location:{}", name, location);
- SwaggerResource swaggerResource = new SwaggerResource();
- swaggerResource.setName(name);
- swaggerResource.setLocation(location);
- swaggerResource.setSwaggerVersion("2.0");
- return swaggerResource;
- }
- }
- //goto ribbon-gateway\src\main\java\com\fly\gateway\GateWayApplication.java
- package com.fly.gateway;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- @SpringBootApplication
- public class GateWayApplication
- {
- public static void main(String[] args)
- {
- SpringApplication.run(GateWayApplication.class, args);
- }
- }
- //goto ribbon-gateway\src\main\java\com\fly\gateway\handler\SwaggerHandler.java
- package com.fly.gateway.handler;
- import java.util.Optional;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.ResponseEntity;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
- import reactor.core.publisher.Mono;
- import springfox.documentation.swagger.web.SecurityConfiguration;
- import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
- import springfox.documentation.swagger.web.SwaggerResourcesProvider;
- import springfox.documentation.swagger.web.UiConfiguration;
- import springfox.documentation.swagger.web.UiConfigurationBuilder;
- /**
- * swagger聚合接口
- *
- */
- @RestController
- public class SwaggerHandler
- {
- @Autowired(required = false)
- private SecurityConfiguration securityConfiguration;
-
- @Autowired(required = false)
- private UiConfiguration uiConfiguration;
-
- private final SwaggerResourcesProvider swaggerResources;
-
- public SwaggerHandler(SwaggerResourcesProvider swaggerResources)
- {
- this.swaggerResources = swaggerResources;
- }
-
- @GetMapping("/swagger-resources/configuration/security")
- public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration()
- {
- return Mono.just(new ResponseEntity<>(Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
- }
-
- @GetMapping("/swagger-resources/configuration/ui")
- public Mono<ResponseEntity<UiConfiguration>> uiConfiguration()
- {
- return Mono.just(new ResponseEntity<>(Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
- }
-
- @GetMapping("/swagger-resources")
- public Mono<ResponseEntity<?>> swaggerResources()
- {
- return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
- }
- }
- //goto ribbon-gateway\src\main\resources\application.yml
- server:
- port: 8080
- spring:
- application:
- name: ribbon-gateway
- cloud:
- gateway:
- discovery:
- locator:
- lowerCaseServiceId: true
- routes:
- - id: ribbon-user
- uri: lb://ribbon-user
- predicates:
- - Path=/user/**
- filters:
- - StripPrefix=1
- - id: ribbon-movie
- uri: lb://ribbon-movie
- predicates:
- - Path=/movie/**
- filters:
- - StripPrefix=1
- ribbon-movie:
- ribbon:
- listOfServers: ${MOVIE_SERVERS:127.0.0.1:8082}
-
- ribbon-user:
- ribbon:
- listOfServers: ${USER_SERVERS:127.0.0.1:8081}
- //goto ribbon-gateway\wait-for.sh
- #!/bin/sh
- TIMEOUT=15
- QUIET=0
- echoerr() {
- if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
- }
- usage() {
- exitcode="$1"
- cat << USAGE >&2
- Usage:
- $cmdname host:port [-t timeout] [-- command args]
- -q | --quiet Do not output any status messages
- -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
- -- COMMAND ARGS Execute command with args after the test finishes
- USAGE
- exit "$exitcode"
- }
- wait_for() {
- for i in `seq $TIMEOUT` ; do
- nc -z "$HOST" "$PORT" > /dev/null 2>&1
- result=$?
- if [ $result -eq 0 ] ; then
- if [ $# -gt 0 ] ; then
- exec "$@"
- fi
- exit 0
- fi
- sleep 1
- done
- echo "Operation timed out" >&2
- exit 1
- }
- while [ $# -gt 0 ]
- do
- case "$1" in
- *:* )
- HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
- PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
- shift 1
- ;;
- -q | --quiet)
- QUIET=1
- shift 1
- ;;
- -t)
- TIMEOUT="$2"
- if [ "$TIMEOUT" = "" ]; then break; fi
- shift 2
- ;;
- --timeout=*)
- TIMEOUT="${1#*=}"
- shift 1
- ;;
- --)
- shift
- break
- ;;
- --help)
- usage 0
- ;;
- *)
- echoerr "Unknown argument: $1"
- usage 1
- ;;
- esac
- done
- if [ "$HOST" = "" -o "$PORT" = "" ]; then
- echoerr "Error: you need to provide a host and port to test."
- usage 2
- fi
- wait_for "$@"
- //goto ribbon-movie\Dockerfile
- #基础镜像
- FROM adoptopenjdk/openjdk8-openj9:alpine-slim
- COPY wait-for.sh /
- RUN chmod +x /wait-for.sh && \
- ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
- #引入运行包
- COPY target/*.jar /app.jar
- #指定交互端口
- EXPOSE 8082
- CMD ["--server.port=8082"]
- #项目的启动方式
- ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-Xshareclasses", "-Xquickstart", "-jar", "/app.jar"]
- //goto ribbon-movie\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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>com.itmuch.cloud</groupId>
- <artifactId>ribbon-all-in-one</artifactId>
- <version>0.0.1</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>ribbon-movie</artifactId>
- <packaging>jar</packaging>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- <exclusions>
- <exclusion>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-log4j2</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-webflux</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- </dependency>
- <!-- 使用Apache HttpClient替换Feign原生httpclient -->
- <dependency>
- <groupId>io.github.openfeign</groupId>
- <artifactId>feign-okhttp</artifactId>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <scope>provided</scope>
- </dependency>
- <!-- 集成 knife4j -->
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>knife4j-spring-boot-starter</artifactId>
- </dependency>
- </dependencies>
- <build>
- <finalName>${project.artifactId}</finalName>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>io.fabric8</groupId>
- <artifactId>docker-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\ConsumerMovieApplication.java
- package com.itmuch.cloud.study;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cache.annotation.EnableCaching;
- import org.springframework.cloud.openfeign.EnableFeignClients;
- @EnableCaching
- @EnableFeignClients
- @SpringBootApplication
- public class ConsumerMovieApplication
- {
- public static void main(String[] args)
- {
- SpringApplication.run(ConsumerMovieApplication.class, args);
- }
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\core\config\Knife4jConfig.java
- package com.itmuch.cloud.study.core.config;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
- import io.swagger.annotations.ApiOperation;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
- /**
- * Knife4j配置
- *
- */
- @Configuration
- @EnableKnife4j
- @EnableSwagger2WebMvc
- public class Knife4jConfig
- {
- @Value("${knife4j.enable:true}")
- private boolean enable;
-
- /**
- * 开发、测试环境接口文档打开
- *
- * @return
- * @see [类、类#方法、类#成员]
- */
- @Bean
- Docket createRestApi()
- {
- return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
- .enable(enable)
- .select()
- .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
- .paths(PathSelectors.any()) // 包下的类,生成接口文档
- .build();
- }
-
- private ApiInfo apiInfo()
- {
- return new ApiInfoBuilder().title("movie模块API").description("接口文档").termsOfServiceUrl("http://00fly.online/").version("1.0.0").build();
- }
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\core\config\RibbonConfiguration.java
- //package com.itmuch.cloud.study.core.config;
- //
- //import org.springframework.context.annotation.Bean;
- //import org.springframework.context.annotation.Configuration;
- //
- //import com.netflix.loadbalancer.IRule;
- //import com.netflix.loadbalancer.RandomRule;
- //
- ///**
- // * 该类为Ribbon的配置类 注意:该类不应该在主应用程序上下文的@ComponentScan 中。
- // *
- // */
- //@Configuration
- //public class RibbonConfiguration
- //{
- // @Bean
- // IRule ribbonRule()
- // {
- // // 负载均衡规则,改为随机
- // return new RandomRule();
- // }
- //}
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\core\config\TestConfiguration.java
- //package com.itmuch.cloud.study.core.config;
- //
- //import org.springframework.cloud.netflix.ribbon.RibbonClient;
- //import org.springframework.context.annotation.Configuration;
- //
- ///**
- // * 使用RibbonClient,为特定name的Ribbon Client自定义配置. 使用@RibbonClient的configuration属性,指定Ribbon的配置类. <br>
- // * 可参考的示例: http://spring.io/guides/gs/client-side-load-balancing/
- // *
- // */
- //@Configuration
- //@RibbonClient(name = "microservice-ribbon-user", configuration = RibbonConfiguration.class)
- //public class TestConfiguration
- //{
- //}
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\core\config\WebMvcConfig.java
- package com.itmuch.cloud.study.core.config;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- /**
- *
- * mvc配置
- *
- * @author 00fly
- * @version [版本号, 2021年4月23日]
- * @see [相关类/方法]
- * @since [产品/模块版本]
- */
- @Configuration
- public class WebMvcConfig implements WebMvcConfigurer
- {
- /**
- * @param registry
- */
- @Override
- public void addViewControllers(final ViewControllerRegistry registry)
- {
- registry.addViewController("/").setViewName("doc.html");
- }
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\core\utils\JsonBeanUtils.java
- package com.itmuch.cloud.study.core.utils;
- import java.io.IOException;
- import com.fasterxml.jackson.core.type.TypeReference;
- import com.fasterxml.jackson.databind.DeserializationFeature;
- import com.fasterxml.jackson.databind.JavaType;
- import com.fasterxml.jackson.databind.ObjectMapper;
- /**
- * JsonBean转换工具
- *
- * @author 00fly
- *
- */
- public class JsonBeanUtils
- {
- private static ObjectMapper objectMapper = new ObjectMapper();
-
- /**
- * bean转json字符串
- *
- * @param bean
- * @return
- * @throws IOException
- */
- public static String beanToJson(Object bean)
- throws IOException
- {
- String jsonText = objectMapper.writeValueAsString(bean);
- return objectMapper.readTree(jsonText).toPrettyString();
- }
-
- /**
- * bean转json字符串
- *
- * @param bean
- * @param pretty 是否格式美化
- * @return
- * @throws IOException
- */
- public static String beanToJson(Object bean, boolean pretty)
- throws IOException
- {
- if (pretty)
- {
- return beanToJson(bean);
- }
- String jsonText = objectMapper.writeValueAsString(bean);
- return objectMapper.readTree(jsonText).toString();
- }
-
- /**
- * json字符串转bean
- *
- * @param jsonText
- * @return
- * @throws IOException
- */
- public static <T> T jsonToBean(String jsonText, Class<T> clazz)
- throws IOException
- {
- return objectMapper.readValue(jsonText, clazz);
- }
-
- /**
- * json字符串转bean
- *
- * @param jsonText
- * @return
- * @throws IOException
- */
- public static <T> T jsonToBean(String jsonText, JavaType javaType)
- throws IOException
- {
- return objectMapper.readValue(jsonText, javaType);
- }
-
- /**
- * json字符串转bean
- *
- * @param jsonText
- * @param clazz
- * @param ingoreError 是否忽略无法识别字段
- * @return
- * @throws IOException
- */
- public static <T> T jsonToBean(String jsonText, Class<T> clazz, boolean ingoreError)
- throws IOException
- {
- objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, !ingoreError);
- return objectMapper.readValue(jsonText, clazz);
- }
-
- /**
- * json字符串转bean
- *
- * @param jsonText
- * @return
- * @throws IOException
- */
- public static <T> T jsonToBean(String jsonText, TypeReference<T> typeRef)
- throws IOException
- {
- return objectMapper.readValue(jsonText, typeRef);
- }
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\controller\DataPushController.java
- package com.itmuch.cloud.study.user.controller;
- import java.io.IOException;
- import java.util.List;
- import java.util.concurrent.ScheduledThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- import javax.annotation.PostConstruct;
- import org.apache.commons.lang3.RandomUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.CrossOrigin;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
- import com.itmuch.cloud.study.core.utils.JsonBeanUtils;
- import com.itmuch.cloud.study.user.entity.Article;
- import com.itmuch.cloud.study.user.service.DataService;
- import com.itmuch.cloud.study.user.service.SSEServer;
- import io.swagger.annotations.Api;
- import lombok.extern.slf4j.Slf4j;
- @Slf4j
- @Api(tags = "DataPush模块")
- @RestController
- public class DataPushController
- {
- long init = 0L;
-
- @Autowired
- DataService dataService;
-
- @PostConstruct
- private void init()
- {
- log.info("Server-Sent Events start");
- new ScheduledThreadPoolExecutor(2).scheduleAtFixedRate(() -> {
- long now = (init + RandomUtils.nextInt(5, 10)) % 101;
- SSEServer.batchSendMessage(String.valueOf(init));
- if (now < init)
- {
- try
- {
- // 随机选择2个,返回访问量小的
- List<Article> articles = dataService.getArticles();
- int length = articles.size();
- Article article001 = articles.get(RandomUtils.nextInt(0, length));
- Article article002 = articles.get(RandomUtils.nextInt(0, length));
- SSEServer.batchSendMessage("json", JsonBeanUtils.beanToJson(article001.getViewCount() > article002.getViewCount() ? article002 : article001, false));
- }
- catch (IOException e)
- {
- }
- }
- init = now;
- }, 2000, 1000, TimeUnit.MILLISECONDS);
- }
-
- @CrossOrigin
- @GetMapping("/sse/connect/{userId}")
- public SseEmitter connect(@PathVariable String userId)
- {
- return SSEServer.connect();
- }
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\controller\MovieController.java
- package com.itmuch.cloud.study.user.controller;
- import java.net.InetAddress;
- import java.net.UnknownHostException;
- import javax.annotation.PostConstruct;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
- import org.springframework.web.bind.annotation.RestController;
- import com.itmuch.cloud.study.user.entity.User;
- import com.itmuch.cloud.study.user.feign.UserFeignClient;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- @Api(tags = "movie模块")
- @RestController
- public class MovieController
- {
- String serverIp;
-
- @Autowired
- private UserFeignClient userFeignClient;
-
- @PostConstruct
- private void init()
- {
- try
- {
- serverIp = InetAddress.getLocalHost().getHostAddress();
- }
- catch (UnknownHostException e)
- {
- }
- }
-
- @ApiOperation("查询用户")
- @GetMapping("/user/{id}")
- public User findById(@PathVariable Long id)
- {
- // 带出serverIp方便判断数据来源容器
- User user = this.userFeignClient.findById(id);
- if (user.getId() > 0)
- {
- user.setName(user.getName() + " === in server:" + serverIp);
- }
- return user;
- }
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\entity\Article.java
- package com.itmuch.cloud.study.user.entity;
- import lombok.Data;
- @Data
- public class Article
- {
- String title;
-
- String url;
-
- Long viewCount;
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\entity\BlogData.java
- package com.itmuch.cloud.study.user.entity;
- import lombok.Data;
- @Data
- public class BlogData
- {
- private Record data;
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\entity\Record.java
- package com.itmuch.cloud.study.user.entity;
- import java.util.List;
- import lombok.Data;
- @Data
- public class Record
- {
- private List<Article> list;
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\entity\User.java
- package com.itmuch.cloud.study.user.entity;
- import java.math.BigDecimal;
- import lombok.Data;
- @Data
- public class User
- {
- private Long id;
-
- private String username;
-
- private String name;
-
- private Integer age;
-
- private BigDecimal balance;
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\feign\UserFeignClient.java
- package com.itmuch.cloud.study.user.feign;
- import org.springframework.cloud.openfeign.FeignClient;
- import org.springframework.stereotype.Component;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
- import com.itmuch.cloud.study.user.entity.User;
- @FeignClient(name = "microservice-ribbon-user", url = "${user.api.url:127.0.0.1:8081}", fallback = FeignClientFallback.class)
- public interface UserFeignClient
- {
- @GetMapping("/{id}")
- public User findById(@PathVariable("id") Long id);
- }
- /**
- * 回退类FeignClientFallback需实现Feign Client接口,FeignClientFallback也可以是public class,没有区别
- *
- */
- @Component
- class FeignClientFallback implements UserFeignClient
- {
- @Override
- public User findById(Long id)
- {
- User user = new User();
- user.setId(-1L);
- user.setUsername("默认用户");
- return user;
- }
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\service\DataService.java
- package com.itmuch.cloud.study.user.service;
- import java.io.IOException;
- import java.nio.charset.StandardCharsets;
- import java.util.List;
- import org.springframework.cache.annotation.Cacheable;
- import org.springframework.http.MediaType;
- import org.springframework.stereotype.Service;
- import org.springframework.web.reactive.function.client.WebClient;
- import com.itmuch.cloud.study.core.utils.JsonBeanUtils;
- import com.itmuch.cloud.study.user.entity.Article;
- import com.itmuch.cloud.study.user.entity.BlogData;
- import lombok.extern.slf4j.Slf4j;
- /**
- * DataService
- */
- @Slf4j
- @Service
- public class DataService
- {
- WebClient webClient = WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build();
-
- /**
- * 获取Article数据列表
- *
- * @return
- * @throws IOException
- */
- @Cacheable(cacheNames = "data", key = "'articles'", sync = true)
- public List<Article> getArticles()
- throws IOException
- {
- log.info("★★★★★★★★ getData from webApi ★★★★★★★★");
- String resp = webClient.get().uri("https://00fly.online/upload/data.json").acceptCharset(StandardCharsets.UTF_8).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(String.class).block();
- return JsonBeanUtils.jsonToBean(resp, BlogData.class, true).getData().getList();
- }
- }
- //goto ribbon-movie\src\main\java\com\itmuch\cloud\study\user\service\SSEServer.java
- package com.itmuch.cloud.study.user.service;
- import java.io.IOException;
- import java.util.List;
- import java.util.concurrent.CopyOnWriteArrayList;
- import java.util.function.Consumer;
- import org.springframework.http.MediaType;
- import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
- import lombok.extern.slf4j.Slf4j;
- /**
- * Server-Sent Events <BR>
- * https://blog.csdn.net/hhl18730252820/article/details/126244274
- */
- @Slf4j
- public class SSEServer
- {
- private static List<SseEmitter> sseEmitters = new CopyOnWriteArrayList<>();
-
- public static SseEmitter connect()
- {
- SseEmitter sseEmitter = new SseEmitter(0L); // 设置超时时间,0表示不过期,默认是30秒,超过时间未完成会抛出异常
-
- // 注册回调
- sseEmitter.onCompletion(completionCallBack(sseEmitter));
- sseEmitter.onError(errorCallBack(sseEmitter));
- sseEmitter.onTimeout(timeOutCallBack(sseEmitter));
- sseEmitters.add(sseEmitter);
- log.info("###### create new sse connect, count: {}", sseEmitters.size());
- return sseEmitter;
- }
-
- public static void batchSendMessage(String message)
- {
- sseEmitters.forEach(it -> {
- try
- {
- it.send(message, MediaType.APPLICATION_JSON);
- }
- catch (IOException e)
- {
- log.error("send message error: {}", e.getMessage());
- remove(it);
- }
- });
- }
-
- /**
- * 指定name,发送message
- *
- * @param name
- * @param message 普通字符串或json数据
- */
- public static void batchSendMessage(String name, String message)
- {
- sseEmitters.forEach(it -> {
- try
- {
- it.send(SseEmitter.event().name(name).data(message));
- }
- catch (IOException e)
- {
- log.error("send message error: {}", e.getMessage());
- remove(it);
- }
- });
- }
-
- public static void remove(SseEmitter s)
- {
- if (sseEmitters.contains(s))
- {
- sseEmitters.remove(s);
- log.info("###### remove SseEmitter, count: {}", sseEmitters.size());
- }
- }
-
- private static Runnable completionCallBack(SseEmitter s)
- {
- return () -> {
- log.info("结束连接");
- remove(s);
- };
- }
-
- private static Runnable timeOutCallBack(SseEmitter s)
- {
- return () -> {
- log.info("连接超时");
- remove(s);
- };
- }
-
- private static Consumer<Throwable> errorCallBack(SseEmitter s)
- {
- return throwable -> {
- log.error("连接异常");
- remove(s);
- };
- }
- }
- //goto ribbon-movie\src\main\resources\application-ribbon.yml
- server:
- port: 8082
- spring:
- application:
- name: ribbon-movie
- cache:
- type: simple
-
- #设置负载均衡参数
- microservice-ribbon-user:
- ribbon:
- #配置规则
- NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
-
- #配置地址:宿主机ip+映射端口或docker自定义网络指定地址
- #listOfServers: 172.22.208.1:8081,172.22.208.1:8091
- listOfServers: 172.88.88.200:8081,172.88.88.201:8081
- feign:
- httpclient:
- enabled: true
- ribbon:
- ReadTimeout: 30000
- ConnectTimeout: 30000
- logging:
- level:
- root: INFO
- //goto ribbon-movie\src\main\resources\application.yml
- server:
- port: 8082
- spring:
- application:
- name: ribbon-movie
- cache:
- type: simple
-
- #feign.okhttp.enabled默认不开启
- #从Spring Cloud Dalston开始,Feign默认是不开启Hystrix的。
- feign:
- okhttp:
- enabled: true
- hystrix:
- enabled: true
- logging:
- level:
- root: INFO
- //goto ribbon-movie\wait-for.sh
- #!/bin/sh
- TIMEOUT=15
- QUIET=0
- echoerr() {
- if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
- }
- usage() {
- exitcode="$1"
- cat << USAGE >&2
- Usage:
- $cmdname host:port [-t timeout] [-- command args]
- -q | --quiet Do not output any status messages
- -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
- -- COMMAND ARGS Execute command with args after the test finishes
- USAGE
- exit "$exitcode"
- }
- wait_for() {
- for i in `seq $TIMEOUT` ; do
- nc -z "$HOST" "$PORT" > /dev/null 2>&1
- result=$?
- if [ $result -eq 0 ] ; then
- if [ $# -gt 0 ] ; then
- exec "$@"
- fi
- exit 0
- fi
- sleep 1
- done
- echo "Operation timed out" >&2
- exit 1
- }
- while [ $# -gt 0 ]
- do
- case "$1" in
- *:* )
- HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
- PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
- shift 1
- ;;
- -q | --quiet)
- QUIET=1
- shift 1
- ;;
- -t)
- TIMEOUT="$2"
- if [ "$TIMEOUT" = "" ]; then break; fi
- shift 2
- ;;
- --timeout=*)
- TIMEOUT="${1#*=}"
- shift 1
- ;;
- --)
- shift
- break
- ;;
- --help)
- usage 0
- ;;
- *)
- echoerr "Unknown argument: $1"
- usage 1
- ;;
- esac
- done
- if [ "$HOST" = "" -o "$PORT" = "" ]; then
- echoerr "Error: you need to provide a host and port to test."
- usage 2
- fi
- wait_for "$@"
- //goto ribbon-user\Dockerfile
- #基础镜像
- FROM adoptopenjdk/openjdk8-openj9:alpine-slim
- COPY wait-for.sh /
- RUN chmod +x /wait-for.sh && \
- ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
- #引入运行包
- COPY target/*.jar /app.jar
- #指定交互端口
- EXPOSE 8081
- CMD ["--server.port=8081"]
- #项目的启动方式
- ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-Xshareclasses", "-Xquickstart", "-jar", "/app.jar"]
- //goto ribbon-user\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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>com.itmuch.cloud</groupId>
- <artifactId>ribbon-all-in-one</artifactId>
- <version>0.0.1</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>ribbon-user</artifactId>
- <packaging>jar</packaging>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- <exclusions>
- <exclusion>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-log4j2</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>com.h2database</groupId>
- <artifactId>h2</artifactId>
- </dependency>
- <!-- 集成 knife4j -->
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>knife4j-spring-boot-starter</artifactId>
- </dependency>
- </dependencies>
- <build>
- <finalName>${project.artifactId}</finalName>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>io.fabric8</groupId>
- <artifactId>docker-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
- //goto ribbon-user\src\main\java\com\itmuch\cloud\study\controller\UserController.java
- package com.itmuch.cloud.study.controller;
- import java.util.List;
- import java.util.Optional;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
- import org.springframework.web.bind.annotation.RestController;
- import com.itmuch.cloud.study.entity.User;
- import com.itmuch.cloud.study.repository.UserRepository;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- @Api(tags = "user模块")
- @RestController
- public class UserController
- {
- @Autowired
- private UserRepository userRepository;
-
- @ApiOperation("查询用户")
- @GetMapping("/{id:\\d+}")
- public Optional<User> findById(@PathVariable Long id)
- {
- return this.userRepository.findById(id);
- }
-
- @ApiOperation("查询全部用户")
- @GetMapping("getAll")
- public List<User> getAll()
- {
- return this.userRepository.findAll();
- }
- }
- //goto ribbon-user\src\main\java\com\itmuch\cloud\study\core\config\Knife4jConfig.java
- package com.itmuch.cloud.study.core.config;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
- import io.swagger.annotations.ApiOperation;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
- /**
- * Knife4j配置
- *
- */
- @Configuration
- @EnableKnife4j
- @EnableSwagger2WebMvc
- public class Knife4jConfig
- {
- @Value("${knife4j.enable:true}")
- private boolean enable;
-
- /**
- * 开发、测试环境接口文档打开
- *
- * @return
- * @see [类、类#方法、类#成员]
- */
- @Bean
- Docket createRestApi()
- {
- return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
- .enable(enable)
- .select()
- .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
- .paths(PathSelectors.any()) // 包下的类,生成接口文档
- .build();
- }
-
- private ApiInfo apiInfo()
- {
- return new ApiInfoBuilder().title("user模块API").description("接口文档").termsOfServiceUrl("http://00fly.online/").version("1.0.0").build();
- }
- }
- //goto ribbon-user\src\main\java\com\itmuch\cloud\study\core\config\WebMvcConfig.java
- package com.itmuch.cloud.study.core.config;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- /**
- *
- * mvc配置
- *
- * @author 00fly
- * @version [版本号, 2021年4月23日]
- * @see [相关类/方法]
- * @since [产品/模块版本]
- */
- @Configuration
- public class WebMvcConfig implements WebMvcConfigurer
- {
- /**
- * @param registry
- */
- @Override
- public void addViewControllers(final ViewControllerRegistry registry)
- {
- registry.addViewController("/").setViewName("doc.html");
- }
- }
- //goto ribbon-user\src\main\java\com\itmuch\cloud\study\entity\User.java
- package com.itmuch.cloud.study.entity;
- import java.math.BigDecimal;
- import javax.persistence.Column;
- import javax.persistence.Entity;
- import javax.persistence.GeneratedValue;
- import javax.persistence.GenerationType;
- import javax.persistence.Id;
- @Entity
- public class User
- {
- public User()
- {
- }
- public User(Long id, String username, String name, Integer age, BigDecimal balance)
- {
- this.id = id;
- this.username = username;
- this.name = name;
- this.age = age;
- this.balance = balance;
- }
- @Id
- @GeneratedValue(strategy = GenerationType.AUTO)
- private Long id;
- @Column
- private String username;
- @Column
- private String name;
- @Column
- private Integer age;
- @Column
- private BigDecimal balance;
- public Long getId()
- {
- return this.id;
- }
- public void setId(Long id)
- {
- this.id = id;
- }
- public String getUsername()
- {
- return this.username;
- }
- public void setUsername(String username)
- {
- this.username = username;
- }
- public String getName()
- {
- return this.name;
- }
- public void setName(String name)
- {
- this.name = name;
- }
- public Integer getAge()
- {
- return this.age;
- }
- public void setAge(Integer age)
- {
- this.age = age;
- }
- public BigDecimal getBalance()
- {
- return this.balance;
- }
- public void setBalance(BigDecimal balance)
- {
- this.balance = balance;
- }
- }
- //goto ribbon-user\src\main\java\com\itmuch\cloud\study\ProviderUserApplication.java
- package com.itmuch.cloud.study;
- import java.math.BigDecimal;
- import java.net.InetAddress;
- import java.util.stream.Stream;
- import org.springframework.boot.ApplicationRunner;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.context.annotation.Bean;
- import com.itmuch.cloud.study.entity.User;
- import com.itmuch.cloud.study.repository.UserRepository;
- @SpringBootApplication
- public class ProviderUserApplication
- {
- public static void main(String[] args)
- {
- SpringApplication.run(ProviderUserApplication.class, args);
- }
-
- /**
- * 初始化用户信息 注:Spring Boot2不能像1.x一样,用spring.datasource.schema/data指定初始化SQL脚本,否则与actuator不能共存<br>
- * 原因:https://github.com/spring-projects/spring-boot/issues/13042<br>
- * https://github.com/spring-projects/spring-boot/issues/13539
- *
- * @param repository repo
- * @return runner
- */
- @Bean
- ApplicationRunner init(UserRepository repository)
- {
- return args -> {
- String ip = InetAddress.getLocalHost().getHostAddress();
- int init = (int)(System.currentTimeMillis() % 10);
- User user1 = new User(1L, "account1", "张三 from " + ip, init + 20, new BigDecimal(100.00));
- User user2 = new User(2L, "account2", "李四 from " + ip, init + 30, new BigDecimal(180.00));
- User user3 = new User(3L, "account3", "王五 from " + ip, init + 40, new BigDecimal(280.00));
- Stream.of(user1, user2, user3).forEach(repository::save);
- };
- }
- }
- //goto ribbon-user\src\main\java\com\itmuch\cloud\study\repository\UserRepository.java
- package com.itmuch.cloud.study.repository;
- import org.springframework.data.jpa.repository.JpaRepository;
- import org.springframework.stereotype.Repository;
- import com.itmuch.cloud.study.entity.User;
- @Repository
- public interface UserRepository extends JpaRepository<User, Long>
- {
- }
- //goto ribbon-user\src\main\resources\application.yml
- server:
- port: 8081
- spring:
- application:
- name: ribbon-user
- jpa:
- generate-ddl: false
- show-sql: true
- hibernate:
- ddl-auto: create-drop
- logging:
- level:
- root: INFO
- //goto ribbon-user\wait-for.sh
- #!/bin/sh
- TIMEOUT=15
- QUIET=0
- echoerr() {
- if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
- }
- usage() {
- exitcode="$1"
- cat << USAGE >&2
- Usage:
- $cmdname host:port [-t timeout] [-- command args]
- -q | --quiet Do not output any status messages
- -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
- -- COMMAND ARGS Execute command with args after the test finishes
- USAGE
- exit "$exitcode"
- }
- wait_for() {
- for i in `seq $TIMEOUT` ; do
- nc -z "$HOST" "$PORT" > /dev/null 2>&1
- result=$?
- if [ $result -eq 0 ] ; then
- if [ $# -gt 0 ] ; then
- exec "$@"
- fi
- exit 0
- fi
- sleep 1
- done
- echo "Operation timed out" >&2
- exit 1
- }
- while [ $# -gt 0 ]
- do
- case "$1" in
- *:* )
- HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
- PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
- shift 1
- ;;
- -q | --quiet)
- QUIET=1
- shift 1
- ;;
- -t)
- TIMEOUT="$2"
- if [ "$TIMEOUT" = "" ]; then break; fi
- shift 2
- ;;
- --timeout=*)
- TIMEOUT="${1#*=}"
- shift 1
- ;;
- --)
- shift
- break
- ;;
- --help)
- usage 0
- ;;
- *)
- echoerr "Unknown argument: $1"
- usage 1
- ;;
- esac
- done
- if [ "$HOST" = "" -o "$PORT" = "" ]; then
- echoerr "Error: you need to provide a host and port to test."
- usage 2
- fi
- wait_for "$@"
复制代码 2. 部署方式
这边提供了3种docker部署方式
- 自动ip(推荐)
- 固定ip
- docker scale 水平扩展
分别对应上图的docker-auto-ip、docker-fix-ip、docker-scale 目录,有兴趣的同砚,可以研究研究!
四,功能演示
http://124.71.129.204:8085/doc.html
五,其他
此实例整合了gateway、ribbon、feign、hystrix、swagger,
大家会发现hystrix熔断器起作用时并不从负载均衡中移除故障节点,大家可以思考比较下hystrix和ribbon 异同!
有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!
-over-
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |