2021-03-17
- 查看集群
select * from system.clusters
- 分片shard是指包含数据不同部分的服务器。要读取所有数据,必须访问所有分片。
- 副本replica是存储复制数据的服务器,要读取所有数据,访问一个切片上的任意副本上的数据即可。
集群部署
#
-
在群集的所有机器上安装clickhouse服务端
-
在配置文件中设置群集配置
<remote_servers>
<perftest_1shards_1replicas>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
</perftest_1shards_1replicas>
</remote_servers>
-
在集群上创建本地表
-
在集群上创建分布式表
- 分布式表实际上是一种视图(view),从分布式表中执行select查询会使用集群所有分片的资源。
internal_replication
:
- 当参数为true时,写操作只选择一个正常的副本写入数据。如果表是复制表Replicated*Merge,请使用此方案。
- 当为false时,写操作会将数据写入所有副本。因为不会检查副本一致性,随着时间的推移,副本数据可能不一样。
数据副本
#
- 对于INSERT和ALTER语句操作数据时会在压缩的情况下被复制。
- CREATE、DROP、ATTACH、DETACH和RENAME语句只会在单个服务器上执行,不会被复制。
- 如果配置文件中没有设置ZooKeeper,则无法创建复制表,并且现在任何现有的复制表都将变为只读。
创建复制表
#
CREATE TABLE table_name
(
EventDate DateTime,
CounterID UInt32,
UserID UInt32,
Ver UInt16
)ENGINE = ReplicatedReplacingMergeTree(zoo_path, replica_name, other_parameters)
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID)
2021-03-16
起步
#
mysite/
manage.py #管理django项目的命令行工具
mysite/ #python包,名字就是当引用它内部任何东西时需要用到的python包名
__init__.py #告诉python这个目录应该被认为是一个python包
settings.py #项目的配置文件
urls.py #url声明
asgi.py #运行在asgi兼容的web服务器上的入口
wsgi.py #运行在wsgi兼容的web服务器上的入口
- 运行项目的命令
python manage.py runserver
- 创建一个应用
python manage.py startapp polls
- 应用urls.py中使用到的函数path()有四个参数,两个必须参数route和view,两个可选参数kwargs和name
- route是一个匹配URL的准则,当响应一个请求时,他会从urlpatterns的第一项开始,按顺序依次匹配列表中的项。这些准则不会匹配get和post参数或域名
- view:当找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个HttpRequest对象作为第一个参数,被捕获的参数以关键字参数的形式传入。
- kwages:任意个关键字参数可作为一个字典传递给目标视图函数。
- name:为url取名
- 函数include()允许引用其他URLconfs
模型
#
设置mysite/sittings.py
#
- 设置时区
TIME_ZONE = 'Asia/Shanghai' USE_TZ = True
- 当启用了时区支持(即
USE_TZ = True
),Django将在数据库中以UTC存储日期信息,而在模板和表单中转化为最终用户的时区
- 设置数据库
INSTALLED_APPS
是在项目中启用的Django应用,这里面的某些应用需要数据表,可以用python manage.py migrate
生成
创建模型
#
- 模型设真实数据的描述,它包含了存储的数据所必要的字段和行为。
- Django的迁移代码是从模型文件中自动生成的
- 每个模型都是
django.db.models.Model
类的子类,每个模型有许多的类变量,他们都表示一个数据库的字段,每个字段都是Field类的实例,每个Field类实例变量的名字也是数据库的列名。
#polls/models.py
from django.db import models
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
激活和修改模型
#
- 首先把应用安装到项目中,在
INSTALLED_APPS
中添加polls.apps.PollsConfig
- 执行
python manage.py makemigrations polls
- 迁移数据存放在应用的
migrations/0001_initial.py
中,可以通过python manage.py sqlmigrate polls 0001
来查看对应的sql
- 运行
python manage.py migtate
来应用数据库迁移
- 通过
python manage.py shell
可进入当前项目对应的命令行
- 给模型增加
__str__
方法能带来一些方便
Django管理页面
#
- 首先创建一个能登陆管理界面的用户
python manage.py createsuperuser
- 将创建的models注册,以便后台可以管理
#polls/admin.py
from django.contrib import admin
from .models import Question
admin.site.register(Question)
- 运行项目
python manage.py runserver
视图view
#
- 向应用的views.py中添加视图函数,这些函数的第一个参数是request,也以有第二、第三个参数。每个视图必须做的只有返回一个包含请求页面内容的HttpResponse对象或者抛出一个异常
def detail(request, question_id):
context = {
'question_id': question_id,
}
#template = loader.get_template('xxx/xxxx.html')
#return HttpResponse(template.render(context, request))
return render(request, 'xxx/xxxx.html', context)
#为url名称添加命名空间
app_name = 'polls'
urlpatterns = [
#...
#int:是一个转换器,决定了应该以什么变量类型匹配这部分url路径
path('<int:question_id>/detail/', views.detail, name='detail'),
]
- 当其他人访问网站的某个页面时,比如’polls/34/detail’,django将会载入项目的urls.py模块,因为这在ROOT_URLCONF中配置了,然后django寻找名为urlpatterns变量并按顺序匹配正则表达式,在找到匹配项’polls/’,它切掉了匹配的文本’polls/’,将剩余文本'34/detail’发送至’polls.urls’中做进一步处理
模板
#
- 在应用路径下创建templates目录,项目的TEMPLATES配置项描述了django如何载入和渲染模板,默认将APP_DIRS设置为True,这一选项让DjangoTemplates能在每个INSTALLED_APPS文件夹中寻找templates子目录。
- 由于django能直接访问到templates文件夹,为了放置出现模板文件名冲突,最好的方法就是将它们放到一个与自身应用重名的子文件夹里
- 指向具有命名空间的视图
<a href="{% url 'polls:detail' question.id %}">{{ context.question_id }}</a>
- 无论何时,当需要创建一个改变服务器端数据的表单时,请使用post。
- django自带了一个非常有用的防御系统,所有针对内部url的post表单都应该使用
{% csrf_token %}
模板标签。
- request.POST是一个字典类型对象,可以通过关键字来获取提交的数据,获取的值永远是字符串。如果key提供错误,将会抛出一个KeyError。
- 函数reverse()能避免在视图函数中硬编码url,他需要我们给出想要跳转的视图的名字和该视图所对应的url模式中需要给该视图提供的参数。
2021-03-15
\d
匹配一个数字
\w
匹配一个数字或字母
.
匹配任意字符
*
匹配0到多个字符,+
匹配1到多个字符,?
匹配0或1个字符,{n}
表示n个字符,{n,m}
表示n-m个字符
\s
匹配一个空格(也包括tab)
\d{3}\s+\d* #匹配3个数字,后面至少有一个空格,再后面有可能有数字,有可能没数字
[0-9a-z\_] #匹配一个数字或一个小写字母或一个下划线
A|B
可匹配A或B
^
表示行的开头,^\d
表示必须以数字开头
$
表示行的结束,$\d
表示必须以数字结束
re模块
#
- 因为python字符串本身也用
\
转义,可以使用r
前缀,就可以不用考虑字符串转义了。r'ABC\-001'
re.match(r'\d{3}\-\d$'),'010-1')
,如果匹配成功就返回一个Mache对象,否则返回none。
re.split(r'[\s\,]+','a,b, c d')
,切割字符串,能切割出['a','b','c']
- 正则表达式还有提取字串的功能,用
()
表示的就是要提取的分组。r'(\d{3})-(\d{3,8})'
就定义了两个组,可以使用group(n)
方法提取出子串
贪婪匹配
#
- 正则默认的时贪婪匹配,加个
?
就可以采用非贪婪匹配。\d+?
r = re.match('(\d+?)(0*)$','1200000')
print(r.groups())
#结果('12', '00000')
2021-03-15
datetime
#
from datetime import datetime,timedelta
- 获取当前时间
datetime.now()
- 指定的某个日期和时间
datetime(2021,3,15,19,48)
- 将datetime类型转化为timestamp,(timestamp是一个浮点数,整数位表示秒)
datetime(2021,3,15,19,49).timestamp()
- timestamp转化为datetime
datetime.fromtimestamp(1615809156.889315)
- str转换为datetime
datetime.strptime('2021-3-15 19:55:59','%Y-%m-%d %H:%M:%S')
- datetime转str
datetime.now().strftime('%a,%b %d %H:%M')
- datetime加减
datetime.now() + timedelta(days = 1, hours = 10)
- 时区转换
utc_dt = datetime.utcnow()
utc_dt.astimezone(timezone(timedelta(hours=8)))
collections
#
- namedtuple:是一个函数,可以用来创建一个自定义的tuple对象,同时规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。
from collections import namedtuple
Point = nametuple('Point',['x', 'y'])
p = Point(1, 2)
- deque:双向列表,方法有
append()、pop()、appendleft()、popleft()
- defaultdict:和dict类似,但是在key不存在的时候返回一个默认值。
- OrderedDict:key会按照插入的顺序排序
- ChainMap:可以把一组dict串起来组成一个逻辑上的dict,本身也是一个dict,但在查找的时候,会按照顺序在内部的dict依次查找。
- Counter是一个计数器,可以统计字符出现的个数,实际上也是dict的子类
base64
#
- base64是一种用64个字符表示任意二进制数据的方法,64个字符指的是
A-Z, a-z, 0-9, +, /
。实现方法是将3个字节(3x8bit)的二进制数据编码为4字节(4x6bit,6bit的数据刚好能用上面这64个字符表示)的数据。6bit的数据刚好能用这64个字符表示。
- 编码
base64.b64encode(b'binary')
;解码base64.b64decode(b'binary')
hashlib
#
- 摘要算法又称为哈希算法,散列算法,它通过一个函数,把任意长度的数据转化为一个固定长度的数据串。它是一个单向函数,对原始数据做一点点的修改都会导致计算出的摘要完全不同。
import hashlib
md5 = hashlib.md5()
#也可以将串分开,多次调用update方法
md5.update("xiaoxiang.space".encode("utf-8"))
#md5结果是128bit,通常用32位的16进制表示
print(md5.hexdigest())
sha1 = hashlib.sha1()
sha1.update("xiaoxiang.space".encode("utf-8"))
#sha1的结果是160bit,通常用一个40位的16进制字符串表示
print(sha1.hexdigest())
- 无限迭代器
itertools.count()
- cycle()会把传入的一个序列无限重复下去
itertools.cycle('ABC')
- repeat()可以把一个元素无限重复下去,提供了第二个参数可以限定重复次数
itertools.repeat('A', 3)
- 可通过takewhile()根据函数条件来截取一个有限的序列。
- chain()可以把一组迭代对象串联起来,形成一个更大的迭代器。
itertools.chain('ABC', 'XYZ')
- groupby()可以把迭代器中相邻的重复元素挑出来放在一起。挑选规则是通过函数完成的,只要作用于函数的两个元素返回的值相等,这两个元素就被认为是一组的,函数的返回值作为组的key。
contextlib
#
- 任何对象,只要正确实现了上下文管理,就可以用于with语句。实现上下文管理是通过
__enter__
和__exit__
这两个方法实现的。可以通过contextlib标准库中的@contextmanager
来实现上下文管理(更简单)
- 有时候,我们希望在某段代码执行前后自动执行特定代码,也可以通过@contextmanager实现
import contextlib
@contextlib.contextmanager
def tag(name):
print("<%s>"%name)
yield
print("</%s>"%name)
with tag("h1"):
print("hello")
print("world")
- 如果一个对象没有实现上下文,就不能把他用于with语句,可以用closing()来将该对象变成上下文。
2021-03-15
错误
#
try:
i = 10 / int('a')
except ValueError as e:
print("ValueError",e)
except ZeroDivisionError as e:
print("ZeroDivisionError",e)
#raise如果不带参数,就会把当前错误原样抛出
raise
finally:
print("END")
单元测试要用到了再回来补充:
单元测试
IO
#
-
以读的方式打开一个文件对象f = open('test.txt','r')
-
文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源。可以使用with open('test.txt','r') as f:
,这种形式会自动帮我们调用close()方法。
-
读取二进制文件open('test','rb')
,读取非UTF-8编码的文件open('gbk.txt', 'r', encoding = 'gbk')
,遇到编码错误后忽略open('test', 'r', encoding = 'gbk', errors = 'ignore')
。
-
写文件就是将r
参数改为w
参数,当在写文件时,操作系统往往不会立刻把数据写入磁盘,而是先缓存起来,只有调用close方法时才会保证把没有写入的数据全部写入磁盘。
-
StringIO和BytesIO是在内存中读写数据。
-
序列化
2021-03-14
- python中所有数据类型都可视为对象。
- 所有的类都会继承object类。
- 类中定义函数的第一个参数是self,调用时不用传递该参数。
- 属性前面加
__
(两个下划线)就变成了一个私有属性。双下划线开头,双下划线结尾的是特殊变量,特殊变量是可以直接访问的,不是private变量。一个下划线开头的变量名是可以被外部访问的,但是按约定俗称的规定,请把它视为私有变量。
- 双下划线开头的属性不能被外部访问是因为这个属性被改名字了。
- 判断一个变量是否是某个类型可以用isinstance()判断,判断对象类型可以使用type方法。
- 使用dir()可获得对象的所有属性和方法。
- 自己写的类如果也想用len()方法的话,可以自己写一个
__len__
方法。实际上,当我们使用len()方法的时候,它会自动去调用该对象的__len__()
方法。
__str__
相当于java中的toString
,还有一个__repr__
是返回开发者看到的字符串。
__getitem__
可按下标访问数据的项。
- 任何类只要定义一个
__call__
方法,就可以对实例进行调用。
类的定义
#
class Student(object):
#类属性
name = 'Student'
def __init__(self, name):
#实例属性
self.name = name
#编写程序不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性
动态绑定
#
- 当创建了一个对象后,我们可以给该对象绑定任何属性和方法,但是该属性和方法只在当前对象上有效。为了给所有实例都绑定方法,可以给class绑定方法。
class Student(object):
#可以用来限制class实例能添加的属性,仅对当前类实例起作用,对继承的子类不起作用
#除非在子类中也定义__slots__,这样子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
__slots__ = ('name', 'age')
def set_age(self, age)
self.age = age
s = Student()
#给对象动态绑定了一个属性
s.name = 'xiaoxiang'
#给对象动态绑定一个方法,只在该对象上有效
s.set_age = MethodType(set_age, s)
#给类动态绑定一个方法
Student.set_age = set_age
getter和setter
#
- 使用@property代替getter,以及创建setter的方法见下面的代码
class Student:
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
student = Student()
student.name = 'xiaoxiang'
print(student.name)
__iter__
和__next__
#
- 如果一个类想被用于for-in,就必须实现
__iter__
和__next__
方法
class Fib:
def __init__(self):
self.a = 0
self.b = 1
def __iter__(self):
#实例自身就是迭代对象
return self
def __next__(self):
self.a, self.b = self.b, self.b + self.a
if self.a > 100:
raise StopIteration()
#返回下一个值
return self.a
for i in Fib():
print(i)
__getattr__
#
__getattr__
用于动态返回一个属性,只在没有找到属性的情况下才会调用该方法。
class Url:
def __init__(self, item = ''):
self.item = item
def __getattr__(self, item):
return Url('%s/%s'%(self.item, item))
def __str__(self):
return self.item
__repr__ = __str__
print(Url().xiaoxiang.ayu.liuchan.list)
#结果:/xiaoxiang/ayu/liuchan/list
枚举
#
from enum import Enum, unique
#该注解用于保证没有重复值
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
- class(类)的类型就是type,对象的类型就是类名
- 可以通过type()函数依次传入3个参数来创建一个class对象:①class的名称;②继承的父类集合;③class的方法名称与函数绑定。
type('hello', (object, ), dict(hello = 'hello world'))
- 通过type()函数创建的类和直接写class是完全一样的,因为python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
- 可以把类看作是metaclass(元类)创建出来的实例。
- metaclass后面用到了再来看吧,链接在这->
metaclass
2021-03-11
- online analytical processing of queries(OLAP):联机分析
- clickhouse是一个用于OLAP的列式数据库管理系统(来自同一列的数据被存储在一起)。
sudo service clickhouse-server start
启动clickhouse.
- clickhouse不要求主键唯一
clickhouse连接客户端
#
clickhouse-client
#
-
clickhouse-client命令行参数
clickhouse-client
配置文件地址(使用以下第一个配置文件)
- 通过
--config-file
参数指定
./clickhouse-client.xml
~/.clickhouse-client/config.xml
/etc/clickhouse-server/config.xml
mysql
#
- 可以通过mysql命令工具连接,需要在配置文件中配置mysql_port:
<mysql_port>9004</mysql_port>
- 使用命令行工具mysql进行连接:
mysql --protocol tcp -u default -P 9004
数据库引擎
#
mysql引擎
#
表引擎
#
merge tree
#
-
MergeTree:设计用于插入大量数据当一张表当中。数据可以以数据片段的形式一个接一个的快速写入,数据片段在后台按照一定的规则进行合并。
-
SummingMergeTree:合并SummingMergeTree表的数据片断时,clickhouse会把所有具有相同主键的行合并为一行,该行包含了被合并的行中具有数值数据类型的列的汇总值。如果主键的组合方式使得单个键值对应于大量的行,则可以显著的减少存储空间并加快数据查询的速度。
-
ReplacingMergeTree:该引擎会删除排序键值相同的重复项。适用于在后台清除重复的数据以节省空间,但不保证没有重复数据出现。在数据合并的时候,如果制定了Replcaing的参数,当参数未指定,则保留最后一条数据,当参数已指定,则保留ver值最大的版本。数据的去重只会在数据的合并期间进行,所以常常今天数据重复了,明天看数据还没有被去重。
-
AggregateMergeTree:改变了数据片段的合并逻辑。clickhouse会将一个数据片段内所有具有相同主键的行替换为一行,这一行会存储一系列聚合函数的状态。可以使用AggregatingMergeTree表来做增量数据的聚合统计,包括物化视图的数据聚合。
-
CollapsingMergeTree:会异步的删除掉所有字段的值都相等(除了列Sign,该列一个为1,一个为-1)的成对的行,下面这两行就会被删除。
-
UserId |
PageViews |
Duration |
Sign |
123 |
5 |
146 |
1 |
123 |
5 |
146 |
-1 |
-
1是状态行,-1是状态取消行
-
VersionedCollapsingMergeTree:用于相同目的的折叠树但使用了不同的折叠算法,允许以多个线程的任何顺序插入数据。Version列有助于正确折叠行,即使他们以错误的顺序插入。而CollapsingMergeTree只允许严格的连续插入。
MergeTree
#
-
数据可以以数据片段的形式一个接一个的快速写入,数据片段在后台按照一定的规则进行合并。
-
存储的数据按主键排序;支持数据分区(如果指定分区键的话);支持数据副本;支持数据采样。
-
建表(
更多介绍看这里)
-
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
name [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2]
...
INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr [DELETE|TO DISK 'xxx'|to volume 'xxx'],...]
[SETTINGS name=value,..]
-
ENGINE:引擎名和参数
-
ORDER BY:排序键,可以是一组列的元组或任意的表达式。如果没有用PRIMARY KEY显式指定主键,clickhouse会使用排序键作为主键,如果不需要排序,可以使用ORDER BY tuple()
-
PARTITION BY:分区键,要按月分区,可以使用表达式toYYYYMM(date_column),这里date_column是一个Date类型的列,分区的格式会是YYYYMM
-
PRIMARY KEY:主键,如果要选择与排序键不同的主键,可选。大部分时间不需要再专门指定一个primary key
-
SIMPLE BY:用于抽样表达式,如果要用抽样表达式,主键中必须包含这个表达式。如SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))
-
TTL:可以为表设置,也可以为每个列单独设置
- 表TTL:表可以设置一个用于移除过期行的表达式,当表中的行过期时,clickhouse会删除所有对应的行。clickhouse在数据片段合并的时候会删除掉过期的数据,如果在合并的过程中执行SELECT查询,则可能得到过期的数据。
-
SETTINGS:控制MergeTree行为的额外参数
-
数据存储
- 表由按主键排序的数据片段组成。
- 当数据被插入到表中时,会创建多个数据片段并按主键的字典排序。
- 不同分区的数据会被分成不同的片段,clickhouse在后台合并数据片段以便更高效存储。合并机制不保证具有相同主键的行全部合并到同一个数据片中。
- 数据片段以wide或compact格式存储。wide格式下,每一列都会在文件系统中存储为单独的文件,compact模式下所有列都存储在同一个文件下。
- 每个数据片段被逻辑分割成颗粒。颗粒是进行数据查询时最小的不可分割数据集。每个颗粒都包含整数个行。
-
可以使用ORDER BY tuple()创建没有主键的表,这种情况下,clickhouse会根据数据插入的顺序存储。
-
对于select查询,如果where子句中有下面这些表达式,就可以使用索引
- 包含一个与主键/分区键中的部分字段或全部字段相等或不等的表达式。
- 主键/分区键 in (xxx,xxx,xxx) 或者 主键/分区键 like ‘xxx’。
- 基于主键/分区键的字段上的某些函数。
- 基于主键/分区键的表达式的逻辑表达式。
-
TTL
- TTL表达式的计算结果必须是日期或日期时间类型的字段
MergeTree这一块越看越迷惑,之后再来接着看吧。
日志引擎
#
- 是为了需要写入许多小数据量(少于一百万行)的表的场景而开发的。
该引擎的共同属性
#
- 写入时将数据追加到文件末尾。
- 不支持索引。
- 非原子地写入数据。
- Log和StripeLog支持并行读取数据,Log引擎将表中的每一列使用不同的文件;StripeLog将所有的数据存储到一个文件中