写在前面
Clickhouse 从 21.11 版本开始,除了提供类似SqlServer、MySQL CREATE FUNCTION 的自定义函数之外,还有一个用户自定义函数(UDF),与其说是"用户自定义函数",为了避免混淆,称之为"用户自定义外部函数"更为准确。官方对此功能的解释:
ClickHouse can call any external executable program or script to process data. 译文:ClickHouse可以调用任何外部可执行程序或脚本来处理数据。
可以调用外部程序或脚本来处理数据,这对于数据建模、数据分析等等来说,无疑是杀手锏的存在。
开始
示例情景:调用python脚本实现向量点积运算。
环境:Docker、Clickhouse 21.11.4.14 、Ubuntu 20.04、Python3
1. 在config.xml里内增加
*_function.xml
2. 增加custom_function.xml自定义函数的声明文件
新建custom_function.xml文件,与config.xml、users.xml文件是同级目录下的,如图
3. 声明方法
打开custom_function.xml文件,编写文件内容如下:
executable
custom_dotProduct
Float32
result
Array(Float32)
v1
Array(Float32)
v2
JSONEachRow
0
python3 /var/lib/clickhouse/user_scripts/custom_dotProduct.py
execute_direct=0,默认是1,1表示将在clickhouse的/data/user_scripts文件夹内搜索脚本,0表是按照用户配置的命令搜索脚本路径,建议设置为0,避免找不到执行的脚本文件。其他参数可以参考文档:Introduction | ClickHouse Documentation
4. 编写python脚本
#!/usr/bin/python3
import sys
import json
if __name__ == '__main__':
for line in sys.stdin:
dict = json.loads(line)
ls = []
for v in dict.values():
ls.insert(1, list(v))
vector1 = tuple(ls[0])
vector2 = tuple(ls[1])
v = sum(p * q for p, q in zip(vector1, vector2))
data = {'result': str(v)}
print(json.dumps(data), end='\n')
sys.stdout.flush()
保存脚本并命名为 custom_dotProduct.py ,再放到 /var/lib/clickhouse/user_scripts 文件夹内。
特别需要注意是脚本运行环境和存放路径问题,Clickhouse如果是放到docker里面,则需要在docker内配置python可运行的环境,其他C++、java也是如此,最起码能保证手动执行脚本的时候能运行。 在 custom_function.xml 声明方法的时候,编写的xml文件中的 <span class="cnblogs_code">command</span>
命令是容器里面的路径,而不是宿主机的路径。
5. 至此已经完成,进行方法测试
--重新加载方法
SYSTEM RELOAD FUNCTIONS;
--查看方法是否加载成功
SELECT * FROM system.functions WHERE name = 'custom_dotProduct';
执行方法:
select custom_dotProduct([1,2,3],[4,5,6]);
最后
还需特别注意的是Clickhouse版本问题,在示例的python脚本中和官网文档中的示例python脚本取值方法不太一样,
官方示例:
first_arg = int(value['argument_1'])
second_arg = int(value['argument_2'])
它是通过自定义配置的name获取值:
executable
test_function_sum_json
UInt64
result_name
UInt64
argument_1
UInt64
argument_2
JSONEachRow
test_function_sum_json.py
而我是通过遍历出来的:
for v in dict.values():
ls.insert(1, list(v))
原因是Clickhouse这种取值方式必须要求在 22.3 版本以上才支持,若低于 22.3的版本用官方的取值方式是永远报错的(巨坑之一)。具体可以看我之前提的Issue: UDFs: JSON Bug ? · Issue #35562 · ClickHouse/ClickHouse (github.com)
另外,从2022年1月后,Clickhouse的Docker镜像将停止 yandex/clickhouse-server 的迭代,使用新的镜像地址 clickhouse/clickhouse-server 。
如继续使用 yandex/clickhouse-server的镜像,最新的版本号停留在 22.1.3.7 (巨坑之二)。
好了,下班!不不不,等下下班!
Original: https://www.cnblogs.com/EminemJK/p/16079583.html
Author: 山治先生
Title: Clickhouse 用户自定义外部函数
相关阅读1
Title: redis的事务不是原子性
Reference: https://blog.csdn.net/u011692780/article/details/81213010
一、事务的四大特性
关系型数据库的事务具有四个特性:
-
原子性
-
一致性
-
隔离性
-
持久性
二、而在我们redis数据库中,事务回事什么样子的呢?
首先我们给出一个定义:redis的事务中,一次执行多条命令,本质是一组命令的集合,一个事务中所有的命令将被序列化,即按顺序执行而不会被其他命令插入
在redis中,事务的作用就是在一个队列中一次性、顺序性、排他性的执行一系列的命令。
事务的生命周期:
-
事务的创建:使用MULTI开启一个事务
-
加入队列:在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真的执行
-
EXEC命令进行提交事务
常用的关于事务的命令有:
-
MULTI:使用该命令,标记一个事务块的开始,通常在执行之后会回复OK,(但不一定真的OK),这个时候用户可以输入多个操作来代替逐条操作,redis会将这些操作放入队列中。
-
EXEC:执行这个事务内的所有命令
-
DISCARD:放弃事务,即该事务内的所有命令都将取消
-
WATCH:监控一个或者多个key,如果这些key在提交事务(EXEC)之前被其他用户修改过,那么事务将执行失败,需要重新获取最新数据重头操作(类似于乐观锁)。
-
UNWATCH:取消WATCH命令对多有key的监控,所有监控锁将会被取消。
注意:关于乐观锁等概念:
乐观锁:就像他的名字,不会认为数据不会出错,他不会为数据上锁,但是为了保证数据的一致性,他会在每条记录的后面添加一个标记(类似于版本号),假设A 获取K1这条标记,得到了k1的版本号是1,并对其进行修改,这个时候B也获取了k1这个数据,当然,B获取的版本号也是1,同样也对k1进行修改,这个时候,如果B先提交了,那么k1的版本号将会改变成2,这个时候,如果A提交数据,他会发现自己的版本号与最新的版本号不一致,这个时候A的提交将不会成功,A的做法是重新获取最新的k1的数据,重复修改数据、提交数据。
悲观锁:这个模式将认定数据一定会出错,所以她的做法是将整张表锁起来,这样会有很强的一致性,但是同时会有极低的并发性(常用语数据库备份工作,类似于表锁)。
那么,现在我们来执行一次具体看看redis的事务机制:
首先我会开启事务,并向数据库中存储4条数据,可以看到没执行一条命令的时候都会显示入队,并不会返回执行结果,说明redis中在事务提交之前,其内部的所有命令将不会被执行:
那么,如果中间有命令出错了会怎样呢?现在我随便打几个字符试一试:
可以看出,在第三条命令中我随便打了几个字符,提交事务的时候并没有成功,这也很符合我们对事务的理解,嗯~具有原子性。但是,有一个细节,那就是错误命令在我输入的时候就已经报错了,也就是说这个条错误命令在进入队列的时候redis就已经知道这是一条错误命令,这样,整个事务的命令将全部失败,那么,有没有一种可能某个错误指令在进入队列的时候redis还没有发现他的错误呢?我们试一试下面这个例子:
问题出现了,我们可以看到,name+1这条指令其实是错误的,但是提交事务的时候会发现,这条错误命令确实没有执行,但是其他正确的命令却执行,这是为什么的?
原因是在redis中,对于一个存在问题的命令,如果在入队的时候就已经出错,整个事务内的命令将都不会被执行(其后续的命令依然可以入队),如果这个错误命令在入队的时候并没有报错,而是在执行的时候出错了,那么redis默认跳过这个命令执行后续命令。也就是说,redis只实现了部分事务。
下面我们来看看刚刚提到的锁的问题,我们说过,redis的锁CAS(check and set)类似于乐观锁,redis的实现原理是使用watch进行监视一个(或多个)数据,如果在事务提交之前数据发生了变化(估计使用了类似于乐观锁的标记),那么整个事务将提交失败,我们可以举一个例子,我们开启两个终端,模拟两个人的操作,设置一条数据为count,初始时100,现在A对其进行监控,并且为count增加20
在没有提交之前,B也获取了这个count,为其减少50,
那么这个时候A如果提交事务,会出现失败提示:
可以看到,在A对数据的修改过程中,B对数据进行了修改,那么这条数据的"标记"就发生了变化,已经不是当初A取出数据的时候的标记了,这样,A的事务也就提交失败了。
最后通过上述的实验,我们总结redis事务的三条性质:
- 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
- 没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行
- 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
三、Redis中的事务为什么没有原子性与watch锁
在传统的关系型数据中,只要有任意一条指令失败,则整个事务都会被撤销回滚,而在Redis中,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做,也因此得出 Redis 事务的执行并不是原子性的。
multi,代表一段事务的开始
exec,代表一个事务的提交
queued,代表某个指令在队列中
但是,也有例外,比如如下这种情况
discard,代表某个事务撤销
watch 锁 ,在事务中不能改变被锁的值 (exec提交后返回nil)
四、总结
在redis中,对于一个存在问题的命令,如果在入队的时候就已经出错,整个事务内的命令将都不会被执行(其后续的命令依然可以入队),如果这个错误命令在入队的时候并没有报错,而是在执行的时候出错了,那么redis默认跳过这个命令执行后续命令。也就是说,redis只实现了部分事务。
总结redis事务的三条性质:
- 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
- 没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行
- 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
Original: https://www.cnblogs.com/skying555/p/10398724.html
Author: alex.shu
Title: redis的事务不是原子性
相关阅读2
Title: Linux 逻辑卷管理器(LVM)
LVM:
LVM: Logical Volume Manager,可以实现动态的扩容和缩容。逻辑卷是一种逻辑上的管理方式,把一块或多块硬盘或分区逻辑的组合在一起,命令成一个卷组(VG),卷组的空间来自所有硬盘空间的总和。(组成逻辑卷的硬盘或分区大小可以不一样)
VG:
多个磁盘或者分区组合在一起的(逻辑上的大硬盘)
LV:
是从VG中取出一块空间而来的(相当于VG这个逻辑上大硬盘的一个分区)
PE:
PE:物理盘区,作用是负责分配空间的最小单位(逻辑卷中),扩容和缩容都是以PE为单位来操作的。PE大小默认为4M
LVM的实现过程:
- 1.将设备设为物理卷 -- 贴个标签,表示这个硬盘要作为物理卷使用了
- 2.指定卷组(一个或多个物理卷加入卷组形成一个大硬盘)
- 3.创建逻辑卷(相当于分区)
- 4.创建文件系统并挂载
逻辑卷的名字:
逻辑卷有三个名称你可以用,一个真名,两个软连接。
第一个逻辑卷对应设备名:/dev/dm-# (逻辑卷的真名)
dm: device mapper,将一个或多个底层块设备组织成一个逻辑设备的模块
逻辑卷的软链接:
- /dev/mapper/VG_NAME-LV_NAME
- /dev/VG_NAME/LV_NAME
逻辑卷的实现范例:
#创建swap空间,分区的id要改为82,创建逻辑卷,物理设备分区的id要改成8e fdisk的t选项。
#如果是使用硬盘的话,就不存在改ID的问题了。
#这些命令依赖lvm2这个工具包
#创建物理卷
pvcreate /dev/sda3
#为卷组分配物理卷
vgcreate vg0(卷组名) /dev/sda3(把创建的pv加进来) #-s可以指定PE的大小
#从卷组创建逻辑卷
lvcreate  -L 256M  -n data vg0 # -n:指定设备名称 -l:PE的个数 -L:指定LV的大小
mkfs.xfs   /dev/vg0/data #创建文件系统
#挂载
mount /dev/vg0/data /mnt/data
逻辑卷的扩展和缩减
扩展逻辑卷:
#两步实现 -- 空间扩展和文件系统扩展 (缺点在于文件系统扩容的时候不同系列的文件系统命令不一样)
#第一步实现逻辑卷的空间扩展
lvextend -L [+]#[mMgGtT](大小) /dev/VG_NAME/LV_NAME #-l:扩容多少个pe -L:扩容的大小 不写+号表示扩容到xx,写了+号表示增加xxx
#第二步实现文件系统的扩展
#使用df命令看到的是文件系统的情况,扩容了的部分但是上面没有文件系统,所以就看不见
#针对ext
resize2fs /dev/VG_NAME/LV_NAME #resize2fs:对ext系列的文件系统进行扩容,把文件系统同步到空间里面去
#针对xfs
xfs_growfs MOUNTPOINT
#一步实现容间和文件系统的扩展(不用关注文件系统的类型,直接扩容)
lvextend -r -l +100%FREE /dev/VG_NAME/LV_NAME #-l:pe的个数 -r:表示不管是什么文件系统都立即同步文件系统 +100%FREE:表示剩下的所有剩余空间
扩展卷组的空间:
##添加物理卷到卷组中(分区需要改id号)
pvcreate 物理卷
##将添加的设备添加到卷组中
vgextend 卷组名 物理卷
缩减逻辑卷(有丢失数据的风险)
#建议先备份再缩减,xfs文件系统不支持缩减
#五个步骤:1.卸载 -- 2.检查文件系统完整性(不管是否完整必须) -- 3.缩减文件系统 -- 4.缩减逻辑卷大小 -- 5.重新挂载
#1.卸载:
umount /dev/VG_NAME/LV_NAME #首先卸载
#2.检查文件系统完整性
e2fsck -f /dev/VG_NAME/LV_NAME
#3.缩减文件系统
resize2fs /dev/VG_NAME/LV_NAME #[mMgGtT](缩减后的大小,例如2G表示缩减到2G)
#4.缩减逻辑卷大小
lvreduce -L [-]#[mMgGtT](这个大小要和上面的大小相匹配) /dev/VG_NAME/LV_NAME(对谁进行缩减)
#5.重新挂载
mount /dev/VG_NAME/LV_NAME mountpoint
清除缓存的方法:
使用 free -h 可以看到缓存
查看缓存对应的目录: find /proc -name 'drop*' --- /proc/sys/vm/drop_caches
清理缓存:echo 3 > /proc/sys/vm/drop_caches --- 3就是清理缓存
#缓存的目的就是为了提高性能(一般做测试的时候才清理缓存)
拆除指定的PV存储设备
要想移除指定的pv存储设备,首先要将它上面的数据移到别的pv上面去。(要确保上面的数据在别的pv上面能存得下)
#实现方法:
#移动指定pv设备上的所有被占用的pe移动到同一卷组的其他成员上面
pvmove 指定的pv设备
#把pv设备从vg里面移走
vgreduce 指定的设备
#不让这个设备作为pv了
pvremove 指定的设备
删除逻辑卷
应该倒过来删除,从逻辑卷、卷组再到物理卷
- 1.取消挂载
- 2.删除逻辑卷: lvremove 逻辑卷
- 3.删除卷组: vgremove 卷组
- 4.删除物理卷:pbremove 物理卷
逻辑卷快照
快照:把当前的状态快速做一个备份,快照类似备份的效果,但是特别快
逻辑卷快照:瞬间把逻辑卷做个备份
工作逻辑:
- 首先有一个卷组,卷组里面存在一个逻辑卷,假设逻辑卷里面有一些文件。
- 做快照就是在同属于同一卷组的空间中创建一个逻辑卷(快照逻辑卷--snapshot,具有特殊属性)。
- 创建快照的时候并没有把原逻辑卷中的数据备份到快照中,只是在硬盘上分配了一个空间。
- 起到备份效果是因为在更改原文件的时候,会自动把被修改文件的旧版本推送到快照里面去了(只存放最初的原始版本)没有修改的文件就不会被放到做快照的这个空间里面去。
总结:
快照备份逻辑:创建快照的时候会分配一块空间,只有原文件发生了修改,才会把这个原文件的最初版本放到快照里面去(快照里面只放发生了改变的数据)。
快照的实现:(快照也是一种逻辑卷,所以使用的命令和创建逻辑卷的命令一样)
#为现有逻辑卷创建快照,注意ext4必须使用-p r 实现只读
#创建快照:
lvcreate -l 64 -s -n data-snapshot /dev/vg0/data
#-l: 指定这个快照的大小(多少个pe)
# -s:表示创建的是快照,不加s表示创建的是普通逻辑卷
# -n: 快照的名字
# /dev/vg0/data -- 表示指定的是谁的快照(原始逻辑卷的名称)
创建完成后可以使用 lvs或lvdisplay来进行查看(因为快照也是一个逻辑卷)
#创建快照以后进行挂载
#挂载快照,xfs注意要使用-o ro实现只读,访止快照被修改
mkdir  -p /mnt/snap
mount -o ro,nouuid /dev/vg0/data-snapshot   /mnt/snap #nouuid--不检查uuid xfs这类文件系统不允许相同的uuid设备挂载, ext系列文件系统不用加nouuid都行的
#挂载以后,进入这个目录进行查看能看的数据的原因是(这里面的数据是来源于原逻辑卷的,并非已经将这些数据备份了,因为之后原逻辑卷的数据修改以后,才会备份被修改数据的初始数据)
#恢复快照(使用快照数据还原修改过的数据)
umount /dev/vg0/data-snapshot #先取消挂载
umount /dev/vg0/data #先取消挂载
lvconvert --merge /dev/vg0/data-snapshot #利用快照还原原来的逻辑卷数据
#merge -- 合并,融入
#还原以后,快照会自动删除
Original: https://www.cnblogs.com/heyongshen/p/16439477.html
Author: 背对背依靠
Title: Linux 逻辑卷管理器(LVM)
相关阅读3
Title: Linux sudo实现授权
sudo:superuser do,实现普通用户执行root命令的授权工具。
一般用户管理系统的方式是利用su切换为超级用户。但是使用su的缺点之一在于必须要先告知超级用户的密码。
sudo使一般用户不需要知道超级用户的密码即可获得权限
#过程:
(1)超级用户授权:首先 超级用户 将普通用户的名字、可以执行的特定命令、按照哪种用户或用户组的身份执行等信息,登记在特殊的文件中(通常是/etc/sudoers),即完成对该用户的授权(此时该用户称为“sudoer”);
(2)管理员确认用户的身份:在一般用户需要取得特殊权限时,其可在命令前加上“sudo”,此时sudo将会询问该用户自己的密码(以确认终端机前的是该用户本人),回答后系统即会将该命令的进程以超级用户的权限运行。
(3)以超级用户身份执行命令:之后的一段时间内(默认为5分钟,可在/etc/sudoers自定义),使用sudo不需要再次输入密码。
sudo的组成:
- 软件包
- 配置文件
- 授权规则配置文件
软件包:sudo
配置文件:/etc/sudo.conf #一般不用修改
授权规则配置文件:#
/etc/sudoers #这个文件不建议直接改,因为改错了就会影响授权x
/etc/sudoers.d #可以把授权文件放在这个目录下面,防止所有授权规则放在一起太臃肿
#配置文件中支持使用通配符 glob
安全编辑授权规则文件和语法检查工具: visudo ≈ vim /etc/sudoers
/usr/sbin/visudo #建议这个命令来改用户的授权,在编辑的时候有语法提示功能,格式错误会提示
sudo工具授权文件的编写格式
#格式:
授权用户 登入的主机=(代表用户) 能执行的操作
user host=(runas) command
user: 表示给谁授权 #%group 表示对组进行授权
host: 表示在哪个主机登录 ALL表示所有主机
(runas):表示以哪个用户的身份运行 一般是root
command: 表示能执行那些操作(命令需要写绝对路径) ALL表示所有
#其中NOPASSWD:command 表示不用验证这个用户的密码
User的形式:
username #用户名
#uid #uid的形式
%group_name #组的形式
%#gid #gid的形式
host的形式:
ip或hostname #ip地址或者主机名
network(/netmask) #ip地址/子网掩码
command的形式:
command name #命令的名字
directory #这个目录里面的所有命令都授权
sudoedit #sudoedit是一个编辑授权配置文件的工具。表示被授权的用户可以编辑sudo的授权配置文件(/etc/sudoers)
#范例:授权tom用户具有挂载光盘的功能
tom ALL=(root) /usr/bin/mount /dev/cdrom /mnt
sudo的别名
- 用户别名
- 主机别名
- 代表者别名
- 操作别名
sudo授权的时候允许加自定义的别名
四种别名:
User_Alias #被授权的用户
Runas_Alias #代表谁
Host_Alias #主机
Cmnd_Alias #执行的操作
#别名必须是大写字母和数字、下划线的结合
#定义别名的格式:
定义别名类型 别名的名称 = 值1,...,值n
范例
User_Alias  NETADMIN= netuser1,netuser2 #定义用户别名
Cmnd_Alias NETCMD = /usr/sbin/ip,/usr/sbin/ifcon #定义操作别名
NETADMIN ALL=(root) NETCMD
sudo命令:
sudo -i -u tom 切换身份功能和 su 相似,但不一样 #因为sudo必须提前授权,而且要输入自已的密码确认自己的身份,而su的话是需要切换者的密码。
#su switch user
sudo [-u user] COMMAND
-V 显示版本信息等配置信息
-u user 默认为root #表示代表谁执行这个操作
-l,ll 列出用户在主机上可用的和被禁止的命令
-v 再延长密码有效期限5分钟,更新时间戳
范例
#修改验证密码间隔为2分钟
[root@centos8 ~]#vim /etc/sudoers
Defaults   env_reset , timestamp_timeout=2
#sudo -V 可以查看用户的默认时间间隔。
#ubuntu 默认用户具有sudo权限
#它是将普通用户都加到了sudo这个组里面了。
root@ubuntu1804:~# grep %sudo /etc/sudoers
%sudo ALL=(ALL:ALL) ALL #ALL:ALL 任何用户:任何组
修改ubuntu的visudo的默认编辑器
#ubuntu的visudo默认是使用nano工具打开的
export EDITOR=vim
Original: https://www.cnblogs.com/heyongshen/p/16547058.html
Author: 背对背依靠
Title: Linux sudo实现授权