https://blog.csdn.net/ym15229994318ym/article/details/105064094在最后提到的问题,概括一下就是分布式服务必然要面临的问题:
1、服务管理
如何自动注册和发现
如何实现状态监管
如何实现动态路由
2、服务如何实现负载均衡
3、服务如何解决容灾问题
4、服务如何实现统一配置
首先我们来解决第一问题,服务的管理。
Eureka架构中的三个核心角色:
增加eureka-server子服务,提供一个注册中心,让服务提供方user-service,在eureka-server中注册服务,服务消费者(调用)consumer在eureka-server中拉取服务,进行调用。eureka会为我们做好负载均衡、服务管理的工作。
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
@EnableEurekaServer
开启eureka功能@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class);
}
}
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapper-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
application.yaml配置
server:
port: 8081
#eureka-client配置
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
# 30秒更新一次
lease-renewal-interval-in-seconds: 30
# 最小过期时长
lease-expiration-duration-in-seconds: 90
#控制日志级别
logging:
level:
com.baidu: debug
# 数据库连接信息
spring:
application:
name: user-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring_study_db?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: admin
#配置mybatis的信息
mybatis:
#pojo别名扫描
type-aliases-package: com.baidu.user.pojo
#加载mybatis映射文件,使用通用Mapper这个就不用了
#mapper-locations: classpath:mapper/*mapper.xml
启动类加上@EnableDiscoveryClient
注解
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.baidu.user.mapper")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
ribbon(一会就说)、依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
dependencies>
application.yaml
server:
port: 8082
spring:
application:
name: consumer-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
根据设置的eureka端口,进入页面,可以看见服务都已经注册上去了。
controller
此时服务的消费方再要去调用时,就不会出现把url写死的的情况了,而是动态的向eureka服务注册中心去拉取服务进行调度。
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
/**
* 远程查询
* @param id
* @return
*/
@GetMapping("{id}")
public User findById(@PathVariable("id") Integer id) {
//DiscoveryClient方式
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
//根据实例中的id取出ip和端口
ServiceInstance instance = instances.get(0);
String path = instance.getHost() + ":" + instance.getPort();
String url = "http://" + path + "/user/findById/" + id;
System.out.println("url:" + url);*/
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
这里的代码虽然解决了这个问题,但是再思考一下?这里的eureka服务、user-service、consumer-service都是注册了一个,如果其中一个服务宕机了,那么就会调用失败,当然这里也包括注册中心服务宕机的情况。
这里只有一个user-service服务,所以只获取第一个即可 , 所以是 .get(0);
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
ServiceInstance instance = instances.get(0);
如果有多个,是不是就要考虑负载均衡问题了呢?
高可用的Eureka Server
Eureka Server即服我的注册中心,在上面的案例中,我们只有一个EurekaServer, 事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。
服务同步
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
配置如下:
server:
# 本注册中心端口号
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
# 注册到注册中心端口号为10087
defaultZone: http://127.0.0.1:10087/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
在idea中,我们可以快速的复制一个相同的服务,只需要通过修改端口号即可区分。(同一个服务,不同端口号,这些服务干的事同一件事,当其中一个宕机时,不至于一直处于等待,回去找其他服务)
此时我们的服务就如同下面所示。当然要记得改你复制的服务的端口号。(我一般是先直接复制,再改原来的那个服务端口号,效果一样)
由于现在存在两个注册中心,那么在配置服务时就要都注册进去。
配置如下:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
此时我们有多个服务,调用时也有了变化!因为我们要考虑负载均衡问题了,
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private RibbonLoadBalancerClient client;
/**
* 远程查询
*
* @param id
* @return
*/
@GetMapping("{id}")
public User findById(@PathVariable("id") Integer id) {
//RibbonLoadBalancerClient方式
ServiceInstance instance=client.choose("user-service");
String path = instance.getHost() + ":" + instance.getPort();
String url = "http://" + path + "/user/findById/" + id;
System.out.println("url:" + url);
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
其实还提供了一种注解的简便方式,在方法上添加 @LoadBalanced
注解,其实内部实现和上面一样。
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
}
//=============================================================================
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
/**
* 远程查询
* @param id
* @return
*/
@GetMapping("{id}")
public User findById(@PathVariable("id") Integer id) {
//主配置类中加了@LoadBalanced注解,底层会有拦截器解析服务id,进行负载均衡
String url="http://user-service/user/findById/"+id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
启动所有服务。
查看端口号为10086的注册中心服务列表
查看端口号为10087的注册中心服务列表
服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。
服务注册
服务提供者在启动时,会检测配置属性中的: eureka.cl ient . register -with-eruekawtrue参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求, 并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。
●第一层Map的Key就是服务id,一般是配置中的spring.applicat ion.name属性
●第二层Map的key是服务的实例id。 一般host+ serviceld + port,例如: locahost :user -service:8081
●值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。
服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求) ,告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew) ;有两个重要参数可以修改服务续约的行为:
cureka :
lnstance:
lease-expiration-durat ion- in-seconds: 90
lease-renewal- interval- in- seconds: 30
lease-renewal-interval-in-seconds: 服务续约(renew)的间隔,默认为30秒
lease expiration-duration-in-seconds: 服务失效时间,默认值90秒
也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳, 证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机, 会从服务列表中移除,这两个值在生产环境不要修改,默认即可。
eureka:
client:
registry- fetch- interval- seconds: 30
eureka:
server :
enable-self-preservation: false #关闭自我保护模式(缺省为打开)