[转]并发编程:Actors模型和CSP模型

一、前言
不同的编程模型与具体的语言无关,大部分现代语言都可以通过巧妙地结构处理实现不同的模型.杂谈的意思是很杂,想到哪儿写到哪儿,不对正确性负责 :D.

二、Actors模型
传统的并发模型主要由两种实现的形式,一是同一个进程下,多个线程天然的共享内存,由程序对读写做同步控制(有锁或无锁). 二是多个进程通过进程间通讯或者内存映射实现数据的同步.

Actors模型更多的使用消息机制来实现并发,目标是让开发者不再考虑线程这种东西,每个Actor最多同时只能进行一样工作,Actor内部可以有自己的变量和数据.

Actors模型避免了由操作系统进行任务调度的问题,在操作系统进程之上,多个Actor可能运行在同一个进程(或线程)中.这就节省了大量的Context切换.

在Actors模型中,每个Actor都有一个专属的命名”邮箱”, 其他Actor可以随时选择一个Actor通过邮箱收发数据,对于“邮箱”的维护,通常是使用发布订阅的机制实现的,比如我们可以定义发布者是自己,订阅者可以是某个Socket接口,另外的消息总线或者直接是目标Actor.

目前akka库是比较流行的Actors编程模型实现,支持Scala和Java语言.

三、CSP模型
CSP(Communicating Sequential Process)模型提供一种多个进程公用的“管道(channel)”, 这个channel中存放的是一个个”任务”.

目前正流行的go语言中的goroutine就是参考的CSP模型,原始的CSP中channel里的任务都是立即执行的,而go语言为其增加了一个缓存,即任务可以先暂存起来,等待执行进程准备好了再逐个按顺序执行.

四、CSP和Actor的区别
CSP进程通常是同步的(即任务被推送进Channel就立即执行,如果任务执行的线程正忙,则发送者就暂时无法推送新任务),Actor进程通常是异步的(消息传递给Actor后并不一定马上执行).
CSP中的Channel通常是匿名的, 即任务放进Channel之后你并不需要知道是哪个Channel在执行任务,而Actor是有“身份”的,你可以明确的知道哪个Actor在执行任务.
在CSP中,我们只能通过Channel在任务间传递消息, 在Actor中我们可以直接从一个Actor往另一个Actor传输数据.
CSP中消息的交互是同步的,Actor中支持异步的消息交互.

五、参考文档
Scala中的actors和Go中的goroutines对比
CSP Model From Wiki

Lucene Query Syntax

Keyboard Matching

Search for word “­foo­” in the title field
title: foo

Search for phrase “foo bar” in the title field
title: “foo bar”

Search for phrase “foo bar” in the title field AND the phrase “­quick fox” in the body field.
title­:”foo bar” AND body:”quick fox”

Search for either the phrase “foo bar” in the title field AND the phrase “­quick fox” in the body field, or the word “­fox­” in the title field.
(titl­e:”foo bar” AND body:”quick fox”) OR title:fox

Search for word “­foo­” and not “­bar­” in the title field.
title:foo -title­:bar

Wildcard matching

Search for any word that starts with “­foo­” in the title field.
title­:foo*

Search for any word that starts with “­foo­” and ends with bar in the title field.
title­:fo­o*bar

Note that Lucene doesn’t support using a symbol as the first character of a *search.

Proximity matching

Search for “foo bar” within 4 words from each other.
“foo bar”~4

Range Searches

Range Queries allow one to match documents whose field(s) values are between the lower and upper bound specified by the Range Query. Range Queries can be inclusive or exclusive of the upper and lower bounds. Sorting is done lexico­gra­phi­cally.
mod_d­ate­:[2­0020101 TO 20030101]

Boosts

Query-time boosts allow one to specify which terms/­clauses are “more import­ant­”. The higher the boost factor, the more relevant the term will be, and therefore the higher the corres­ponding document scores.

A typical boosting technique is assigning higher boosts to title matches than to body content matches:
(titl­e:foo OR title:­bar­)^1.5 (body:foo OR body:bar)

Boolean Operators

To search for all transa­ctions except MySQL transa­ctions:
NOT type: mysql

To search for all MySQL SELECT queries with large attach­ments:
mysql.me­thod: SELECT AND mysql.s­ize: [10000 TO *]

