SpringCloud微服务治理二(Robbin,Hystix,Feign)

SpringCloud微服务治理一(介绍,环境搭建,Eureka)
SpringCloud微服务治理二(Robbin,Hystix,Feign)
SpringCloud微服务治理三(Zuul网关)html

6.负载均衡Ribbon

在刚才的案例中,咱们启动了一个user-service,而后经过DiscoveryClient来获取服务实例信息,而后获取ip和端口来访问。java

可是实际环境中,咱们每每会开启不少个user-service的集群。此时咱们获取的服务列表中就会有多个,到底该访问哪个呢?算法

通常这种状况下咱们就须要编写负载均衡算法,在多个实例列表中进行选择。spring

接下来,咱们就来使用Ribbon实现负载均衡。json

6.1.启动两个服务实例

首先咱们启动两个user-service实例,一个8081,一个8082。mybatis

Eureka监控面板:app

1525619546904

6.2.开启负载均衡

由于Eureka中已经集成了Ribbon,因此咱们无需引入新的依赖。直接修改代码:负载均衡

在RestTemplate的配置方法上添加@LoadBalanced注解:框架

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
复制代码

修改调用方式,再也不手动获取ip和端口,而是直接经过服务名称调用:dom

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        // 地址直接写服务名称便可
        String baseUrl = "http://user-service/user/";
        ids.forEach(id -> {
            // 咱们测试屡次查询,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 每次间隔500毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
}
复制代码

6.3.负载均衡策略

Ribbon默认的负载均衡策略是简单的轮询,咱们能够测试一下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserConsumerDemoApplication.class)
public class LoadBalanceTest {

    @Autowired
    RibbonLoadBalancerClient client;

    @Test
    public void test(){
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("user-service");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }
}

复制代码

结果:

1525622357371

符合了咱们的预期推测,确实是轮询方式。

SpringBoot也帮咱们提供了修改负载均衡规则的配置入口:

user-service:
 ribbon:
 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
复制代码

格式是:{服务名称}.ribbon.NFLoadBalancerRuleClassName

6.4.重试机制

Eureka的服务治理强调了CAP原则中的AP,便可用性和可靠性。它与Zookeeper这一类强调CP(一致性,可靠性)的服务治理框架最大的区别在于:Eureka为了实现更高的服务可用性,牺牲了必定的一致性,极端状况下它宁愿接收故障实例也不肯丢掉健康实例,正如咱们上面所说的自我保护机制。

可是,此时若是咱们调用了这些不正常的服务,调用就会失败,从而致使其它服务不能正常工做!这显然不是咱们愿意看到的。

咱们如今关闭一个user-service实例:

1525653565855

由于服务剔除的延迟,consumer并不会当即获得最新的服务列表,此时再次访问你会获得错误提示:

1525653715488

可是此时,8081服务实际上是正常的。

所以Spring Cloud 整合了Spring Retry 来加强RestTemplate的重试能力,当一次服务调用失败后,不会当即抛出一次,而是再次重试另外一个服务。

只须要简单配置便可实现Ribbon的重试:

spring:
 cloud:
 loadbalancer:
 retry:
 enabled: true # 开启Spring Cloud的重试功能
user-service:
 ribbon:
 ConnectTimeout: 250 # Ribbon的链接超时时间
 ReadTimeout: 1000 # Ribbon的数据读取超时时间
 OkToRetryOnAllOperations: true # 是否对全部操做都进行重试
 MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
 MaxAutoRetries: 1 # 对当前实例的重试次数
复制代码

根据如上配置,当访问到某个服务超时后,它会再次尝试访问下一个服务实例,若是不行就再换一个实例,若是不行,则返回失败。切换次数取决于MaxAutoRetriesNextServer参数的值

引入spring-retry依赖

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
复制代码

咱们重启user-consumer-demo,测试,发现即便user-service2宕机,也能经过另外一台服务实例获取到结果!

1525658269456

7.Hystix

7.1.简介

Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

7.2.熔断器的工做机制:

正常工做的状况下,客户端请求调用服务API接口:

当有服务出现异常时,直接进行失败回滚,服务降级处理:

当服务繁忙时,若是服务出现异常,不是粗暴的直接报错,而是返回一个友好的提示,虽然拒绝了用户的访问,可是会返回一个结果。

7.3.动手实践

7.3.1.引入依赖

首先在user-consumer中引入Hystix依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
复制代码

7.3.2.开启熔断

7.3.2.改造消费者

咱们改造user-consumer,添加一个用来访问的user服务的DAO,而且声明一个失败时的回滚处理函数:

@Component
public class UserDao {

    @Autowired
    private RestTemplate restTemplate;

    private static final Logger logger = LoggerFactory.getLogger(UserDao.class);

    @HystrixCommand(fallbackMethod = "queryUserByIdFallback")
    public User queryUserById(Long id){
        long begin = System.currentTimeMillis();
        String url = "http://user-service/user/" + id;
        User user = this.restTemplate.getForObject(url, User.class);
        long end = System.currentTimeMillis();
        // 记录访问用时:
        logger.info("访问用时:{}", end - begin);
        return user;
    }

    public User queryUserByIdFallback(Long id){
        User user = new User();
        user.setId(id);
        user.setName("用户信息查询出现异常!");
        return user;
    }
}
复制代码
  • @HystrixCommand(fallbackMethod="queryUserByIdFallback"):声明一个失败回滚处理函数queryUserByIdFallback,当queryUserById执行超时(默认是1000毫秒),就会执行fallback函数,返回错误提示。
  • 为了方便查看熔断的触发时机,咱们记录请求访问时间。

