Spring Cloud
Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。 Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。
以下所有的Spring Cloud文章Demo都基于父工程parent实现 新建一个基础Maven工程pom.xml文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <modules> <!-- Eureka注册中心--> <module>eureka</module> <!-- 配置中心--> <module>config</module> <!-- 智能网关路由--> <module>zuul</module> <!-- Zipkin服务追踪--> <module>zipkin</module> <!--服务生产者--> <module>service-producer</module> <!-- 服务消费者--> <module>service-consumer</module> <!-- Feign服务消费者--> <module>service-consumer-feign</module> <!-- 带有负载均衡的消费者--> <module>service-consumer-ribbon</module> <!-- 带有熔断机制的消费者--> <module>service-consumer-ribbon-hystrix</module> </modules> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Edgware.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> <build> <plugins> <!-- compiler --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <!-- resources --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> </plugins> </build>
这里使用的org.springframework.boot版本为1.5.2.RELEASE 依赖的org.springframework.cloud版本为Edgware.SR5
为了配合后续数据库使用,请先执行sql目录下的initSQL.sql文件:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 CREATE SCHEMA spring_cloud_config; CREATE SCHEMA spring_cloud_demo; CREATE SCHEMA spring_cloud_zipkin; USE spring_cloud_demo; CREATE TABLE user ( id INT AUTO_INCREMENT COMMENT '主键' PRIMARY KEY, name VARCHAR(64) NOT NULL COMMENT '姓名', birthday DATE COMMENT '生日', address VARCHAR(256) COMMENT '地址' ) CHARSET = utf8; INSERT INTO user ( name, birthday, address) VALUES('test-admin', '1994-12-21', '测试地址');
服务注册中心(Spring Cloud Eureka)
Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。而Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。
创建一个”服务注册中心”工程
在父工程中新建一个基础Spring的Maven Moudle工程命名为eureka
pom.xml文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <parent> <groupId>com.wkedong.springcloud</groupId> <artifactId>parent</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath/><!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories>
申明一个eureka服务很简单,通过@EnableEurekaServer注解启动一个应用作为服务注册中心,提供给其他应用进行注册访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 /** * @author wkedong */ @EnableEurekaServer @SpringBootApplication public class EurekaApplication { public static void main(String[] args) { new SpringApplicationBuilder(EurekaApplication.class).web(true).run(args); } }
@EnableEurekaServer:表示该应用注册为eureka服务注册中心,提供给其他应用使用@SpringBootApplication:spring boot提供了一个统一的注解@SpringBootApplication,作为应用标识 @SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。
@Configuration:提到@Configuration就要提到他的搭档@Bean。使用这两个注解就可以创建一个简单的spring配置类,可以用来替代相应的xml配置文件。 @Configuration的注解类标识这个类可以使用Spring IoC容器作为bean定义的来源。@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册为在Spring应用程序上下文中的bean。
@EnableAutoConfiguration:能够自动配置spring的上下文,试图猜测和配置你想要的bean类,通常会自动根据你的类路径和你的bean定义自动配置。
@ComponentScan:会自动扫描指定包下的全部标有@Component的类,并注册成bean,当然包括@Component下的子注解@Service,@Repository,@Controller。
启动一个注册中心所有需要一些基础的配置,这里使用yml格式作为配置文件
application.yml如下:
1 2 3 4 5 6 7 8 9 10 11 12 server: port: 6060 #服务端口号为 6060 eureka: instance: hostname: localhost #主机名 client: healthcheck: enabled: true #健康检查开启 registerWithEureka: false #禁止注册中心注册自己 fetchRegistry: false #禁止检索服务 serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #注册中心地址
此时我们启动应用并访问 http://localhost:6060/ 将会看到下面的页面,由于我们没有注册其他的服务,所以没有发现任何服务。
服务提供方(service-producer)
有了服务注册中心,下面来新建一个服务的提供方即service-producer,并向eureka中注册自己
在父工程中新建一个基础Spring的Maven Moudle工程命名为service-producer
pom.xml如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <parent> <groupId>com.wkedong.springcloud</groupId> <artifactId>parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <properties> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</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-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sleuth-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- fastjson依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.39</version> </dependency> <!-- 与数据库操作相关的依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>4.6</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
由于demo中后续使用到了MyBatis和json的相关操作,所以在service-producer中我们事先依赖了阿里的fastjson和mybatis相应的jar包。
然后我们对工程做一些配置工作,bootstrap.yml如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 eureka: client: healthcheck: enabled: true #健康检查开启 serviceUrl: defaultZone: http://localhost:6060/eureka/ #注册中心服务地址 spring: ## 从配置中心读取文件 cloud: config: uri: http://localhost:6010/ label: develop profile: dev name: service-producer application: name: service-producer #当前服务ID zipkin: base-url: http://localhost:6040 #zipkin服务地址 sleuth: enabled: true #服务追踪开启 sampler: percentage: 1 #zipkin收集率 datasource: #数据库信息 url: jdbc:mysql://127.0.0.1:3306/spring_cloud_demo?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver management: security: enabled: false #mybatis config mybatis: type-aliases-package: com.wkedong.springcloud.serviceproducer.entity #entity实体对象所在的路径位置
通过spring:application:name属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。
eureka:client:serviceUrl:defaultZone属性对应服务注册中心的配置内容,指定服务注册中心的位置。 多端口设置application-peer1.yml:1 2 3 4 5 spring: profiles: active: peer1 server: port: 6070
application-peer2.yml:1 2 3 4 5 spring: profiles: active: peer2 server: port: 6080
application-peer3.yml:1 2 3 4 5 spring: profiles: active: peer3 server: port: 6090
spring:profiles:active属性设置使用配置文件。 server:port属性设置不同的端口。
接下来我们来实现服务的提供,应用启动类Application,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 /** * @author wkedong */ @SpringBootApplication @EnableDiscoveryClient @MapperScan("com.wkedong.springcloud.serviceproducer.mapper") public class ServiceProducerApplication { public static void main(String[] args) { SpringApplication.run(ServiceProducerApplication.class, args); } }
@MapperScan:扫描mybatis所配置的mapper路径
新建一个controller类来实现/testGet,/testPost,/testFile,/testConfig,/testRibbon,/testFeign,/testHystrix接口ProducerController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 /** * @author wkedong * <p> * 2019/1/14 */ @RestController public class ProducerController { private final Logger logger = Logger.getLogger(getClass()); @Autowired ProducerService producerService; @GetMapping(value = "/testGet") public String testGet() { logger.info("===<call testGet>==="); return producerService.testGet(); } @PostMapping(value = "/testPost") public String testPost(@RequestBody JSONObject jsonRequest) { logger.info("===<call testPost>==="); return producerService.testPost(jsonRequest); } @PostMapping(value = "/testFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) { logger.info("===<call testFile>==="); return file.getOriginalFilename(); } @GetMapping(value = "/testConfig") public String testConfig() { logger.info("===<call testConfig>==="); return producerService.testConfig(); } @GetMapping(value = "/testRibbon") public String testRibbon() { logger.info("===<call testRibbon>==="); return producerService.testRibbon(); } @GetMapping(value = "/testFeign") public String testFeign() { logger.info("===<call testFeign>==="); return producerService.testFeign(); } @GetMapping(value = "/testHystrix") public String testHystrix() { logger.info("===<call testHystrix>==="); return producerService.testHystrix(); } }
controller调用的Service,新建了ProducerService接口类及ProducerServiceImpl实现类,分别如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** * @author wkedong * <p> * 2019/1/15 */ public interface ProducerService { String testGet(); String testPost(JSONObject jsonRequest); String testConfig(); String testRibbon(); String testFeign(); String testHystrix(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 /** * @author wkedong * <p> * 2019/1/14 */ @Service public class ProducerServiceImpl implements ProducerService { private Logger logger = LoggerFactory.getLogger(ProducerServiceImpl.class); @Autowired private UserMapper userMapper; @Autowired private EurekaInstanceConfig eurekaInstanceConfig; @Value("${server.port}") private int serverPort = 0; @Value("${name}") private String configName = ""; private String returnMessage = ""; @Override public String testGet() { this.logger.info("/testGet, instanceId:{}, host:{}", eurekaInstanceConfig.getInstanceId(), eurekaInstanceConfig.getHostName(false)); setReturnMessage(" Get info is testGet Success"); return returnMessage; } @Override public String testPost(@RequestBody JSONObject jsonRequest) { this.logger.info("/testPost, instanceId:{}, host:{}", eurekaInstanceConfig.getInstanceId(), eurekaInstanceConfig.getHostName(false)); setReturnMessage(" PostParam is " + jsonRequest.toString()); return returnMessage; } @Override public String testConfig() { this.logger.info("/testConfig, instanceId:{}, host:{}", eurekaInstanceConfig.getInstanceId(), eurekaInstanceConfig.getHostName(false)); setReturnMessage(" ConfigName is " + configName); return returnMessage; } @Override public String testRibbon() { this.logger.info("/testRibbon, instanceId:{}, host:{}", eurekaInstanceConfig.getInstanceId(), eurekaInstanceConfig.getHostName(false)); setReturnMessage(" This is a testRibbon result"); return returnMessage; } @Override public String testFeign() { this.logger.info("/testFeign, instanceId:{}, host:{}", eurekaInstanceConfig.getInstanceId(), eurekaInstanceConfig.getHostName(false)); setReturnMessage(" This is a testFeign result"); return returnMessage; } @Override public String testHystrix() { this.logger.info("/testHystrix, instanceId:{}, host:{}", eurekaInstanceConfig.getInstanceId(), eurekaInstanceConfig.getHostName(false)); try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } setReturnMessage(" This is a testHystrix result"); return returnMessage; } private void setReturnMessage(String info) { returnMessage = "Hello, Spring Cloud! My port is " + String.valueOf(serverPort) + info; } }
接口实现类里实现了后续各消费者所调用的各个接口功能,所以事先放出来
数据库操作的mapper文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /** * @author wkedong * <p> * 2019/1/15 */ public interface UserMapper { @Select("SELECT * FROM user") List<UserEntity> getAll(); @Select("SELECT * FROM user WHERE id = #{id}") UserEntity getOne(int id); @Insert("INSERT INTO user(name,birthday,address) VALUES(#{name}, #{birthday}, #{address})") void insert(UserEntity user); @Update("UPDATE user SET name=#{name},birthday=#{birthday},address=#{address} WHERE id =#{id}") void update(UserEntity user); @Delete("DELETE FROM user WHERE id =#{id}") void delete(int id); }
分别实现了增删查改基础的实现方法
对应的实体类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 /** * @author wkedong * 测试实体 * 2019/1/15 */ @RefreshScope @Component//加入到Spring容器 public class UserEntity { private String id; private String name; @DateTimeFormat(style = "yyyy-MM-dd") private String birthday; private String address; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getBirthday() { return birthday; } public void setBirthday(String birthday) { this.birthday = birthday; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return super.toString(); }
启动该工程后,再次访问:http://localhost:6060/ 可以如下图内容,我们定义的服务被成功注册了。
访问 http://localhost:6070/testGet 得到以下返回值:
1 Hello, Spring Cloud! My port is 6070 Get info By Mybatis is {"address":"江苏南京","birthday":"1994-12-21","name":"wkedong"}
本项目内容为Spring Cloud程序的样例:
样例列表:(SpringCloud版本基于Edgware.SR5)