文章出處

有過dubbo/dubbox使用經驗的朋友,看到下面這張圖,一定很熟悉,就是SOA架構的最基本套路。

與dubbo對比,上圖的3大要素中,spring cloud是借助以下組件來實現的:

1、注冊中心:

spring cloud默認使用eureka server來做注冊中心,而dubbo默認使用的是zookeeper。eureka的注冊信息是保存在一個雙層的Map對象中的,換句話說在內存中,不象zookeeper是長久保存在節點中。

2、服務提供方:

spring-web(Spring MVC)提供了完善的http rest服務框架,用這一套就能提供rest服務。(目前spring cloud官方提供的示例基本上都是http rest服務,理論上講,應該也可以擴展成rpc服務,而dubbo是以rpc為主的,這點有些區別)

3、服務消費方:

依賴于spring-web,負載均衡采用ribbon組件來完成,大致原理是從注冊中心發現可用服務的信息,緩存在本地,然后按一定的負載均衡算法進行調用。(跟dubbo類似,只不過dubbo是自己實現的負載均衡)

 

下面是這三方的最基本示例:

一、項目結構

注:spring-cloud是完全基于Spring Boot來構建項目的,所以對spring boot不熟悉的,建議先看本博客的spring boot系列

register-center 即 eureka 注冊中心

service-api 為服務契約

service-consumer 為服務消費方

service-provider 為服務提供方

 

二、register-center

2.1 依賴項

buildscript {
    repositories {
        maven {
            url "http://maven.aliyun.com/nexus/content/groups/public/"
        }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.4.RELEASE")
    }
}

apply plugin: 'spring-boot'

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE"
    }
}

dependencies {
    compile 'org.springframework.cloud:spring-cloud-starter-eureka-server'
    compile 'org.springframework.boot:spring-boot-starter-actuator'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
}

2.2 main入口程序

package com.cnblogs.yjmyzz.spring.cloud.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * Created by 菩提樹下的楊過 on 2017/6/17.
 */
@SpringBootApplication
@EnableEurekaServer
public class RegisterServer {

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

主要是靠最上面的@EnableEurekaServer這個注解,其它完全沒有花頭。

2.3 配置

server:
  port: 8000

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8000/eureka  

解釋一下:

注冊中心本身也是一個服務,也可以當成普通服務向其它注冊中心來注冊,由于本示例中,只有一個eureka server自己就充當注冊中心,也不需要跟其它注冊中心同步注冊信息,所以都設置成false。最后一行的defaultZone,初次接觸可以先不管,先理解成注冊中心對外暴露的地址即可。

2.4 啟動 

啟動后,瀏覽http://localhost:8000/,可以看到類似下圖:

現在沒有任何服務注冊,所以在Application里,顯示No instances available.

 

三、service-api

為了方便后面講解,先定義一個服務接口,以及對應的DTO

package com.cnblogs.yjmyzz.spring.cloud.study.api;

import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO;

/**
 * Created by 菩提樹下的楊過 on 2017/6/17.
 */
public interface UserService {

    UserDTO findUser(Integer userId);
}

以及

package com.cnblogs.yjmyzz.spring.cloud.study.dto;

import lombok.Data;

/**
 * Created by 菩提樹下的楊過 on 2017/6/17.
 */
@Data
public class UserDTO {

    private Integer userId;

    private String userName;
}

 

四、service-provider

4.1 依賴項

buildscript {
    repositories {
        maven {
            url "http://maven.aliyun.com/nexus/content/groups/public/"
        }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.4.RELEASE")
    }
}

apply plugin: 'spring-boot'

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE"
    }
}

dependencies {
    compile(project(":service-api"))
    compile 'org.springframework.cloud:spring-cloud-starter-eureka'
    compile 'org.springframework.boot:spring-boot-starter-actuator'
    compile 'org.springframework.boot:spring-boot-starter-web'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
}

4.2 接口實現

package com.cnblogs.yjmyzz.spring.cloud.study.service.impl;

import com.cnblogs.yjmyzz.spring.cloud.study.api.UserService;
import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO;
import org.springframework.stereotype.Service;

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

    @Override
    public UserDTO findUser(Integer userId) {
        UserDTO user = new UserDTO();
        user.setUserId(userId);
        user.setUserName("菩提樹下的楊過");
        return user;
    }
}

這里只是隨便示意一下,直接返回一個固定的UserDTO實例。

4.3 controller

package com.cnblogs.yjmyzz.spring.cloud.study.controller;

import com.cnblogs.yjmyzz.spring.cloud.study.api.UserService;
import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{id}")
    public UserDTO findUser(@PathVariable Integer id) {
        return userService.findUser(id);
    }
}

這里用了一個新的注解GetMapping,相當于之前SpringMVC中@RequestMapping(method = RequestMethod.GET),更簡潔而已。

