SpringCloud Microservice Governance II (Robbin, Hystix, Feign)

Spring Cloud

SpringCloud Microservice Governance I (Введение, Создание среды, Эврика)
SpringCloud Microservice Governance II (Robbin, Hystix, Feign)
Управление микросервисом SpringCloud 3 (шлюз Zuul)

6. Лента балансировки нагрузки

В случае только что мы запустили пользовательскую службу, а затем получили информацию об экземпляре службы через DiscoveryClient, а затем получили IP-адрес и порт для доступа.

Но в реальной среде мы часто открываем много кластеров пользовательских сервисов. На этом этапе в списке получаемых нами услуг будет несколько сервисов. К какому из них мы должны получить доступ?

Как правило, в этом случае нам нужно написать алгоритм балансировки нагрузки для выбора из нескольких списков экземпляров.

Далее воспользуемся Ribbon для балансировки нагрузки.

6.1. Запустите два экземпляра службы

Сначала мы запускаем два экземпляра пользовательской службы, один для 8081 и один для 8082.

Панель мониторинга Эврика:

1525619546904

6.2 Включить балансировку нагрузки

Поскольку Ribbon уже интегрирован в Eureka, нам не нужно вводить новые зависимости. Измените код напрямую:

Добавьте метод конфигурации RestTemplate@LoadBalancedаннотация:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

Измените метод вызова, больше не получайте вручную ip и порт, а звоните напрямую через имя службы:

@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 делает упор на AP в принципе CAP, а именно на доступности и надежности. Самая большая разница между ним и Zookeeper, который делает упор на CP (непротиворечивость, надежность), заключается в том, что Eureka жертвует определенной согласованностью, чтобы добиться более высокой доступности службы.В крайних случаях он скорее получит неисправные экземпляры, чем будет готов выбросить исправный экземпляр, как мы сказали выше механизм самозащиты.

Однако, если мы вызовем эти ненормальные службы в это время, вызов завершится ошибкой, в результате чего другие службы не будут работать должным образом! Это явно не то, что мы хотим видеть.

Теперь мы отключаем экземпляр пользовательской службы:

1525653565855

Из-за задержки в отбраковке услуг потребитель не сразу получит последний список услуг, и вы получите сообщение об ошибке при повторном посещении в это время:

1525653715488

Но в настоящее время служба 8081 на самом деле работает нормально.

Поэтому Spring Cloud интегрирует Spring Retry, чтобы расширить возможности повторных попыток RestTemplate.При сбое вызова службы он не будет запущен один раз, а снова попытается выполнить другую службу.

Повтор ленты может быть реализован с помощью простой конфигурации:

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. Введение зависимостей

Сначала введите зависимость Hystix в пользователь-потребитель:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

7.3.2 Включите предохранитель

7.3.2 Преобразование потребителей

Преобразуем пользователя-потребителя, добавим 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 миллисекунд), будет выполнена функция отката, и будет возвращено сообщение об ошибке.
  • Чтобы удобно было проверять время срабатывания выключателя, записываем время доступа запроса.

Назовите этот 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 Запустите тест

Затем запустите и посмотрите логи:

Время доступа для идентификаторов 9, 10 и 11:

1525661641660

Время доступа с id 12:

1525661669136

Таким образом, только 12 являются нормальными доступами, а остальные вызовут срабатывание фьюзов.Проверим результаты:

1525661720656

7.3.5 Оптимизация

Несмотря на то, что автоматический выключатель реализован, наш механизм повторных попыток, похоже, не работает, не так ли?

На самом деле это потому, что тайм-аут нашей ленты установлен на 1000 мс:

1525666632542

Тайм-аут Hystix по умолчанию составляет 1000 мс, поэтому механизм повтора не срабатывает, а сначала срабатывает предохранитель.

Следовательно, период ожидания ленты должен быть меньше периода ожидания 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-подобные контроллеры. Вам больше не нужно объединять URL-адреса, параметры и т. д., все остается за Feign.

8.2. Быстрый старт

8.2.1 Импорт зависимостей

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

8.2.2. Притворяться клиентом

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

    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
  • Прежде всего, это интерфейс, и Feign будет генерировать для нас классы реализации через динамические прокси. Это очень похоже на маппер mybatis
  • @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, поэтому нам не нужно самостоятельно определять RestTemplate.

8.3 Балансировка нагрузки

Сам Feign интегрировал зависимости Ribbon и автоматическую настройку:

1525672070679

Поэтому нам не нужно ни вводить дополнительные зависимости, ни регистрироватьRestTemplateобъект.

Кроме того, мы можем настроить ленту, как описано в предыдущем уроке,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的熔断功能

Однако конфигурация Fallback в Feign не так проста, как в Ribbon.

1) Во-первых, нам нужно определить класс для реализации UserFeignClient, только что написанный как резервный класс обработки.

@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, чтобы отладить

logging:
  level:
    com.leyou: debug

2) Напишите класс конфигурации, чтобы определить уровень журнала

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

Здесь указан уровень FULL, и Feign поддерживает 4 уровня:

  • NONE: не записывать никакую информацию журнала, это значение по умолчанию.
  • BASIC: регистрируйте только запрошенный метод, URL-адрес, код состояния ответа и время выполнения.
  • ЗАГОЛОВКИ: На основе BASIC дополнительно записывать информацию заголовка запроса и ответа
  • ПОЛНЫЙ: запись сведений обо всех запросах и ответах, включая информацию заголовка, тело запроса и метаданные.

3) Укажите класс конфигурации в FignClient:

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

4) Перезапускаем проект, можно посмотреть лог каждого доступа:

1525674544569