在原来的业务逻辑中调用这个DAO:

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        ids.forEach(id -> {
            // 咱们测试屡次查询,
            users.add(this.userDao.queryUserById(id));
        });
        return users;
    }
}
复制代码

7.3.3.改造服务提供者

改造服务提供者,随机休眠一段时间,以触发熔断:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User queryById(Long id) throws InterruptedException {
        // 为了演示超时现象,咱们在这里然线程休眠,时间随机 0~2000毫秒
        Thread.sleep(new Random().nextInt(2000));
        return this.userMapper.selectByPrimaryKey(id);
    }
}

复制代码

7.3.4.启动测试

而后运行并查看日志:

id为九、十、11的访问时间分别是:

1525661641660

id为12的访问时间:

1525661669136

所以,只有12是正常访问,其它都会触发熔断,咱们来查看结果:

1525661720656

7.3.5.优化

虽然熔断实现了,可是咱们的重试机制彷佛没有生效,是这样吗?

其实这里是由于咱们的Ribbon超时时间设置的是1000ms:

1525666632542

而Hystix的超时时间默认也是1000ms,所以重试机制没有被触发,而是先触发了熔断。

因此,Ribbon的超时时间必定要小于Hystix的超时时间。

咱们能够经过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。

hystrix:
 command:
  	default:
 execution:
 isolation:
 thread:
 timeoutInMillisecond: 6000 # 设置hystrix的超时时间为6000ms
复制代码

8.Feign

8.1.简介

Feign能够把Rest的请求进行隐藏,假装成相似SpringMVC的Controller同样。你不用再本身拼接url,拼接参数等等操做,一切都交给Feign去作。

8.2.快速入门

8.2.1.导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
复制代码

8.2.2.Feign的客户端

@FeignClient("user-service")
public interface UserFeignClient {

    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
复制代码
  • 首先这是一个接口,Feign会经过动态代理,帮咱们生成实现类。这点跟mybatis的mapper很像
  • @FeignClient,声明这是一个Feign客户端,相似@Mapper注解。同时经过value属性指定服务名称
  • 接口中的定义方法,彻底采用SpringMVC的注解,Feign会根据注解帮咱们生成URL,并访问获取结果

改造原来的调用逻辑,再也不调用UserDao:

@Service
public class UserService {

    @Autowired
    private UserFeignClient userFeignClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        ids.forEach(id -> {
            // 咱们测试屡次查询,
            users.add(this.userFeignClient.queryUserById(id));
        });
        return users;
    }
}
复制代码

8.2.3.开启Feign功能

咱们在启动类上,添加注解,开启Feign功能

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients // 开启Feign功能
public class UserConsumerDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerDemoApplication.class, args);
    }
}
复制代码
  • 你会发现RestTemplate的注册被我删除了。Feign中已经自动集成了Ribbon负载均衡,所以咱们不须要本身定义RestTemplate了

8.3.负载均衡

Feign中自己已经集成了Ribbon依赖和自动配置:

1525672070679

所以咱们不须要额外引入依赖,也不须要再注册RestTemplate对象。

另外,咱们能够像上节课中讲的那样去配置Ribbon,能够经过ribbon.xx来进行全局配置。也能够经过服务名.ribbon.xx来对指定服务配置:

user-service:
 ribbon:
 ConnectTimeout: 250 # 链接超时时间(ms)
 ReadTimeout: 1000 # 通讯超时时间(ms)
 OkToRetryOnAllOperations: true # 是否对全部操做重试
 MaxAutoRetriesNextServer: 1 # 同一服务不一样实例的重试次数
 MaxAutoRetries: 1 # 同一实例的重试次数
复制代码

8.4.Feign对Hystix的集成

经过下面的参数来开启:

feign:
 hystrix:
 enabled: true # 开启Feign的熔断功能
复制代码

可是,Feign中的Fallback配置不像Ribbon中那样简单了。

1)首先,咱们要定义一个类,实现刚才编写的UserFeignClient,做为fallback的处理类

@Component
public class UserFeignClientFallback implements UserFeignClient {
    @Override
    public User queryUserById(Long id) {
        User user = new User();
        user.setId(id);
        user.setName("用户查询出现异常!");
        return user;
    }
}

复制代码

2)而后在UserFeignClient中,指定刚才编写的实现类

@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {

    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}

复制代码

8.5.请求压缩

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减小通讯过程当中的性能损耗。经过下面的参数便可开启请求与响应的压缩功能:

feign:
 compression:
 request:
 enabled: true # 开启请求压缩
 response:
 enabled: true # 开启响应压缩
复制代码

同时,咱们也能够对请求的数据类型,以及触发压缩的大小下限进行设置:

feign:
 compression:
 request:
 enabled: true # 开启请求压缩
 mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
 min-request-size: 2048 # 设置触发压缩的大小下限
复制代码

注:上面的数据类型、压缩大小下限均为默认值。

8.6.日志级别

前面讲过,经过logging.level.xx=debug来设置日志级别。然而这个对Fegin客户端而言不会产生效果。由于@FeignClient注解修改的客户端在被代理时,都会建立一个新的Fegin.Logger实例。咱们须要额外指定这个日志的级别才能够。

1)设置com.leyou包下的日志级别都为debug

logging:
 level:
    com.leyou: debug
复制代码

2)编写配置类,定义日志级别

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}
复制代码

这里指定的Level级别是FULL,Feign支持4种级别:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录全部请求和响应的明细,包括头信息、请求体、元数据。

3)在FeignClient中指定配置类:

@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class, configuration = FeignConfig.class)
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
复制代码

4)重启项目,便可看到每次访问的日志:

1525674544569
相关文章
相关标签/搜索