Java-SpringBoot整合SpringCloud

SpringBoot整合SpringCloud


1. SpringCloud特點

SpringCloud專註於為典型的用例和擴展機制提供良好的開箱即用體驗,以涵蓋其他情況:

  • 分散式/版本化配置

  • 服務註冊和發現 Eureka

  • 路由 Zuul

  • 服務到服務的呼叫

  • 負載均衡 Ribbon

  • 斷路器 Hystrix

  • 分散式消息傳遞

2. 分散式系統的三個指標CAP

在介紹SpringCloud默認使用的註冊中心前,先介紹分散式系統的三個指標,分別是:

  • Consistency:一致性,在分散式系統中,更新操作執行成功後所有的用戶的讀操作必須返回最新值;client寫入,server同步至整個系統;

  • Availability:可用性,只要收到用戶的請求,在一定的時間內伺服器就必須給出回應,回應的結果可以是成功或是失敗;

  • Partition tolerance:分區容錯,即區間通訊可能失敗,在網路中斷,消息丟失的情況下,仍對外提供服務;一般無法避免,可以認為CAP中的P總是成立

CAP定律說的是,在一個分散式電腦系統中,一致性C,可用性A,分區容錯性P這三種保證無法同時得到滿足,最多滿足兩個:

  1. 放棄P

為了避免分區容錯性問題的發生,一種做法是將所有與事務相關的數據都放在一台伺服器上,雖然不能保證100%系統不會出錯,但是不會碰到由分區帶來的負面效果,這樣的做法會嚴重影響系統的擴展性。

  1. 放棄A

放棄可用性,一旦遇到分區容錯故障,受到影響的伺服器需要等待一定的時間,因此會導致在等待期間系統無法對外提供服務。

  1. 放棄C

這兒說的放棄一致性,並不是完全放棄數據的一致性,而是放棄數據的強一致性,而保留數據的最終一致性。以網路購物為例,對只剩下一件庫存的商品,如果同時接受到了兩份訂單,那麼較晚的訂單將被告知商品告罄。

文中部分CAP理論摘自://www.cnblogs.com/hxsyl/p/4381980.html

3. Eureka

基本介紹

Eureka是SpringCloud官方推薦用於服務註冊和發現,一個基於REST的服務

SpringBoot實現了Netflix OSS的集成,使用Eureka的原因之一是因為其可以利用Spring Cloud Netflix的其他組件:智慧路由(Zuul)、客戶端負載均衡(Ribbon)等

基本組成

Eureka由多個Instance(服務實例)組成,分為Eureka ServerEureka Client

其中Eureka Client又可細分為:Service Provider、Service Consumer

  • Eureka Server:服務端,提供服務的註冊和發現;

  • Eureka Client:客戶端

    • Service Provider:服務提供方,將自身服務註冊到Eureka,讓消費方找到
    • Service Consumer:服務消費方,從Eureka獲取註冊服務列表,從而消費服務

Eureka和Zookeeper

1)由CAP理論的角度來看

Zookeeper:ZK保證CP,突出強一致性,但無法保證每次訪問服務可用性,比如ZK會出現這樣一種情況:當master節點因為網路故障與其他節點失去聯繫時,剩餘節點會重新進行leader選舉,在ZK選舉leader期間整個ZK集群都是不可用的,這就導致了在選舉期間註冊服務癱瘓。在雲部署的環境下,因網路問題使得ZK集群失去master節點是較大概率會發生的事兒,雖然服務最終會恢復,但是漫長的選舉時間導致的註冊長期不可用是難以容忍的。

Eureka:Eureka保證AP,Eureka在設計時就優先保證可用性。對於Eureka中的節點,每個節點都是平等的,幾個節點掛掉也不會影響正常節點的工作,剩餘的節點依然可以提供註冊和查詢服務。但Eureka不保證強一致性,即查到的資訊可能不是最新的。此外Eureka還有一種自我保護機制,如果在15分鐘內超過85%的節點都沒有正常的心跳,那麼Eureka就會認為客戶端和註冊中心出現了網路故障,但會保證當前節點依然可用,不會像ZK導致整個註冊服務癱瘓。

2)由節點分工的角度來看

Zookeeper:ZK集群中節點分為三類,承擔不同的任務

  • Leader:事務請求唯一的調度者和處理者(除了查詢請求)
  • Follower:處理非事務請求,參與Leader選舉投票
  • Observer:處理非事務請求,但不參與Leader選舉投票

