跳至主要內容

SpringCloudNetflixEureka

soulballad微服务SpringCloud NetfilxSpringCloud约 2016 字大约 7 分钟

Spring Cloud Netflix Eureka

分布式系统基本组成

  • 服务提供方(Provider)
  • 服务消费方(Consumer)
  • 服务注册中心(Registry)
  • 服务路由(Router)
  • 服务代理(Broker)
  • 通讯协议(Protocol)

传统的服务治理

通讯协议

XML-RPC -> XML 方法描述、方法参数 -> WSDL(WebServices 定义语言)
WebServices -> SOAP(HTTP、SMTP) -> 文本协议(头部分、体部分)
REST -> JSON/XML( Schema :类型、结构) -> 文本协议(HTTP Header、Body)
W3C Schema :xsd:string 原子类型,自定义自由组合原子类型
Java POJO : int、String
Response Header -> Content-Type: application/json;charset=UTF-8

Dubbo:Hession、 Java Serialization(二进制),跨语言不变,一般通过 Client(Java、C++)

二进制的性能是非常好(字节流,免去字符流(字符编码),免去了字符解释,机器友好、对人不友好)

序列化:把编程语言数据结构转换成字节流;反序列化:字节流转换成编程语言的数据结构(原生类型的组合)

高可用架构

1570177901213

URI:统一资源定位符

URI:用于网络资源定位的描述 Universal Resource Identifier

URL: Universal Resource Locator

网络是通讯方式

资源是需要消费媒介

定位是路由

Proxy:一般性代理,路由

​ Nginx:反向代理

Broker:包括路由,并且管理,老的称谓(MOM)

​ Message Broker:消息路由、消息管理(消息是否可达)

可用性比率计算

可用性比率:通过时间来计算(一年或者一月)

比如:一年 99.99 %

可用时间:365 * 24 * 3600 * 99.99%

不可用时间:365 * 24 * 3600 * 0.01% = 3153.6 秒 < 一个小时

不可用时间:1个小时 推算一年 1 / 24 / 365 = 0.01 %

单台机器不可用比率:1%

两台机器不可用比率:1% * 1%

N 机器不可用比率:1% ^ n

可靠性

微服务里面的问题:

一次调用:

A -> B -> C

99% -> 99% -> 99% = 97%

A -> B -> C -> D

99% -> 99% -> 99% -> 99% = 96%

结论:增加机器可以提高可用性,增加服务调用会降低可靠性,同时降低了可用性

服务发现

1570178572396

Eureka 使用

Eureka 服务器

步骤

  1. 项目结构

    1570183796104

  2. maven 依赖配置

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
    
  3. 启动类配置 @EnableEurekaServer 标记

    @SpringBootApplication
    @EnableEurekaServer
    public class SpringCloudEurekaServerDemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringCloudEurekaServerDemoApplication.class, args);
        }
    }
    
  4. 配置文件 application.properties 添加配置

    spring.application.name=spring-cloud-eureka-server
    server.port=9090
    # 取消服务器自我注册
    eureka.client.register-with-eureka=false
    # 注册中心的服务器,没有必要再去检索服务
    eureka.client.fetch-registry=false
    # Eureka Server 服务 URL,用于客户端注册
    eureka.client.service-url.defaultZone=http://localhost:${server.port}/eureka
    
  5. 浏览器访问 http://localhost:9090

    1570183977896

    • DS Replicas: 备用服务地址
    • Instances currently registered with Eureka: eureka 上当前注册的应用实例
  6. 注意事项:

    Eureka 服务器一般不需要自我注册,也不需要注册其他服务器

    Eureka 自我注册的问题,服务器本身没有启动

    通常经验,Eureka 服务器不需要开启自动注册,也不需要检索服务

    ### 取消服务器自我注册
    eureka.client.register-with-eureka=false
    ### 注册中心的服务器,没有必要再去检索服务
    eureka.client.fetch-registry = false
    

    但是这两个设置并不是影响作为服务器的使用,不过建议关闭,为了减少不必要的异常堆栈,减少错误的干扰(比如:系统异常和业务异常)

eureka 错误机制:

Fast Fail : 快速失败

Fault-Tolerance :容错

Eureka 客户端

