什么是springcloud?
这是spring官网对springcloud的介绍,大致意思就是:让分布式系统简单化。springcloud是建立在springboot之上的,也就是说他是需要依赖springboot的,因此学习springcloud首先就要了解springboot。在上一篇文章中有介绍到springboot。
这张图说明了Spring Cloud是实施微服务的一系列套件,包括:服务注册与发现、断路器、服务状态监控、配置管理、智能路由、一次性令牌、全局锁、分布式会话管理、集群状态管理等。
下面就用IDEA搭建springcloud项目,来看看springcloud是如何集成这些组件。
搭建一个小型的springcloud项目
1.首先使用IDEA创建一个maven父工程,在pom.xml里引入需要的jar包依赖,dependencyManagement用来管理jar包版本。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
2.然后创建子工程,首先创建一个provider工程,作为服务提供者。
右键选中父工程后new一个module,选择maven就可以新建一个子工程了。
每创建一个新工程首先在pom.xml里添加一些基础的依赖。
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId><!--简化javabean开发的jar,用注解的方式快速写setter,getter,-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</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-web</artifactId>
</dependency>
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId><!--eureka组件,后面没带server,即为客户端client-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId><!--因该是将javabean变为配置文件的jar包-->
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
接着是编写application.yml文件
server:
port: 8001
spring:
application:
name: userapi #服务提供者名字,最终会显示在eureka注册中心的名字上,消费者通过这个名字找到服务并调用
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/testspringboot
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource #阿里巴巴的连接池
dbcp2:
max-total: 5 #最大维持连接数
min-idle: 5 #最小维持连接数
initial-size: 5 #初始化大小
max-wait-millis: 200 #等待连接获取的最大等待时间
jpa:
hibernate:
ddl-auto: update #若数据库中有表,则不创建。
show-sql: true
这里的持久层框架使用的是SpringJPA,比较方便。
下面看下provide工程的目录结构:
这里provider8001代表是运行在8001端口上的服务提供者,后面要实现负载均衡还会建立多个服务提供者。
注意每个子工程都是一个独立的springboot项目,因此每次新建完一个子module都要编写springboot的启动类。且启动类的位置要包含其他所有的包,这样这个启动类在能在启动时加载其他的需要放入spring容器中的bean,也就是对其他的包进行管理。
启动类的通用写法:
package com.tellhow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
public class ProviderApplicationRun8001 {
public static void main(String args[]){
SpringApplication.run(ProviderApplicationRun8001.class,args);
}
}
controller层代码:
package com.tellhow.controller;
import com.tellhow.repository.UserRepository;
import com.tellhow.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserRepository userRepository;
@RequestMapping("test")
public String testProvider(){
return "Hello world";
}
@RequestMapping(value = "/users",method = RequestMethod.GET)
public List<User> getUsers(){
return userRepository.findAll();
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public boolean addUser(User user){
userRepository.save(user);
return true;
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public boolean updateUser(User user){
userRepository.save(user);//如果主键存在则修改。
return true;
}
}
主要就是查询数据库里的user列表,用来展示分布式调用的。持久层实现参见上一篇文章springboot整合jpa.
服务层就大致写完了,写完之后需要验证一下是否正常可用,保证下写消费者调用时能正常运行。
访问http://localhost:8001/user/users 运行结果:
代表服务消费者可以正常启动,下面就可以来写消费者了。
1.与建立服务提供者同样的操作,右键父工程,新建module,添加pom.xml依赖,编写applicaton.yml文件。编写启动类
applicaton.yml文件里只需要配置一个端口即可
server:
port: 80
启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.tellhow"})//指定启动时扫描哪些需要管理的包,若将启动类放在需要管理的包的上一级,则不需要此注解
public class ConsumerApplicationRun {
public static void main(String args[]){
SpringApplication.run(ConsumerApplicationRun.class,args);
}
}
conctroller层:通过RestTemplate调用服务提供者。由此可以看出,springcloud的分布式调用,是用rest来进行调用,不同于dubbo的RPC调用。
使用RestTemplate之前需要将其注入到spring容器中。
package com.tellhow.cfgBean;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RetryRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
controller:
package com.tellhow.controller;
import com.tellhow.beans.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class UserController {
@Autowired
public RestTemplate restTemplate;
public static final String PREX="http://localhost:8001";
//public static final String PREX="http://USERAPI";//通过eureka上注册的服务名来调用
@RequestMapping(value = "/consumer/user",method = RequestMethod.GET)
public List<User> getUsers(){
return restTemplate.getForObject(PREX+"/user/users",List.class);
}
}
然后先启动服务提供者,再启动服务消者,查看结果,因为消费者工程的端口后是80,因此可以访问地址可以省略端口
访问:http://localhost/consumer/user
可以看到,成功调用。
加入eureka注册中心
eureka是用来做服务注册与发现的,也就是服务提供者将服务注册到eureka注册中心,然后消费者在注册中心对服务进行调用消费。例如:想要开淘宝店需要先到淘宝平台注册店铺,然后买家才可以在淘宝上找到该店铺进行消费购买。
当然eureka还有很多功能:通过心跳检测、健康检查、客户端缓存等机制,保证了系统具有高可用和灵活性。
接下来建立一个eureka的子工程。建立module的过程不做赘述了。
pom.xml依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId><!--eureka 服务-->
</dependency>
</dependencies>
application.yml
server:
port: 7000
eureka:
instance:
hostname: eureka7000.com #eureka主机名
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false # 表示自己端就是注册中心,职责就是维护服务实例,不需要检索服务。
service-url:
defaultZone: http://eureka7001.com:7000/eureka/
启动类:
package com.tellhow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //Eureka服务类,接受其他服务注册进来。
public class EurekaApplicationRun {
public static void main(String args[]){
SpringApplication.run(EurekaApplicationRun.class,args);
}
}
如此,eureka注册中心就建立完成了,之后需要在privoder工程里添加向注册中心注册的相关东西。pom.xml添加eureka依赖
首先provider工程启动类上添加注解:@EnableEurekaClient //启动eureka客户端
然后application.yml改为如下:
server:
port: 8001
spring:
application:
name: userapi #服务提供者名字,最终会显示在eureka注册中心的名字上,消费者通过这个名字找到服务并调用
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/testspringboot
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource #阿里巴巴的连接池
dbcp2:
max-total: 5 #最大维持连接数
min-idle: 5 #最小维持连接数
initial-size: 5 #初始化大小
max-wait-millis: 200 #等待连接获取的最大等待时间
jpa:
hibernate:
ddl-auto: update #若数据库中有表,则不创建。
show-sql: true
eureka:
client:
service-url:
defaultZone: http://eureka7000.com:7000/eureka #后面必须加个eureka 才能注册上去。
instance:
instance-id: userAPI-8001 #注册在eureka上的服务的实例的id
prefer-ip-address: true #显示IP地址,方便查找
info:
app.name: TestMaven
company.name: www.tellhow.com
.artifactId: $project.artifactId$
buibuildld.version: $project.version$
消费者工程里也做相似的改动:启动类添加注解:
@EnableEurekaClient //eureka是服务注册中心,所以对用向服务中心注册服务的,和向服务中心消费服务的,都是eureka客户端
application.yml
server:
port: 80
eureka:
client:
register-with-eureka: false #消费者 不向服务端注册
service-url:
defaultZone: http://eureka7000.com:7000/eureka/
修改完成后先启动注册中心工程,再启动服务提供者工程,接着是消费者。
启动完成后访问:http://127.0.0.1:7000如果能出现一下画面则代表eureak注册中心工程建立成功。
eureka服务中心能正常启动后,将consumer项目controller里的访问连接前缀由http://localhost:8001换为:http://USERAPI,也就是通过服务端注册在服务中心的id来找到要调用的服务。
用到服务中心之后,有一个问题,就是当服务中心挂掉之后,那么消费者便无法找到要调用的服务了,系统就挂了。为了解决此问题,就可以用到eureka的集群,也就是高可用。
eureka集群非常简单,只需要按照之前建立eureka项目的步骤再多建立几个eureka注册中心就ok了,我这里再建立个,并演示集群eureka的application.yml文件该怎么写
#第一个eureka的yml文件:
server:
port: 7000
eureka:
instance:
hostname: eureka7000.com #eureka主机名
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false # 表示自己端就是注册中心,职责就是维护服务实例,不需要检索服务。
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#第二个eureka的yml文件:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://eureka7000.com:7000/eureka/,http://eureka7002.com:7002/eureka/
#第三个eureka的yml文件:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://eureka7000.com/eureka/,http://eureka7001.com/eureka/
然后将消费端和生产者的yml文件里的defaultZone都加上上面的访问地址,便可以将服务注册进三个注册中心了,这样某一个注册中心挂掉的话对整个系统没有任何影响。
现在注册中心已经高可用了,但又有一个新的问题,如果服务提供者挂了,那么系统也就挂了。所以也需要实现服务提供者的高可用,实现步骤与实现eureka高可用的步骤一样,建立多个服务提供者,注册进三个注册中心即可。步骤就省略了。
那么有了三个服务提供者,每次消费者在eureka上调用服务时,由哪个服务提供者来提供服务呢,怎样才能做到高效呢。也就是怎样做到负载均衡来提高效率呢。
springcloud提供了两种负载均衡的组件:Ribbon和Feign。
一般我们都使用Feign来做负载均衡,因为Feign本身就包含了Ribbon,而且Feign是使用面向接口编程的方式,更符合大众的方式。
使用一个新组件 第一步pom.xml添加依赖,第二步修改application.yml文件 第三部相关编码。
添加依赖:
<!-- feign相关 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
yml文件不用需改,
在消费者启动类里加入Feign客户端注解。
@SpringBootApplication @EnableEurekaClient @EnableFeignClients public class ConsumerFeignApplication { public static void main(String[] args) { SpringApplication.run(ConsumerFeignApplication.class,args); } }
编写service接口
package com.tellhow.service;
import com.tellhow.beans.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
@FeignClient(value = "USERAPI")
public interface UserService {
/**
* 此接口还是相当于rest请求转发,会去找eureak里USERAPI这个服务里,根据RequestMapping的路径找对应的方法,其实就是找provider里的方法。
* @return
*/
@RequestMapping(value = "user/users",method = RequestMethod.GET)
public List<User> getUsers();
}
controller层:
package com.tellhow.controller;
import com.tellhow.beans.User;
import com.tellhow.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/feign/users")
public List<User> getUsers(){
return userService.getUsers();
}
}
然后依次启动各个项目,访问:
就能看到结果啦。