Lucene also supports parent­heses to group sub queries.
To search for either INSERT or UPDATE MySQL queries with a respon­setime greater or equal with 30ms:
(mysq­l.m­ethod: INSERT OR mysql.m­ethod: UPDATE) AND respon­set­ime:[30 TO *]

git rebasing/merging forked repo

添加 upstream

git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git

查看 upstream

# origin    https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
# origin    https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
# upstream  https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (fetch)
# upstream  https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (push)
git fetch upstream

rebase 或者 merge

git rebase upstream/master
# or
git merge upstream/master

Used Resources
https://help.github.com/articles/configuring-a-remote-for-a-fork/
https://help.github.com/articles/syncing-a-fork/

SQL注入

SQL注入 维基百科

PreparedStatement

Secure Usage

PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE userid=? AND password=?");
stmt.setString(1, userid);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();

Vulnerable Usage

// Example #1
String query = "SELECT * FROM users WHERE userid ='"+ userid + "'" + " AND password='" + password + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);
// Example #2
String query = "SELECT * FROM users WHERE userid ='"+ userid + "'" + " AND password='" + password + "'";
PreparedStatement stmt = connection.prepareStatement(query);
ResultSet rs = stmt.executeQuery();

Hibernate

Secure Usage

/* Positional parameter in HQL */
Query hqlQuery = session.createQuery("from Orders as orders where orders.id = ?");
List results = hqlQuery.setString(0, "123-ADB-567-QTWYTFDL").list();

/* named parameter in HQL */
Query hqlQuery = session.createQuery("from Employees as emp where emp.incentive > :incentive");
List results = hqlQuery.setLong("incentive", new Long(10000)).list();

/* named parameter list in HQL */
List items = new ArrayList(); 
items.add("book"); items.add("clock"); items.add("ink");
List results = session.createQuery("from Cart as cart where cart.item in (:itemList)").setParameterList("itemList", items).list();

/* JavaBean in HQL */
Query hqlQuery = session.createQuery("from Books as books where book.name = :name and book.author = :author");
List results = hqlQuery.setProperties(javaBean).list(); //assumes javaBean has getName() & getAuthor() methods.

/* Native-SQL */
Query sqlQuery = session.createSQLQuery("Select * from Books where author = ?");
List results = sqlQuery.setString(0, "Charles Dickens").list();

Vulnerable Usage

List results = session.createQuery("from Orders as orders where orders.id = " + currentOrder.getId()).list();
List results = session.createSQLQuery("Select * from Books where author = " + book.getAuthor()).list();

JPA

Secure Usage

/* positional parameter in JPQL */
Query jpqlQuery = entityManager.createQuery("Select order from Orders order where order.id = ?1");
List results = jpqlQuery.setParameter(1, "123-ADB-567-QTWYTFDL").getResultList();

/* named parameter in JPQL */
Query jpqlQuery = entityManager.createQuery("Select emp from Employees emp where emp.incentive > :incentive");
List results = jpqlQuery.setParameter("incentive", new Long(10000)).getResultList();

/* named query in JPQL - Query named "myCart" being "Select c from Cart c where c.itemId = :itemId" */
Query jpqlQuery = entityManager.createNamedQuery("myCart");
List results = jpqlQuery.setParameter("itemId", "item-id-0001").getResultList();

/* Native SQL */
Query sqlQuery = entityManager.createNativeQuery("Select * from Books where author = ?", Book.class);
List results = sqlQuery.setParameter(1, "Charles Dickens").getResultList();

Vulnerable Usage

List results = entityManager.createQuery("Select order from Orders order where order.id = " + orderId).getResultList();
List results = entityManager.createNativeQuery("Select * from Books where author = " + author).getResultList();
int resultCode = entityManager.createNativeQuery("Delete from Cart where itemId = " + itemId).executeUpdate();

MyBatis

Secure Usage

<select id="getPerson" parameterType="int" resultType="org.application.vo.Person">
SELECT * FROM PERSON WHERE ID = #{id}
</select>

/* Comparable JDBC code */
String selectPerson = "SELECT * FROM PERSON WHERE ID = ?"; 
PreparedStatement ps = conn.prepareStatement(selectPerson); 
ps.setInt(1, id);

