之前的时候,这篇文章引起了很多同伴的共鸣
5W字微服务搭建手册:从原理到架构搭建全覆盖,你还学不会吗?
但是因为字数限制,当时之讲解了相应技术的原理以及初步的基础框架的搭建,今天来点硬货,剩下的框架搭建以及源码展示
来吧,让你的大脑动起来,手也要动起来,接下来可是脑力+体力的双重考验,准备好了我们开始吧
二. 搭建Eureka
1. 创建Maven项目springbootEureka
同springbootService
2. 项目结构和代码
pom.xml
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">
4.0.0
com.sun
springbootEureka
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.5.12.RELEASE
UTF-8
UTF-8
1.8
Edgware.RELEASE
org.springframework.cloud
spring-cloud-starter-eureka-server
1.3.5.RELEASE
application.properties
server.port=5000
eureka.instance.hostname=localhost
#是否向服务注册中心注册自己,默认为true
eureka.client.register-with-eureka=false
#是否检索服务
eureka.client.fetch-registry=true
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
ConsumerClientApplication.java
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
3. 启动Eureka
3.1. 运行Debug,启动ConsumerClientApplication.java
3.2. 打开http://localhost:5000,表示正常启动
4. 修改springbootService,使Eureka可以发现此服务
pom.xml中增加
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
1.4.0.RELEASE
application.properties中增加
eureka.client.service-url.defaultZone=http://localhost:5000/eureka/
ConsumerClientApplication.java中增加
@EnableDiscoveryClient
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
启动后,在Eureka中可以看到springbootService已注册
三. 搭建SpringBootConfig配置中心
我这里用的是SVN,在config目录下创建文件springbootService-release.properties。
将springbootService的application.properties的内容复制进去。
为了与原文件区分看效果,我这里将server.port设为6003(原来是6001)
spring.application.name=springbootService
server.port=6003
eureka.client.service-url.defaultZone=http://localhost:5000/eureka/
配置中心其实就是读取SVN上的文件后,发送给其他服务,让他们读取。
1. 创建Maven项目springbootConfig
同springbootService
2. 项目结构和代码
pom.xml
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">
4.0.0
com.sun
springbootConfig
1.0-SNAPSHOT
jar
org.springframework.boot
spring-boot-starter-parent
1.5.12.RELEASE
UTF-8
UTF-8
1.8
Edgware.RELEASE
org.springframework.cloud
spring-cloud-config-server
org.tmatesoft.svnkit
svnkit
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
application.yml
server:
port: 6002
spring:
application:
name: springbootConfig
profiles:
active: subversion
cloud:
config:
server:
svn:
uri: https://192.168.3.97/svn/SourceCode/SMPH/Beats/trunk/test/config
#username: *****
#password: *****
default-label:
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/
instance:
preferIpAddress: true
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
lease-expiration-duration-in-seconds: 30
lease-renewal-interval-in-seconds: 30
ConsumerClientApplication
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
3. 启动Config配置中心
3.1. Eureka中能够看到Config配置中心已注册
3.2. 打开http://localhost:6002/springbootService/release,能够看到配置文件的内容
{"name":"springbootService","profiles":["release"],"label":null,"version":"523","state":null,"propertySources":[{"name":"https://192.168.3.97/svn/SourceCode/SMPH/Beats/trunk/test/config/springbootService-release.properties","source":{"server.port":"6003","eureka.client.service-url.defaultZone":"http://localhost:5000/eureka/","spring.application.name":"springbootService"}}]}
4. 修改springbootService,使它能从配置中心读取配置文件
4.1. 在resources目录下创建bootstrap.yml,用来配置读取Config配置中心下的哪个文件
这里对应localhost:6002/springbootService-release.properties文件
spring:
application:
name : springbootService
cloud:
config:
uri : http://localhost:6002/
profile : release
4.2. 修改pom.xml,增加
org.springframework.cloud
spring-cloud-starter-config
1.4.0.RELEASE
org.springframework.boot
spring-boot-starter-actuator
4.3. 在ConsumerClientApplication.java中增加
@RefreshScope //开启配置更新功能
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
@RefreshScope //开启配置更新功能
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
4.4. 启动springbootService,在Eureka中可以看到,多了个端口是6003的服务。
4.5. 接口可以从6003访问,原来的6001不能用了,说明配置中心的文件覆盖了本地的application.properties
四. 搭建SpringBootConsumerFeign,使用Feign来调用各个微服务
1.直接从springbootService复制一份,命名为springbootConsumerFeign
别忘了把配置里面的名字都改掉
2. 项目结构和代码
2.1. 在pom中添加Feign
org.springframework.cloud
spring-cloud-starter-openfeign
1.4.0.RELEASE
org.springframework.cloud
spring-cloud-starter-hystrix
com.netflix.hystrix
hystrix-javanica
RELEASE
2.2. 修改application.properties
spring.application.name=springbootConsumerFeign
server.port=6004
feign.hystrix.enabled=true
eureka.client.service-url.defaultZone=http://localhost:5000/eureka/
2.3. 在ConsumerClientApplication.java中增加
@EnableFeignClients
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
2.4. 在sun目录下添加client目录,并新建文件ServiceFeignClient
这里是声明接口,指向springbootService的接口
package sun.client;
import common.entity.RestfulResult;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import sun.entity.ServiceInfo;
@Component
@FeignClient(value = "springbootService", fallback=ServiceFallback.class) //这里的value对应调用服务的spring.applicatoin.name
public interface ServiceFeignClient {
@RequestMapping(value = "/service/hello")
RestfulResult hello(@RequestBody ServiceInfo serviceInfo);
}
添加ServiceFallback.java,用于熔断发生时的处理。
package sun.client;
import common.entity.RestfulResult;
import org.springframework.stereotype.Component;
import sun.entity.ServiceInfo;
@Component
public class ServiceFallback implements ServiceFeignClient{
@Override
public RestfulResult hello(ServiceInfo serviceInfo) {
RestfulResult result = new RestfulResult();
result.setData("服务调用失败");
return result;
}
}
2.5. 修改ServiceController.java
/**
* Copyright © 2012-2014 JeeSite All rights reserved.
*/
package sun.controller;
import common.entity.RestfulResult;
import common.utils.CommUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sun.client.ServiceFeignClient;
import sun.entity.ServiceInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class ServiceController {
@Autowired
ServiceFeignClient serviceFeignClient;
// 调用:localhost:6004/consumerService?token=1
@RequestMapping("/consumerService")
public void consumerService(HttpServletRequest request, HttpServletResponse response,
@RequestBody ServiceInfo serviceInfo){
RestfulResult restfulResult = serviceFeignClient.hello(serviceInfo);
CommUtils.printDataJason(response, restfulResult);
}
}
3. 启动springbootConsumer并测试接口
3.1. 启动后,在Eureka中发现服务
3.2. 调用SpringBootConsumer的接口http://localhost:6004/consumerService
连续调用,会轮询Service1和Service2
关闭Service1,看看Hystrix是否起作用:
五. 搭建SpringBootConsumerRibbon,使用Ribbon+RestTemplate来调用各个微服务
1.直接从springbootService复制一份,命名为springbootConsumerRibbon
别忘了把配置里面的名字都改掉
2. 项目结构和代码
2.1. 在pom中添加Feign
org.springframework.cloud
spring-cloud-starter-openfeign
1.4.0.RELEASE
org.springframework.cloud
spring-cloud-starter-hystrix
com.netflix.hystrix
hystrix-javanica
RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
1.4.0.RELEASE
2.2. 修改application.properties
spring.application.name=springbootConsumerRibbon
server.port=6007
eureka.client.service-url.defaultZone=http://localhost:5000/eureka/
2.3. 修改ConsumerClientApplication.java
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
@EnableHystrix
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.4. 修改ServiceController.java
/**
* Copyright © 2012-2014 JeeSite All rights reserved.
*/
package sun.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import sun.entity.ServiceInfo;
@RestController
public class ServiceController {
@Autowired
RestTemplate restTemplate;
@Value("${server.port}")
String port;
// 调用:localhost:6007/consumerServiceRibbon?token=1
@RequestMapping("/consumerServiceRibbon")
@HystrixCommand(fallbackMethod="consumerServiceRibbonFallback")
public String consumerServiceRibbon(@RequestBody ServiceInfo serviceInfo){
String result = this.restTemplate.postForObject("http://springbootService/service/rest?token=1", serviceInfo, String.class);
return result;
}
public String consumerServiceRibbonFallback(@RequestBody ServiceInfo serviceInfo){
return "consumerServiceRibbon异常,端口:" + port + ",Name=" + serviceInfo.getName();
}
}
3. 启动springbootConsumer并测试接口
3.1. 启动后,在Eureka中发现服务
3.2. 调用SpringBootConsumer的接口 localhost:6007/consumerServiceRibbon?token=1
连续调用,会轮询Service1和Service2
关闭Service1,看看Hystrix是否起作用:
六. 搭建Zuul+Hystrix
Zuul对外提供统一的服务入口,主要是用作网址重定向。
还可以通过Filter实现过滤器。
配合Hystrix实现熔断器,当服务宕机时可以做异常处理。
1. 创建Maven项目springbootZuul
同springbootService
2. 项目结构和代码
pom.xml
这里我把spring boot改成了2.0版本,spring cloud改成了Finchley.SR2。
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">
4.0.0
com.sun
springbootZuul
1.0-SNAPSHOT
jar
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
UTF-8
UTF-8
1.8
Finchley.SR2
org.springframework.cloud
spring-cloud-starter-eureka
1.4.0.RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
application.yml
spring:
application:
name : springbootZuul
server:
port : 6005
eureka:
client:
service-url:
defaultZone : http://localhost:5000/eureka/
zuul:
routes:
sbService :
path : /sbService/**
serviceId : springbootService
ConsumerClientApplication.java
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@RefreshScope
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
serviceFilter(过滤器,非必须)
这里判断了请求中是否带token,如果没有,则显示"there is no request token"
package sun.filter;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
@Component
public class serviceFilter extends ZuulFilter {
private static Logger log=LoggerFactory.getLogger(serviceFilter.class);
@Override
public String filterType() {
return "pre"; // 定义filter的类型,有pre、route、post、error四种
}
@Override
public int filterOrder() {
return 0; // 定义filter的顺序,数字越小表示顺序越高,越先执行
}
@Override
public boolean shouldFilter() {
return true; // 表示是否需要执行该filter,true表示执行,false表示不执行
}
@Override
public Object run() {
// filter需要执行的具体操作
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
System.out.println(token);
if(token==null){
log.warn("there is no request token");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("there is no request token");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
log.info("ok");
return null;
}
}
ServiceFallbackProvider(熔断器,非必须)
当没有服务时,显示"Sorry, the service is unavailable now."
package sun.fallbackProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
@Component
public class ServiceFallbackProvider implements FallbackProvider {
@Override
// 指定熔断器功能应用于哪些路由的服务
public String getRoute() {
// 这里只针对"springbootService"服务进行熔断
// 如果需要针对所有服务熔断,则return "*"
return "springbootService";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("route:"+route);
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "ok";
}
@Override
public void close() {
}
@Override
// 发生熔断式,返回的信息
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("Sorry, the service is unavailable now.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
3. 启动springbootZuul并测试
3.1. 在Eureka上看到springbootZuul服务已启动
3.2. 现在可以通过统一路由访问服务了
3.3. 使用Filter,请求不带token时的效果:
3.4. 使用Filter,请求带token时的效果:
3.5. 使用Hystrix,关闭springbootService服务时的效果:
六. 自动化部署Jenkins
1. 将代码上传到Github
SVN也可以,这里我就用Github了。
先去下载Git官网下载Git-2.23.0-64-bit.exe
再去下载TortoiseGit,有了这个就不用老是打命令commit了。
1.1. 图形界面的更新代码
Git的项目初始化就不多说了,这里介绍一下用图形界面的代码更新步骤。
① Git Commit -> "master"
② Push
1.2. 我在将Git从1.X升级到2.X的过程中,遇到了
SourceTree error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
的问题,将Git版本升级后也解决不了。
后来想起来,是不是在环境变量中配置了Git1.X的路径
果然,在系统里面的Path中找到了Git的配置。
原来Git2.X以后,默认路径放到了C:Program FilesGit下,和原来1.X的路径不一样了。
所以需要把1.X的路径配置删除,否则还是会优先使用1.X的Git。
2. 搭建Jenkins环境
2.1. 去Jenkins官网下载war包
2.2. 启动Jenkins的war包
java -jar jenkins.war --httpPort=8080
启动后,访问http://localhost:8080即可。
使用的时候需要注册,这些就省略不说了。
2.3. Jenkins插件的安装
需要安装Maven,Git等插件,才能和项目关联使用
Manage Jenkins -> Manage Plugins -> Available里面找
2.4. 创建新项目
在Source Code Management里面选择Git,填入自己的仓库地址:
在Build Triggers里面,选择Poll SCM,在Schedule中填入 * * * * * :
在Build中,设置pom.xml的路径
一开始可能会报找不到pom.xml的Error,因为Jenkins需要先从Git库中同步项目,等同步完以后,Error就会自动消失。
在Post Steps中,Add post-build stemp -> Execute Windows bath command
添加的windows命令如下:
D:JenkinsTestprocess.bat 6001
D:
del D:JenkinsTestspringbootService-1.0-SNAPSHOT.jar
copy C:甥敳獲Administrator.jenkinsworkspaceSpringbootServiceargetspringbootService-1.0-SNAPSHOT.jar D:JenkinsTestspringbootService-1.0-SNAPSHOT.jar
SET BUILD_ID=donKillMe
start javaw -Dhudson.util.ProcessTree.disable=true -jar D:JenkinsTestspringbootService-1.0-SNAPSHOT.jar
效果如图:
其中,D:JenkinsTestprocess.bat的内容如下:
::demo
@echo off
::延迟环境变量扩展
setlocal enabledelayedexpansion
for /f "delims= tokens=1" %%i in ('netstat -aon ^| findstr %1') do (
set a=%%i)
::判断服务是否已经启动,如果启动则杀掉进程
if defined a (taskkill /F /pid "!a:~71,7!") else (echo Service does not exist)
::等待你按任意键结束
pause>nul
::执行时后面带上端口即可
命令说明:
① 创建D:JenkinsTest目录
② process.bat 6001
是为了查找是否有占用6001端口的进程,如果有,则关闭6001端口的进程,因为我这里的Service启用的是6001端口。
③ 删除D:JenkinsTest目录下的Service的Jar包,并将Jenkins自动打包生成的Jar包拷过来
然后通过Start javaw -jar *.jar命令在后台启动jar包。
④ 由于Jenkins默认在自动Build完成后,会关闭所有子进程,所以用下面这个命令可以避免Service被关闭
SET BUILD_ID=donKillMe
⑤ 据说start javaw -Dhudson.util.ProcessTree.disable=true -jar *.jar也有用,但是我用下来好像子进程还是被关闭了。
3. 测试
现在,当我更新代码到Git上以后,Jenkins就会自动将代码打包成Jar,然后执行我预先写好的命令自动部署启动。
在Console Output中可以看到Jenkins的执行内容:
结果验证: