客户端负载均衡Ribbon实例

文章目录

  • 一,概述
  • 二,实现过程
  • 三,项目源码
    • 1. 源码放送:
    • 2. 部署方式
  • 四,功能演示
  • 五,其他

一,概述

一般来说,提到负载均衡,大家一般很容易想到浏览器 -> NGINX -> 反向代理多个Tomcat这样的架构图——业界管这种负载均衡模式叫“服务器端负载均衡”,因为此种模式下,负载均衡算法是NGINX提供的,而NGINX部署在服务器端。

本文所讲的Ribbon则是一个客户端侧负载均衡组件——通俗地说,就是集成在客户端(服务消费者一侧),并提供负载均衡算法的一个组件。Ribbon是Netflix发布的负载均衡器,它可以帮我们控制HTTP和TCP客户端的行为。只需为Ribbon配置服务提供者地址列表,Ribbon就可基于负载均衡算法计算出要请求的目标服务地址。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机、响应时间加权等。

二,实现过程

一般情况下,负载均衡组件Ribbon和微服务注册中心Eureka是配合使用的。
为了在非springCloud微服务项目中,使用Ribbon的客户端负载均衡能力,我们可以按如下步骤实现:

  1. 定义服务的被调用方Client,编写webApi接口
  2. 定义服务的调用方Cousumer,调用webApi接口
  3. 定义网关模块Gateway,通过定义路由的方式重新定义webApi接口路径,并引入Ribbon客户端负载均衡
  4. 将步骤2中的WebApi地址切换为网关模块路由接口地址,从而使原来的webApi地址具有了客户端负载均衡功能
  5. 使用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:#gatewayribbon-gateway:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-gateway:0.0.1container_name: ribbon-gatewaydeploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mports:- 8085:8080environment:USER_SERVERS: ribbon-user-0:8081,ribbon-user-1:8081MOVIE_SERVERS: ribbon-movie:8082restart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'#client01ribbon-user-0:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1container_name: ribbon-user-0deploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mrestart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'#client02ribbon-user-1:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1container_name: ribbon-user-1deploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mrestart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'ribbon-movie:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-movie:0.0.1container_name: ribbon-moviedeploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Menvironment:USER_API_URL: ribbon-gateway:8080/user/restart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-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: devopsdriver: bridgeipam:config:- subnet: 172.88.88.0/24
