首页 > 经验记录 > 高可用的服务注册中心:Eureka 集群搭建 , 并使用Spring Security 为 Eureka 增加 Basic 安全认证机制

高可用的服务注册中心:Eureka 集群搭建 , 并使用Spring Security 为 Eureka 增加 Basic 安全认证机制

上一篇 Spring Cloud 项目的搭建文章 : 链接
 
再说下我的版本号 , 用的比较新:
 
Spring Boot 2.0.5 RELEASE
Spring Cloud Finchley.SR3
项目管理工具: gradle


进入正题:

从之前的项目构造来看,RPC虽然是实现了,但是调用的链接确是写死在代码中的,比较丑陋。
要是提供服务的地址突然换了,那这边消费者直接雪崩,只能更改代码重新部署。而且无法实现负载均衡,这在一个微服务架构中是很不合理的。
要解决这问题,作为服务消费者,必须要有一个牛逼的服务发现机制。
Spring Cloud 就提供了多种服务发现组件的支持。Eureka、ZooKeeper 等,我这就用 Eureka 了, 在 Spring Cloud 这个生态圈里Eureka是比较流行的。
Eureka是网飞(Netflix)开源的服务发现组件。有Server和Client两部分,按需使用就行,我这就使用他们来构建一个高可用的服务注册中心。
 
我先把之前父模块的的Mybatis、mysql依赖给去了,放到了实际的微服务中(user/movie)。因为Eureka服务不需要这几个依赖,要是加载了mysql依赖会自动连接mysql,不加mysql的配置就会报一堆错误。
 
既然 Eureka 也是个服务,那当然也得在父模块下创建一个Eureka子模块。和其余的模块创建相同,没什么好说的。
给 Eureka 服务注册中心模块加上这个 Eureka Server 依赖
compile(“org.springframework.cloud:spring-cloud-starter-netflix-eureka-server“)
然后在 Eureka 服务的入口 java 文件上加上该注解 @EnableEurekaServer , 这个Application就是完全体了。

package com.skypyb.sc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //表示这个服务是一个 Eureka 服务注册中心
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(EurekaServerApplication.class);
        application.run(args);
    }
}

 
此时可以在网页里通过 ip:port 来访问页面,可以得知注册中心的状态、多少服务链接到本机。
 
Eureka Server 端写完后,需要注册的服务则需要导入Eureka的Client依赖,就是这个:
compile(“org.springframework.cloud:spring-cloud-starter-netflix-eureka-client“)
然后只要在yml文件中配置好要连接的服务注册中心的地址就完事了,其实此时一个服务注册发现功能已经可以完成了。
但是还不够,需要考虑到若是 Eureka Server 端突然宕掉,那么整个系统就会出问题。
客户端虽然会有相应的缓存(服务注册时会将Server端储存的数据拿到本地缓存),但是在此时若是其他的服务发生变更,靠缓存中的信息去请求就不正确了。
 

所以,想要高可用的一个服务注册中心,则需要搞一个集群。宕掉一个,还有另外的,这样容错率将大大提高。

其实Eureka搞集群非常简单。不过是开启两个实例,A实例注册进B实例,B实例注册进入A实例而已。
只需要使用两种不同的配置即可。(你要是想开五个就是五个不同的配置,看着加就完事了)
像我这样,我这是写在一个yml文件里了,用“—”来隔开,要是配置比较多是可以另起一个文件的,就叫application-xxx.yml。
可以使用 –spring.profiles.active=xxx 来启动不同的服务。
(这里有部分Security的配置,下面就讲)

spring:
  profiles:
    active: eureka1 #启动时加上这个命令使用不同的配置 --spring.profiles.active=xxx
  security:
    #验证的用户名和密码
    user:
      name: user
      password: 614
#Eureka Server 端配置
eureka:
  client:
    healthcheck:
      enabled: true
    service-url:
        defaultZone: http://user:614@localhost:8080/eureka/,http://user:614@localhost:8081/eureka/
---
spring:
  profiles: eureka1
  application:
      name: sc-demo-microservice-eureka_1
server:
  port: 8080
eureka:
  instance:
    hostname: eureka1
---
spring:
  profiles: eureka2
  application:
        name: sc-demo-microservice-eureka_2
server:
  port: 8081
eureka:
  instance:
    hostname: eureka2

在IDEA里边,可以使用这种方式来启动不同的实例:

集群也搭完了,启动第一个Eureka Server时可能会出点连接错误,不过不影响启动,这是因为第二个Server还没启动起来,而配置里需要它注册进去导致的,第二个启动起来后就没错误了。
你要是一下子就从网页进去看Eureka的状态,可能会出现红色粗体的:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
这个是Eureka的自我保护机制导致的。Eureka Server和Client之间每隔一段时间会进行一次心跳通信,告诉Server,Client还活着。要是某一段时间Server感觉自己这边少了几个心跳就会出这问题,只要不是真的你服务宕掉了,过一会就好了。
 
配置完毕后,客户端也只需要在配置文件中写两个链接就行,用逗号隔开。其实写一个也可以,因为Eureka集群会自动同步其他Eureka的注册信息。但还是推荐写俩,就是怕某些极端情况发生。
 
此时集群已经搭建完毕。客户端也可以连接得上,但是,这些端口就这样暴露出来也是很不科学的。攻击者可以轻易地得到你的服务信息。
最好的,当然还是加个验证啦!用户名密码这种验证永远不过时哈。
搭建完集群后,用 Spring Security 为 Eureka 增加 Basic 安全认证就很不错了!
只需要在 Eureka Server这边添加 Spring Security 的依赖就够了,Client端不用。
compile(“org.springframework.boot:spring-boot-starter-security“)
 
添加Security的依赖后就会自动开启验证,不用配置。用户名和密码的设定就像上边文件一样,只需要这样就行。

spring:
  security:
    #验证的用户名和密码
    user:
      name: user
      password: 614

 
配置完用户名和密码后,连接Eureka Server自然就需要用到这些用户名和密码。
本来连接Server的的URL:   http://ip:port/eureka/
现在连接Server的的URL:   http://username:password@ip:port/eureka/
就算是从网页查看状态,也需要输入用户名和密码了哦。
 
Client端最终配置文件如下(反正配置都长得差不多,我这就放其中一个服务的文件就行了):

server:
  port: 8089
spring:
  application:
    name: sc-demo-microservice-movie #这个名字会注册到 Eureka 里去
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.8.113:3306/scdemo?serverTimezone=Asia/Shanghai
    username: root
    password: 614
#mybatis实体类名
mybatis:
  type-aliases-package: com.skypyb.sc.entity
  configuration:
  #到下划线的表字段自动映射成驼峰命名法
    map-underscore-to-camel-case: true
  mapper-locations: classpath:mybatis/mapper/*.xml
#Actuato 配置
management:
  endpoint:
# 暴露shutdown功能
#    shutdown:
#      enabled: true
  endpoints:
    web:
      exposure:
        include: '*'  #暴露哪些端点
        exclude:      #隐藏哪些端点
#Eureka client端配置
eureka:
  client:
    service-url:
        defaultZone: http://user:614@localhost:8080/eureka/,http://user:614@localhost:8081/eureka/
  instance:
    prefer-ip-address: true #将自己ip注册到Eureka Server

 
这样,其实应该就完事了,但是,在2.x版本的 Spring Security 中,引入依赖后,自动开启CSRF安全认证。
任何一次服务请求默认都需要CSRF 的token , 而我区区一个小Client,哪里来的这种东西。就是Eureka Server 自注册都会挂掉。
那么还是要让 Security 的CSRF拦截自动忽略掉注册Eureka的链接比较好。
在 Eureka Server 端增加这么一个配置类:

package com.skypyb.sc.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().ignoringAntMatchers("/eureka/**");//这个链接不使用csrf
        super.configure(http);
    }
}

这样子去除 /eureka/  这个连接的 CSRF 效验。就都可以连接上了。
 
OK 整体 Eureka 注册中心都搭建完毕了。并且实现了集群高可用和安全策略,比较完善。
 
服务之间的调用在做一点点微小的改进:

package com.skypyb.sc.controller;
import com.skypyb.sc.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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 java.util.List;
@RestController
@RequestMapping("/movie")
public class MovieController {
    private Logger logger = LoggerFactory.getLogger(MovieController.class);
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        //获取 user 服务的信息
        List<ServiceInstance> instances =
                discoveryClient.getInstances("sc-demo-microservice-user");
        String userUrl = instances.get(0).getUri().toString();
        logger.info("access getUser method.");
        return restTemplate.getForObject(userUrl + "/user/" + id, User.class);
    }
}

 
通过 DiscoveryClient 这个类,可以获取到对应服务的信息,这里我便是用 movie 服务获取到了user 服务的 url  来进行的调用。
当然,就算是这样,也不是那么的方便,关于feign声明式调用之后也会写篇文章的。
 
我的项目地址: github

           


2 COMMENTS

EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00