<insert id="insertPerson" parameterType="org.application.vo.Person">
insert into Person (id, name, email, phone)
values (#{id}, #{name}, #{email}, #{phone})
</insert>
 
<update id="updatePerson" parameterType="org.application.vo.Person">
update Person set name = #{name}, email = #{email}, phone = #{phone}
where id = #{id}
</update>
 
 
<delete id="deletePerson" parameterType="int">
delete from Person where id = #{id}
</delete>

Vulnerable Usage

<select id="getPerson" parameterType="string" resultType="org.application.vo.Person">
SELECT * FROM PERSON WHERE NAME = #{name} AND PHONE LIKE '${phone}'; 
</select>

<insert id="insertPerson" parameterType="org.application.vo.Person">
insert into Person (id, name, email, phone)
values (#{id}, #{name}, #{email}, ${phone})
</insert>
 
<update id="updatePerson" parameterType="org.application.vo.Person">
update Person set phone = ${phone}
where id = #{id}
</update>
 
 
<delete id="deletePerson" parameterType="int">
delete from Person where id = ${id}
</delete>

Java Ring Buffer

Ring Buffer 是一个固定容量大小的数组。维护两个下标,一个写入,一个读取。
类似一个环,怎么计算写入和读取的下标,有2种方式实现:
1.记录当前的已经写入的数量
2.记录一个反转标记

//记录写入数量,实现RingBuffer
public class RingBufferFillCount {

    public Object[] elements = null;
    private int capacity = 0;
    private int writePos = 0;
    private int available = 0;

    public RingBufferFillCount(int capacity) {
        this.capacity = capacity;
        this.elements = new Object[capacity];
    }

    public void reset() {
        this.writePos = 0;
        this.available = 0;
    }

    public int getCapacity() {
        return capacity;
    }

    public int getAvailable() {
        return available;
    }

    public int remainingCapacity() {
        return this.capacity - this.available;
    }

    public boolean put(Object element) {
        if (available < capacity) {
            if (writePos >= capacity) {
                writePos = 0;
            }
            elements[writePos] = element;
            writePos++;
            available++;
            return true;
        }
        return false;
    }

    public Object take() {
        if (available == 0) {
            return null;
        }
        int nextSlot = writePos - available;
        if (nextSlot < 0) {
            nextSlot += capacity;
        }
        Object nextObj = elements[nextSlot];
        available--;
        return nextObj;
    }

    public int put(Object[] newElements) {
        return put(newElements, newElements.length);
    }

    public int put(Object[] newElements, int length) {
        int readPos = 0;
        if (this.writePos > this.available) {
            //直接追加
            if (length <= this.capacity - this.writePos) {
                for (; readPos < length; readPos++) {
                    this.elements[this.writePos++] = newElements[readPos];
                }
                this.available += readPos;
                return length;
            } else {
                //需要在数组尾部和头部添加
                for (; this.writePos < this.capacity; this.writePos++) {
                    this.elements[this.writePos] = newElements[readPos++];
                }
                //重置写入下标
                this.writePos = 0;
                //计算还能写入多少
                int endPos = Math.min(length - readPos, capacity - available - readPos);
                for (; this.writePos < endPos; this.writePos++) {
                    this.elements[this.writePos] = newElements[readPos++];
                }
                this.available += readPos;
                return readPos;
            }
        } else {
            int endPos = this.capacity - this.available + this.writePos;
            for (; this.writePos < endPos; this.writePos++) {
                this.elements[this.writePos] = newElements[readPos++];
            }
            this.available += readPos;
            return readPos;
        }
    }

    public int take(Object[] into) {
        return take(into, into.length);
    }


    public int take(Object[] into, int length) {
        int intoPos = 0;

        if (available <= writePos) {
            int nextPos = writePos - available;
            int endPos = nextPos + Math.min(available, length);
            for (; nextPos < endPos; nextPos++) {
                into[intoPos++] = this.elements[nextPos];
            }
            this.available -= intoPos;
            return intoPos;
        } else {
            int nextPos = writePos - available + capacity;
            int leftInTop = capacity - nextPos;
            if (length <= leftInTop) {
                //copy directly
                for (; intoPos < length; intoPos++) {
                    into[intoPos] = this.elements[nextPos++];
                }
                this.available -= length;
                return length;
            } else {
                //copy top
                for (; nextPos < capacity; nextPos++) {
                    into[intoPos++] = this.elements[nextPos];
                }
                //copy bottom - from 0 to writePos
                nextPos = 0;
                int leftToCopy = length - intoPos;
                int endPos = Math.min(writePos, leftToCopy);
                for (; nextPos < endPos; nextPos++) {
                    into[intoPos++] = this.elements[nextPos];
                }
                this.available -= intoPos;
                return intoPos;
            }
        }
    }
}
//使用反转标记,实现RingBuffer
public class RingBufferFlipMarker {

    public Object[] elements = null;

    public int capacity = 0;
    public int writePos = 0;
    public int readPos = 0;
    public boolean flipped = false;

    public RingBufferFlipMarker(int capacity) {
        this.capacity = capacity;
        this.elements = new Object[capacity];
    }

    public void reset() {
        this.writePos = 0;
        this.readPos = 0;
        this.flipped = false;
    }

    public int remainingCapacity() {
        if (!flipped) {
            return capacity - writePos;
        }
        return readPos - writePos;
    }

    public int available() {
        if (!flipped) {
            return writePos - readPos;
        }
        return capacity - (writePos - readPos);
    }

    public boolean put(Object element) {
        if (!flipped) {
            if (writePos == capacity) {
                writePos = 0;
                flipped = true;
                if (writePos < readPos) {
                    elements[writePos++] = element;
                    return true;
                } else {
                    return false;
                }
            } else {
                elements[writePos++] = element;
                return true;
            }
        } else {
            if (writePos < readPos) {
                elements[writePos++] = element;
                return true;
            } else {
                return false;
            }
        }
    }

    public Object take() {
        if (!flipped) {
            if (readPos < writePos) {
                return elements[readPos++];
            } else {
                return null;
            }
        } else {
            if (readPos == capacity) {
                readPos = 0;
                flipped = false;
                if (readPos < writePos) {
                    return elements[readPos++];
                } else {
                    return null;
                }
            } else {
                return elements[readPos++];
            }
        }
    }

    public int put(Object[] newElements, int length) {
        int newElementsReadPos = 0;
        if (!flipped) {
            //readPos lower than writePos - free sections are:
            //1) from writePos to capacity
            //2) from 0 to readPos
            if (length <= capacity - writePos) {
                //new elements fit into top of elements array - copy directly
                for (; newElementsReadPos < length; newElementsReadPos++) {
                    this.elements[this.writePos++] = newElements[newElementsReadPos];
                }
                return newElementsReadPos;
            } else {
                //new elements must be divided between top and bottom of elements array
                //writing to top
                for (; this.writePos < capacity; this.writePos++) {
                    this.elements[this.writePos] = newElements[newElementsReadPos++];
                }
                //writing to bottom
                this.writePos = 0;
                this.flipped = true;
                int endPos = Math.min(this.readPos, length - newElementsReadPos);
                for (; this.writePos < endPos; this.writePos++) {
                    this.elements[writePos] = newElements[newElementsReadPos++];
                }
                return newElementsReadPos;
            }
        } else {
            //readPos higher than writePos - free sections are:
            //1) from writePos to readPos
            int endPos = Math.min(this.readPos, this.writePos + length);
            for (; this.writePos < endPos; this.writePos++) {
                this.elements[this.writePos] = newElements[newElementsReadPos++];
            }
            return newElementsReadPos;
        }
    }

    public int take(Object[] into, int length) {
        int intoWritePos = 0;
        if (!flipped) {
            //writePos higher than readPos - available section is writePos - readPos
            int endPos = Math.min(this.writePos, this.readPos + length);
            for (; this.readPos < endPos; this.readPos++) {
                into[intoWritePos++] = this.elements[this.readPos];
            }
            return intoWritePos;
        } else {
            //readPos higher than writePos - available sections are
            //top + bottom of elements array
            if (length <= capacity - readPos) {
                //length is lower than the elements available at the top
                //of the elements array - copy directly
                for (; intoWritePos < length; intoWritePos++) {
                    into[intoWritePos] = this.elements[this.readPos++];
                }
                return intoWritePos;
            } else {
                //length is higher than elements available at the top of the elements array
                //split copy into a copy from both top and bottom of elements array.
                //copy from top
                for (; this.readPos < capacity; this.readPos++) {
                    into[intoWritePos++] = this.elements[this.readPos];
                }
                //copy from bottom
                this.readPos = 0;
                this.flipped = false;
                int endPos = Math.min(this.writePos, length - intoWritePos);
                for (; this.readPos < endPos; this.readPos++) {
                    into[intoWritePos++] = this.elements[this.readPos];
                }
                return intoWritePos;
            }
        }
    }
}

数据分析 – 数据化汇报

场景:
1. 展示:数据分析结果可视化
2. 汇报:用数据向上级汇报工作成果
3. 说服:用数据高效说服别人

展示:
1.比较类图表:柱形图,条形图 描述数据对比关系
2.组合类图表:扇形图,饼图 描述数据的占比
3.描述类图表:散点图,气泡图 呈现数据分布情况

汇报:
向领导展示成绩 这件事情存在一些误区:
1.觉得领导什么都知道,其实他很难知道,管理者花一半的时间花在管理上面,这一半里面,一般有一多半在跟他的上级以及跨部门的沟通,所以他花在自己团队时间只有1/4,这些时间需要平均到6-8个下属,到个人只有3%,所以领导不知道你在干什么,需要跟他说,千里马常有而伯乐不常有,只是千里马不出众。
2.把苦劳当功劳,只说做了什么事,没有量化结果,价值不能量化,公司就不能给你定价,找到你的工作,与公司量化目标的关联。
公司的量化目标有哪些?无非就4类:
1.财务目标
流水,收入,利润,成本,现金流
2.客户目标
客户量,满意度,活跃度,转化率,复购率,品牌知名度,等等
3.运营目标
与公司价值链各项流程相关,比如研发周期,库存周转率,等等
4.组织目标
关键人才流失率,人才敬业度,人才匹配度

说服:

先看一个案例

公司销售经理,发现这个月市场投放过来的用户,很多不是目标用户,希望市场部同事能够调整渠道,以便获取更加精准的用户,来提升销售业绩,下面2种沟通方式:

第一种:

“这个月我们的销售目标没有完成,销售转化率下降了20%,接通率和愿意再聊的客户,平均下降了25%,我们发现市场部获取的目标用户不是很匹配,希望市场部同事能够调整策略,找到更匹配的客户渠道”

第二种:

“今年公司的整体销售目标,月度环比要增长10%,但是我们这个月的目标没有完成,做了全面分析之后,我们发现,虽然市场部提供的销售线索增加了10%,但是销售转化率却下降了20%,从过程指标来看,接通率和愿意再沟通的客户比例,平均下降了25%,这导致我们的销售在没有意向的客户上花费时间。我们需要更精准的目标客户,所以希望和市场团队就用户画像讨论一下,优化渠道策略”

第二种表达,多了一些故事起伏,不是单纯的列数据。

当我们在说服他人的时候,我们需要故事性表达,才能让对方集中注意以及产生认同,这里有一个常用的故事性表达结构:SCQA

SCQA 是四个英文单词缩写:

S是Situation 背景,也就是事情发生的背景信息是什么
C是Complication 复杂性,也就是实际情况和理想情况的差距是什么
Q是Question 问题,也就是当务之急需要解决什么问题
A是Answer 答案,我提出的解决方案是什么

在SCQA的每个要素之下,都有一个观点,把你数据分析的结果,数据可视化图表呈现在这个观点下面,去支持你的观点。

总结:

1)完全一致的数据,改变展现方式,就会改变和扭转一个人对数据的解读和判断

2)工作中常用三种图表