Eureka:在Eureka集群中每個節點都是平等的,每個節點扮演相同的角色,它們通過相互註冊的方式來感知對方的存在,當由註冊消息時,它們會同步給集群內的其他節點

中文文檔://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_netflix

4. SpringCloud Demo

Demo整體結構(父子項目)

  • api:Bean、DTO、POJO等以及Service介面

  • controller:服務消費方,前端交互

  • provider:服務提供方,服務實現

  • registry:服務註冊中心

4.1 registry

1)pom.xml

關鍵點在於導入eureka服務端依賴以及SpringCloud依賴

  <dependencies>
        <!-- //mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
        <!-- eureka服務端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2)application.properties

進行Eureka服務端的相關配置

spring.application.name=SpringCloudDemoRegistry

# server埠,自己喜歡
server.port=8888

# Eureka Server服務url以及默認zone,結尾必須eureka
eureka.client.service-url.defaultZone= //127.0.0.1:8888/eureka/

# eureka的自我保護機制,k8s環境下建議false
eureka.server.enable-self-preservation=false

# false表示自己端就是註冊中心,我的功能就是維護服務實例,不需要從server獲取註冊的服務資訊
eureka.client.fetch-registry=false

# false表示不將自己註冊到Eureka Server
eureka.client.register-with-eureka=false

3)EurekaregistryApplication

使用@EnableEurekaServer激活相關配置,使registry模組作為註冊中心Server

@EnableEurekaServer //聲明為註冊中心服務端
@SpringBootApplication
public class EurekaregistryApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaregistryApplication.class, args);
    }
}

4.2 api

1)entity

實體類以部門和用戶資訊為例,由於演示使用了Lombok省點程式碼,用於實現基本的CRUD

Department.class

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Department {
    private Long id;
    private Integer dId;
    private String dName;
    private Date updateTime;
}

UserInfo.class

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
    private Long id;
    private Integer uId;
    private Integer dId;
    private String uName;
    private String uPhone;
    @Email(message = "非法郵件格式") //Validator格式校驗
    private String uEmail;
    private String uAddress;
    private Date updateTime;
    private Department department;
}

2)UserService

UserService為服務介面,聲明需要提供和消費的方法,寫了幾個簡單查詢

public interface UserService {

    /**
     * 判斷用戶是否存在,0為不存在,返回值與uId相等則存在
     * @param uId
     * @return
     */
    Integer userExist(Integer uId);

    /**
     * 獲取用戶個人基本資訊
     * @param uId
     * @return
     */
    UserInfo getUserInfo(Integer uId);

    /**
     * 獲取用戶個人詳情資訊
     * @param uId
     * @return
     */
    UserInfo getUserDetailsInfo(Integer uId);
}

4.3 provider

1)pom.xml

關鍵點是引入eureka客戶端依賴openfeign依賴、SpringCloud依賴

<dependencies>
    	<!-- Springboot的依賴省略... -->
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

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

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>

2)application.properties

配置Eureka、Mybatis以及DataSource數據源等資訊

spring.application.name=SpringCloudDemoProvider

# Server埠
server.port=9999

# Eureka服務url
eureka.client.service-url.defaultZone= //127.0.0.1:8888/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true

