途人路上回望我,只因我的怪摸样

微服务呀微服务_java

微服务是什么?

似乎"微"这个字很难有一个明确的定义,这个"微" 具体是怎样一种粒度才能叫"微";

其实在我们开发中我们对于整体服务的把控,在不断实践中去调整服务的粒度,这才是微服务架构想要达到的一个理想状态;

R C Martain曾这样论述:

把因相同原因而变化的东西聚合到一起,而把因不同原因而变化的东西分离开来

其实就是内聚性

我们在考虑微服务时,应该考虑的几个点:

  1. 微服务的粒度
  2. 微服务之间的通信使用哪种方式
  3. 哪些服务暴露在外,那些不能暴露

微服务粒度的一个检验标准:

是否修改一个服务而不对其他服务产生影响

细粒度的微服务架构可以帮助他们更快地交付软件 在微服务架构中,各个服务的部署是独立的,这样就可以更快地对特定部分的代码进行部署。如果真的出了问题,也只会影响一个服务,并且容易快速回滚

我们应该考虑不同的服务之间如何交互,或者说保证我们能够对整个系统的健康状态进行监控。至于多大程度地介入区域内部事务,在不同的情况下则有所不同

微服务的切分

水平切分:将同一个系统部署到多态机器上
垂直切分:按照业务进行切分

微服务之间的交互

在进行微服务的设计时还要考虑各个服务之间是如何交互的,现阶段主流的远程交互–

  1. RPC----Dubbo 一种使用简化的(http)交互方式
  2. SOAP----比较老的一套交互方式了,之前邮箱开发时用的这个,就是给一个xml文件,然后根据这个xml文件去调用相应的接口
  3. REST----比较新的一种交互的风格了 get/post/put/delete方式来实现交互

交互时候要考虑到:

  1. 节点故障
  2. 网络的可靠性~比如延时,乱序,丢包等
  3. 服务器的故障~断电,磁盘损坏等
  4. 数据的一致性问题(CAP和BASE)

为了提高系统的性能,可以选择数据的最终一致性,总之看实际情况合理选择

返回码的设计:

1)正常并且被正确处理的请求;
2)错误请求,并且服务识别出了它是错误的,但什么也没做;
3)被访问的服务宕机了,所以无法判断请求是否正常。

中间件的设计

尽量让中间件保持简单,而把业务逻辑放在自己的服务中间

说实话,spring虽然方便我们的代码开发,但是微服务这块 版本不兼容,这个版本移除了某个类,然后运行时就报错了,

今天准备整理一下 版本 这令人…的问题;
eureka-server

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gavin.huawei</groupId>
<artifactId>eureka_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka_server</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>3.1.1</version>
<exclusions>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.4</version>
</dependency>


</dependencies>

</project>

yml文件

server:
port: 7000 #注册中心的端口
eureka:
instance:
hostname: eureka_server #这里可以设置ip(host)有对应的就行
prefer-ip-address: false
client:
service-url:
defaultZone: http://127.0.0.1:7000/eureka
register-with-eureka: false #服务自己就是治理中心,所以不向自己注册自己 取消当前微服务,寻找其他Eureka服务治理中心进行注册。
fetch-registry: false #服务获取,用于注册中心之间服务的同步
spring:
application:
name: eureka_server #给微服务起个名

eureka_client

<?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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>com.gavin.huawei</groupId>
<artifactId>user</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.1.1</version>
<exclusions>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.4</version>
</dependency>

</dependencies>

</project>

yml文件

spring:
application:
name: user_client
server:
port: 8000
eureka:
instance:
hostname: user_client
client:
service-url:
defaultZone: http://127.0.0.1:7000/eureka

启动一下:

微服务呀微服务_负载均衡_02

微服务之间的调用:

举个例子:
两个controller:

package com.gavin.controller;

import com.gavin.huawei.ResultMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import sun.misc.Request;

import javax.servlet.http.HttpServletRequest;

/**
* @author Gavin
*/
@RestController
@RequestMapping("/fund")
public class AccountController {
@CrossOrigin
@PostMapping("/account/balance/{userId}/{amount}")
public ResultMessage deductingBalance(
@PathVariable("userId") Long userId,
@PathVariable("amount") Double amount,
HttpServletRequest request
){
String message="端口:"+ request.getServerPort()+"扣减成功";
return new ResultMessage(true,message);
}


}
package com.gavin.controller;