3)通过把自己工作和公司目标关联,量化自己的工作,才能让老板认识到你的价值

4)公司目标可以从这4个方面来找:财务目标,客户目标,运营目标,组织目标

5)与同事沟通时,通过SCQA 故事框架,用数据来佐证框架中的每个观点,就能更有说服力

6)学会使用SCQA来表达

数据分析 – 相关分析法

数据分析 – 相关分析法

目前我们已经知道了,在工作中,我们可以用对比分析来发现并定位问题;用拆解分析,将复杂问题简单化,帮我们找到小的切入点。

但是这样就会存在,同一个问题,我们有时候会找到很多小的切入点。

这个时候需要相关分析,来使用数据判断这些因素是否和最终结果相关,以及相关密切程度高低,据此判断工作优先级。

相关的定义?什么是相关?

相关,就是指两个变量,当一个变量发生变化的时候,另一个变量也跟着变化。比如:工资随着工作年限发生变化,销售量随着价格发生变化,体重随着身高发生变化,等等。

相关分析定义?什么是相关分析?

相关分析,就是去找到两种变量之间的关系。也就是B是怎么随着A的变化产生变化的。

怎么使用相关分析? 相关分析的三大场景:

1、快速锁定大问题相关的一个个小因素,

案例:一位TED演讲嘉宾,她想知道线上交友网站上,怎么设置自己信息才能最好的呈现自己,让自己在社交网站上非常受欢迎。于是,她收集了大量女性账户的行为数据,包含幽默感,语调,声音,沟通方式,个人描述的平均长度,与男性发私信之间隔多久等等。 然后,把这些数据和对应女性受欢迎的程度进行相关分析。发现:最受欢迎的女性都在用诸如“有趣”,“女孩“,”爱“ 这样的关键词,自我介绍一般97个词,以及她们会等23个小时进行下一次沟通。掌握这些信息之后,她建立了一个超级档案,还是本人真实信息,按照分析结果,重新包装,结果她成了网站最受欢迎的人。而且在众多男性里面发现了潜在的”白马王子“,三年之后他们结婚了。

