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 a Future with which you can obtain the single response from the dependency
    • observe() — subscribes to the Observable that represents the response(s) from the dependency and returns an Observable that replicates that source Observable
    • toObservable() — returns an Observable that, when you subscribe to it, will execute the Hystrix command and emit its responses

4、Flow Chart

说明:

  1. Construct a HystrixCommand or HystrixObservableCommand Object
  2. Execute the Command
  3. Is the Response Cached?
  4. Is the Circuit Open?
  5. Is the Thread Pool/Queue/Semaphore Full?
  6. HystrixObservableCommand.construct() or HystrixCommand.run()
  7. Calculate Circuit Health
  8. Get the Fallback
  9. 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

2、hystrix-dashboard-1.5.1.war

3、jetty-runner-9.2.10.v20150310.jar

Hystrix入门指南的更多相关文章

  1. 转 Hystrix入门指南 Introduction

    https://www.cnblogs.com/gaoyanqing/p/7470085.html

  2. Web API 入门指南 - 闲话安全

    Web API入门指南有些朋友回复问了些安全方面的问题,安全方面可以写的东西实在太多了,这里尽量围绕着Web API的安全性来展开,介绍一些安全的基本概念,常见安全隐患.相关的防御技巧以及Web AP ...

  3. Vue.js 入门指南之“前传”(含sublime text 3 配置)

    题记:关注Vue.js 很久了,但就是没有动手写过一行代码,今天准备入手,却发现自己比菜鸟还菜,于是四方寻找大牛指点,才终于找到了入门的“入门”,就算是“入门指南”的“前传”吧.此文献给跟我一样“白痴 ...

  4. yii2实战教程之新手入门指南-简单博客管理系统

    作者:白狼 出处:http://www.manks.top/document/easy_blog_manage_system.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文 ...

  5. 【翻译】Fluent NHibernate介绍和入门指南

    英文原文地址:https://github.com/jagregory/fluent-nhibernate/wiki/Getting-started 翻译原文地址:http://www.cnblogs ...

  6. ASP.NET MVC 5 入门指南汇总

    经过前一段时间的翻译和编辑,我们陆续发出12篇ASP.NET MVC 5的入门文章.其中大部分翻译自ASP.NET MVC 5 官方教程,由于本系列文章言简意赅,篇幅适中,从一个web网站示例开始讲解 ...

  7. 一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍

    我们在前一篇文章微软新神器-Power BI,一个简单易用,还用得起的BI产品中,我们初步介绍了Power BI的基本知识.由于Power BI是去年开始微软新发布的一个产品,虽然已经可以企业级应用, ...

  8. 一起学微软Power BI系列-官方文档-入门指南(2)获取源数据

    我们在文章: 一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍中,我们介绍了官方入门文档的第一章.今天继续给大家介绍官方文档中,如何获取数据源的相关内容.虽然是英文,但 ...

  9. 一起学微软Power BI系列-官方文档-入门指南(3)Power BI建模

    我们前2篇文章:一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍 和一起学微软Power BI系列-官方文档-入门指南(2)获取源数据 中,我们介绍了官方入门文档与获取 ...

随机推荐

  1. 改写python round()函数,解决四舍五入问题 round(1.365,2)=1.36

    round()函数四舍五入存在一个问题,遇到5不一定进一.如下图所示: print(round(1.365,2)) #1.36 没进一 print('%.2f'%1.365) print(round( ...

  2. vue-router异步加载组件

    export default { routes: [ { path: '/fund', name: 'FundManagement', component: function(resolve) { r ...

  3. IOS MapKit框架的使用(专门用于地图显示)

    ● MapKit框架使用前提 ● 导入框架 ● 导入主头文件#import <MapKit/MapKit.h>   ●  MapKit框架使用须知 ●  MapKit框架中所有数据类型的前 ...

  4. C++设计模式实现--訪问者(Visitor)模式

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/L_Andy/article/details/36896645 一. 訪问者模式 定义:表示一个作用于 ...

  5. POJ 1379 模拟退火

    模拟退火算法,很久之前就写过一篇文章了.双倍经验题(POJ 2420) 题意: 在一个矩形区域内,求一个点的距离到所有点的距离最短的那个,最大. 这个题意,很像二分定义,但是毫无思路,也不能暴力枚举, ...

  6. 2018.9.16 Redis 边学习边总结

    Redis 是一个使用 C 语言写成的,开源的 key-value 数据库..和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合) ...

  7. 剑指offer23 从上往下打印二叉树

    没有把队列的头部弹出,出现内存错误:

  8. Visual Studio C++ Win32控制台应用程序,Win32项目,MFC的区别

    背景 Visual Studio C++ 创建新项目蹦出来如下选项: Win32控制台应用程序,Win32项目,MFC有什么区别? 正文: Win32控制台,没有界面,命令行执行生成的文件则直接在后台 ...

  9. 使用vba doc转docx

    创建vbs文件,doctodocx.vbs内容如下: '创建一个word对象 set wApp=CreateObject("word.Application") '获取文件传递到参 ...

  10. 使用 Cordova(PhoneGap)构建Android程序

    移动终端开发随着智能手机的普及变得越来越热,什么项目都想移动一把,但反观要去学这些各个终端的控件使用,实属不易,又特别是每个终端的控件及所用语言都各不相同,使得这种学习变得更加困难. 有没有一种简单的 ...