Hystrix入门指南
Introduction
1、Where does the name come from?
hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix,并使用了对应的卡通形象做作为logo。
2、What Is Hystrix?
在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。
3、Hello Hystrix
public class CommandHelloWorld extends HystrixCommand<String> { private final String name; public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); //必须
this.name = name;
} @Override
protected String run() {
/*
网络调用 或者其他一些业务逻辑,可能会超时或者抛异常
*/
return "Hello " + name + "!";
}
} String s = new CommandHelloWorld("Bob").execute(); //
Future<String> s = new CommandHelloWorld("Bob").queue();
Observable<String> s = new CommandHelloWorld("Bob").observe();
Observable<String> s = new CommandHelloWorld("Bob").toObservable()
说明:
execute()
— blocks, then returns the single response received from the dependency (or throws an exception in case of an error)queue()
— returns aFuture
with which you can obtain the single response from the dependencyobserve()
— subscribes to theObservable
that represents the response(s) from the dependency and returns anObservable
that replicates that sourceObservable
toObservable()
— returns anObservable
that, when you subscribe to it, will execute the Hystrix command and emit its responses
4、Flow Chart
说明:
- Construct a HystrixCommand or HystrixObservableCommand Object
- Execute the Command
- Is the Response Cached?
- Is the Circuit Open?
- Is the Thread Pool/Queue/Semaphore Full?
- HystrixObservableCommand.construct() or HystrixCommand.run()
- Calculate Circuit Health
- Get the Fallback
- Return the Successful Response
常用功能介绍
依赖隔离
一个用户请求的成功执行,肯能依赖数十上百个外部服务,如果没有隔离,单个依赖的失败,可能会印象其他依赖的正常执行。如下图所示,为每个依赖配置了单独线程池
在下图中,当Dep I 出现问题时,DepA 和Dep M大以来可以正常执行
线程池隔离的使用例子
public class CommandHelloWorld extends HystrixCommand<String> { private final String name; public CommandHelloWorld(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) //必须
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool")) //可选,默认 使用 this.getClass().getSimpleName();
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4))); this.name = name;
} @Override
protected String run() throws InterruptedException {
System.out.println("running");
TimeUnit.MILLISECONDS.sleep(1000);
return "Hello " + name + "!";
} }
线程池常用参数设置:
实现类:HystrixThreadPoolProperties
名称
|
类型
|
含义
|
默认值
|
---|---|---|---|
coreSize |
Integer |
线程池大小 | 10 |
maxQueueSize |
Integer |
队列大小,一经初始化后不能修改 | -1 |
queueSizeRejectionThreshold |
Integer |
队列reject阈值,可以动态修改
maxQueueSize>0是生效,一般设置为小于 maxQueueSizede 的数值 |
5 |
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) //必须
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(500)) //超时时间
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool")) //可选,默认 使用 this.getClass().getSimpleName();
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4)
.withMaxQueueSize(10).withQueueSizeRejectionThreshold(7))
Q: 怎么设置线程池大小?
A:Qps* Tp99 +冗余线程
信号量隔离
线程池隔离中,发起请求的线程和真实执行的线程不是同一个线程,使用信号量隔离时,它们是同一个线程, 两种隔离的区别如下图:
public class CommandUsingSemaphoreIsolation extends HystrixCommand<String> { private final int id;
private long start,end ; public CommandUsingSemaphoreIsolation(int id) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
// since we're doing an in-memory cache lookup we choose SEMAPHORE isolation
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE) //设置使用信号量隔离策略
.withExecutionIsolationSemaphoreMaxConcurrentRequests(3) //设置信号量隔离时的最大并发请求数
.withFallbackIsolationSemaphoreMaxConcurrentRequests(5) //设置fallback的最大并发数
.withExecutionTimeoutInMilliseconds(300))); //设置超时时间
this.id = id;
this.start = System.currentTimeMillis();
} @Override
protected String run() throws InterruptedException {
// a real implementation would retrieve data from in memory data structure
TimeUnit.MILLISECONDS.sleep(id*30);
System.out.println("running normal, id="+id);
return "ValueFromHashMap_" + id;
} @Override
protected String getFallback(){
System.out.println(" fallback, id="+id);
return "fallback:"+id;
} } @Test
public void maxCurrentRequst() throws InterruptedException {
int count =10;
while (count >0){
int id = count--;
new Thread(() -> {
try {
new CommandUsingSemaphoreIsolation(id).execute();
}catch (Exception ex){
System.out.println("Exception:"+ex.getMessage()+" id="+id);
}
}).start();
} TimeUnit.SECONDS.sleep(100);
}
//注:使用信号量隔离,在同一个线程中即使循环调用new CommandUsingSemaphoreIsolation(id).queue(),run方法也是顺序执行;
//控制台输出
fallback, id=10
fallback, id=9
fallback, id=5
fallback, id=8
fallback, id=1
Exception:CommandUsingSemaphoreIsolation fallback execution rejected. id=4
Exception:CommandUsingSemaphoreIsolation fallback execution rejected. id=7
running normal, id=2
running normal, id=3
running normal, id=6
Q: 什么时候使用线程池隔离,什么使用信号量隔离?
A: 线程池隔离缺点是带来一定的开销,但不会阻塞请求线程,适合于于IO密集型的任务
信号量隔离使用用户请求线程,没有格外线程切换开销,使用与执行时间和执行逻辑都比较短的本地计算。比如CPU密集型的任务
Fallback
Q1: 为什么需要fallback?
简单来说,在依赖调用失败时,我们一般会需要提供降级方案,Hystrix对此提供了支持
public class CommandHelloWorld extends HystrixCommand<String> { private final String name; public CommandHelloWorld(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) //必须
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(500)) //超时时间
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool")) //可选,默认 使用 this.getClass().getSimpleName();
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4))); this.name = name;
} @Override
protected String run() throws InterruptedException {
System.out.println("running");
TimeUnit.MILLISECONDS.sleep(1000);
return "Hello " + name + "!";
} @Override
protected String getFallback() {
return "Hello "+"Fallback";
}
} @Test
public void fallbackTest(){
assertEquals("Hello Fallback",new CommandHelloWorld("World").execute());
}
Q2:什么情况下会触发fallback?
简单来说,就是run方法抛异常,超时,线程/信号量reject、短路
Failure Type
|
Exception class
|
Exception.cause
|
subject to fallback
|
---|---|---|---|
FAILURE | HystrixRuntimeException |
underlying exception (user-controlled) | YES |
TIMEOUT | HystrixRuntimeException |
j.u.c.TimeoutException |
YES |
SHORT_CIRCUITED | HystrixRuntimeException |
j.l.RuntimeException |
YES |
THREAD_POOL_REJECTED | HystrixRuntimeException |
j.u.c.RejectedExecutionException |
YES |
SEMAPHORE_REJECTED | HystrixRuntimeException |
j.l.RuntimeException |
YES |
BAD_REQUEST | HystrixBadRequestException |
underlying exception (user-controlled) | NO |
以下为测试的主程序:
public class CommandHelloFailure extends HystrixCommand<String> { private final String name; public CommandHelloFailure(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) //必须
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(1000)) //超时时间
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool"))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(3))); this.name = name;
} @Override
protected String run() throws InterruptedException {
String theadName = this.getThreadPoolKey().name();
String cmdKey=this.getThreadPoolKey().name();
System.out.println("running begin , threadPool="+theadName+" cmdKey="+cmdKey+" name="+name); if("Exception".equals(name)) {
throw new RuntimeException("this command always fails");
}else if("Timeout".equals(name)){
TimeUnit.SECONDS.sleep(2);
}else if("Reject".equals(name)){
TimeUnit.MILLISECONDS.sleep(800);
}
System.out.println(" run end"); return "Hello " + name + "!";
} @Override
protected String getFallback() {
StringBuilder sb = new StringBuilder("running fallback");
boolean isRejected = isResponseRejected();
boolean isException = isFailedExecution();
boolean isTimeout= isResponseTimedOut();
boolean isCircut = isCircuitBreakerOpen(); sb.append(", isRejected:").append(isRejected);
sb.append(", isException:"+isException);
if(isException){
sb.append(" msg=").append(getExecutionException().getMessage());
}
sb.append(", isTimeout: "+isTimeout);
sb.append(", isCircut:"+isCircut); sb.append(", group:").append(this.getCommandGroup().name());
sb.append(", threadpool:").append(getThreadPoolKey().name());
System.out.println(sb.toString()); String msg="Hello Failure " + name + "!";
return msg;
}
}
FAILURE
测试由异常导致的fallback
@Test
public void expTest() {
assertEquals("Hello Failure Exception!", new CommandHelloFailure("Exception").execute());
}
//控制台输出
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Exception
running fallback, isRejected:false, isException:true msg=this command always fails, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
TIMEOUT
测试有超时导致的fallback
@Test
public void timeOutTest() {
assertEquals("Hello Failure Timeout!", new CommandHelloFailure("Timeout").execute());
}
//控制台输出
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Timeout
running fallback, isRejected:false, isException:false, isTimeout: true, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
THREAD_POOL_REJECTED
并发执行的任务数超过线程池和队列之和会被reject,导致fallback
@Test
public void rejectTest() throws InterruptedException {
int count = 5;
while (count-- > 0){
new CommandHelloFailure("Reject").queue();
TimeUnit.MILLISECONDS.sleep(100);
}
}
//控制台输出
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running fallback, isRejected:true, isException:false, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
running fallback, isRejected:true, isException:false, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
SEMAPHORE_REJECTED 与 THREAD_POOL_REJECTED 类似,不再演示
SHORT_CIRCUITED
在一定时间内,用户请求超过一定的比例失败时(timeout, failure, reject),断路器就会打开;短路器打开后所有请求直接走fallback
参数设置
名称
|
类型
|
含义
|
默认值
|
---|---|---|---|
circuitBreakerEnabled | Boolean | 是否启用断路器 | true |
circuitBreakerErrorThresholdPercentage | Integer | 错误百分比,超过该值打开断路器 | 50 |
circuitBreakerForceClosed | Boolean | 强制断路器打开 | false |
circuitBreakerForceOpen | Boolean | 强制短路器关闭 | false |
circuitBreakerRequestVolumeThreshold | Integer | 10s中内最少的请求量,大于该值,断路器配置才会生效 | 20 |
circuitBreakerSleepWindowInMilliseconds | Integer | 短路器打开后多长时间尝试关闭(Half open) | 5s |
一般配置如下:
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) //必须
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(50)//超时时间
.withCircuitBreakerRequestVolumeThreshold(5)
.withCircuitBreakerSleepWindowInMilliseconds(1000)
.withCircuitBreakerErrorThresholdPercentage(50))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool")) //可选,默认 使用 this.getClass().getSimpleName();
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4));
以上配置的含义是: 在10s内,如果请求在5个及以上,且有50%失败的情况下,开启断路器;断路器开启1000ms后尝试关闭
短路器的工作机制,引用自官方文档:
The precise way that the circuit opening and closing occurs is as follows:
Assuming the volume across a circuit meets a certain threshold (HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())...
And assuming that the error percentage exceeds the threshold error percentage (HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())...
Then the circuit-breaker transitions from CLOSED to OPEN.
While it is open, it short-circuits all requests made against that circuit-breaker.
After some amount of time (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()), the next single request is let through (this is the HALF-OPEN state). If the request fails, the circuit-breaker returns to the OPEN state for the duration of the sleep window. If the request succeeds, the circuit-breaker transitions to CLOSED and the logic in 1. takes over again.
Q3:fallback时我们应该怎么办?
一般有以下几种策略:
1、不实现getFallback方法:依赖调用失败时直接抛出异常
2、实现getFallback方法,返回默认值:这是一种常见的策略
3、实现getFallback方法,走降级方案
此外,生产环境中,fallback时,一般需要打点记录
请求合并
简单来说,就是将一段时间内的多次请求合并为一次请求,常用于网络IO中,能减少IO次数,缺点是增加平均延迟
以下是测试代码主程序:
public class CommandCollapserGetValueForKey extends HystrixCollapser<List<String>, String, Integer> { private final Integer key; public CommandCollapserGetValueForKey(Integer key) {
super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("Collapser"))
.andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()
.withMaxRequestsInBatch(3)
.withTimerDelayInMilliseconds(10)));
this.key = key;
} @Override
public Integer getRequestArgument() {
return key;
} @Override
protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {
return new BatchCommand(requests);
} @Override
protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {
int count = 0;
for (CollapsedRequest<String, Integer> request : requests) {
request.setResponse(batchResponse.get(count++));
}
} private static final class BatchCommand extends HystrixCommand<List<String>> {
private final Collection<CollapsedRequest<String, Integer>> requests; private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
this.requests = requests;
} @Override
protected List<String> run() {
System.out.println("BatchCommand run "+requests.size());
ArrayList<String> response = new ArrayList<String>();
for (CollapsedRequest<String, Integer> request : requests) {
// artificial response for each argument received in the batch
response.add("ValueForKey: " + request.getArgument());
}
return response;
}
}
} @Test
public void testCollapser() throws Exception {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
Future<String> f1 = new CommandCollapserGetValueForKey(1).queue();
Future<String> f2 = new CommandCollapserGetValueForKey(2).queue();
Future<String> f3 = new CommandCollapserGetValueForKey(3).queue();
Future<String> f4 = new CommandCollapserGetValueForKey(4).queue(); assertEquals("ValueForKey: 1", f1.get());
assertEquals("ValueForKey: 2", f2.get());
assertEquals("ValueForKey: 3", f3.get());
assertEquals("ValueForKey: 4", f4.get()); // assert that the batch command 'GetValueForKey' was in fact
// executed and that it executed only once
assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
HystrixCommand<?> command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixCommand<?>[1])[0];
// assert the command is the one we're expecting
assertEquals("GetValueForKey", command.getCommandKey().name());
// confirm that it was a COLLAPSED command execution
assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
// and that it was successful
assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
} finally {
context.shutdown();
}
} //控制输出
BatchCommand run 3
BatchCommand run 1
执行流程:
使用该特性
1、必须继承HystrixCollapser类,
2、实现以下方法:
getRequestArgument: 返回请求参数对象
createCommand : 返回BatchCommand
mapResponseToRequests:实现Response和Request的映射
3、创建对应的BatchCommand类:批量请求的具体实现
参数配置:
名称
|
类型
|
含义
|
默认值
|
---|---|---|---|
maxRequestsInBatch |
Integer |
每个批次最大的请求数,超过该值,创建新的batch请求 |
Integer.MAX_VALUE |
timerDelayInMilliseconds |
Integer |
等待时间窗口,超过该值,创建新的batch请求 | 10ms |
requestCacheEnabled |
Boolean |
是否启用cache | true |
一般配置如下
Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("Collapser"))
.andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()
.withMaxRequestsInBatch(3)
.withTimerDelayInMilliseconds(5));
请求cache
public class CommandUsingRequestCache extends HystrixCommand<Boolean> {
private final int value; public CommandUsingRequestCache(int value) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.value = value;
} @Override
public Boolean run() {
return value == 0 || value % 2 == 0;
} //使用cache功能,必须实现该方法
@Override
public String getCacheKey() {
return String.valueOf(value);
}
} @Test
public void testWithCacheHits() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
CommandUsingRequestCache command2a = new CommandUsingRequestCache(2);
CommandUsingRequestCache command2b = new CommandUsingRequestCache(2); assertTrue(command2a.execute());
//第一次请求,没有cache
assertFalse(command2a.isResponseFromCache()); assertTrue(command2b.execute());
// 第二次请求,从cache中拿的结果
assertTrue(command2b.isResponseFromCache());
} finally {
context.shutdown();
} context = HystrixRequestContext.initializeContext();
try {
CommandUsingRequestCache command3b = new CommandUsingRequestCache(2);
assertTrue(command3b.execute());
// this is a new request context so this
//new了新的 request context后,之前的cache失效
assertFalse(command3b.isResponseFromCache());
} finally {
context.shutdown();
}
}
Hystrix Context
Global Context
UserRequest Context
使用与监控
1、工程中使用
使用Hystrix很简单,只需要添加相应依赖即可,以Maven为例:
<!-- hystrix 依赖 -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.9</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>1.5.9</version>
</dependency>
2、DashBoard使用
web.xml中配置相应的Servlet
<servlet>
<display-name>HystrixMetricsStreamServlet</display-name>
<servlet-name>HystrixMetricsStreamServlet</servlet-name>
<servlet-class>com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HystrixMetricsStreamServlet</servlet-name>
<url-pattern>/hystrix.stream</url-pattern>
</servlet-mapping>
下载附件中的war文件和jar文件到任意目录,执行
java -jar jetty-runner-9.2.10.v20150310.jar --port 8410 hystrix-dashboard-1.5.1.war
然后在浏览器中打开:http://localhost:8410/ ,在输入框中填写 http://hostname:port/application/hystrix.stream, 点击 Add Stream ,然后在点击Monitor Stream, 看到如下图:
每个指标对应的含义:
一般来说: Thread-pool Rejections 和Failuress/Exception应该是0,Thread timeouts是个很小的值。
代码结构
附件
1、启动脚本 start.sh
Hystrix入门指南的更多相关文章
- 转 Hystrix入门指南 Introduction
https://www.cnblogs.com/gaoyanqing/p/7470085.html
- Web API 入门指南 - 闲话安全
Web API入门指南有些朋友回复问了些安全方面的问题,安全方面可以写的东西实在太多了,这里尽量围绕着Web API的安全性来展开,介绍一些安全的基本概念,常见安全隐患.相关的防御技巧以及Web AP ...
- Vue.js 入门指南之“前传”(含sublime text 3 配置)
题记:关注Vue.js 很久了,但就是没有动手写过一行代码,今天准备入手,却发现自己比菜鸟还菜,于是四方寻找大牛指点,才终于找到了入门的“入门”,就算是“入门指南”的“前传”吧.此文献给跟我一样“白痴 ...
- yii2实战教程之新手入门指南-简单博客管理系统
作者:白狼 出处:http://www.manks.top/document/easy_blog_manage_system.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文 ...
- 【翻译】Fluent NHibernate介绍和入门指南
英文原文地址:https://github.com/jagregory/fluent-nhibernate/wiki/Getting-started 翻译原文地址:http://www.cnblogs ...
- ASP.NET MVC 5 入门指南汇总
经过前一段时间的翻译和编辑,我们陆续发出12篇ASP.NET MVC 5的入门文章.其中大部分翻译自ASP.NET MVC 5 官方教程,由于本系列文章言简意赅,篇幅适中,从一个web网站示例开始讲解 ...
- 一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍
我们在前一篇文章微软新神器-Power BI,一个简单易用,还用得起的BI产品中,我们初步介绍了Power BI的基本知识.由于Power BI是去年开始微软新发布的一个产品,虽然已经可以企业级应用, ...
- 一起学微软Power BI系列-官方文档-入门指南(2)获取源数据
我们在文章: 一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍中,我们介绍了官方入门文档的第一章.今天继续给大家介绍官方文档中,如何获取数据源的相关内容.虽然是英文,但 ...
- 一起学微软Power BI系列-官方文档-入门指南(3)Power BI建模
我们前2篇文章:一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍 和一起学微软Power BI系列-官方文档-入门指南(2)获取源数据 中,我们介绍了官方入门文档与获取 ...
随机推荐
- 改写python round()函数,解决四舍五入问题 round(1.365,2)=1.36
round()函数四舍五入存在一个问题,遇到5不一定进一.如下图所示: print(round(1.365,2)) #1.36 没进一 print('%.2f'%1.365) print(round( ...
- vue-router异步加载组件
export default { routes: [ { path: '/fund', name: 'FundManagement', component: function(resolve) { r ...
- IOS MapKit框架的使用(专门用于地图显示)
● MapKit框架使用前提 ● 导入框架 ● 导入主头文件#import <MapKit/MapKit.h> ● MapKit框架使用须知 ● MapKit框架中所有数据类型的前 ...
- C++设计模式实现--訪问者(Visitor)模式
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/L_Andy/article/details/36896645 一. 訪问者模式 定义:表示一个作用于 ...
- POJ 1379 模拟退火
模拟退火算法,很久之前就写过一篇文章了.双倍经验题(POJ 2420) 题意: 在一个矩形区域内,求一个点的距离到所有点的距离最短的那个,最大. 这个题意,很像二分定义,但是毫无思路,也不能暴力枚举, ...
- 2018.9.16 Redis 边学习边总结
Redis 是一个使用 C 语言写成的,开源的 key-value 数据库..和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合) ...
- 剑指offer23 从上往下打印二叉树
没有把队列的头部弹出,出现内存错误:
- Visual Studio C++ Win32控制台应用程序,Win32项目,MFC的区别
背景 Visual Studio C++ 创建新项目蹦出来如下选项: Win32控制台应用程序,Win32项目,MFC有什么区别? 正文: Win32控制台,没有界面,命令行执行生成的文件则直接在后台 ...
- 使用vba doc转docx
创建vbs文件,doctodocx.vbs内容如下: '创建一个word对象 set wApp=CreateObject("word.Application") '获取文件传递到参 ...
- 使用 Cordova(PhoneGap)构建Android程序
移动终端开发随着智能手机的普及变得越来越热,什么项目都想移动一把,但反观要去学这些各个终端的控件使用,实属不易,又特别是每个终端的控件及所用语言都各不相同,使得这种学习变得更加困难. 有没有一种简单的 ...