tonglin0325的个人主页

go学习笔记——go-redis

官方文档

https://pkg.go.dev/github.com/go-redis/redis/v8#section-readme

添加依赖

1
2
3
go get github.com/go-redis/redis/v8
go get github.com/go-redis/redis/extra/redisotel/v8

初始化client

1
2
3
4
5
6
7
8
9
10
11
12
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})

client.AddHook(&redisotel.TracingHook{})
if err := client.Ping(context.Background()).Err(); err != nil {
logger.Error("redis connect failed, err:", zap.Any("err", err))
panic("failed to connect redis")
}

set key

1
2
3
4
5
err = rdb.Set(ctx, "key", 10, time.Hour).Err()
if err != nil {
fmt.Println(err)
}

get key

1
2
3
4
5
6
7
result := client.Get(ctx, "key")
str, err := result.Result()
if err != nil {
fmt.Println(err)
}
fmt.Println(str)

参考:Go语言操作Redis

全文 >>

JavaWeb学习笔记——HelloWord Servlet

1.实现servlet打印日志

开发一个动态web资源,即开发一个Java程序向浏览器输出数据,需要完成以下2个步骤:

1.编写一个Java类,实现Servlet接口

开发一个动态web资源必须实现javax.servlet.Servlet接口,Servlet接口定义了Servlet引擎与Servlet程序之间通信的协议约定

以下是MyServlet.java文件中的代码(写的这个类的名字叫做MyServlet):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package org.MyServlet.MyServlet;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

//开发一个动态web资源必须实现javax.servlet.Servlet接口
//Servlet接口定义了Servlet引擎与Servlet程序之间通信的协议约定

//Q:MyServlet完成了一个动态网页程序,或者说是一个功能,如何让客户端能否准确得找到我们得这个Servlet服务
//A:服务器需要预先为我们预留出扩展接口,我们只需要按照一定的规则去提供相应的扩展功能

//Q:如何和服务器进行通讯
//A:web.xml就是服务器提供给我们的完成功能的地方
public class MyServlet implements Servlet{

@Override
public void destroy() {
// TODO Auto-generated method stub

}

@Override
public ServletConfig getServletConfig() {
// TODO Auto-generated method stub
return null;
}

@Override
public String getServletInfo() {
// TODO Auto-generated method stub
return null;
}

@Override
public void init(ServletConfig arg0) throws ServletException {
// TODO Auto-generated method stub

}

//所有客户端请求会自动调用Service方法进行处理
//ServletRequest 是一个对象,封装所有HTTP请求信息
//ServletResponse 是一个对象,封装所有HTTP响应信息
//这两个对象是Tomcat服务器给我们的
@Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
// TODO Auto-generated method stub

System.out.println("执行 MyServlet 的 service() 方法。。。。。");
}

}

关于其中的Service方法的一些Tip:  

1
2
3
4
5
6
7
//所有客户端请求会自动调用Service方法进行处理
//ServletRequest 是一个对象,封装所有HTTP请求信息
//ServletResponse 是一个对象,封装所有HTTP响应信息
//这两个对象是Tomcat服务器给我们的

此外,如果是只实现service方法,则称为适配器模式

以下是web.xml文件中的代码:



MyServlet

index.html
index.htm
index.jsp
default.html
default.htm
default.jsp


//定义一个Servlet服务
//Servlet服务的名字叫做aaa

aaa
org.MyServlet.MyServlet.MyServlet


//定义一个Servlet服务的映射关系
//Servlet服务的名字叫做aaa
//请求的路径是/myServlet.do

//1.服务器启动模式加载webapps下面所有的应用,加载web应用的时候会读取每个应用的web。xml文件
//2.客户单发送请求http://127.0.0.1:8080/MyServlet/myServlet.do
//3.请求就找到http://127.0.0.1:8080,找到MyServlet(Context)
//去mapping里面查找/myServlet.do,如果找到,定位到aaa
//4.去Servlet的定义里面查找Servlet-name是aaa的Servlet服务
//然后定位到org.MyServlet.MyServlet.MyServlet,执行该class的service方法

aaa
/myServlet.do

然后在浏览器中输入

1
2
http://127.0.0.1:8080/MyServlet/myServlet.do

便可以在Tomcat中看到