# Mybatis
mybatis.mapper-locations= classpath:com.maziyao.provider.mapper/*.xml
mybatis.type-aliases-package= com.maziyao.common.entity

# DataSource數據源配置
spring.datasource.type=com.zaxxer.hikari.HikariDataSource

# DataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/boot-demo?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

3)EurekaproviderApplication

provider啟動類使用@EnableEurekaClient聲明為EurekaClient客戶端,作為ServiceProvider

@EnableEurekaClient
@SpringBootApplication
@EnableAspectJAutoProxy
@MapperScan("com.maziyao.provider.mapper")
@ComponentScan(basePackages = {
        "com.maziyao.provider.rest",
        "com.maziyao.provider.redis",
        "com.maziyao.provider.service",
        "com.maziyao.provider.config"
})
public class EurekaproviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaproviderApplication.class, args);
    }
}

4)service

UserServiceImpl作為UserService介面實現類,@Service用於標註業務層組件

自動注入mapper層的mapper介面並調用,mapper的SQL語句下面就略了,很簡單

需要注意的是,對於數據訪問層組件mapper,使用@Repository進行標註

@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public Integer userExist(Integer uId) {
        return userMapper.userExist(uId);
    }

    @Override
    public UserInfo getUserInfo(Integer uId) {
        return userMapper.getUserInfo(uId);
    }

    @Override
    public UserInfo getUserDetailsInfo(Integer uId) {
        return userMapper.getUserDetailsInfo(uId);
    }
}

5)rest

重點是rest包下的UserServer,要知道Eureka是一個基於REST的服務

使用@RestController標註並聲明為userServer,這個是SpringBoot註解,用於標註控制層組件

@RestController("userServer")
public class UserServer {

    public static final Logger logger = LoggerFactory.getLogger(UserServer.class);

    @Autowired
    private UserService userService;

    @ResponseBody
    @RequestMapping(value = "/exist",method = RequestMethod.GET)
    public Integer existUserByUid(@RequestParam("uId")Integer uId){
        return userService.userExist(uId);
    }

    @ResponseBody
    @RequestMapping(value = "/userInfo",method = RequestMethod.GET)
    public UserInfo getUserInfo(@RequestParam("uId")Integer uId){
        return userService.getUserInfo(uId);
    }

    @ResponseBody
    @RequestMapping(value="/userDetailsInfo",method = RequestMethod.GET)
    public UserInfo getUserDetailsInfo(@RequestParam("uId")Integer uId){
        return userService.getUserDetailsInfo(uId);
    }
}

4.4 consumer

1)pom.xml

在Controller的pom.xml中也需要導入EurekaClient的依賴,對於Provider和Controller都為Client

<dependencies>
    	<!-- SpringBoot依賴省略... -->
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
</dependencies>

2)application.properties

在application.properties中配置Eureka相關資訊

spring.application.name=springCloudDemoController

# Server埠
server.port=8899

# 註冊中心url
eureka.client.service-url.defaultZone= //127.0.0.1:8888/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true

3)EurekacontrollerApplication

與Provider的啟動類一致,聲明啟動類為Eureka的客戶端

並使用@EnableFeignClients開啟Feign,聲明性的web服務客戶端

@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
@EnableAspectJAutoProxy
@ComponentScan({
        "com.maziyao.controller.config",
        "com.maziyao.controller.controller",
        "com.maziyao.controller.rest"
})
public class EurekacontrollerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekacontrollerApplication.class, args);
    }

}

4)rest

controller作為前端交互層,即控制層,此處UserClient聲明調用方法

使用@FeignClient聲明服務提供方在application.properties中配置的spring.application.name

以及綁定服務提供方對應rest包下的UserServer類的@RestController(“userServer“)

@FeignClient(name = "springCloudDemoProvider",contextId = "userServer")
public interface UserClient {

    @ResponseBody
    @RequestMapping(value = "/exist",method = RequestMethod.GET)
    public Integer existUserByUid(@RequestParam("uId")Integer uId);

    @ResponseBody
    @RequestMapping(value = "/userInfo",method = RequestMethod.GET)
    public UserInfo getUserInfo(@RequestParam("uId")Integer uId);

    @ResponseBody
    @RequestMapping(value="/userDetailsInfo",method = RequestMethod.GET)
    public UserInfo getUserDetailsInfo(@RequestParam("uId")Integer uId);

}

5)UserController

控制層,使用@RestController標註,並注入UserClient進行調用

@RestController
public class UserController {

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

    @Autowired
    private UserClient userClient;

    @ResponseBody
    @RequestMapping(value = "/exist",method = RequestMethod.GET)
    public Integer existUserByUid(@RequestParam("uId") Integer uId){
        return userClient.existUserByUid(uId);
    }

    @ResponseBody
    @RequestMapping(value = "/userInfo",method = RequestMethod.GET)
    public UserInfo getUserInfoByUid(@RequestParam("uId")Integer uId){
        return userClient.getUserInfo(uId);
    }

    @ResponseBody
    @RequestMapping(value = "/userDetailsInfo",method = RequestMethod.GET)
    public UserInfo getUserDetailsInfoByUid(@RequestParam("uId")Integer uId){
        return userClient.getUserDetailsInfo(uId);
    }

}

4.5 POSTMAN一下

注意:最後記得mvn install,需要先將registry運行起來,再分別運行provider和controller

啟動registry:

啟動provider:

啟動consumer:

result: