SpringCloud系列——负载均衡(三)

191 阅读10分钟

什么是负载均衡?

将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性

将处理分发给多个服务


客户端负载均衡和服务端负载均衡

常见的负载均衡方式有两种:

  • 服务端负载均衡
  • 客户端负载均衡

服务端负载均衡

image.png

所有客户端的请求都发送给一个组件,该组件存放着所有服务端清单,客户端的请求都会被改组件按照一定的策略转发给它所熟知的服务端

客户端负载均衡

image.png

客户端从注册中心获取服务列表清单,客户端在需要发送请求时拦截,接着按照一定的策略选取一个服务端,将请求发送给它

对比

不同点服务端负载均衡客户端负载均衡
是否需要建立负载均衡服务器需要在客户端和服务端之间建立一个独立的负载均衡服务器。将负载均衡的逻辑以代码的形式封装到客户端上,因此不需要单独建立负载均衡服务器。
是否需要服务注册中心不需要服务注册中心。需要服务注册中心。  在客户端负载均衡中,所有的客户端和服务端都需要将其提供的服务注册到服务注册中心上。
可用服务清单存储的位置可用服务清单存储在位于客户端与服务器之间的负载均衡服务器上。所有的客户端都维护了一份可用服务清单,这些清单都是从服务注册中心获取的。
负载均衡的时机先将请求发送到负载均衡服务器,然后由负载均衡服务器通过负载均衡算法,在多个服务端之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。  简单点说就是,先发送请求,再进行负载均衡。在发送请求前,由位于客户端的服务负载均衡器(例如 Ribbon)通过负载均衡算法选择一个服务器,然后进行访问。  简单点说就是,先进行负载均衡,再发送请求。
客户端是否了解服务提供方信息由于负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。负载均衡是在客户端发送请求前进行的,因此客户端清楚的知道是哪个服务端提供的服务。

记住,客户端负载均衡和服务端负载均衡不都是代替关系,也可以是配合关系。从上图所示,我们知道客户端负载均衡有个前提:需要客户端(消费者)持有服务端(生产者)的列表,是对服务端的负载均衡,而不是客户端,所以客户端也可能卡顿,所以也需要nginx负载均衡。

负载均衡开始前

创建新的项目cloud-provider-payment8002复制 cloud-provider-payment8001的源码和配置文件

修改:

application.yml:

server:
  port: 8002
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver             # mysql驱动包
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.zhazha.springcloud.entities    # 所有Entity别名类所在包
  configuration:
    map-underscore-to-camel-case: true

#Eureka配置
eureka:
  client:
    # 表示是否将自己注册进EurekaServer,默认为 true
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,默认为 true 。单节点无所谓,
    # 集群必须设置为true才能配合 ribbon 使用负载均衡
    fetch-registry: true
    # EurekaServer的地址
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

这里我们仅需要修改端口 8001 -》 8002 其他配置不需要修改

接着先启动 7001 和 7002 而后启动 8001 8002 项目 最后启动 80 端口项目

打开网页:eureka7001.com:7001/

就能看到下列情况:

image.png

出现两个服务的提供者

有没有觉得这名字太长了? 修改下吧! 给 8001 项目的 application.yml添加eureka.instance.instance-id=payment8001 为了更好的显示出 ip 还可以添加:eureka.instance.prefer-ip-address=true 给8002项目也改上eureka.instance.instance-id=payment8002 eureka.instance.prefer-ip-address=true 重启项目就可以看到: image.png

访问:http://localhost/order/payment/get/1

image.png

不管怎么刷新都是 8001 没有达到负载均衡的地步

为什么?

根源在 order80 项目
image.png
这里已经定死了只能访问 8001

我们可以这么搞:

@RestController
@Slf4j
@RequestMapping("order")
public class OrderController {
	//定义服务端URL
//	public static final String PAYMENT_URL = "http://localhost:8001";
	
	// 使用微服务的名字
	public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
	
	//客户端通过RestTemplate调用服务端
	@Resource
	private RestTemplate restTemplate;
	
	@GetMapping("payment/create")
	public CommonResult<Payment> create(Payment payment) {
		return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
	}
	
	@GetMapping("payment/get/{id}")
	public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
		return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
	}
}

修改public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";这段

@SpringBootApplication
@EnableEurekaClient
public class OrderMain80 {
	
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}
	
	public static void main(String[] args) {
		SpringApplication.run(OrderMain80.class, args);
	}
	
}

