什么是负载均衡?
将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性
将处理分发给多个服务
客户端负载均衡和服务端负载均衡
常见的负载均衡方式有两种:
- 服务端负载均衡
- 客户端负载均衡
服务端负载均衡
所有客户端的请求都发送给一个组件,该组件存放着所有服务端清单,客户端的请求都会被改组件按照一定的策略转发给它所熟知的服务端
客户端负载均衡
客户端从注册中心获取服务列表清单,客户端在需要发送请求时拦截,接着按照一定的策略选取一个服务端,将请求发送给它
对比
| 不同点 | 服务端负载均衡 | 客户端负载均衡 |
|---|---|---|
| 是否需要建立负载均衡服务器 | 需要在客户端和服务端之间建立一个独立的负载均衡服务器。 | 将负载均衡的逻辑以代码的形式封装到客户端上,因此不需要单独建立负载均衡服务器。 |
| 是否需要服务注册中心 | 不需要服务注册中心。 | 需要服务注册中心。 在客户端负载均衡中,所有的客户端和服务端都需要将其提供的服务注册到服务注册中心上。 |
| 可用服务清单存储的位置 | 可用服务清单存储在位于客户端与服务器之间的负载均衡服务器上。 | 所有的客户端都维护了一份可用服务清单,这些清单都是从服务注册中心获取的。 |
| 负载均衡的时机 | 先将请求发送到负载均衡服务器,然后由负载均衡服务器通过负载均衡算法,在多个服务端之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。 简单点说就是,先发送请求,再进行负载均衡。 | 在发送请求前,由位于客户端的服务负载均衡器(例如 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/
就能看到下列情况:
出现两个服务的提供者
有没有觉得这名字太长了? 修改下吧! 给 8001 项目的
application.yml添加eureka.instance.instance-id=payment8001为了更好的显示出 ip 还可以添加:eureka.instance.prefer-ip-address=true给8002项目也改上eureka.instance.instance-id=payment8002eureka.instance.prefer-ip-address=true重启项目就可以看到:
访问:http://localhost/order/payment/get/1
不管怎么刷新都是 8001 没有达到负载均衡的地步
为什么?
根源在 order80 项目
这里已经定死了只能访问 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注解
重启项目
此时测试发现是轮询策略,负载均衡成功~~~
这样修改,虽然实现了负载均衡,但却需要 CLOUD-PAYMENT-SERVICE微服务名字,将微服务彻底写死了
所以需要改造
Ribbon负载均衡
基本已经被
LoadBalancer代替,但理论大于实际,所以理论部分还是得过过
Ribbon是什么?
Netflix Ribbon 是 Netflix 公司发布的开源组件,其主要功能是提供客户端的负载均衡算法和服务调用。
记住:这是客户端负载均衡算法
Ribbon有什么用?
通过它,我们可以将面向服务的 REST 模板(RestTemplate)请求转换为客户端负载均衡的服务调用。
说白了,就是在客户端拿到几个服务的列表,按照一定的策略选取一个服务,将请求转发给该服务。就这么简单 负载均衡的核心在于不把所有的请求都转发给一个服务,而是按照一定的策略将请求分发出去
Ribbon工作原理
- 第一步:Ribbon 拦截所有标注@loadBalance注解的 RestTemplate。RestTemplate 是用来发送 HTTP 请求的。
- 第二步:将 Ribbon 默认的拦截器 LoadBalancerInterceptor 添加到 RestTemplate 的执行逻辑中,当 RestTemplate 每次发送 HTTP 请求时,都会被 Ribbon 拦截。
- 第三步:拦截后,Ribbon 会创建一个 ILoadBalancer 实例。
- 第四步:ILoadBalancer 实例会使用 RibbonClientConfiguration 完成自动配置。就会配置好 IRule,IPing,ServerList。
- 第五步:Ribbon 会从服务列表中选择一个服务,将请求转发给这个服务。
拦截 + 配置 + 选取 + 转发
同时这张图片我们可以再次使用
从这里可以我们可以知道
Ribbon 会先从 Eureka Server(服务注册中心)去获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务端,从而达到负载均衡的目的
同时前面说的 IRule 接口是 Ribbon 提供的负载均衡策略
它有 7 中默认的实现类,每个类都是一种负载均衡策略
| 序号 | 实现类 | 负载均衡策略 |
|---|---|---|
| 1 | RoundRobinRule | 按照线性轮询策略,即按照一定的顺序依次选取服务实例 |
| 2 | RandomRule | 随机选取一个服务实例 |
| 3 | RetryRule | 按照 RoundRobinRule(轮询)的策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试(重试时获取服务的策略还是 RoundRobinRule 中定义的策略),如果超过指定时间依然没获取到服务实例则返回 null 。 |
| 4 | WeightedResponseTimeRule | WeightedResponseTimeRule 是 RoundRobinRule 的一个子类,它对 RoundRobinRule 的功能进行了扩展。 根据平均响应时间,来计算所有服务实例的权重,响应时间越短的服务实例权重越高,被选中的概率越大。刚启动时,如果统计信息不足,则使用线性轮询策略,等信息足够时,再切换到 WeightedResponseTimeRule。 |
| 5 | BestAvailableRule | 继承自 ClientConfigEnabledRoundRobinRule。先过滤点故障或失效的服务实例,然后再选择并发量最小的服务实例。 |
| 6 | AvailabilityFilteringRule | 先过滤掉故障或失效的服务实例,然后再选择并发量较小的服务实例。 |
| 7 | ZoneAvoidanceRule | 默认的负载均衡策略,综合判断服务所在区域(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-payment8001 和 8002
提供方的 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;
}
}
非常简单
唯一需要注意的地方是别忘了
然后就有 ribbon 负载均衡了
可以看的出来这是轮询策略
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就是8001和8002的spring.application.name, 主要的目的就是限定MySelfRule负载均衡策略只针对cloud-provider-payment的服务提供方
还有哪些负载均衡策略?
| 策略名称 | 策略对应的类名 | 实现原理 |
|---|---|---|
| 轮询策略(默认) | RoundRobinRule | 轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个provider,第 1 次取第 1 个,第 2次取第 2 个,第 3 次取第 3 个,以此类推 |
| 权重轮询策略 | WeightedResponseTimeRule | 1.根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。2.原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider附上一个权重,并按权重随机选择provider,高权越重的 provider会被高概率选中。 |
| 随机策略 | RandomRule | 从 provider 列表中随机选择一个provider |
| 最少并发数策略 | BestAvailableRule | 选择正在请求中的并发数最小的 provider,除非这个provider 在熔断中。 |
| 在“选定的负载均衡策略”基础上进行重试机制 | RetryRule | 1.“选定的负载均衡策略”这个策略是轮询策略RoundRobinRule2.该重试策略先设定一个阈值时间段,如果在这个阈值时间段内当选择 provider 不成功,则一直尝试采用“选定的负载均衡策略:轮询策略”最后选择一个可用的provider |
| 可用性敏感策略 | AvailabilityFilteringRule | 过滤性能差的 provider,有 2种:第一种:过滤掉在 eureka 中处于一直连接失败 provider 第二种:过滤掉高并发的 provider |
| 区域敏感性策略 | ZoneAvoidanceRule | 1.以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的provider2.如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权重。 |
实际上还有一部分用于 做注册中心专属的负载均衡策略. 比如 zookeepper 的
比如 旧版本的 nacos 也有属于自己的 ribbon 负载均衡策略
LoadBalancer负载均衡
需要知道什么?
新版本SpringCloud默认使用LoadBalancer的负载均衡,且是默认使用的
启动这几个项目,然后访问:http://localhost/order/payment/get/1
就会看到
上面的端口不断在 8001 和 8002 之间来回 切换
可以发现其默认使用的也是 轮询
而他本身也就提供了两种负载均衡模式:
默认是RoundRobinLoadBalancer轮询
如果引入 nacos,还是多个
NacosLoadBalancer方案
源码大体思路
可以使用 idea 的覆盖运行方式先将
order80项目运行起来,然后访问下网址:http://localhost/order/payment/get/1 最后在关闭微服务。 这样可以借助可以更加方便查看源码
- 使用
LoadBalancerInterceptorConfig配置LoadBalancerInterceptor
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
ClientHttpRequestInterceptor实现的LoadBalancerInterceptor拦截器拦截下请求LoadBalancerInterceptor接口会拦截客户端 http 请求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));
}
- 最终代码走到核心接口
LoadBalancerClient,核心类BlockingLoadBalancerClient - 然后是核心代码段:
@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);
}
怎么自定义?
首先如果需要自定义负载均衡策略,需要查看:
我们可以效仿上面两个已经实现的类自定义一个新的负载均衡策略
直接复制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-payment8001和cloud-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);
}
}
成功了