1
2
执行 MyServlet 的 service() 方法。。。。。

2.把开发好的Java类部署到web服务器中

全文 >>

代理前端请求到本地服务

在开发过程中,有时候我们需要将前端的请求(当然也可以是部分请求)代理到我们的本地开发环境的服务中进行调试,下面借助whistle+SwitchyOmega来实现这个功能

1.安装whistle#

1
2
3
4
5
6
7
8
9
npm install whistle -g --registry=https://registry.npmmirror.com

added 130 packages in 8s
npm notice
npm notice New major version of npm available! 9.1.2 -> 10.5.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.5.0
npm notice Run npm install -g npm@10.5.0 to update!
npm notice

启动

1
2
3
4
5
6
7
8
9
10
11
w2 start

[i] whistle@2.9.66 started
[i] 1. use your device to visit the following URL list, gets the IP of the URL you can access:
http://127.0.0.1:8899/
http://192.168.8.188:8899/
Note: If all the above URLs are unable to access, check the firewall settings
For help see https://github.com/avwo/whistle
[i] 2. set the HTTP proxy on your device with the above IP & PORT(8899)
[i] 3. use Chrome to visit http://local.whistlejs.com/ to get started

启动后访问http://127.0.0.1:8899/界面如下

2.浏览器安装#

将proxy设置成代理到whistle的8899端口,并将127.0.0.1的本地代理限制删掉

并将改proxy启动

3.代理前端请求#

比如我们要代理https://xx.com/api/abc的接口代理到我们本地的http://127.0.0.1/api/v2/abc的服务上,这时我们应该如下配置

1
2
https://xx.com/api/abc 127.0.0.1/api/v2/abc/

如果是http协议,在启动点击OFF来启动whistle就可以实现代理功能了

如果是https协议,所以我们还需要点击HTTPS来添加HTTPS证书和勾选相关选项

参考:whistle+Proxy SwitchyOmega代理前端页面接口到本地开发服务

https://wproxy.org/whistle/

MySQL学习笔记——SQL注入

1.常见SQL注入的方法#

假设我们使用goland的GORM框架写了以下面SQL

1
2
3
4
5
6
err := u.data.db.Raw("select id, username, email from user where username = '" + s + "'").Scan(&user).Error
if err != nil {
u.log.Error(fmt.Sprintf("find user by id username fail, error: %v", err))
return nil, err
}

如果正常的查询参数的值为test123,请求如下接口传入该值

1
2
http://localhost:8080/api/v1/user?username=test123

接口输出的结果为

1
2
3
4
5
6
7
8
9
10
11
12
{
"Code": 200,
"Msg": "find user by username success",
"Data": [
{
"id": 18,
"username": "test123",
"email": "test@test123"
}
]
}

但是使用字符串拼接来实现查询逻辑的话,很容易被人使用SQL注入的方法进行攻击

1.Error-based#

基于错误的SQL注入主要是用于获得数据的相关信息,方便进行后序的攻击,比如输入单引号 ‘

1
2
http://localhost:8080/api/v1/user?username='

此时从接口的返回值中就可以知道使用的是MySQL数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Code": 500,
"Msg": "find user by username fail",
"Data": {
"Number": 1064,
"SQLState": [
52,
50,
48,
48,
48
],
"Message": "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''''' at line 1"
}
}

2.数字型#

比如使用数组id来请求用户信息的接口

1
2
http://localhost:8080/api/v1/user/1

如果使用3-2可以查询到id=1的用户信息,表示可以使用数字型SQL注入

1
2
http://localhost:8080/api/v1/user/3-2

参考:【第九天 - 數字型 SQL注入】

3.布尔型#

可以通过布尔表达式来判断猜测的数据是否最正确

1
2
<原SQL語法> and length(database())>=1--

参考:【第十一天 - 布林SQL盲注】

4.Union-based#

基于union的SQL注入可以通过拼接上UNION语句来实现SQL注入,比如输入’ union all select 123,system_user(),user()%23,其中%23是#

1
2
http://localhost:8080/api/v1/user?username=' union all select 123,system_user(),user()%23

最终执行的SQL是

1
2
select id, username, email from user where username = '' union all select 123,system_user(),user()#'