import com.gavin.huawei.ResultMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

/**
* @author Gavin
*/
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private RestTemplate restTemplate;

@CrossOrigin
@GetMapping("/purchase/{userId}/{productId}/{amount}")
public ResultMessage purchaseProduct(@PathVariable("userId") Long userId,
@PathVariable("productId") Long productId,
@PathVariable("amount") Double amount
) {
System.out.println("扣减产品金额");
//这里的FUNDATION就是微服务fund中的eureka.instance.appname---对应application列,是一个微服务的统称, 而spring.application.name就是status的名字对用注册中心的名字,
String url = "http://FUNDATION/fund/account/balance/{userId}/{amount}";
Map<String, Object> params = new HashMap<>();
params.put("userId", userId);
params.put("amount", amount);
ResultMessage resultMessage = restTemplate.postForObject(url, null, ResultMessage.class, params);
System.out.println(resultMessage.getMsg());
System.out.println("记录交易信息");
return new ResultMessage(true, "交易成功");
}

}

可以看一下调用的方式:
首先是跨域的问题:
另一个是url

这里的url通过名称进行调用;

这是因为配置文件中:
有两个关于名称的配置项

server:
port: 9000
spring:
application:
name: fundation
eureka:
instance:
hostname: 127.0.0.1
appname: fundation
client:
service-url:
defaultZone: http://127.0.0.1:7000/eureka,http://127.0.0.1:7001/eureka,http://127.0.0.1:7002/eureka
fetch-registry: true
register-with-eureka: true #用于向注册中心注册

微服务呀微服务_spring_03

spring.application.name 对用eureka注册中心中 status 那一列
eureka.instance.appname 对应 APPLICATION那一列

最好是名称一致

所以我们在调用微服务时,通过向注册中心寻找名字的方式来寻找我们需要的服务;

如果找不到就会报404 path找不到的错误;

微服务呀微服务_spring_04


另一个在数据传输的时候会涉及序列话,所以对于数据实体类要有一个空参的构造函数才能保证调用的顺利;

这里的服务提供者和消费者并不是对立的,一个微服务可以同时是服务消费者和服务提供者

配置文件

微服务呀微服务_负载均衡_05


微服务呀微服务_微服务_06

微服务的命名规则:

微服务的实例默认的名称规则是:
如果我们配置了spring.application.appname ,则名称为

设备名称:spring.application.name:server.port

微服务呀微服务_java_07

负载均衡:

负载均衡是分布式环境中必须要实现的功能:
其主要优点是:

  1. 降低对单个服务器的压力
  2. 高可用性和高性能
    如果某个节点出现问题,可以通过负载均衡去选择屏蔽掉他,或者某个节点的处理能力弱,那么可以减少访问该服务器;
  3. 可伸缩
    可以通过增加节点的方式来提高服务器的能力,或者减少节点
  4. 请求过滤
    可以屏蔽掉一些不合法的请求来减轻服务器的压力

负载均衡是怎样运作的?

负载均衡要生效首先要知道

  • 1,有哪些微服务,这些微服务哪一些能用,
  • 2,如何选择要调用的微服务;

所以Ribbon要有一个实例清单,并定期更新这个清单中的微服务列表
在源码中有:

  • 1, 获取服务实例清单 接口
  • 2,获取可用实例的清单(由线程定时更新这个清单)

负载均衡策略:

BestAvailableRule—>>当前服务器被分配最少请求的那个

会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略。

AvailabilityFilteringRule–>>过滤掉那些被标记为tripped/无法连接的服务器以及超过最大请求阈值的服务实例

会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表安装轮询策略进行访问。

ResponseTimeWeightedRule–>>响应时间权重策略

WeightdResponseTimeRule–>>对于响应时间短的有更大的概率分配到请求

据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule。响应时间长,权重低,被选择的概率低。反之,同样道理。此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间。

RetryRule–>>重试服务策略

RoundRobinRule–>> 轮询选择服务(通过下标选择)

RandomRule–>>随机选择服务(通过随机数来选择)
ZoneAvoidanceRule–>判断实例所在区域的性能和故障选择合适的实例 默认的策略

区域权衡策略)**默认策略
复合判断Server所在区域的性能和Server的可用性,轮询选择服务器。
简单来讲就是:找到那些性能较差的zone,然后将其排除在外,随机选择性能较好的Zone;

