个人实践 和 备忘
项目结构 :
分布式项目, 利用maven构建了多模块, 开发还是 ssm , 项目中 有mq 和 redis , memcached。都是通过 配置文件加载,开头是web.xml 引入spring-x.xml , spring-x.xml 再去引入 其他配置文件,znf4-common-config 用来配置 *.properties 文件,mq ,jdbc , redis 系统等相关配置。
现引入Boot 先关jar包 :
我是一次性引入了很多个相关jar , 有些也没用上,你们自行去除就行。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>1.3.1.RELEASE</spring-boot.version>
<tomcat.version>8.0.28</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- spring-boot start -->
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-remote-shell</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<!--spring boot end-->
加入启动模块 :
ApplicationStarter .java
package com.znf4.app;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
/**
* Created by agui on 15/10/26.
*/
@SpringBootApplication
@ImportResource("classpath:spring/spring-context.xml")
public class ApplicationStarter {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ApplicationStarter.class);
app.setAdditionalProfiles();
app.setBannerMode(Banner.Mode.LOG);
app.run(args);
}
}
上面类中 注解 :@ImportResource(“classpath:spring/spring-context.xml”) ,用来引入:
说明 :spring-contet-service.xml 这个配置文件在我其他模块,该模块通过它又引入了一些其他 xml 配置。
JvmConfiger.java
package com.znf4.app;
import java.net.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.ResourceBundle;
/**
* Created by agui on 15/11/2.
*/
public class JvmConfiger {
public final static ResourceBundle RESOURCE = ResourceBundle.getBundle("application");
public static String getJvmConfig() {
StringBuilder buffer = new StringBuilder();
append(buffer, RESOURCE, "jvm.mem");
append(buffer, RESOURCE, "jvm.log");
append(buffer, RESOURCE, "jvm.gc");
append(buffer, RESOURCE, "jvm.others");
append(buffer, RESOURCE, "jvm.args");
return buffer.toString();
}
public static String getRun() {
StringBuilder buffer = new StringBuilder();
buffer.append("nohup java ");
buffer.append(String.format("`java -jar %s -jvm`",getPackageName()));
buffer.append(" -jar ");
buffer.append(getPackageName());
buffer.append(" > ");
buffer.append("./console.log");
buffer.append(" 2>&1 &");
return buffer.toString();
}
private static String getPackageName(){
return getAppConfig("project.name") + "." + getAppConfig("project.package");
}
private static StringBuilder append(StringBuilder buffer, ResourceBundle resource, String key) {
if (resource.containsKey(key)) {
String value = resource.getString(key);
if (value.contains("hostname=%s")) {
value = String.format(value, getLocalAddress());
}
return buffer.append(" " + value);
}
return buffer;
}
public static String getAppConfig(String key) {
if (RESOURCE.containsKey(key)) {
return RESOURCE.getString(key);
}
return null;
}
public static String getLocalAddress() {
try {
// Traversal Network interface to get the first non-loopback and non-private address
Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
ArrayList<String> ipv4Result = new ArrayList<String>();
ArrayList<String> ipv6Result = new ArrayList<String>();
while (enumeration.hasMoreElements()) {
final NetworkInterface networkInterface = enumeration.nextElement();
final Enumeration<InetAddress> en = networkInterface.getInetAddresses();
while (en.hasMoreElements()) {
final InetAddress address = en.nextElement();
if (!address.isLoopbackAddress()) {
if (address instanceof Inet6Address) {
ipv6Result.add(normalizeHostAddress(address));
} else {
ipv4Result.add(normalizeHostAddress(address));
}
}
}
}
// prefer ipv4
if (!ipv4Result.isEmpty()) {
for (String ip : ipv4Result) {
if (ip.startsWith("127.0") || ip.startsWith("192.168")) {
continue;
}
return ip;
}
return ipv4Result.get(ipv4Result.size() - 1);
} else if (!ipv6Result.isEmpty()) {
return ipv6Result.get(0);
}
//If failed to find,fall back to localhost
final InetAddress localHost = InetAddress.getLocalHost();
return normalizeHostAddress(localHost);
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return null;
}
public static String normalizeHostAddress(final InetAddress localHost) {
if (localHost instanceof Inet6Address) {
return "[" + localHost.getHostAddress() + "]";
} else {
return localHost.getHostAddress();
}
}
}
继续引入Boot相关配置:
值得说的是 : 我把 znf4-common-config 中的jdbc.properties 中的 相关数据库配置远方不动的粘贴到了 boot 约定下的 application.properties
# sys config
isDebug = true
application.message=znf4
# 容器端口
server.port=9999
server.sessionTimeout= 3600
server.contextPath=
# tomcat config
#url = http://localhost:9999/merchant/trade/free?id=11111111111111&type=free
server.tomcat.compression=2048 # is compression enabled (off, on, or an integer content length limit)
server.tomcat.compressable-mime-types=text/html,text/xml,text/plain,text/javascript,application/json,application/xml
tomcat.accessLogEnabled= false
tomcat.protocolHeader=x-forwarded-proto
tomcat.remoteIpHeader=x-forwarded-for
tomcat.basedir=
tomcat.backgroundProcessorDelay=30 # secs
server.tomcat.max-threads = 500
server.tomcat.uri-encoding = UTF-8
#############################
##JDBC Setting
#############################
##common
jdbc.validationQuery=select current_timestamp()
jdbc.maxWait=60000
##web
web.jdbc.driver=com.mysql.jdbc.Driver
web.jdbc.url=jdbc:mysql://123.26.6.2:3306/znf4?useUnicode=true&characterEncoding=UTF-8
web.jdbc.username=root
web.jdbc.password=T5U+tbyGJ6mm6UVSZfo9qEDcmZdAx3Pk81KsYtpdhiN08WuPLwrVilyEK0yezprsCz25ghR2L11QJKQP/2+41w==
web.jdbc.initialSize=2
web.jdbc.minIdle=2
web.jdbc.maxActive=10
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
#classpath中存在velocity 忽略spring-boot的VelocityAutoConfiguration
spring.velocity.enabled=false
spring.velocity.checkTemplateLocation=true
最好以上我们就完成了 原生Springmvc SM 下的项目加进boot 相关组件,尝试启动一下 :
没有任何 问题 , 解释一下我为什么要引入boot :
之前设计这个项目就是根据需求直接设计成分布式的项目,各种中间件都是通过spring -xml 文件配置的,因为一些三方接口不是很给力,之前上网上找了一个 通过redis 对接口限流的方法,感觉还是有很大局限性,维护那不容易。之前在学Springcloud 中全家桶组件时,也是基于boot , 这把要用hystrix组件, 就想到项目通过boot 启动 , 用boot 去 引入 sringcloud 中的其他组件(现有的东西不动),后续也可以上 微服务(虽然对dubbo 情有独钟,还是选择了cloud)。 下面会展示 boot 引入 hystrix ,并且进行测试的相关代码和图片说明。
jar准备:
我是在partent 中下载jar, core 模块去继承, server 模块去引入core , merchant模块作为入口,去引用 core , server 。
直白说:引入下面这个jar 就行了,上面说的那些都跟我设计的maven多模块有关,跟jar没直接关系。
<!-- 限流,熔断,降级 -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.13</version>
</dependency>
启动一个测试服务,模拟三方服务 :
我们在我们的项目中加入测试代码 :
我们通过jemter 工具模拟并发,访问merchant 模块,merchant逻辑中有通过http 访问上面 9001 服务的代码,我们的目的就是通过 boot 去配置 hystrix 实现, 限流,降级,熔断。
我们先压测一下我们测试服务器 9001 ,直接 50 个线程 10次循环,共500次请求 :
结果 :
蹦了, 那么基本上可以肯定,我们通过merchant 去访问测试服务器,500次去访问也是扛不住的,我们不可能因为一个三方服务性能不好,出先了异常,导致我们的系统也用不了,那么我对他进行处理 :
SmsSendCommand.java 类
package com.znf4.test;
import java.io.InputStream;
import java.net.URL;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
public class SmsSendCommand extends HystrixCommand<String>{
protected SmsSendCommand() {
super(HystrixCommandGroupKey.Factory.asKey("smsGroup"));
}
@Override
protected String run() throws Exception {
URL url = new URL("http://localhost:9001/send/agui");
byte[] result = new byte[3];
InputStream input = url.openStream();
IOUtils.readFully(input, result);
// 远程服务不稳定或网络抖动时暂时关闭
return new String(result);
}
@Override
protected String getFallback() {
// 降级 策略 再次查询, 查询备用接口 缓存 mock值
// 根据业务自定义
return "降级";
}
}
测试 :
@RequestMapping("test_hystrix")
public String test_hystrix() throws IOException{
return new SmsSendCommand().execute();
}
config.properties 文件 跟 apllication.properties 文件放在同一个目录下。 配置如下
注意 : hystrix.threadpool.smsGroup.coreSize=20 中的 smsGroup 为 上述 代码中的key : HystrixCommandGroupKey.Factory.asKey(“smsGroup”) 。
# Hystrix 默认加载的配置文件 - 限流、 熔断示例
# 超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000000
# 信号量 netflix分布式配置中心 archaius
# 线程池大小
hystrix.threadpool.default.coreSize=10
hystrix.threadpool.default.maxQueueSize=-1
hystrix.threadpool.default.queueSizeRejectionThreshold=5
# 限流策略
hystrix.threadpool.smsGroup.coreSize=20
hystrix.threadpool.smsGroup.maxQueueSize=1000
# 超过就报错
hystrix.threadpool.smsGroup.queueSizeRejectionThreshold=800
结果 :
解释 : 我们 通过jmeter 直接 80 * 10 次访问我们限流后的 接口, 可以发现服务器 并没有被压垮。 证明我门的配置好用。 继续我们补充一下降级,熔断。
降级 :
降级我们要么通过 SmsSendCommand 类中的 :
进行降级逻辑,可以 在此查询,查询别用接口,缓存mock 值等。
说明一下: 我们的限流 或者熔断 报错之后都会进行降级 。
@Override
protected String getFallback() {
// 降级 策略 再次查询, 查询备用接口 缓存 mock值
// 根据业务自定义
return "降级";
}
熔断 :
继续追加到config.properties 文件 , 都有注释,就不解释了。
# 熔断策略
# 启用/禁用熔断机制
hystrix.command.default.circuitBreaker.enabled=true
# 强制开启 禁止访问相关接口
hystrix.command.default.circuitBreaker.forceOpen=false
# 强制关闭
hystrix.command.default.circuitBreaker.forceClosed=false
# 在一定前提条件下,暂停接口访问
# 10秒统计一次
hystrix.command.default.metrics.rollingStats.timeInMilliseconds=10000
# 令牌桶数量
# hystrix.command.default.metrics.rollingStats.numBuckets=10
# 前提条件,时间内发起一定数量的请求。 也就是10秒钟内至少请求5次,熔断器才发挥起作用
hystrix.command.default.circuitBreaker.requestVolumeThreshold=5
# 错误百分比。达到或超过这个百分比,启用熔断 不会访问接口
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
# 10秒后,半打开状态
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=10000
测试 :
完善测试服务器代码,加入随机数,对2取余,满足就响应一个错误,来触发熔断 ,。
重启测试服务9001 , 继续访问 80 * 10 。看结果 :
上面可以看到,我们 800次请求,:
错误百分比。达到或超过这个百分比,启用熔断 不会访问接口
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50 时候我们这个接口就不会在继续接收请求,以进行了熔断。
以上,我们我们完成了 boot 的引入,顺便 我用boot 引入了hystrix 进行了 限流,熔断,降级的演示。
实例代码 : 链接: https://pan.baidu.com/s/1o8n6pw2 密码: dnsf