2、用数据说服他人,终结撕逼

除了给自己的工作进行问题分析与定位之外,相关分析可以用在沟通方面,比如:当你在和领导汇报的时候,常常就一件事情进行原因阐述和分析,这个时候每个人都有自己经验和主见,很难达成一致结果,这时候使用相关分析进行验证,就很容易有说服力。

案例:市场部负责投放市场预算在一些渠道,来拉目标用户,而销售负责把产品卖给用户。这个时候,市场和销售会经常撕逼,销售会说:因为市场渠道质量不行,导致销售业绩差,市场会说:销售能力不行,我的渠道质量杠杠的。这个时候就需要采用相关分析,对渠道和销售业绩做一个相关分析,用数据说话,很有说服力。

3、帮我们判断事情优先级

比如,影响销量有非常多的因素,要提升销量,应该做那个?我们就看相关度最高的,因为相关分析得出一个相关系数,根据分值高低判断相关程度,这样就能确定最相关因素,来优先改进和提升。

那么相关分析怎么用了?相关都有哪些类型?

相关可以分为:线性相关,非线性相关

线性相关: y = ax + b , 相关程度就是求斜率。
可以使用Excel或者Python 画出散点图,

相关度R定义: (求出斜率之后 计算数据的拟合度)
1.绝对值大小: 表示相关程度
2.正负:表示正相关还是负相关
3.数值范围:-1 ~ 1
4.数值定义:-1表示负相关,1表示相关,0表示非线性相关(可能其他形式相关)

