在寫代碼以前先了解幾個概念。java
單系統架構mysql
須要JAVA Spring Cloud大型企業分佈式微服務雲構建的B2B2C電子商務平臺源碼 一零三八七七四六二六nginx
將業務邏輯層、數據庫訪問層、控制層放入在一個項目中。 優勢:適合於我的或者小團隊開發,不適合大團隊開發。程序員
分佈式項目架構web
根據業務需求進行拆分紅N個子系統,多個子系統相互協做才能完成業務流程子系統之間通信使用RPC遠程通信技術。 優勢:spring
1.把模塊拆分,使用接口通訊,下降模塊之間的耦合度。sql
2.把項目拆分紅若干個子項目,不一樣的團隊負責不一樣的子項目。數據庫
3.增長功能時只須要再增長一個子項目,調用其它系統的接口就能夠。apache
4.能夠靈活的進行分佈式部署。api
有優勢就有缺點,缺點以下:
1.系統之間交互須要使用遠程通訊,接口開發增長工做量。
2.各個模塊有一些通用的業務邏輯沒法共用。
爲了解決上面分佈式架構的缺點,咱們引入了soa架構,SOA:Service Oriented Architecture面向服務的架構。也就是把工程拆分紅服務層、表現層兩個工程。服務層中包含業務邏輯,只須要對外提供服務便可。表現層只須要處理和頁面的交互,業務邏輯都是調用服務層的服務來實現。
什麼是項目集羣
多臺服務器部署相同應用構成一個集羣
做用:經過負載均衡設備共同對外提供服務
RPC遠程調用
RPC 的全稱是 Remote Procedure Call 是一種進程間通訊方式。
它容許程序調用另外一個地址空間(一般是共享網絡的另外一臺機器上)的過程或函數,而不用程序員顯式編碼這個遠程調用的細節。即不管是調用本地接口/服務的仍是遠程的接口/服務,本質上編寫的調用代碼基本相同。
好比兩臺服務器A,B,一個應用部署在A服務器上,想要調用B服務器上應用提供的函數或者方法,因爲不在一個內存空間,不能直接調用,這時候須要經過就能夠應用RPC框架的實現來解決
restful、soap、rpc
(1)restful是一種架構設計風格,提供了設計原則和約束條件,而不是架構。而知足這些約束條件和原則的應用程序或設 計就是 RESTful架構或服務。
(2)soap象訪問協議是一種數據交換協議規範,是一種輕量的、簡單的、基於XML的協議的規範。SOAP協議和HTTP協議 同樣,都是底層的通訊協議,只是請求包的格式不一樣而已,SOAP包是XML格式的。
soap基於xml並封裝成了符合http協議,所以,它符合任何路由器、 防火牆或代理服務器的要求。
soap可使用任何語言來完成,只要發送正確的soap請求便可,基於soap的服務能夠在任何平臺無需修改便可正常使用。
(3)RPC就是從一臺機器(客戶端)上經過參數傳遞的方式調用另外一臺機器(服務器)上的一個函數或方法(能夠統稱爲服務)並獲得返回的結果。
RPC 會隱藏底層的通信細節(不須要直接處理Socket通信或Http通信)
RPC 是一個請求響應模型。客戶端發起請求,服務器返回響應(相似於Http的工做方式)
RPC 在使用形式上像調用本地函數(或方法)同樣去調用遠程的函數(或方法)。
rpc遠程調用框架
幾種比較典型的RPC的實現和調用框架。
(1)RMI實現,利用java.rmi包實現,基於Java遠程方法協議(Java Remote Method Protocol) 和java的原生序列化。
(2)Hessian,是一個輕量級的remoting onhttp工具,使用簡單的方法提供了RMI的功能。 基於HTTP協議,採用二進制編解碼。
(3)thrift是一種可伸縮的跨語言服務的軟件框架。thrift容許你定義一個描述文件,描述數據類型和服務接口。依據該文件,編譯器方便地生成RPC客戶端和服務器通訊代碼。
(4)SpringCloud 爲開發人員提供了快速構建分佈式系統的一些工具,包括配置管理、服務發現、斷路器、路由、微代理、事件總線、全局鎖、決策競選、分佈式會話等等。
(4) Dubbo是阿里巴巴公司開源的一個高性能優秀的服務框架,使得應用可經過高性能的 RPC 實現服務的輸出和輸入功能,能夠和 Spring框架無縫集成。
面向於服務架構(SOA)
業務系統分解爲多個組件,讓每一個組件都獨立提供離散,自治,可複用的服務能力,經過服務的組合和編排來實現上層的業務流程 做用:簡化維護,下降總體風險,伸縮靈活
什麼是微服務架構
架構設計概念,各服務間隔離(分佈式也是隔離),自治(分佈式依賴總體組合)其它特性(單一職責,邊界,異步通訊,獨立部署)是分佈式概念的跟嚴格執行 SOA到微服務架構的演進過程
做用:各服務可獨立應用,組合服務也可系統應用(巨石應用[monolith]的簡化實現策略-平臺思想)
服務提供者與消費關係
服務提供者:提供服務被人調用
消費者:調用被人服務
服務的註冊與發現(Eureka )
在這裏,咱們須要用的的組件上Spring Cloud Netflix的Eureka ,eureka是一個服務註冊和發現模塊。
什麼是Eureka
官方的介紹在這裏Eureka wiki。Eureka是Netflix開源的一個RESTful服務,主要用於服務的註冊發現。Eureka由兩個組件組成:Eureka服務器和Eureka客戶端。Eureka服務器用做服務註冊服務器。Eureka客戶端是一個java客戶端,用來簡化與服務器的交互、做爲輪詢負載均衡器,並提供服務的故障切換支持。Netflix在其生產環境中使用的是另外的客戶端,它提供基於流量、資源利用率以及出錯狀態的加權負載均衡。
在我看來,Eureka的吸引力來源於如下幾點:
開源:你們能夠對實現一探究竟,甚至修改源碼。
可靠:通過Netflix多年的生產環境考驗,使用應該比較靠譜省心
功能齊全:不但提供了完整的註冊發現服務,還有Ribbon等能夠配合使用的服務。
基於Java:對於Java程序員來講,使用起來,內心比較有底。
Spring Cloud,與Eureka進行了很好的集成,使用起來很是方便。
代碼實現
Eureka服務端,代碼結構以下:
EurekaApplication.javapackage com.example.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //這個註釋必定要加 eureka服務端
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
複製代碼
application.yml
server:
port: 8888
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
#eureka 訪問地址爲 http://localhost:8888
複製代碼
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>service-member</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>service-member</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製代碼
啓動 EurekaApplication.java
訪問http://localhost:8888,如圖:
因爲沒有如今尚未服務註冊進來,因此實例爲空。下面咱們寫個服務註冊到eureka上面:
代碼結構以下圖:
MemberController.java
package com.example.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class MemberController {
@Value("${server.port}")
private String port; //port 參數用來測試 當這個服務啓動多個的時候 客戶端訪問的時候採用輪詢的方式
@RequestMapping("/getUser")
public List getUser(){
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add(port);
return list;
}
}
複製代碼
ServiceMemberApplication.java
package com.example.servicemember;
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
@EnableEurekaClient //eureka 客戶端
@ComponentScan("com.example.controller")//掃描包
public class ServiceMemberApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceMemberApplication.class, args);
}
}
複製代碼
application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka/
#將服務註冊到上面的地址
server:
port: 8762
spring:
application:
name: service-member
複製代碼
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>service-member</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>service-member</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.M9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<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>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
複製代碼
啓動ServiceMemberApplication.java ,啓動完畢,這時候這個服務就被註冊到eureka中了。打開http://localhost:8888/,如圖:
如圖,服務被註冊進來了。接下來,寫一個調用SERVICE-MEMBER服務的服務,即消費者服務。
項目結構以下圖:
OrderController.javapackage com.example.controller;
import com.example.service.OrderService;
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 OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/getUser")
public List<String> getUser(){
return orderService.getMember();
}
}
複製代碼
OrderService.java
package com.example.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public List<String> getMember(){
return restTemplate.getForObject("http://service-member/getUser",List.class);
}
}
複製代碼
ServiceOrderApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableEurekaClient
@SpringBootApplication
public class ServiceOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOrderApplication.class, args);
}
@Bean
@LoadBalanced //ribbon實現負載均衡,須要加入ribbon依賴
RestTemplate restTemplate() {
return new RestTemplate();
}
}
複製代碼
application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka/
#將該服務註冊到上面地址的eureka中
server:
port: 8764
tomcat:
max-threads: 50
spring:
application:
name: service-order
複製代碼
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製代碼
好了,啓動ServiceOrderApplication ,再打開http://localhost:8888/ 會發現又多了一個服務,以下圖:
咱們訪問getUser這個接口,獲得結果:如圖:這樣咱們一個簡單的微服務就寫完了。
咱們能夠將服務提供者service-member的端口改掉,如今是8762,咱們將端口改成8763,再啓動一下service-member,以前啓動的8762不要停,如今至關於service-member這個服務有兩個。再刷新一下http://localhost:8888/ 這個地址,發現service-member變成了兩個。以下圖:
這是咱們重複的去訪問getUser這個接口,發現返回的port這個參數在8762和8763之間來回切換,這個就說明是對service-member服務的輪詢訪問。zuul攔截
Zuul的主要功能是路由轉發和過濾器。路由功能是微服務的一部分,好比/api/user轉發到到user服務,/api/shop轉發到到shop服務。zuul默認和Ribbon結合實現了負載均衡的功能, 相似於nginx轉發。
新建一個工程用來實現zuul轉發功能。
項目結構以下圖:
依賴:pom.xml<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>service-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>service-zuul</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RC1</spring-cloud.version>
</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-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<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>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
複製代碼
ServiceZuulApplication.java
package com.example.servicezuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy //必需要加這個註解
@EnableEurekaClient
@SpringBootApplication
public class ServiceZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceZuulApplication.class, args);
}
}
複製代碼
application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka/
server:
port: 8769
spring:
application:
name: service-zuul
zuul:
routes:
api-a:
path: /api-member/**
#path自定義便可
service-id: service-member
#service-id是對應得服務工程名,這裏表示訪問/api-member/** 會轉發到service-member這個服務
api-b:
path: /api-order/**
service-id: service-order
複製代碼
以上,zuul轉發就配置好了,當訪問http://localhost:8769/api-member/getUser 是就會轉發到service-member這個服務的getUser接口。
ZuulFilter攔截器
對全部請求進行攔截過濾,新建攔截器,MyFilter.java
package com.example.servicezuul;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class MyFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(MyFilter.class);
/**
* filterType:返回一個字符串表明過濾器的類型,在zuul中定義了四種不一樣生命週期的過濾器類型,具體以下:
* pre:能夠在請求被路由以前調用
* route:在路由請求時候被調用
* post:在route和error過濾器以後被調用
* error:處理請求時發生錯誤時被調用
* @return
*/
@Override
public String filterType() {
return "pre";// 前置過濾器
}
/**
* filterOrder:經過int值來定義過濾器的執行順序
* @return
*/
@Override
public int filterOrder() {
return 0;// 優先級爲0,數字越大,優先級越低
}
/**
* shouldFilter:返回一個boolean類型來判斷該過濾器是否要執行,因此經過此函數可實現過濾器的開關。在上例中,咱們直接返回true,因此該過濾器老是生效
* @return
*/
@Override
public boolean shouldFilter() {
return true;// 是否執行該過濾器,此處爲true,說明須要過濾
}
/**
* run:過濾器的具體邏輯。須要注意,這裏咱們經過ctx.setSendZuulResponse(false)令zuul過濾該請求,不對其進行路由,而後經過ctx.setResponseStatusCode(401)設置了其返回的錯誤碼
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
String refer=request.getHeader("refer");
Object accessToken = request.getParameter("token");
if (accessToken != null) {
return null;
}
logger.warn("token is empty");
ctx.setSendZuulResponse(false);//過濾該請求,不對其進行路由
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is empty");
} catch (Exception e) {
}
return null;
}
public static void main(String[] args) {
logger.info(String.format("%s >>> %s", "jiangjz", "www.baidu.con"));
}
}
複製代碼