此时从接口的返回值中可以查询到数据库的用户名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"Code": 200,
"Msg": "find user by username success",
"Data": [
{
"id": 19,
"username": "",
"email": ""
},
{
"id": 123,
"username": "root@172.17.0.1",
"email": "root@172.17.0.1"
}
]
}

5.Time-based#

基于sleep函数的SQL注入可以通过拼接上sleep函数来实现sql注入,配合上IF语句可以通过sleep函数是否执行来判断IF条件是否正确(例如在没有权限使用database()等函数的情况下猜测库名表名等),

比如输入test123’ and sleep(5)%23,其中%23是#

1
2
http://localhost:8080/api/v1/user?username=test123' and sleep(5)%23

最终执行的SQL是

1
2
select id, username, email from user where username = 'test123' and sleep(5)#'

注入成功的话,请求会延时5秒之后再返回

参考:SQL注入-时间盲注整理

6.堆叠型#

1
2
<原SQL語法>;DROP DATABASE 資料庫名

参考:【第十四天 - 堆疊型 SQL注入】

2.防范SQL注入的方法#

1.使用参数化#

不使用字符串拼接的方式

1
2
3
4
5
6
err := dao.User.Where(dao.User.Username.Eq(s)).Scan(&amp;user)
if err != nil {
u.log.Error(fmt.Sprintf("find user by id username fail, error: %v", err))
return nil, err
}

2.输入过滤#

通过检查SQL中有某些特殊意思的字符来防止SQL注入,比如

