1.基于springboot2.0.5,springcloud2.X(Finchley.SR1),本文会先搭建一个简单的springcloud示例,包括feign、hystrix、zuul、springcloud-config、springcloud-stream这些的整合。

2.项目模块说明

eureka-server 注册中心

config-server 配置中心

user-service 用户服务

order-service 订单服务

message-service 消息服务

gateway 路由服务

consumer 前端服务

2.开始

2.1 注册中心

依赖如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

application.yml

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false #是否将自身注册到eureka
    fetchRegistry: false #是否抓取注册信息
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动类加上注解@EnableEurekaServer

2.2 服务提供者

order-service,message-service,user-service,三者依赖都一样:

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

order-service的配置文件:
spring:
  application:
    name: order-service

server:
  port: 8763

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

user-service的配置文件:

spring:
  application:
    name: user-service

server:
  port: 8762

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

message-service的配置文件:

spring:
  application:
    name: message-service

server:
  port: 8764

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

然后我们分别启动这4个项目,浏览器输入http://localhost:8761/ 可以看到服务启动成功。

2.3 整合feign和hystrix

2.3.1 新建模块consumer,前端模块,consumer不需要注册到eureka,在这里我们通过consumer去调用服务集群。

2.3.2 consumer引入feign依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>

2.3.3 给order-service新建一个简单的controller

@RestController
public class OrderController {

    static Logger log = LogManager.getLogger(OrderController.class);

    @RequestMapping(value = "/get")
    public OrderEntity get(LocalDateTime dateTime){
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setId(8L);
        orderEntity.setCustomerName("test测试");
        orderEntity.setDate(dateTime);
        return orderEntity;
    }

    @RequestMapping(value = "/getById")
    public OrderEntity get(Long id){
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setId(id);
        orderEntity.setCustomerName("test测试");
        orderEntity.setDate(LocalDateTime.now());
        return orderEntity;
    }

    @RequestMapping(value = "/normal")
    public String normal(){

        return "1";
    }

    @RequestMapping(value = "/abnormal")
    public String abNormal() throws InterruptedException {
        log.info("abnormal-start:{}",LocalDateTime.now());
        Thread.sleep(8000);
        log.info("abnormal-end:{}",LocalDateTime.now());
        return "2";
    }

    @RequestMapping(value = "/abnormalWithCallBack")
    public String abnormalWithCallBack() throws InterruptedException {
        log.info("abnormalWithCallBack-start:{}",LocalDateTime.now());
        Thread.sleep(6000);
        log.info("abnormalWithCallBack-end:{}",LocalDateTime.now());
        return "3";
    }

}

2.3.4 consumer模块中新建feign客户端

@FeignClient(name = "order",url = "http://localhost:8861/order",fallbackFactory = OrderClientFallBack.class)
public interface OrderClient {

    @RequestMapping(method = RequestMethod.GET,value = "/get")
    OrderEntity getOne(LocalDateTime dateTime);

    @RequestMapping(method = RequestMethod.GET,value = "/getById")
    OrderEntity getOne(Long id);

    @RequestMapping(method = RequestMethod.GET,value = "/normal")
    String normal();

    @RequestMapping(method = RequestMethod.GET,value = "/abnormal")
    String abnormal();

    @RequestMapping(method = RequestMethod.GET,value = "/abnormalWithCallBack")
    String abnormalWithCallBack();
}

fallback处理:

@Component
public class OrderClientFallBack implements FallbackFactory<OrderClient> {

    static Logger log = LogManager.getLogger(OrderClientFallBack.class);

    @Override
    public OrderClient create(Throwable throwable) {
        return new OrderClient() {

            @Override
            public OrderEntity getOne(LocalDateTime dateTime) {
                return null;
            }

            @Override
            public OrderEntity getOne(Long id) {
                return null;
            }

            @Override
            public String normal() {
                return null;
            }

            @Override
            public String abnormal() {
                return null;
            }

            @Override
            public String abnormalWithCallBack() {
                log.info("abnormalWithCallBack - fail");
                return "abnormalWithCallBack - fail";
            }
        };

    }
}

consumer配置文件如下:增加超时时间的设置(feign默认1秒钟超时),并打开feign-hystrix(默认是关),一般可以将hystrix的超时时间设置的比feign的超时时间长一些,否则feign的重试(如果配置了)将会失效。

spring:
  application:
    name: consumer
server:
  port: 8862

feign:
  client:
    config:
      default:
        connectTimeout: 7000
        readTimeout: 7000
  hystrix:
    enabled: true

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 8000

2.3.5 consumer增加controller

@RestController
@RequestMapping(value = "/consumer/test")
public class ConsumerController {

    static Logger log = LogManager.getLogger(ConsumerController.class);

    @Autowired
    OrderClient orderClient;

    @RequestMapping(value = "/order/get" )
    public OrderEntity get(){
        return orderClient.getOne(LocalDateTime.now());
    }

    @RequestMapping(value = "/order/getById" )
    public OrderEntity getById(){
        return orderClient.getOne(8L);
    }

    @RequestMapping(value = "/order/normal" )
    public String normal(){
        return orderClient.normal();
    }

    @RequestMapping(value = "/order/abnormal" )
    public String abnormal(){
        return orderClient.abnormal();
    }

    @RequestMapping(value = "/order/abnormalWithCallBack" )
    public String abnormalWithCallBack(){
        try {
            String returnStr = orderClient.abnormalWithCallBack();
            log.info("s:{}",returnStr);
            return returnStr;
        }catch (Exception e){
            //FeignException
            log.error("xxx",e);
            return "catch Exception";
        }
    }
}

2.3.6 启动类上加上@EnableFeignClients、@EnableHystrixDashboard、@EnableCircuitBreaker。

2.4 整合zuul

新建gateway项目,依赖如下:

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

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

配置文件如下:

spring:
  application:
    name: gateway

server:
  port: 8861

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

zuul:
  routes:
    order:
      path: /order/**
      serviceId: order-service
#      url: http://localhost:8763
    user:
      path: /user/**
      serviceId: user-service
  host:
    connect-timeout-millis: 10000
    socket-timeout-millis: 10000

order-service:
  ribbon:
    ReadTimeout: 10000
    ConnectTimeout: 10000

#超时配置说明:
#如果路由方式是serviceId的方式,配置为:zuul:routes:order:serviceId,那么ribbon的超时配置生效(order-service:ribbon:ReadTimeout)
#如果如果是url的方式,配置为:zuul:routes:order:url,则zuul.host开头的生效。

启动类加上注解@EnableZuulProxy开启zuul

到这里有几个超时时间要注意以下,一个是consumer的feign客户端超时时间,一个是consumer启用的hystrix的超时时间,还有一个是zuul的超时时间。只要其中一个超时了,就会触发fallback。

2.5 整合spring cloud cofig

先在git上准备一下环境,如下:

其中test内容为:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.101:3306/test?characterEncoding=utf8&useSSL=false
    username: root
    password: root

version: test

test-string: abcdefghijk

新建config-server项目:

依赖如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

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

配置文件如下:

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/pjypjy/learning-config #uri
          search-paths: /** #配置文件目录
      label: master
      username: solider #github账号
      password: 123456 #github密码

server:
  port: 8862

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

启动类加上注解@EnableConfigServer

为order-service增加配置文件bootstrap.yml:

spring:
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
      profile: test

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

并且为order-service增加依赖:

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--<version>5.1.47</version>-->
            <version>8.0.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

order-service增加请求处理:

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    Environment environment;

    @RequestMapping(value = "/config/query")
    public List<Map<String, Object>> query() {
        String sql = "select * from user_info ";
        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
        return list;
    }

    @RequestMapping(value = "/config/version")
    public String version() {
        String version = environment.getProperty("version");
        return version;
    }

    @RequestMapping(value = "/config/testString")
    public String testString() {
        String version = environment.getProperty("test-string");
        return version;
    }

    @RequestMapping(value = "/config/refresh")
    public String refresh() {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> requestEntity = new HttpEntity<>(null,requestHeaders);
        ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity("http://localhost:8763/actuator/refresh", requestEntity, String.class);
        return stringResponseEntity.getStatusCode().value() + ":" +stringResponseEntity.getBody();
    }

order-service配置文件修改:这里需暴露refresh

management:
  endpoints:
    web:
      exposure:
        include: ["refresh"]

然后先启动config-server,再重新启动order-service,测试一下,dataSource成功创建,可以查询出数据,然后访问/config/testString,得到字符串:abcdefghijk,然后我们将github上的test-string项改为:abc,访问/config/refresh,发送post请求让order-service重新拉取配置,然后再访问/config/testString可以得到:abc。

2.6 整合spring cloud stream

为order-service、user-service、message-service增加依赖:

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

配置文件也统一增加rabbitmq的配置信息:

order-service的部分配置如下:

spring:
  application:
    name: order-service
  rabbitmq:
    host: 192.168.1.102
    port: 5672
    username: root
    password: root
    virtual-host: test
  cloud:
    stream:
      bindings:
        myTest:
          group: groupA

message-service的部分配置如下:

spring:
  application:
    name: message-service
  rabbitmq:
    host: 192.168.1.102
    port: 5672
    username: root
    password: root
    virtual-host: test

user-service部分配置如下:

spring:
  application:
    name: user-service
  rabbitmq:
    host: 192.168.1.102
    port: 5672
    username: root
    password: root
    virtual-host: test

首先是接收端:

为这order-service、message-service项目增加接收端:

public interface ReceiveService {

    @Input("myTest")
    SubscribableChannel receive();

}
启动类增加代码:
    @StreamListener("myTest")
    public void myReceive1(byte[] msg){
        System.out.println("===receive:"+new String(msg));
    }

启动类增加注解

@EnableBinding(value = {ReceiveService.class})

然后我们把user-service改造成发送端

为user-service增加发送端:

public interface ReceiveService {

    @Input("myTest")
    SubscribableChannel receive();

}

为user-service增加注解:

@EnableBinding(value = {SendService.class})

为user-service增加controller:

@RestController
public class UserController {

    @Autowired
    SendService sendService;


    /**
     * 1.如果在配置文件中未配置消费者组,系统会自动生成一个临时队列,连接断开,队列消失,队列名:myTest.anonymous.fsd15hu64....随机生成
     * 2.如果配置了消费者组,则自动生成队列为: myTest.消费者组名称 (非临时队列)
     * 3.默认发送消息,会向所有消费者组推送消息,如果一个有多个消费者 在 同一个消费者组里,消息会轮询发给这个组里的消费者
     * @return
     */
    @RequestMapping(value = "/mySend")
    public boolean mySend(){
        Message msg = MessageBuilder.withPayload("from: user-service  to: order-service".getBytes()).build();
        boolean send = sendService.send().send(msg);
        return send;
    }

}

访问/mySend可以看到message-service和order-service都能接收到消息,要注意,这里没有为message-service配置消费者组,springcloud stream会自动创建临时队列,用于order-service接收消息。order-service设置了消费者组则自动生成队列为: myTest.消费者组名称 (非临时队列)。消息会推送给所有消费者组,如果有多个项目都用了同一个消费者组(如groupA),消息将会轮询发送给这些项目。
 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