负载均衡的使用:

首先明确负载均衡是通过eureka治理中心来发出的,所注意在eureka的配置文件中要定义好负载均衡的策略:
明确对那个服务开启负载均衡:
然后配置负载均衡的策略:

ribbon:
eureka:
enabled: true # 开启负载均衡 默认使用 ZoneAwareLoadBalancer 负载均衡器,使用ZoneAvoidanceRule策略
# 设置调用某个微服务时的负载均衡的模式
fundation: # 在远程调用模块里配置~ 服务提供者的负载均衡 模式
ribbon: #负载均衡由ribbon实现

# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
ConnectTimeout: 500 #请求连接超时时间
ReadTimeout: 1000 #请求处理的超时时间
OkToRetryOnAllOperations: true #对所有请求都进行重试
MaxAutoRetriesNextServer: 2 #切换实例的重试次数
MaxAutoRetries: 1 #对当前实例的重试次数

一个配置文件模板

微服务呀微服务_微服务_08


运行时的截图:

微服务呀微服务_微服务_09

负载均衡的源码

之前的文章解析过,这里只放上链接 ​​负载均衡源码分析​​

自定义负载均衡策略

一般情况下使用自带的就好了,但是有时候我们需要自定义一下负载均衡策略,所以如果我们要自定义,就需要知道ribbon的工作原理:

1, 服务实例的监测

在ribbon中要维护服务实例的清单,所以必须要有一个线程来负责获取实例的状态,以便及时上架/下架可用的服务实例列表

@Configuration
@ConditionalOnBean({LoadBalancerZoneConfig.class, EurekaLoadBalancerProperties.class})
public class EurekaLoadBalancerClientConfiguration {
private static final Log LOG = LogFactory.getLog(EurekaLoadBalancerClientConfiguration.class);
private final EurekaClientConfig clientConfig;
private final EurekaInstanceConfig eurekaConfig;
private final LoadBalancerZoneConfig zoneConfig;
private final EurekaLoadBalancerProperties eurekaLoadBalancerProperties;

public EurekaLoadBalancerClientConfiguration(@Autowired(required = false) EurekaClientConfig clientConfig, @Autowired(required = false) EurekaInstanceConfig eurekaInstanceConfig, LoadBalancerZoneConfig zoneConfig, EurekaLoadBalancerProperties eurekaLoadBalancerProperties) {
this.clientConfig = clientConfig;
this.eurekaConfig = eurekaInstanceConfig;
this.zoneConfig = zoneConfig;
this.eurekaLoadBalancerProperties = eurekaLoadBalancerProperties;
}

@PostConstruct
public void postprocess() {
if (StringUtils.isEmpty(this.zoneConfig.getZone())) {
String zone = this.getZoneFromEureka();
if (!StringUtils.isEmpty(zone)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Setting the value of 'spring.cloud.loadbalancer.zone' to " + zone);
}

this.zoneConfig.setZone(zone);
}

}
}

private String getZoneFromEureka() {
boolean approximateZoneFromHostname = this.eurekaLoadBalancerProperties.isApproximateZoneFromHostname();
if (approximateZoneFromHostname && this.eurekaConfig != null) {
return ZoneUtils.extractApproximateZone(this.eurekaConfig.getHostName(false));
} else {
String zone = this.eurekaConfig == null ? null : (String)this.eurekaConfig.getMetadataMap().get("zone");
if (StringUtils.isEmpty(zone) && this.clientConfig != null) {
String[] zones = this.clientConfig.getAvailabilityZones(this.clientConfig.getRegion());
zone = zones != null && zones.length > 0 ? zones[0] : null;
}

return zone;
}
}
}

ribbon的饥渴加载机制

我们知道服务在向eureka注册时会有延时,当第一次调用的时候可能会发生延时的情况,没关系,ribbon提供了饥渴加载的方式:

ribbon:
eureka:
enabled: true # 开启负载均衡 默认使用 ZoneAwareLoadBalancer 负载均衡器,使用ZoneAvoidanceRule策略
eager-load:
enable: true
clients: user, product ,fund #加载的微服务名

这样那三个微服务就可以得到及时加载,

另一个我们了解一下 --ribbon可以不通过 eureka 来使用,就是获取服务的列表需要自己来配置(没必要呀!!)