1
2
3
4
5
6
7
8
9
\
;
'
"
`
--
#
/* */

参考:秒懂 SQL Injection

Mybatis学习笔记——mybatis-generator

通用mabatis-generator可以由mysql表自动生成model类,mapper映射文件和mapper接口,参考:MyBatis通用Mapper和PageHelper

1.依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- mybatis -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>3.3.9</version>
</dependency>

2.插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- mybatis-generator -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<configurationFile>src/main/resources/generator-config.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>3.3.9</version>
</dependency>
</dependencies>
</plugin>

3.在application.properties中配置数据库相关参数

1
2
3
4
5
6
# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&amp;characterEncoding=utf-8&amp;useLegacyDatetimeCode=false&amp;serverTimezone=Hongkong&amp;zeroDateTimeBehavior=convertToNull
spring.datasource.username=root
spring.datasource.password=xxxx
spring.datasource.driver=com.mysql.jdbc.Driver

4.配置generator-config.xml,其中通用MyMapper参考:Mybatis学习笔记——通用mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
<!-- 引用外部配置文件-->
<properties resource="application.properties" />

<context id="context" targetRuntime="MyBatis3Simple">
<!-- MapperPlugin -->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<!-- 该配置会使生产的Mapper自动继承MyMapper -->
<property name="mappers" value="com.example.demo.core.mapper.MyMapper" />
<!-- caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true -->
<property name="caseSensitive" value="false"/>
</plugin>
<!-- 去掉生成出来的代码的注解 -->
<commentGenerator>
<property name="suppressAllComments" value="true" />
<property name="suppressDate" value="true" />
</commentGenerator>
<!-- 数据库信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="${spring.datasource.url}"
userId="root"
password="xxxx">
</jdbcConnection>
<!-- 生成Model类的包名和位置 -->
<javaModelGenerator targetPackage="com.example.demo.model" targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 生成Mapper映射文件的包名和位置 -->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- 生成Mapper接口的包名和位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.example.demo.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 要生成代码的表 tableName=%时为所有表生成 表名字段名都有默认规则生成 -->
<table tableName="%">
<!-- 指定生成的主键属性名 生成SQL语句的类型 -->
<generatedKey column="id" sqlStatement="MySql"/>
</table>
</context>

</generatorConfiguration>

5.生成,点击plugin中的mabatis-generator:generate

6.自动生成model,mapper和xml

7.添加

全文 >>

SpringBoot学习笔记——kaptcha

kaptcha是一个java验证码生成框架,可以和spring集成用于验证码服务

和spring集成的官方文档

1
2
https://code.google.com/archive/p/kaptcha/wikis/SpringUsage.wiki

1.依赖

1
2
3
4
5
6
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>

2.kaptcha生成验证码的配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

@Configuration
public class KaptchaConfig {

/**
* 验证码生成器参数
*/
@Bean
public DefaultKaptcha captchaProducer() {
DefaultKaptcha captchaProducer = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "100");
properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "30");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "22");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "6");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "LIGHT_GRAY");
properties.setProperty(Constants.KAPTCHA_BACKGROUND_CLR_FROM, "WHITE");
properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
properties.setProperty(Constants.KAPTCHA_SESSION_CONFIG_KEY, "checkCode");
Config config = new Config(properties);
captchaProducer.setConfig(config);
return captchaProducer;
}

}

配置的含义参考

1
https://code.google.com/archive/p/kaptcha/wikis/ConfigParameters.wiki

验证码图片生成接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;

@Slf4j
@Controller
@RequestMapping("/kaptcha")
public class KaptchaController {

@Autowired
private Producer captchaProducer;

@RequestMapping("/getCode")
public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setDateHeader("Expires", 0);
// Set standard HTTP/1.1 no-cache headers.
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
// Set standard HTTP/1.0 no-cache header.
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
// create the text for the image
String capText = captchaProducer.createText();
log.info("******************验证码是: " + capText + "******************");
// store the text in the session
request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
// create the image with the text
BufferedImage bi = captchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
// write the data out
ImageIO.write(bi, "jpg", out);
try {
out.flush();
} finally {
out.close();
}
return null;
}
}

验证码校验和参数含义参考:Google-kaptcha验证码使用步骤(基于springboot/使用redis存储)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
kaptcha.border                     	| Border around kaptcha. Legal values are yes or no.                     			| yes 
kaptcha.border.color | Color of the border. Legal values are r,g,b (and optional alpha) or white,black,blue. | black
kaptcha.border.thickness | Thickness of the border around kaptcha. Legal values are > 0. | 1
kaptcha.image.width | Width in pixels of the kaptcha image. | 200
kaptcha.image.height | Height in pixels of the kaptcha image. | 50
kaptcha.producer.impl | The image producer. | com.google.code.kaptcha.impl.DefaultKaptcha
kaptcha.textproducer.impl | The text producer. | com.google.code.kaptcha.text.impl.DefaultTextCreator
kaptcha.textproducer.char.string | The characters that will create the kaptcha. | abcde2345678gfynmnpwx
kaptcha.textproducer.char.length | The number of characters to display. | 5
kaptcha.textproducer.font.names | A list of comma separated font names. | Arial, Courier
kaptcha.textproducer.font.size | The size of the font to use. | 40px.
kaptcha.textproducer.font.color | The color to use for the font. Legal values are r,g,b. | black
kaptcha.textproducer.char.space | The space between the characters | 2
kaptcha.noise.impl | The noise producer. | com.google.code.kaptcha.impl.DefaultNoise
kaptcha.noise.color | The noise color. Legal values are r,g,b. | black
kaptcha.obscurificator.impl | The obscurificator implementation. | com.google.code.kaptcha.impl.WaterRipple
kaptcha.background.impl | The background implementation. | com.google.code.kaptcha.impl.DefaultBackground
kaptcha.background.clear.from | Starting background color. Legal values are r,g,b. | light grey
kaptcha.background.clear.to | Ending background color. Legal values are r,g,b. | white
kaptcha.word.impl | The word renderer implementation. | com.google.code.kaptcha.text.impl.DefaultWordRenderer
kaptcha.session.key | The value for the kaptcha is generated and is put into the HttpSession. | KAPTCHA_SESSION_KEY
This is the key value for that item in the session.
kaptcha.session.date | The date the kaptcha is generated is put into the HttpSession. | KAPTCHA_SESSION_DATE
This is the key value for that item in the session.

效果

校验的原理:

生成验证码的时候返回一个uuid给浏览器,同时将uuid作为key,验证码作为value存在服务器session当中,比如redis,前端提交验证码的时候将uuid带上,查询redis进行验证

全文 >>

数据仓库建模的一些理论

1.数据分层#

数据明细层:DWD(Data Warehouse Detail)

数据中间层:DWM(Data WareHouse Middle)

数据服务层:DWS(Data WareHouse Servce)

数据应用层:ADS(Application Data Service)

2.数仓建模方法#

在数据仓库模型中,星型模型和雪花型模型是两个常用的设计模式。参考:数据仓库系列:星型模型和雪花型模型

1.星型模型#

星型模型是一种简单的数据仓库模型,也是最常见的模型之一。在星型模型中,中心表(称为业务事实表)连接到几个维度表(称为业务维度表)。维度表中包含了业务的各个特征,如时间、区域、产品等。

在 SQL 中,我们可以使用以下语句来创建一个星型模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
CREATE TABLE fact_sales ( # 都是key
sales_id INT PRIMARY KEY,
date_key INT,
product_key INT,
store_key INT,
sales_amount DECIMAL(15,2)
);

CREATE TABLE dim_date (
date_key INT PRIMARY KEY,
date_full DATE,
year INT,
quarter INT,
month INT,
day_of_week CHAR(9),
holiday VARCHAR(32)
);

CREATE TABLE dim_product (
product_key INT PRIMARY KEY,
product_name VARCHAR(128),
category VARCHAR(32),
subcategory VARCHAR(32)
);

CREATE TABLE dim_store (
store_key INT PRIMARY KEY,
store_name VARCHAR(128),
city VARCHAR(32),
state VARCHAR(2),
country VARCHAR(64)
);

2.雪花型模型#

雪花型模型是在星型模型基础上的扩展,因其形似雪花而得名。这种模型在星型模型的基础上,将维度表拆分成更小的表形式,形成多层表的结构。

雪花型模型:当有一个或多个维表没有直接连接到事实表上,而是通过其他维表连接到事实表上时,其图解就像多个雪花连接在一起,故称雪花模型。雪花模型是对星型模型的扩展。它对星型模型的维表进一步层次化,原有的各维表可能被扩展为小的事实表,形成一些局部的 “层次 “ 区域,这些被分解的表都连接到主维度表而不是事实表。

雪花型模型通过最大限度地减少数据存储量以及联合较小的维表来改善查询性能。雪花型结构去除了数据冗余。

星型模型对比雪花模型#

1、查询性能角度来看

在OLAP(Hive)建议用星型模型,Hive表通过大量的冗余来提升查询效率,星型模型对OLAP的分析引擎支持比较友好;而雪花型要做多个表联接,性能会低于星型架构

在OLTP(MySQL)建议用雪花模型,由于雪花型架构更有利于度量值的聚合,因此性能要高于星型架构,所以雪花模型在关系型数据库中如MySQL,Oracle中非常常见

2、模型复杂度角度

星型架构更简单方便处理

3、层次结构角度

雪花型架构更加贴近OLTP系统的结构,比较符合业务逻辑,层次比较清晰。

4、存储角度

雪花型架构具有关系数据模型的所有优点,不会产生冗余数据,而相比之下星型架构会产生数据冗余。

3.Data Vault 模型#

Data Vault 是另一种数据仓库建模方法,是 Dan Linstedt 在 20 世纪 90 年代提出的,主要用于企业级的数据仓库建模

Data Vault 模型由中心表(Hub)、链接表(Link)、附属表(Satellite)三个主要组成部分。其中,中心表是核心,用于存储业务主键,链接表记录业务关系,附属表记录业务描述。

(1)中心表

中心表用来存储企业每个业务实体的业务主键,业务主键唯一标识某个业务实体。中心表和源系统是相互独立的,即无论业务主键是否用于多个业务系统,它在 Data Vault 中只保留一份,其他的组件都链接到这一个业务主键上。

出于设计上的考虑,中心表一般由主键、业务主键、装载时间戳、数据来源系统四个字段组成。其中主键是系统生成的代理键,仅供内部使用。

(2)链接表

链接表是不同中心表的链接。一个链接表一般在两个或多个中心表之间有关联。一个链接表通常是一个外键,表示一种业务关系,比如:交易表、客户关联账户等。

链接表主要包括主键、外键1、……、外键n、装载时间戳、数据来源系统等字段构成,其中主键对应多个外键的唯一组合,一般是与业务无关的序列数值。

(3)附属表

附属表用来保存中心表和链接表的描述属性,包含所有历史变化数据,附属表有且仅有一个唯一外键关联到中心表或链接表。

附属表主要包括主键、外键、属性1、……、属性n、装载时间、失效时间、数据来源系统,主键用于唯一标识附属表中的一行记录,一般是与业务无关的序列数值。

参考:数据仓库进阶之路