什麼是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();
}
}
然後依次啓動各個項目,訪問:
就能看到結果啦。