到目前為止,都跟常規的SpringMVC無異。

4.4 main入口

package com.cnblogs.yjmyzz.spring.cloud.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * Created by yangjunming on 2017/6/17.
 */
@EnableDiscoveryClient
@SpringBootApplication
public class ServiceProvider {

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

依舊還是@EnableDiscoveryClient挑大梁,表明這是一個eureka的客戶端程序(即:能向eureka server注冊)  

4.5 配置

server:
  port: 8001

spring:
  application:
    name: "service-provider-demo"
eureka:
  instance:
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka/  

應該不難理解,最后那幾行,表示用自己IP地址向 http://localhost:8000/eureka/注冊

4.6 啟動

啟動成功后,再看eureka 剛才的頁面,會發現已經注冊進來了。

注:大家可以把service-provider多啟動幾個實例(端口錯開,不要沖突即可),然后再觀察下這個界面,可以看到注冊了多個provider實例

 

五、service-consumer

5.1 依賴項

buildscript {
    repositories {
        maven {
            url "http://maven.aliyun.com/nexus/content/groups/public/"
        }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.4.RELEASE")
    }
}

apply plugin: 'spring-boot'

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE"
    }
}

dependencies {
    compile(project(":service-api"))
    compile 'org.springframework.cloud:spring-cloud-starter-eureka'
    compile 'org.springframework.boot:spring-boot-starter-actuator'
    compile 'org.springframework.cloud:spring-cloud-starter-ribbon'
    compile 'org.springframework.boot:spring-boot-starter-web'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
}

5.2 建一個調用的Controller

package com.cnblogs.yjmyzz.spring.cloud.study.service.controller;

import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * Created by yangjunming on 2017/6/17.
 */
@RestController
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/order/{userId}/{orderNo}")
    public String findOrder(@PathVariable Integer userId, @PathVariable String orderNo) {
        UserDTO user = restTemplate.getForEntity("http://SERVICE-PROVIDER-DEMO/user/" + userId, UserDTO.class).getBody();
        if (user != null) {
            return user.getUserName() + " 的訂單" + orderNo + " 找到啦!";
        }

        return "用戶不存在!";
    }

    @GetMapping("/user-instance")
    public List<ServiceInstance> showInfo() {
        return this.discoveryClient.getInstances("SERVICE-PROVIDER-DEMO");
    }

    @GetMapping("/log-instance")
    public ServiceInstance chooseInstance() {
        return this.loadBalancerClient.choose("SERVICE-PROVIDER-DEMO");
    }


}

這里暴露了3個url,一個個來看:  
a. /order/{userId}/{orderNo} 這個用來示例如何調用service-provider中的方法,注意這里我們并沒有用http://localhost:8001/user/1 來調用,而通過http://service-provider-demo/user/ 指定service-provider的application name,讓系統從注冊中心去發現服務。

b. /user-instance , /log-instance 這二個url 用來輔助輸出從注冊中心發現的服務實例相關的信息,并非必須。

這里面還有二個注入的實例:restTemplate 、loadBalancerClient ,分別用來發起rest的http請求,以及使用負載均衡從可用的服務列表中,挑出一個可用實例。

5.3 main入口

package com.cnblogs.yjmyzz.spring.cloud.study.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
public class ServiceConsumer {

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

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

依然靠二個關鍵的注解:@EnableDiscoveryClient、@LoadBalanced,特別是@LoadBalanced,經過這個修飾的restTemplate,就不是普通的restTemplate了,而是具備負載均衡能力的restTemplate,即每次都會用負載均衡算法,從可用服務列表中,挑一個進行調用。

5.3 啟動

可以從eukera中看到,service-provider與service-consumer都注冊進來了。

調用一下試試:http://localhost:8002/order/1/1000,成功的話會看到下面的輸出

注:此時可以把注冊中心eureka server停掉,然后再調用下http://localhost:8002/order/1/1000,會發現仍然可以正常調用,說明注冊中心的服務列表,在本機是有緩存的,這跟dubbo/dubbox類似。

 

另外還可以驗證下負載均衡,方法如下:

先把service-provider啟2個,開二個終端窗口:

java -jar xxx.jar --server.port=9001

java -jar xxx.jar --server.port=9002

這樣就能跑二個應用起來,然后看注冊中心

然后再調用下consumer的log-instance

可以看到,這次選擇的是9002端口應對的實例,然后再刷新一下:

這回選擇的是另一個端口9001的實例,說明負載均衡確實起作用了。

 

至此,一個最基本的SOA框架雛形搭建起來了,當然還有很多地方需要完善,比如:注冊中心如何做到HA,服務融斷如何處理,注冊中心如何安全認證(防止其它服務亂注冊)等等,后面再講。

附:文中示例源碼 https://github.com/yjmyzz/spring-cloud-demo


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()