步骤:

  1. 项目结构

    1570184968696

  2. maven 依赖配置,spring-cloud-eureka-client-demo 配置

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
    
  3. 各个子 module 实现

    1. user-api 实现

      • user 实体类

        public class User {
        
            private Long id;
            private String name;
            // get&set
        }
        
      • user service 接口

        public interface UserService {
        
            /**
             * 保存用户
             *
             * @param user
             * @return 如果保存成功的话,返回 <code>true</code>
             * 否则返回<code>false</code>
             */
            boolean save(User user);
        
            /**
             * 查询所有用户
             * @return 不会返回 <code>null</code>
             */
            Collection findAll();
        }
        
    2. user-service-consumer 实现

      • UserRestConsumerController,对外提供web服务

        @RestController
        public class UserRestConsumerController {
        
            @Autowired
            private UserService userService;
        
            @PostMapping("/user/save")
            public User saveUser(@RequestBody User user) {
        
                if (userService.save(user)) {
                    return user;
                } else {
                    return null;
                }
            }
        
            @GetMapping("/user/list")
            public Collection<User> list() {
                return userService.findAll();
            }
        }
        
      • UserServiceProxy,用户服务代理

        这里通过 RestTemplate 调用 user-service-provider 提供的 web 服务,然后对外提供

        @Service
        public class UserServiceProxy implements UserService {
        
            private static final String PROVIDER_SERVER_URL_PREFIX = "http://user-service-provider";
        
            @Autowired
            private RestTemplate restTemplate;
        
            @Override
            public boolean save(User user) {
                User returnValue = restTemplate.postForObject(PROVIDER_SERVER_URL_PREFIX + "/user/save", user, User.class);
                return returnValue != null;
            }
        
            @Override
            public Collection<User> findAll() {
                return restTemplate.getForObject(PROVIDER_SERVER_URL_PREFIX + "/user/list", Collection.class);
            }
        }
        
      • UserServiceConsumerBootstrap,用户提供服务启动类

        • @EnableDiscoveryClient:用于服务发现

        • @Bean RestTemplate:注入RestTemplate,在 UserServiceProxy 中使用

          @LoadBalanced:有多个服务提供者时,使用负载均衡

        @SpringBootApplication
        @EnableDiscoveryClient
        public class UserServiceConsumerBootstrap {
        
            public static void main(String[] args) {
                SpringApplication.run(UserServiceConsumerBootstrap.class, args);
            }
        
            @LoadBalanced
            @Bean
            public RestTemplate restTemplate() {
                return new RestTemplate();
            }
        }
        
      • application.properties 配置

        spring.application.name=user-service-consumer
        # Eureka Server 服务器端口
        eureka.server.port=9090
        server.port=8080
        # Eureka Server 服务 URL,用于客户端注册
        eureka.client.service-url.defaultZone=\
          http://localhost:${eureka.server.port}/eureka
        
    3. user-service-provider 实现

      • UserServiceImpl:用户服务api实现类

        @Service
        public class UserServiceImpl implements UserService {
        
            @Autowired
            private UserRepository userRepository;
        
            @Override
            public boolean save(User user) {
                return userRepository.save(user);
            }
        
            @Override
            public Collection<User> findAll() {
                return userRepository.findAll();
            }
        }
        
      • UserRepository:用户仓储类

        @Repository
        public class UserRepository {
        
            private ConcurrentMap<Long, User> repository = 
                new ConcurrentHashMap<>();
            private final AtomicLong idGenerator = new AtomicLong(0);
        
            public boolean save(User user) {
                long id = idGenerator.incrementAndGet();
                user.setId(id);
                return repository.putIfAbsent(id, user) == null;
            }
        
            public Collection<User> findAll() {
                return repository.values();
            }
        }
        
      • UserRestProviderController:用户服务提供者控制器,提供web服务

        @RestController
        public class UserRestProviderController {
        
            @Autowired
            private UserService userService;
        
            @PostMapping("/user/save")
            public User saveUser(@RequestBody User user) {
        
                if (userService.save(user)) {
                    return user;
                } else {
                    return null;
                }
            }
        
            @GetMapping("/user/list")
            public Collection<User> list() {
                return userService.findAll();
            }
        }
        
      • UserServiceProviderBootstrap:用户服务提供者启动类

        @SpringBootApplication
        @EnableDiscoveryClient
        public class UserServiceProviderBootstrap {
        
            public static void main(String[] args) {
                SpringApplication.run(UserServiceProviderBootstrap.class, args);
            }
        }
        
      • application.properties 配置

        spring.application.name=user-service-provider
        eureka.server.port=9090
        server.port=7070
        # Eureka Server 服务 URL,用于客户端注册
        eureka.client.service-url.defaultZone=\
         http://localhost:${eureka.server.port}/eureka
        
  4. 分别启动 user-service-provideruser-service-consumer

    可以在 eureka-server 上看到二者注册成功

    1570185930464

  5. 使用 postman 测试

    • user-service-provider:/user/save

      1570186055933

    • user-service-provider:/user/list

      1570186103748

    • user-service-consumer:/user/save

      1570186152635

    • user-service-consumer:/user/list

      1570186184436

  6. 注意事项

    • eureka.client.service-url.defaultZone 的配置
    • @EnableDiscoveryClient 注解配置

问答部分

  1. consul 和 Eureka 是一样的吗

    答:提供功能类似,consul 功能更强大,广播式服务发现/注册

  2. 重启eureka 服务器,客户端应用要重启吗

    答:不用,客户端在不停地上报信息,不过在 Eureka 服务器启动过程中,客户端大量报错

  3. 生产环境中,consumer是分别注册成多个服务,还是统一放在一起注册成一个服务?权限应该如何处理?

    答:consumer 是否要分为多个服务,要情况,大多数情况是需要,根据应用职责划分。权限根据服务方法需要,比如有些敏感操作的话,可以根据不同用户做鉴权。

  4. 客户端上报的信息存储在哪里?内存中还是数据库中

    答:都是在内存里面缓存着,EurekaClient 并不是缓存所有的服务,需要的服务。比如:Eureka Server 管理了 200个应用,每个应用存在 100个实例,总体管理 20000 个实例。客户端根据自己的需要的应用实例。

  5. 要是其他模块查询列表里面 有用到用户信息怎么办呢 是循环调用户接口 还是直接关联用户表呢 怎么实现好呢

    答:用户 API 依赖即可

  6. consumer 调用 Aprovider-a 挂了,会自动切换 Aprovider-b吗,保证请求可用吗

    答:当 Aprovider-a 挂,会自动切换,不过不一定及时。不及时,服务端可能存在脏数据,或者轮训更新时间未达。

  7. 一个业务中调用多个service时如何保证事务

    答:需要分布式事务实现(JTA),可是一般互联网项目,没有这种昂贵的操作。

上次编辑于:
贡献者: soulballad