添加@LoadBalanced注解

重启项目

image.png

此时测试发现是轮询策略,负载均衡成功~~~

这样修改,虽然实现了负载均衡,但却需要 CLOUD-PAYMENT-SERVICE微服务名字,将微服务彻底写死了

所以需要改造

Ribbon负载均衡

基本已经被LoadBalancer代替,但理论大于实际,所以理论部分还是得过过

Ribbon是什么?

Netflix Ribbon 是 Netflix 公司发布的开源组件,其主要功能是提供客户端的负载均衡算法和服务调用

记住:这是客户端负载均衡算法

Ribbon有什么用?

通过它,我们可以将面向服务的 REST 模板(RestTemplate)请求转换为客户端负载均衡的服务调用

说白了,就是在客户端拿到几个服务的列表,按照一定的策略选取一个服务,将请求转发给该服务。就这么简单 负载均衡的核心在于不把所有的请求都转发给一个服务,而是按照一定的策略将请求分发出去

Ribbon工作原理

image.png

  • 第一步:Ribbon 拦截所有标注@loadBalance注解的 RestTemplate。RestTemplate 是用来发送 HTTP 请求的。
  • 第二步:将 Ribbon 默认的拦截器 LoadBalancerInterceptor 添加到 RestTemplate 的执行逻辑中,当 RestTemplate 每次发送 HTTP 请求时,都会被 Ribbon 拦截。
  • 第三步:拦截后,Ribbon 会创建一个 ILoadBalancer 实例。
  • 第四步:ILoadBalancer 实例会使用 RibbonClientConfiguration 完成自动配置。就会配置好 IRule,IPing,ServerList。
  • 第五步:Ribbon 会从服务列表中选择一个服务,将请求转发给这个服务

拦截 + 配置 + 选取 + 转发

同时这张图片我们可以再次使用
image.png

从这里可以我们可以知道
Ribbon 会先从 Eureka Server(服务注册中心)去获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务端,从而达到负载均衡的目的

同时前面说的 IRule 接口是 Ribbon 提供的负载均衡策略

它有 7 中默认的实现类,每个类都是一种负载均衡策略

序号实现类负载均衡策略
1RoundRobinRule按照线性轮询策略,即按照一定的顺序依次选取服务实例
2RandomRule随机选取一个服务实例
3RetryRule按照 RoundRobinRule(轮询)的策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试(重试时获取服务的策略还是 RoundRobinRule 中定义的策略),如果超过指定时间依然没获取到服务实例则返回 null
4WeightedResponseTimeRuleWeightedResponseTimeRule 是 RoundRobinRule 的一个子类,它对 RoundRobinRule 的功能进行了扩展。  根据平均响应时间,来计算所有服务实例的权重,响应时间越短的服务实例权重越高,被选中的概率越大。刚启动时,如果统计信息不足,则使用线性轮询策略,等信息足够时,再切换到 WeightedResponseTimeRule。
5BestAvailableRule继承自 ClientConfigEnabledRoundRobinRule。先过滤点故障或失效的服务实例,然后再选择并发量最小的服务实例
6AvailabilityFilteringRule先过滤掉故障或失效的服务实例,然后再选择并发量较小的服务实例
7ZoneAvoidanceRule默认的负载均衡策略,综合判断服务所在区域(zone)的性能和服务(server)的可用性,来选择服务实例在没有区域的环境下,该策略与轮询(RandomRule)策略类似

Ribbon怎么玩?(看看就好, 不需要自己实现)

注意,现在业界Eureka基本很少使用了,所以本篇讲的 Ribbon(扩展版)现阶段只有 Nacos在使用(而且新版本nacos也放弃使用Ribbon) 本篇也就稍微讲讲基于 zookeeper 做服务的注册和发现中心 的方式吧, 而不是 eureka

首先, 第一步就是降级

将 springboot 降级, 修改 parent 项目

<!--spring boot 2.2.2-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.2.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Hoxton.SR1</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.1.0.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

再创建一个项目cloud-provider-payment80018002

image.png

提供方的 pom

这里只提供 8002 的配置和源码, 8001 基本相同, 只需要改下 server.port 端口就行

<dependencies>
        <!-- SpringBoot整合zookeeper客户端,这里不再使用Eureka进行服务注册,所以这里导入的是Zookeeper的相关依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <!-- 先排除自带的zookeeper -->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--添加zookeeper3.8.0版本,注意这里要和 zookeeper 应用启动的版本一致 -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.8.0</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