相关度经验值:
1.绝对值 R >= 0.6 高度相关
2.绝对值 0.3 =< R < 0.6 中度相关
3.绝对值 R < 0.3 弱相关

经典案例:
谷歌收集了美国从2003 到 2008 年期间,流感传播时间和地点,匹配当地,当时人们使用谷歌搜索关键词,寻找其中关系。成功预测了2009冬季流感的传播趋势,甚至可以精确预测流感发生的地区。 根据搜索词语和流感传播关系,建立数据模型,能够提前2周预测大规模流感传播,让人们提前预防。

Facebook,People You May Know, 好友推荐,“你可能认识的人”,这个习以为常的功能,是facebook提升用户留存,进行相关分析,发现有更多的好友,用户粘性越高,越活跃。

相关分析应用范围很广,但是不能滥用,比如我们分析孩子长高的原因不能联想到院子里面的树也长高了。二者虽然都长高了,但是二者并没有相关。

总结:

1)分析问题本质就是对大问题拆解
2)相关分析,去找到2个变量之间的关系
3)相关分析三个场景:1.锁定大问题相关因素 2.数据说服,停止撕逼 3.判断事情优先级
4)了解应用场景,知行合一才能完美解决问题
5)相关分为:线性和非线性
6)相关分析可以做预测
7)因果关系才是相关分析的目标