spring cloud 学习(1) - 基本的SOA示例

有过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

时间: 2024-08-26 18:50:58

spring cloud 学习(1) - 基本的SOA示例的相关文章

spring cloud 学习(4) - hystrix 服务熔断处理

hystrix 是一个专用于服务熔断处理的开源项目,当依赖的服务方出现故障不可用时,hystrix有一个所谓的断路器,一但打开,就会直接拦截掉对故障服务的调用,从而防止故障进一步扩大(类似中电路中的跳闸,保护家用电器). 使用步骤:(仍然在之前的示例代码上加以改造) 一.添加hystrix依赖 compile 'org.springframework.cloud:spring-cloud-starter-hystrix' 二.在需要熔断的方法上添加注解 package com.cnblogs.y

Spring Cloud 学习——5.使用 feign 的 hystrix 支持

1.前言 hystrix 是一个微服务系统的断路器组件,上文介绍了 spring cloud 通过 netfix hystrix 提供对 hystrix 的支持.同时 spring cloud 也提供了 openfeign 的支持, 而 openfeign 本身就已经内置了 hystrix 支持.所以本文介绍一个使用 openfeign 内置 hystrix 的简单示例. 前文链接: Spring Cloud 学习——3.openfeign实现声明式服务调用 Spring Cloud 学习——4

Spring Cloud 学习——6.zuul实现路由、负载均衡、安全验证

1.前言 在一个大微服务架构的系统中,可能存在着很多服务,如果将这些服务全部对外暴露,会带来很多问题.比如安全问题,有些核心服务直接对外暴露很容易被攻击:比如身份验证问题,有些接口服务是要求登录的,如果各种服务各自对外暴露,那么这些要求登录的请求第一个触达的服务模块都要向“用户服务模块”查询鉴权结果,这样既对“用户服务模块”造成额外压力,也增加了这些其它服务模块的开发成本,所以应该考虑将身份验证的事情交到网关模块中直接完成:比如运维难度和成本问题,如果每一种服务都各自对外暴露,那么整个系统就需要

Spring Cloud学习--配置中心(Config)

Spring Cloud学习--配置中心(Config) 一 Spring Cloud Config简介 二 编写 Config Server 三 编写Config Client 四 使用refresh端点手动刷新配置 五 Spring Config Server与Eurelka配合使用 六 Config Server的高可用 一. Spring Cloud Config简介 微服务要实现集中管理微服务配置.不同环境不同配置.运行期间也可动态调整.配置修改后可以自动更新的需求,Spring Cl

Spring Cloud学习之-什么是Spring Cloud?

SpringCloud 什么是微服务? 要想学习微服务,首先需要知道什么是微服务?为什么会有微服务?相信看完架构的发展史读者就会明白 架构发展史 单体应用架构 如图所示:将所有的模块,所有内容(页面.Dao.Service.Controller)全部写入一个项目中,放在一个Tomcat容器中启动适用于小型项目 优点:开发速度快,可以利用代码生成工具快速的开发一个项目 缺点:不易扩展,代码耦合度高,且不容错(当某部分出错后整个服务就会停止运行) 垂直架构 既然原来单体架构中代码耦合度高,不利于维护

spring cloud 学习(5) - config server

分布式环境下的统一配置框架,已经有不少了,比如百度的disconf,阿里的diamand.今天来看下spring cloud对应的解决方案: 如上图,从架构上就可以看出与disconf之类的有很大不同,主要区别在于: 配置的存储方式不同 disconf是把配置信息保存在mysql.zookeeper中,而spring cloud config是将配置保存在git/svn上 (即:配置当成源代码一样管理) 配置的管理方式不同 spring cloud config没有类似disconf的统一管理界

spring cloud 学习(6) - zuul 微服务网关

微服务架构体系中,通常一个业务系统会有很多的微服务,比如:OrderService.ProductService.UserService...,为了让调用更简单,一般会在这些服务前端再封装一层,类似下面这样: 前面这一层俗称为“网关层”,其存在意义在于,将"1对N"问题 转换成了"1对1”问题,同时在请求到达真正的微服务之前,可以做一些预处理,比如:来源合法性检测,权限校验,反爬虫之类... 传统方式下,最土的办法,网关层可以人肉封装,类似以下示例代码: LoginResul

Spring Cloud 学习总结001-服务治理-Eureka

学习参考:http://blog.didispace.com/Spring-Cloud%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B/ spring cloud由[服务注册中心,服务提供者,服务消费者]组成: 服务注册中心存储各个服务的信息,将一个原数据存储在一个[双层结构的map中], 第一城的key是服务名,第二层的key是服务的实例名, { 服务1:{ 实例1:实例1 实例2:实例2 } } 自我保护 Eureka server在运行期间会统计心跳失败比例在15分

Spring Cloud学习系列第五篇【API网关服务】

这篇随笔接着学习微服务中一个比较重要的组件API网关服务.当我们微服务架构完成后最终是要提供给外部访问的,于是我们需要一个统一的访问入口,能隐藏我们内部服务URL细节,这就有点像局域网里那个网关的概念了,这是API网关服务就应运而生了.API网关作用有能为实现请求路由.负载均衡.校验过滤等基础功能,还能实现请求转发的熔断机制.服务集合等高级功能.补充下通常我们对外服务统一入口可以采用F5.Nginx等方式也能实现前面的请求路由与负载均衡,但是要实现后面功能了F5.Nginx就无能为力了吧,这就是