application

server:
  port: 8002

spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      connect-string: localhost:2181

源码

package com.zhazha.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain8002 {
	
	public static void main(String[] args) throws Exception {
		SpringApplication.run(PaymentMain8002.class, args);
	}
	
}

package com.zhazha.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@Slf4j
public class PaymentController {
	@Value("${server.port}")
	private String serverPort;
	
	@GetMapping("/payment")
	public String paymentInfo() {
		return "springcloud: " + serverPort + "\t\t" + UUID.randomUUID();
	}
}

然后是修改 cloud-consumer-order80

pom唯一的区别:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    <!-- 先排除自带的zookeeper -->
    <exclusions>
        <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </exclusion>
        <!-- 排除和不排除都一样, 低版本的 springcloud 底层默认就使用 ribbon -->
        <exclusion>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
            <groupId>org.springframework.cloud</groupId>
        </exclusion>
    </exclusions>
</dependency>
package com.zhazha.springcloud.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class OrderConsulController {
    public static final String INVOKE_URL = "http://cloud-provider-payment";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/payment")
    public String paymentInfo() {
        String result = restTemplate.getForObject(INVOKE_URL + "/payment/", String.class);
        System.out.println("消费者调用支付服务(consule)--->result:" + result);
        return result;
    }
}

非常简单

唯一需要注意的地方是别忘了
image.png

然后就有 ribbon 负载均衡了

image.png

可以看的出来这是轮询策略

ribbon切换策略模式

唯一需要注意的一点:

注意:The CustomConfiguration clas must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).

大意是说,ribbon自定义的 配置必须放在 @SpringBootApplication注解或者说@ComponentScan注解能扫描到的包之外,否则你自定义的配置将被所有@RibbonClients注解共享

由于 ribbon 是客户端负载均衡落地方式的一种, 所以代码的改造主要是在 order80(客户端) 项目上进行

package com.zhazha.rule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule() {
        return new RandomRule();
    }
}

记住, 策略必须放在 springboot 不能扫描到的包

@RibbonClient(value = "cloud-provider-payment", configuration = {MySelfRule.class})
public class OrderMain80

cloud-provider-payment 就是 80018002spring.application.name, 主要的目的就是限定 MySelfRule负载均衡策略只针对 cloud-provider-payment 的服务提供方

还有哪些负载均衡策略?

策略名称策略对应的类名实现原理
轮询策略(默认)RoundRobinRule轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个provider,第 1 次取第 1 个,第 2次取第 2 个,第 3 次取第 3 个,以此类推
权重轮询策略WeightedResponseTimeRule1.根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。2.原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider附上一个权重,并按权重随机选择provider,高权越重的 provider会被高概率选中。
随机策略RandomRule从 provider 列表中随机选择一个provider
最少并发数策略BestAvailableRule选择正在请求中的并发数最小的 provider,除非这个provider 在熔断中。
在“选定的负载均衡策略”基础上进行重试机制RetryRule1.“选定的负载均衡策略”这个策略是轮询策略RoundRobinRule2.该重试策略先设定一个阈值时间段,如果在这个阈值时间段内当选择 provider 不成功,则一直尝试采用“选定的负载均衡策略:轮询策略”最后选择一个可用的provider
可用性敏感策略AvailabilityFilteringRule过滤性能差的 provider,有 2种:第一种:过滤掉在 eureka 中处于一直连接失败 provider 第二种:过滤掉高并发的 provider
区域敏感性策略ZoneAvoidanceRule1.以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的provider2.如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权重。

实际上还有一部分用于 做注册中心专属的负载均衡策略. 比如 zookeepper 的 image.png 比如 旧版本的 nacos 也有属于自己的 ribbon 负载均衡策略

LoadBalancer负载均衡

需要知道什么?

新版本SpringCloud默认使用LoadBalancer的负载均衡,且是默认使用的

image.png

启动这几个项目,然后访问:http://localhost/order/payment/get/1
就会看到image.png

上面的端口不断在 8001 和 8002 之间来回 切换

可以发现其默认使用的也是 轮询

而他本身也就提供了两种负载均衡模式:image.png

默认是RoundRobinLoadBalancer轮询

如果引入 nacos,还是多个 NacosLoadBalancer方案

源码大体思路

可以使用 idea 的覆盖运行方式先将 order80项目运行起来,然后访问下网址:http://localhost/order/payment/get/1 最后在关闭微服务。 这样可以借助image.png 可以更加方便查看源码

  1. 使用LoadBalancerInterceptorConfig 配置LoadBalancerInterceptor
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
                                                       LoadBalancerRequestFactory requestFactory) {
    return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
  1. ClientHttpRequestInterceptor实现的LoadBalancerInterceptor拦截器拦截下请求
  2. LoadBalancerInterceptor接口会拦截客户端 http 请求
  3. LoadBalancerInterceptor内的拦截器源码:
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
                                    final ClientHttpRequestExecution execution) throws IOException {
    final URI originalUri = request.getURI();
    String serviceName = originalUri.getHost();
    Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
    return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
  1. 最终代码走到核心接口LoadBalancerClient,核心类BlockingLoadBalancerClient
  2. 然后是核心代码段:
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
    String hint = getHint(serviceId);
    LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
                                                                                                    buildRequestContext(request, hint));
    Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
    supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
    ServiceInstance serviceInstance = choose(serviceId, lbRequest);
    return execute(serviceId, serviceInstance, lbRequest);
}

怎么自定义?

首先如果需要自定义负载均衡策略,需要查看:
image.png

我们可以效仿上面两个已经实现的类自定义一个新的负载均衡策略

直接复制RandomLoadBalancer然后进行修改

@Slf4j
public class MySelfRule implements ReactorServiceInstanceLoadBalancer {

	final String serviceId;
	ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

	public MySelfRule(String serviceId, ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
		this.serviceId = serviceId;
		this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
	}

	@Override
	public Mono<Response<ServiceInstance>> choose(Request request) {
		ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
				.getIfAvailable(NoopServiceInstanceListSupplier::new);
		return supplier.get(request).next()
				.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
	}

	private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
															  List<ServiceInstance> serviceInstances) {
		Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
		if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
			((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
		}
		return serviceInstanceResponse;
	}

	private int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5};

	private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
		if (instances.isEmpty()) {
			if (log.isWarnEnabled()) {
				log.warn("No servers available for service: " + serviceId);
			}
			return new EmptyResponse();
		}
		int index = ThreadLocalRandom.current().nextInt(arr.length);
		ServiceInstance instance = instances.get(arr[index] % instances.size());

		return new DefaultResponse(instance);
	}

}

核心算法随便写的,就三行代码
private int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5};
int index = ThreadLocalRandom.current().nextInt(arr.length);
ServiceInstance instance = instances.get(arr[index] % instances.size());

public class MyRuleConfig {

    @Bean
    public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new MySelfRule(name, loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));
    }

}

记住:这里不要添加注解 @Configuration

@LoadBalancerClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRuleConfig.class)
public class OrderMain80 {
	
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}
	
	public static void main(String[] args) {
		SpringApplication.run(OrderMain80.class, args);
	}
	
}

nacos和loadbalancer

服务的提供者

创建两个项目:cloud-provider02-payment8001cloud-provider02-payment8002

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.zhazha</groupId>
            <artifactId>cloud-api-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

application.yml: 8001项目

server:
  port: 8001
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver             # mysql驱动包
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        username: nacos
        password: nacos
#        namespace: public
mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.zhazha.springcloud.entities    # 所有Entity别名类所在包
  configuration:
    map-underscore-to-camel-case: true

application.yml: 8002项目的配置

server:
  port: 8002
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver             # mysql驱动包
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        username: nacos
        password: nacos
#        namespace: public
mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.zhazha.springcloud.entities    # 所有Entity别名类所在包
  configuration:
    map-underscore-to-camel-case: true
package com.zhazha.springcloud;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@MapperScan(value = "com.zhazha.springcloud.dao")
@EnableDiscoveryClient
public class PaymentMain8001_2 {
	
	public static void main(String[] args) {
		SpringApplication.run(PaymentMain8001_2.class, args);
	}
	
}

package com.zhazha.springcloud.dao;

import com.zhazha.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;

import java.util.List;

public interface PaymentDao {
	@Insert("insert into payment(serial) values (#{serial})")
	int create(Payment payment);
//	@Select("select * from payment p where p.id = #{id}")
	Payment getPaymentById(@Param("id") Long id);
	
	@SelectProvider(type = PaymentProvider.class, method = "getAll")
	List<Payment> getAll();
}

package com.zhazha.springcloud.dao;

import org.apache.ibatis.jdbc.SQL;

public class PaymentProvider {
	public String getAll() {
		return new SQL().SELECT("*")
				.FROM("payment").toString();
	}
}

package com.zhazha.springcloud.service;

import com.zhazha.springcloud.entities.Payment;

public interface PaymentService {
	int create(Payment payment);
	Payment getPaymentById(Long id);
}

package com.zhazha.springcloud.service.impl;

import com.zhazha.springcloud.dao.PaymentDao;
import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class PaymentServiceImpl implements PaymentService {
	@Resource
	private PaymentDao paymentDao;
	
	@Override
	public int create(Payment payment) {
		return paymentDao.create(payment);
	}
	@Override
	public Payment getPaymentById(Long id) {
		return paymentDao.getPaymentById(id);
	}
}

package com.zhazha.springcloud.controller;

import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.service.PaymentService;
import com.zhazha.springcloud.utils.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;

@RestController
@Slf4j
@RequestMapping("payment")
public class PaymentController {
	@Resource
	private PaymentService paymentService;
	
	@Value("${server.port}")
	private String serverPort;
	
	@Resource
	private DiscoveryClient discoveryClient;
	
	/**
	 * 返回给前端的结果集
	 * 下面的 RequestBody 注解如果不加上, restTemplate 的 postForObject 将无法保存参数
	 *
	 * @param payment
	 * @return
	 */
	@PostMapping(value = "create")
	public CommonResult create(@RequestBody Payment payment) {
		Integer result = paymentService.create(payment);
		log.info("******插入结果:" + result);
		if (result > 0) {
			return new CommonResult(200, "插入数据库成功 port: " + serverPort, result);
		} else {
			return new CommonResult(444, "插入数据库失败", null);
		}
	}
	
	@GetMapping(value = "get/{id}")
	public CommonResult getPaymentByID(@PathVariable("id") Long id) {
		Payment payment = paymentService.getPaymentById(id);
		log.info("******插入结果:" + payment);
		if (payment != null) {
			return new CommonResult(200, "查询成功 port: " + serverPort, payment);
		} else {
			return new CommonResult(444, "没有查询记录", null);
		}
	}
	
	@GetMapping(value = "discovery")
	@ResponseBody
	public Object discovery() {
		List<String> services = this.discoveryClient.getServices();
		for (String service : services) {
			log.info("*********** service: " + service);
			
		}
		List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
		for (ServiceInstance instance : instances) {
			log.info("service application host = {}, port = {}, uri = {}", instance.getHost(), instance.getPort(), instance.getUri());
		}
		return this.discoveryClient;
	}
	
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhazha.springcloud.dao.PaymentDao">

    <resultMap type="payment" id="PaymentMap">
        <result property="id" column="id" jdbcType="BIGINT"/>
        <result property="serial" column="serial" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="getPaymentById" resultMap="PaymentMap">
        select * from payment p where p.id = #{id}
    </select>

</mapper>

服务的消费者

创建项目:cloud-consumer02-order80

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.zhazha</groupId>
            <artifactId>cloud-api-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

application.yml

server:
  port: 80
spring:
  application:
    name: cloud-order-service02
  cloud:
    nacos:
      server-addr: '127.0.0.1:8848'
#      server-addr: 192.168.133.128:8847  #集群 nginx 负载均衡访问 nacos
      discovery:
        username: nacos
        password: nacos
#        namespace: public
#    loadbalancer:
#      enabled: true
package com.zhazha.springcloud;

import com.zhazha.rule.LoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
@LoadBalancerClient(configuration = LoadBalancerConfig.class, value = "cloud-payment-service")
public class OrderMain80_2 {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(OrderMain80_2.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

package com.zhazha.springcloud.controller;

import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.utils.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;

@RestController
@Slf4j
@RequestMapping("order")
public class OrderController {
	//定义服务端URL
//	public static final String PAYMENT_URL = "http://localhost:8001";
	
	// 使用微服务的名字
	public static final String PAYMENT_URL = "http://cloud-payment-service";
	
	//客户端通过RestTemplate调用服务端
	@Resource
	private RestTemplate restTemplate;
	
	@GetMapping("payment/create")
	public CommonResult create(Payment payment) {
		return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
	}
	
	@GetMapping("payment/get/{id}")
	public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
		return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
	}
}

自定义Rule

package com.zhazha.rule;

import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class LoadBalancerConfig {

    @Bean
    public ReactorServiceInstanceLoadBalancer loadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }

}

image.png

成功了