services:#gatewayribbon-gateway:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-gateway:0.0.1container_name: ribbon-gatewaydeploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mports:- 8085:8080environment:MOVIE_SERVERS: 172.88.88.101:8082USER_SERVERS: 172.88.88.200:8081,172.88.88.201:8081restart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'networks:default:ipv4_address: 172.88.88.100#client01ribbon-user-0:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1container_name: ribbon-user-0deploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mrestart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'networks:default:ipv4_address: 172.88.88.200#client02ribbon-user-1:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1container_name: ribbon-user-1deploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mrestart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'networks:default:ipv4_address: 172.88.88.201ribbon-movie:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-movie:0.0.1container_name: ribbon-moviedeploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Menvironment:USER_API_URL: http://172.88.88.100:8080/user/restart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-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:#gatewayribbon-gateway:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-gateway:0.0.1container_name: ribbon-gatewaydeploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mports:- 8085:8080environment:USER_SERVERS: ribbon-user:8081MOVIE_SERVERS: ribbon-movie:8082restart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'#clientribbon-user:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user:0.0.1deploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mrestart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'ribbon-movie:image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-movie:0.0.1deploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Menvironment:USER_API_URL: ribbon-gateway:8080/user/restart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-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-slimCOPY 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 8080CMD ["--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";/*** 网关过滤器*/@Overridepublic 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;@Overridepublic 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-gatewaycloud:gateway:discovery:locator:lowerCaseServiceId: trueroutes:- id: ribbon-useruri: lb://ribbon-userpredicates:- Path=/user/**filters:- StripPrefix=1- id:  ribbon-movieuri: lb://ribbon-moviepredicates:- Path=/movie/**filters:- StripPrefix=1ribbon-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/shTIMEOUT=15
QUIET=0echoerr() {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
USAGEexit "$exitcode"
}wait_for() {for i in `seq $TIMEOUT` ; donc -z "$HOST" "$PORT" > /dev/null 2>&1result=$?if [ $result -eq 0 ] ; thenif [ $# -gt 0 ] ; thenexec "$@"fiexit 0fisleep 1doneecho "Operation timed out" >&2exit 1
}while [ $# -gt 0 ]
docase "$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=1shift 1;;-t)TIMEOUT="$2"if [ "$TIMEOUT" = "" ]; then break; fishift 2;;--timeout=*)TIMEOUT="${1#*=}"shift 1;;--)shiftbreak;;--help)usage 0;;*)echoerr "Unknown argument: $1"usage 1;;esac
doneif [ "$HOST" = "" -o "$PORT" = "" ]; thenechoerr "Error: you need to provide a host and port to test."usage 2
fiwait_for "$@"
//goto ribbon-movie\Dockerfile
#基础镜像
FROM adoptopenjdk/openjdk8-openj9:alpine-slimCOPY 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 8082CMD ["--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 [类、类#方法、类#成员]*/@BeanDocket 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*/@Overridepublic 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;@AutowiredDataService dataService;@PostConstructprivate 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;@Autowiredprivate UserFeignClient userFeignClient;@PostConstructprivate 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
{@Overridepublic 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-moviecache:type: simple#设置负载均衡参数
microservice-ribbon-user:ribbon:#配置规则NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule#配置地址:宿主机ip+映射端口或docker自定义网络指定地址#listOfServers: 172.22.208.1:8081,172.22.208.1:8091listOfServers: 172.88.88.200:8081,172.88.88.201:8081
feign:httpclient:enabled: true
ribbon:ReadTimeout: 30000ConnectTimeout: 30000
logging:level:root: INFO
//goto ribbon-movie\src\main\resources\application.yml
server:port: 8082
spring:application:name: ribbon-moviecache:type: simple#feign.okhttp.enabled默认不开启 
#从Spring Cloud Dalston开始,Feign默认是不开启Hystrix的。 
feign:okhttp:enabled: truehystrix:enabled: true
logging:level:root: INFO
//goto ribbon-movie\wait-for.sh
#!/bin/shTIMEOUT=15
QUIET=0echoerr() {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
USAGEexit "$exitcode"
}wait_for() {for i in `seq $TIMEOUT` ; donc -z "$HOST" "$PORT" > /dev/null 2>&1result=$?if [ $result -eq 0 ] ; thenif [ $# -gt 0 ] ; thenexec "$@"fiexit 0fisleep 1doneecho "Operation timed out" >&2exit 1
}while [ $# -gt 0 ]
docase "$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=1shift 1;;-t)TIMEOUT="$2"if [ "$TIMEOUT" = "" ]; then break; fishift 2;;--timeout=*)TIMEOUT="${1#*=}"shift 1;;--)shiftbreak;;--help)usage 0;;*)echoerr "Unknown argument: $1"usage 1;;esac
doneif [ "$HOST" = "" -o "$PORT" = "" ]; thenechoerr "Error: you need to provide a host and port to test."usage 2
fiwait_for "$@"
//goto ribbon-user\Dockerfile
#基础镜像
FROM adoptopenjdk/openjdk8-openj9:alpine-slimCOPY 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 8081CMD ["--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
{@Autowiredprivate 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 [类、类#方法、类#成员]*/@BeanDocket 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*/@Overridepublic 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;@Columnprivate String username;@Columnprivate String name;@Columnprivate Integer age;@Columnprivate 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*/@BeanApplicationRunner 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-userjpa:generate-ddl: falseshow-sql: truehibernate:ddl-auto: create-drop
logging:level:root: INFO
//goto ribbon-user\wait-for.sh
#!/bin/shTIMEOUT=15
QUIET=0echoerr() {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
USAGEexit "$exitcode"
}wait_for() {for i in `seq $TIMEOUT` ; donc -z "$HOST" "$PORT" > /dev/null 2>&1result=$?if [ $result -eq 0 ] ; thenif [ $# -gt 0 ] ; thenexec "$@"fiexit 0fisleep 1doneecho "Operation timed out" >&2exit 1
}while [ $# -gt 0 ]
docase "$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=1shift 1;;-t)TIMEOUT="$2"if [ "$TIMEOUT" = "" ]; then break; fishift 2;;--timeout=*)TIMEOUT="${1#*=}"shift 1;;--)shiftbreak;;--help)usage 0;;*)echoerr "Unknown argument: $1"usage 1;;esac
doneif [ "$HOST" = "" -o "$PORT" = "" ]; thenechoerr "Error: you need to provide a host and port to test."usage 2
fiwait_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-

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/422591.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

加密与安全_ sm-crypto 国密算法sm2、sm3和sm4的Java库

文章目录 Presm-crypto如何使用如何引入依赖 sm2获取密钥对加密解密签名验签获取椭圆曲线点 sm3sm4加密解密 Pre 加密与安全_三种方式实现基于国密非对称加密算法的加解密和签名验签 sm-crypto https://github.com/antherd/sm-crypto 国密算法sm2、sm3和sm4的java版。基于js…

PMP--一模--解题--21-30

文章目录 9.资源管理21、 [单选] 项目经理发现一个不可预料的高影响风险已经成为项目的一个因素&#xff0c;团队成员之间的自身利益导致问题得不到解决&#xff0c;项目经理必须快速行动&#xff0c;让团队重新集中精力&#xff0c;以便项目恢复进度&#xff0c;项目经理应该使…

vue3项目实现全局国际化

本文主要梳理vue3项目实现全项目格式化&#xff0c;例如在我前面文章使用若依创建vue3的项目中&#xff0c;地址&#xff1a;若依搭建vue3项目在导航栏中切换&#xff0c;页面中所有的组件的默认语言随之切换&#xff0c;使用的组件库依旧是element-plus&#xff0c;搭配vue-i1…

09-排序1 排序(C)

这一节&#xff0c;测试各类排序算法的运行速度&#xff08;没有基数排序&#xff08;桶&#xff09; 其实在实际学习中&#xff0c;还是有意义的 给定 n 个&#xff08;长整型范围内的&#xff09;整数&#xff0c;要求输出从小到大排序后的结果。 本题旨在测试各种不同的排序…

Windows与Linux下 SDL2的第一个窗口程序

Windows效果和Linux效果如下&#xff1a; 下面是代码&#xff1a; #include <stdio.h> #include "SDL.h"int main(int argc, char* argv[]) { // 初始化SDL视频子系统if (SDL_Init(SDL_INIT_VIDEO) ! 0){// 如果初始化失败&#xff0c;打印错误信息printf(&…

proteus+51单片机+实验(LCD1620、定时器)

目录 1.LCD1602液晶显示屏 1.1基本概念 1.1.1LCD的简介 1.1.2LCD的显示原理 ​​​1.1.3LCD的硬件电路 1.1.4LCD的常见指令 1.1.5LCD的时序 ​​​​​​​1.2代码 1.2.1写命令和写数据操作 1.2.2初始化和测试代码 1. 3.3功能函数 1.3proteus代码 1.3.1器件代码 1.…

探索Python世界的隐藏宝石:Pika库的神秘力量

文章目录 探索Python世界的隐藏宝石&#xff1a;Pika库的神秘力量背景&#xff1a;为何选择Pika&#xff1f;Pik库简介如何安装Pika&#xff1f;简单库函数使用方法场景应用常见Bug及解决方案总结 探索Python世界的隐藏宝石&#xff1a;Pika库的神秘力量 背景&#xff1a;为何…

ELK预警方案:API+XXLJob

目录 步骤一&#xff1a;出一个接口&#xff0c;接口内查询出10分钟内是否有异常信息 步骤二&#xff1a;XXLJob中设置预警的频率 步骤三&#xff1a;在重要的业务处输出指定格式日志即可 步骤一&#xff1a;出一个接口&#xff0c;接口内查询出10分钟内是否有异常信息 {&qu…

Java | Leetcode Java题解之第402题移掉K位数字

题目&#xff1a; 题解&#xff1a; class Solution {public String removeKdigits(String num, int k) {Deque<Character> deque new LinkedList<Character>();int length num.length();for (int i 0; i < length; i) {char digit num.charAt(i);while (!…

C语言字符函数和字符串函数(20)

文章目录 前言一、字符分类函数小练习 二、字符转换函数三、strlen的使用和模拟实现四、strcpy的使用和模拟实现五、strcat的使用和模拟实现六、strcmp的使用和模拟实现七、strncpy函数的使用八、strncat函数的使用九、strncmp函数的使用十、strstr函数的使用和模拟实现十一、s…

OpenGL3.3_C++_Windows(37)

调试&#xff1a; 视觉错误与CPU调试不同&#xff0c;在GLSL代码中也不能设置断点&#xff0c;出现错误的时候寻找错误的源头可能会非常困难。 glGetError&#xff08;&#xff09; GLenum glGetError();返回整形数字&#xff0c;查询错误标记&#xff0c;但是当一个错误标记…

C#开发基础之使用四种流行的数据库访问技术ADO.NET、Dapper、EF Core 和 SqlSugar 连接 SQL Server

前言 在这篇文章中&#xff0c;我们将介绍四种流行的数据库访问技术&#xff1a;ADO.NET、Dapper、Entity Framework Core (EF Core) 和 SqlSugar。每种技术都提供了与 SQL Server 进行交互的不同方法&#xff0c;我们将以 TestDB 数据库中的 User 表为例&#xff0c;展示如何…

关于malloc/free的一些知识点

序 关于malloc/free&#xff0c;我们都不陌生&#xff0c;在最开始学习c语言时就相当了解&#xff0c;包括c中的new也是封装的malloc。下边我以glibc实现的malloc来讲述一些关于malloc/free的知识点。 malloc/free malloc和free并不是系统调用&#xff0c;而是运行时库&…

C语言的结构体类型

在我们使用C语言进行编写代码时&#xff0c;常常会使用已经给定的类型来创建变量&#xff0c;比如int型&#xff0c;char型&#xff0c;double型等&#xff0c;而当我们想创建一些较为复杂的东西时&#xff0c;单单用一个类型变量是没办法做到的&#xff0c;比如我们想创建一个…

shader 案例学习笔记之fract函数

fract函数 可以理解为模1取余&#xff0c;获取一个数的小数部分&#xff0c;如果参数是向量&#xff0c;那就是获取每个向量分量上的小数 案例一 #ifdef GL_ES precision mediump float; #endif// 渲染分辨率 uniform vec2 u_resolution; // 程序运行时间 uniform float u_ti…

如何利用 Smarter Balanced 塑造教育领域的 AI 治理

目录 定义挑战 以人为本的设计引领 融入多样性 探索以学生为中心的价值观 探索效果的层次和不同的影响 部位于加利福尼亚州的Smarter Balanced Assessment Consortium 是一个由会员主导的公共组织&#xff0c;为 K-12 和高等教育领域的教育工作者提供评估系统。该组织成立…

初学者指南:MyBatis 入门教程

主要介绍了Mybatis的基本使用、JDBC、数据库连接池、lombok注解&#xff01; 文章目录 前言 什么是Mybatis? 快速入门 使用Mybatis查询所有的用户信息 配置SQL提示 JDBC介绍 Mybatis 数据库连接池 lombok 总结 前言 主要介绍了Mybatis的基本使用、JDBC、数据库连接…

基于stm32单片机使用 RT-Thread 系统的 ADC 外设

一、ADC 介绍 来源&#xff1a;RT-Thread 文档中心   ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号&#xff0c;例如温度、压力、声音或者图像等&#xff0c;需要转换成更容易储存、处理和发射…

【Linux】:信号与信号产生

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家带来信号和信号的产生相关代码和知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到…

YOLOv10优改系列一:YOLOv10融合C2f_Ghost网络,让YoloV10实现性能的均衡

&#x1f4a5; &#x1f4a5;&#x1f4a5; &#x1f4a5;&#x1f4a5; &#x1f4a5;&#x1f4a5; &#x1f4a5;&#x1f4a5;神经网络专栏改进完整目录&#xff1a;点击 &#x1f497; 只需订阅一个专栏即可享用所有网络改进内容&#xff0c;每周定时更新 文章内容&#x…