本文转载自: 掘金
EasyC++38,右值引用
大家好,我是梁唐。
这是EasyC++系列的第38篇,来聊聊右值引用。
想要追求更好阅读体验的同学,可以访问github仓库:EasyLeetCode。
右值引用
左值和右值
在我们之前的文章当中,介绍的都是左值引用。C++11在左值引用的基础上推出了右值引用,由于是新特性,加上使用的频率也不是很高,有一定的学习成本。
我们先把引用这个概念抛开,先来看看什么是左值什么是右值。其实很简单,左值可以取地址,位于等于号的左侧。而右值没办法取地址,位于等于号的右侧。
1 | C++复制代码int a = 4; |
比如我们定义了一个int型的变量a,让它的值等于4。其中a位于等于号的左侧,并且我们可以求a的地址。而4位于等于号的右侧,我们没有办法对4取地址。所以a是左值,4是右值。
再比如:
1 | C++复制代码int test() { |
同样,a位于等于号的左侧,有办法取地址是个左值。而test()是一个临时值没办法取地址,是个右值。
所以到这里就比较清楚了,有地址的变量就是左值,没有地址的常量值、临时变量就是右值。
左值引用和右值引用
明白了左值、右值的概念再来看看左值引用、右值引用就简单了。左值引用顾名思义就是能够指向左值不能指向右值的引用。
1 | C++复制代码int a = 4; |
但是左值引用也有例外,就是使用const修饰的左值引用是可以指向右值的:
1 | C++复制代码const int &b = 4; |
因为const修饰的引用无法再更改,所以可以指向右值。如果大家度过STL代码的话,会发现其中一些函数的入参是const &目的就是为了能够兼容传参是常量的情况。比如vector当中的push_back:
1 | C++复制代码void push_back (const value_type& val); |
右值引用和左值引用的概念类似,也就是能够指向右值但不能指向左值的引用。为了和左值引用区别, 使用&&也就是两个&符。老实讲这个符号很令人费解,因为它和and的含义相同。
1 | C++复制代码int a = 4; |
上面第三行代码非法的原因是c是一个右值引用,它不能指向左值。如果我们非要指向呢?也不是没有办法,可以使用std::move函数,它可以将一个左值转换成右值。
1 | C++复制代码using namespace std; |
move函数听起来似乎是移动的意思,但其实它并没有移动变量,只不过做了一个类似于类型转换的操作。
不知道大家看到这里有没有觉得头大,其实还没有结束,还有一点很重要。即左值引用和右值引用这两者本身都是左值引用:
1 | C++复制代码void test(int && tmp) { |
C++中的引用是一个非常大的范畴,除了左值引用、右值引用之外还有非常多的细节。比如万能引用、引用折叠、完美转发等……
本文转载自: 掘金
如果还不懂如何使用 Consumer 接口,来公司我当面给你
「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」。
上文中的问题大家思考的怎么样了?有没有小伙伴把自己的思路或者代码留在评论区呀?
说一下我的思路:如果我们想要将公共的部分抽取出来,发现都比较零散,还不如不抽取,但是不抽取代码又存在大量重复的代码不符合我的风格。于是我便将手伸向了 Consumer 接口。
更改之后的伪代码
1 | java复制代码B b = this.baseMapper.selectOne(queryWrapper); |
看到这,如果大家都已经看懂了,那么恭喜你,说明你对 Consumer 的使用已经全部掌握了。如果你还存在一丝丝的疑虑,那么就接着往下看,我们将介绍一下四种常见的函数式接口。
函数式接口
那什么是函数式接口呢?函数式接口是只有一个抽象方法(Object的方法除外),但是可以有多个非抽象方法的接口,它表达的是一种逻辑上的单一功能。
@FunctionalInterface
@FunctionalInterface 注解用来表示该接口是函数式接口。它有助于及早发现函数式接口中出现的或接口继承的不适当的方法声明。
如果接口用该注解来注释,但实际上不是函数式接口,则会在编译时报错。
Supplier
我们一般称之为“生产者”,没有参数输入,但是能返回结果,为结果的提供者。
1 | java复制代码@FunctionalInterface |
可以举个简单的例子感受下:
1 | java复制代码Optional<Double> optional = Optional.empty(); |
看了我的解决方案之后,有没有小伙伴和我是一样实现的?评论区举个爪爪吧,下文我们将继续讲解函数式接口的用法。如果你有不同的意见或者更好的idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!
本文转载自: 掘金
Golang 中比较常见的 panic 异常原因之一
「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」
目录
- 前言
- 正文
- 结尾
前言
在 Golang 中,当异常发生时不管是主动触发 panic 还是由于编码错误导致的 panic,我们都可以使用 recover 进行捕获。当时前提必须定义 defer 语句,且 defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。
正文
今天本文介绍一个编码不规范导致的 panic 异常,下面是错误日志,从日志中我们可以看到程序发生了 panic 异常,并且这个 panic 已经 Gin 框架自带的 Recovery 中间件捕获到了。
报错信息详情:
1 | bash复制代码2021/11/22 09:22:47 [Recovery] 2021/11/22 - 09:22:47 panic recovered: |
日志的下半段是报错的具体原因,总结下来就是:
interface conversion: interface {} is nil, not map[string]interface {}
根本原因是在服务器端解析参数时,没有对 video 字段进行校验,当 video 字段为空时就会发生上述错误。因为设计之初,云转码服务仅支持视频转码,后来支持了单音频编码功能,比如 mp3、aac。业务端调用原来接口的时候就去掉了 video 字段,如下图所示:
避免上述错误有两种方式,第一种方法,也是最根本的方法,即修改云转码服务代码,不再认为 video 字段是必传项,对 video 字段进行校验。
第二种方法是业务端调用云转码接口传参数时,video 字段可以传空结构体,如下图所示:
注意:空结构体的形式是大括号 {},不是 "",也不是 nil。
结尾
最后,总结一下 Golang 在处理异常捕获时的一些注意事项,首先,defer 语句需要提前定义,否则当 panic 发生时,recover 是无法捕获到 panic 异常的。其次,recover 处理异常后,逻辑并不会恢复到 panic 的实际发生位置,而是执行 defer 之后的语句段。最后,多个 defer 语句会形成 defer 栈,后定义的 defer 语句会先执行。
作者简介:大家好,我是 liuzhen007,是一位音视频技术爱好者,同时也是CSDN博客专家、华为云社区云享专家、签约作者,欢迎关注我分享更多干货!
本文转载自: 掘金
Elasticsearch 中的近实时搜索与持久化
近实时搜索(Near-real-Time Search)
一条记录从调用 Index API 到最终持久化,会经历如下过程:
- index 阶段,先在 document buffer 积聚(此时还不是 searchable ),然后 Index API 请求被响应成功.
- refresh 阶段,被动(Refresh API)或主动(每隔1s一次)将 document buffer 中的文档生成 segment(此时 searchable),这一过程称为refresh,此时 segment 存在于 file system cache
周期性refresh只针对最近30秒收到过搜索过请求的索引:
By default, Elasticsearch periodically refreshes indices every second, but only on indices that have received one search request or more in the last 30 seconds.
3. segment merge, 由于 segment 的生成很频繁,导致搜索时要遍历过多小的 segment 影响性能,所以ES会定期地将多个小的segment合并为一个大的.
4. flush 阶段,被动(调用Flush API)或主动(取决于配置index.translog.durability)将 file system cache 中的 segment 刷盘,此时真正的持久化.
“Near-real-Time Search”的意思是指在“第一步完成”到“第二步完成之前”这个时间段中,无法通过 Search API 查到 Index API 插入的记录, 因为这时记录还没被索引到 lucene segment 中.
段合并(segment merge)
InnoDB 中表的索引是可变的,CRUD 都作用于索引页,页空间不足以容纳记录时产生页分裂,页中有效利用空间不足MERGE_THRESHOLD(默认 50%)时做页合并.
ES shard 是一个 Lucene index,由若干个segment组成,segment是不可变的,所以增删改在refresh时总是会产生新的 segment,而refresh默认1s一次,这会导致lucene index 的segment过多. 另一方面,查询的时候需要遍历segment,过多的小segment会导致性能降低.
基于以上原因,ES会周期性的做段合并,在合并完成后,新的段被打开,旧的段被删除.
合并操作由后台线程执行,线程数量可通过index.merge.scheduler.max_thread_count设置.
持久化(Translog)
Translog的产生背景
在 InnoDB 中,如果直接持久化缓冲页,会带来大量随机 IO (因为事务造成的修改通常发生在多个不连续的页),并且即使只改了页中一个字节,也要产生 16KB 的IO.
对此问题,InnoDB的解决方案是 redolog,其有体积小,且连续IO写入的特点,在保证 Durability 的同时,也尽可能提升了性能.
ES 中,持久化增删改需要执行一次“Lucene commit”,这个操作用官方的形容是: ”which is a relatively expensive operation“,可以合理推测,ES 面临着 InnoDB 一样的问题————持久化与性能之间的矛盾,类似地,ES也引入了类似redolog的概念————tanslog.
Translog的回收策略
InnoDB 中用一个 ring buffer 来存储redolog,随着增删改操作的执行,redolog 会越积越多,因此在适当的时候也需要将脏页刷盘,这样就可以回收相应的redolog,这一过程称为checkpoint,它让ring buffer的头指针向后移动,也就释放了空间,ring就这样“转动”起来了.
ES 中,可通过 index.translog.flush_threshold_size 控制,当translog积累多少后,执行“lucene commit”将 file system cache中的segment刷盘,以回收相应的translog. 这个过程也类似 InnoDB 中的 checkpoint.
Durability 的几种级别
人们对性能的追求是不会停止的
- 有时,性能大于持久性,允许丢失最近的数据
- 有时,持久性大于性能,只要一个操作被“acknowledged”,那么之后即使crash,数据也不能丢失.
对于MySql,”acknowledged“意味着commit操作返回成功,对于ES,这意味着Index/Update/Delete API返回了成功.
对此,InnoDB 与 ES 都给出了对应的配置,让用户根据需要定义合适的持久化策略
InnoDB中,提供了 innodb_flush_log_at_trx_commit选项,可选值如下:
- 0,异步持久化redolog,
acknowledged前不立即刷盘redolog,发生crash会丢失最近数据 - 1(默认值),同步持久化redolog,
acknowledged前刷盘redolog, - 2,同步写到 file system cache,如果只是mysql进程挂了,那么cache数据会由OS负责刷盘; 如果OS也挂了,那么数据就没了.
在ES中,提供了 index.translog.durability 选项,可选值如下:
- request(默认值),同步持久化,
acknowledged之前,translog已经被持久化 - async,异步持久化,后台线程定期持久化(取决于
index.translog.sync_interval),crash会丢失最近数据.
基于Translog的实时CRUD
InnoDB 对页的修改直接作用于buffer pool中的缓冲页(没有document buffer),后续的读写也是优先先走缓冲页,所以任何写操作都是实时可见的.
ES 用 translog 被用来提供实时 CRUD ,对于 GET API、Update API、Delete API,共同特点是都提供了文档 ID,ES 首先通过 ID 去检查 translog 中有无最近的变更(由此可以合理推测,ES 的 translog 在数据组织上是用文档 ID 索引的),这使得增删改操作对之后基于文档 ID 的操作来说是立即可见的.
而对于 Search API,由于其依赖 lucene segment 去实现各种复杂的索引方式,所以只能等 document buffer 被 refresh 到 segment. 因此 Search API 是 “near-real-time” 的.
参考
本文转载自: 掘金
用轻量服务器搭建imgproxy来获取不同尺寸的图片
现在很多站长都喜欢搭建一个自己的私有图床来管理图片,使用的一般都是第三方的开源图床程序。有时候可能第三方的图床程序不能完全满足我们的需要,比如说,我们上传了一张图片以后,在不同的页面下,会要求显示不同的分辨率。常见的在图片列表页面显示的64*64的小缩略图,在瀑布流模式下显示的中型尺寸图片,以及原图模式下显示的完全尺寸的原图。除此之外,我们也可能会想对图片进行压缩来节省流量,比如上传的png图片,但是显示的时候来显示jpeg图片来加快用户的访问速度。针对这些需求,要怎么办呢?
经过我的谷歌搜索,终于发现了这么一款神奇的应用imgproxy,官方网站是imgproxy.net/。通过名字就知道,这是一个图片代理服务,简单的来说,这个软件会反代真正的图片地址,当请求图片的时候,请求的是imgproxy的图片地址,然后imgproxy再去请求原始图片,将其转换为要求的尺寸和格式以后,再发送给用户。除了这些以外,imgproxy还可以对原始图片进行模糊化、旋转、亮度调节、加水印等很多功能,具体可以去看官方的文档说明。
官方也给了一个其工作的原理图。从下图中可以看出,可以在imgproxy前面再配一层CDN来缓存已经配置好的图片。
话不多说,下面开始教程,有需要的小伙伴可以跟着部署一下。
购买服务器
如果没有服务器的话,需要先购买一台腾讯云的轻量服务器。
轻量应用服务器(TencentCloud Lighthouse)是新一代开箱即用、面向轻量应用场景的云服务器产品,助力中小企业和开发者便捷高效的在云端构建网站、小程序/小游戏、电商、云盘/图床以及各类开发测试和学习环境,相比普通云服务器更加简单易用,提供高带宽流量包并以套餐形式整体售卖基础云资源,将热门开源软件融合打包实现一键构建应用,是您使用腾讯云的最佳入门途径。为了方便安装应用,我们需要准备Docker环境。
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。使用docker来部署应用是非常简单的,一般情况下,只需要一行命令即可完成。
腾讯云经常举办各种活动,可以点击这里进入他们的活动页面进行购买,可以选择距离最近的区域购买,不过不想备案的话,可以选择境外的服务器(比如香港区域)进行购买。镜像的话,选择【官方镜像】下的【docker基础镜像】,实例套餐选择适合自己的,然后提交订单,付款即可。如果不选择【Docker基础镜像】,后面需要自行安装docker容器。
安装docker
如果没有选择docker基础镜像,则需要自行安装docker镜像。这里假设使用的Ubuntu系统。安装docker的步骤如下
1 | sql复制代码sudo apt-get update |
安装imgproxy
imgproxy的安装十分简单,简单的只需要一行命令就可以搞定
1 | css复制代码docker run -d --name imgproxy --restart always -p 8081:8080 darthsim/imgproxy |
上面命令运行结束以后,imgproxy就已经运行起来了。我们可以测试一下效果
说明imgproxy已经正常启动了。我们尝试让其代理一下nasa的这张2000*1125的这张大图来测试一下效果
原图
1 | ruby复制代码https://www.nasa.gov/sites/default/files/thumbnails/image/pia22228.jpg |
将其转换为 400*400的图片
1 | ruby复制代码http://ip:8081/xxxxxx/resize:fill:400:400:0/plain/https://www.nasa.gov/sites/default/files/thumbnails/image/pia22228.jpg@png |
其中 xxxxxx 代表签名字符串,默认启动的docker容器没有开启签名功能,因此这个签名参数可以任意填写,我这里填写的xxxxx。
resize代表指示imgproxy要调整图片的大小,fill代表调整大小的模式是填充模式,一共有fit、fill、fill-down、force、auto五种模式可以选。400:400这个代表分辨率调整到400*400,后面的0代表是否启用enlarge模式,这个模式如果被启用,如果给定的大小大于图片的原始大小的话,imgproxy会放大图片。
plain表示后面的图片地址是明文模式。plain后面就是原始图片的地址。最后@png代表要将原始的jpeg图片转换为png格式。
更多处理参数可以查看原始文档 docs.imgproxy.net/generating_…
访问上面的地址,已经变成了400*400的中等图片了。
将其转换为200*200的图片,并旋转90度
1 | bash复制代码http://ip:8081/xxxxxx/resize:fill:200:200:0/rot:90/plain/https://www.nasa.gov/sites/default/files/thumbnails/image/pia22228.jpg@png |
配置imgproxy
可以通过环境变量来配置imgproxy,改变其工作模式。下面说一下我用的几个参数。其余更多的配置项可以参考官方文档docs.imgproxy.net/configurati…
1 | bash复制代码IMGPROXY_READ_TIMEOUT #读取原始图片的时间,如果imgproxy和原始图片的服务器之间的网络不是太好的话,可以适当调整这个参数 |
综上,启动imgproxy的命令可以是
1 | bash复制代码ocker run -d --name imgproxy --restart always --env "IMGPROXY_MAX_SRC_RESOLUTION=25" --env "IMGPROXY_ALLOWED_SOURCES=https://img.xxxx.cc/,https://img.yyyy.cc/" --env="IMGPROXY_READ_TIMEOUT=1000" darthsim/imgproxy |
上面讲的内容可以满足一般用户的需要,除此之外,imgproxy还支持更高级的防滥用机制,那就是签名参数。不过这个方法需要自己在后台生成签名的url,不能在前端直接拼接,因此我暂时没有用到,有兴趣的小伙伴可以研究一下
最后
imgproxy的功能是十分强大的。当然如果小伙伴觉得使用起来很麻烦的话,可以直接试试腾讯云的COS,使用起来更加简单容易。
本文转载自: 掘金
MySQL学习-为啥系统有时候会选错索引
「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」
作者:汤圆
个人博客:javalover.cc
前言
大部分时候,系统选择的索引都是正确的,但是机器毕竟是机器,还是会有出错的时候;
今天就来分析一下,什么情况下会出现选错索引的问题,以及怎么去解决。
目录
- 选择索引的几个因素
- 索引的区分度
- 索引的采样统计
- 选错索引的解决办法
正文
1. 选择索引的几个因素
前面我们学习了一条SQL语句的查询和更新过程,知道了大概的流程为:连接器->分析器->优化器->执行器;
其中索引的选择就是在优化器那一步,因为选择一个好的索引就会对语句的优化起到关键的作用;
优化器选择索引,会考虑如下几个因素:
- 扫描的行数:
这里的扫描行数不是真实的扫描行数,而是一个预估值;
这个预估的扫描行数是根据索引的区分度来统计得出的;
索引的区分度简单点来理解,就是一个表中的索引的不同值的个数,不同值越多,区分度越好(具体的统计细节下面会有介绍),
2. 是否使用临时表:
临时表顾名思义就是临时使用的表,在会话完成后就会结束;
比如我们在使用union联合查询并集时,就会用到临时表,如下所示:可以看到 extra这一列显示为Using temporary就是用了临时表才存储查询结果;
- 是否需要排序:如果索引A需要排序,而索引B不需要排序,那么系统会优先考虑不需要排序的索引;
你可能会有疑问,索引不都是有序的吗?怎么会需要排序呢
这里排序的场景是:当筛选条件中有多个索引的情况下,且需要 order by 的场景,比如下面的语句:假设有10万条数据,a和b都是索引
1 | sql复制代码explain select * from t where (a between 1 and 10000) and (b between 10000 and 50000) order by b limit 1; |
此时按扫描行数来看的话,应该是选择索引a,因为索引a只需要扫描1000行,而索引b需要扫描50000行;
但是实际上系统选择的索引是b,通过执行explain可以看到,如下所示:
这是因为如果用了索引a来查询数据,那么查询到的结果集还要根据b字段进行排序;
在这里,系统自觉地认为排序会影响查询的性能,且影响要大于多扫描的5万行数据,所以就选择了索引b;
这里我们可以试着把order by b 去掉,此时系统就会选择索引a;
4. 还有其他的一些因素,比如是否需要回表等等;
2. 索引的区分度
索引的区分度:指的是索引上不同值的个数,也称为”基数”(cardinality);
当一个索引上,不同值的个数越多,基数就越大,这个索引的区分度越好;
通过如下的命令可以查看索引的区分度:
1 | sql复制代码show index from t |
打印如下所示:
系统是怎么取得这个基数值的呢?
首先可以肯定的是,基数值不是通过逐行扫描比对获取的,因为这样的话效率就太低了;
实际上系统的方法还是比较粗暴的,它是通过采样统计的方法来获取;
下面我们就介绍下采样统计;
3. 索引的采样统计
为啥要用采样统计呢?
就是上面我们介绍的,如果全表扫描的话效率很低,所以通过这种简单直接的方式,会提高效率,当然会损失一些精度;
那采样统计的流程是怎么样的呢?
首先系统会去取出N个数据页来做采样统计;这里的N是有默认值的,下面会介绍;
然后再统计每个数据页上不同值的个数,再做平均得到一个平均基数值 Avg;此时我们有了每个数据页上的基数值Avg;
最后再用Avg*数据页的数量,就是整个表的基数值。
采样统计的时机是什么时候?
在更新数据时,如果更新的记录数比例超过 1/M,就会重新执行一次采样统计(M有默认值,下面介绍);
怎么存储采样统计的结果?
通过设置 innodb_stats_persistent 的值来选对应的存储方式:
- on 统计信息会持久化存储,默认的N=20,M=10
- off 统计信息只会存在内存中,默认的N=8,M=16
如果采样统计偏差太大怎么办?
比如我们用explain命令查看的预估扫描行数为10000,但实际上通过show index 命令查看的基数值为20000多(可能由于多个会话同时更新数据导致);
这是我们就可以手动修正,命令如下:
1 | sql复制代码analyze table t; |
修正后,再次执行show index 就可以看到跟预估的扫描行数差不多了
4. 选错索引的解决办法
选错索引有多种情况,比如上面分析的预估扫描行数出错,需要排序等等,都会导致系统使用错误的索引;
当然办法总比困难多,下面就简单介绍下对应的解决办法;
- 预估扫描行数跟实际的差太多?
可以通过analyze table t来进行修正;
比如下面的例子,我本来只有2万多数据,用show index分析也是显示基数值为2万多;
但是当我把数据增加到5万多时,再次用show index分析还是显示2万多;
这时就可以执行analyze table t命令来修正,修正后如下所示,显示为5万多,正常了;
- 排序导致的索引选错:
解铃还须系铃人,既然排序导致的选错索引,那么我们可以修改排序;
比如下面的例子:
1 | sql复制代码select * from t where (a between 1 and 10000) and (b between 10000 and 50000) order by b limit 1; |
这里系统选择索引b的原因上面有介绍,就是系统认为索引b虽然扫描行数多,但是可以减少排序带来的性能消耗,所以系统选了索引b;
**那么我们可以将order by b改为order by b,a,**这样一来,索引b和索引a都需要排序,那么排序就不在性能考虑范围之内了,剩下的考虑因素就是扫描行数,此时系统就会选择索引a了;
不过这种改法会修改原有的语义,比如上面的例子只是返回一个数据limit 1;所以将order by b改为order by b,a都是返回结果集中b最小的那一个;
但是如果没有limit限制,那么改了之后返回的结果集顺序就不一致了;
还有一个办法就是删除索引b,前提是确保其他地方没有用到索引b;
- 通用的解决办法:
上面两种是针对特定的场景而言,其实有一个通用的办法就是强制系统选择某个索引,命令为:
1 | sql复制代码force index (a) |
不过这种办法缺点也很明显,就是不够敏捷,比如发现问题、修改索引、测试上线整个过程会比较耗时;
总结
上面分析了可能选错索引的几种情况:扫描行数、临时表、排序、回表等等
也介绍了对应的几种解决办法:分析表analyze table t、修改排序规则order by b,a、强制选择索引 force index( k )等
不过系统选错索引的情况也不多见,基本上出现了就按照上面这几个思路去排查,问题应该就可以解决。
有时候感觉查询慢,就可以先通过explain可以查看系统使用的索引是不是预期的,然后再对症下药
本文转载自: 掘金
Go语言中如何在零内存分配情况下反转字符串?
一日午饭后散步中,同事问了一道Go相关的测试题目,是他之前面试中面试官问的一个题目,他到现在还没有找到答案。这道测试题就是本篇博文的标题:Go语言中如何在零内存分配情况下反转字符串?
Go语言中反转字符串很好处理。我们只需要将使用[]byte(string)强制将字符串转换成字节切片,然后将该字节切片中第一个字节和最后一个字节对调,第二个字节和倒数第二个字节对调,依次类推,完成整个字节切片反转后,再将字节切片转换成字符串就行了。整个反转操作的时间复杂度是O(n)。需要注意的是对于包含中文等多字节文本的字符串需要转换成[]rune类型。为了减少处理起来的复杂性,本博文就只考虑英文字符串的反转了。相关代码如下:
1 | go复制代码func main() { |
上面处理是完成了反转字符串的目标,但是string和[]byte或[]rune类型互转时候,会进行内存分配的。至于为啥进行了内存分配可以参见本人写的电子书《深入Go语言之旅》中[]byte(string) 和 string([]byte)为什么需要进行内存拷贝?这一小节。本篇博文不再详述。
既然上面处理使用的[]byte(string)和 string([]byte)方法进行字符串和字节切片互转时候需要进行内存分配,那么有没有不进行内存分配的转换方法呢?
答案是有的。因为string和[]byte底层类型大致一样,我们可以通过非类型安全指针unsafe.Pointer进行指针类型转换,该方法是优化字符串和字节切片互转的常见手段。具体实现可以参考下面:
1 | go复制代码func bytes2string(b []byte) string{ |
再接着上面的反转字符串处理,我们使用无内存分配的方式试一下,点击在线运行:
1 | go复制代码func main() { |
运行上面代码我们可以看到类似下面的SEGV内存错误:
1 | less复制代码unexpected fault address 0x461f48 |
这是因为我们直接操作的是字符底层内容,而字符串底层内容存储在的进程内存布局的.rodata段(准确说应该是data段中.rodata节)中,该段是只读的。我们反转字符时候,会进行写入操作,故运行时会报出上面的段错误提示,这也是Go中字符串只读的原因。字符串的底层结构如下:
一路下来貌似无法做到在零内存分配情况下反转字符串。其实只需要改变上面字符底层内容所在内存的权限,让它可写就行了。Linux中提供了mprotect系统调用,可以用来更改进程内存页的读写权限。需要注意的mprotect操作的最小单位是内存页,传入的地址参数需要以页边界对齐。最后代码如下,点击在线运行。
1 | go复制代码func main() { |
至此任务完成。需要注意的是上面代码中使用到固定大小的数组,不是非常完美的解决方案。
本文转载自: 掘金
Go语言搬砖 kylin任务自动化
「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」。
前言
kylin是一个开源的OLAP分析引擎,具有亚秒级查询大表的能力
通过kylin提供的cube预构建功能,省去了不断写sql查询hive的麻烦,强化了任务统一管理和结果快速呈现的效果
kylin官网: kylin.apache.org/cn/
任务
当kylin集群比较大,和有多个kylin集群时,说明cube也越来越多,几百上千个cube便是常用便饭了
这些任务的运行就成了难题,人工去界面上点点点完全不实现了。此时就需要做成自动化周期性的任务
因为官方没有提供Go的客户端,只提供了http的api请求。下列例子使用Go中的http包来实现自动化任务
自动化实现
初始化
使用第三方http包(HttpRequest)来做http相关的请求,该包支持GET,POST,DELETE,PUT等四种请求方法,正好完全满足请求kylin的要求
1 | js复制代码var ( |
cube提交build
该方法接收三个参数,需要构建的cube名称,以及开始时间戳和结束时间戳
调用示例: cubeBuild(“dwd_jd_order”,”1637193600000”,”1637280000000”)
时间戳获取方法,在第6小节
1 | js复制代码func cubeBuild(cube,startTime,endTime string) { |
cube运行结果检查
检查cube运行结果,是成功还是失败了,还提供一个重新构建开关,如果cube失败,调用重构
kylin job检查接口属性说明
- jobSearchMode 搜索模式(检查点和cubeing两种) ALL所有模式的数据
- limit 限制返回条数
- offset 位置(0是从第一条开始)
- status 状态类型(8是错误类型,0是new,1是pending,2是running,32是stopped,4是finished,16是discarded)
- timeFilter 时间范围过滤(1是一天,2是一周,3是一月,4是一年,5是全部)
调用示例: jobCheck(false)
为什么要在检查里面调重构方法,是因为重构cube需要拿到uuid,但uuid只能在这个接口中获取到,且uuid不是固定的,需要运cube运行后才可得到
1 | js复制代码func jobCheck(resumeSwitch bool) { |
重构cube
重构cube在job失败后,自动构建非常有用,避免人工频繁介入到这些工作中,是自动化中关键一步
调用示例: cubeResume(“uuid”)
1 | js复制代码func cubeResume(uuid string) { |
历史job清理
kylin在运行一段时间后,就会产生很多冗余,且时需要周期性的清理这些历史job
调用示例: jobHistoryDelete(“uuid”)
需要先检查job,获取uuid,然后再删除历史job
1 | js复制代码func jobHistoryDelete(uuid string) { |
时间戳
kylin要求的时间毫秒,这里使用纳秒时间戳方法除一下就得到了毫秒
1 | js复制代码func timestamp() { |
小结
以上方法配合定时任务,就可以实现kylin自动化运维工作了
当然kylin官网还提供了更多接口,有需求的同学可以看看
传送门: kylin.apache.org/cn/docs31/h…
本文转载自: 掘金
MySQL高级 - 备份与恢复
「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」
为什么要进行数据备份
我们试着想一想,在生产环境中什么最重要?如果我们服务器的硬件坏了可以维修或者换新,软件问题可以修复或重新安装,但是如果数据没了呢?对于一些网站、系统来说,数据库就是一切,所以做好数据库的备份是至关重要的!
数据库备份的应用场景
数据备份在很多工作中都是经常会用到的,因为数据容易因为各种原因而丢失,造成数据丢失的原因有哪些呢?
- 数据丢失应用场景
- 系统硬件或软件故障
- 自然灾害,比如水灾、火灾、地震等
- 黑客攻击,非法访问者故意破坏
- 误操作,人为的误操作占比最大
- 非数据丢失应用场景
- 开发测试环境数据库搭建
- 数据库或者数据迁移
数据备份的类型
按照业务方式分
- 完全备份
- 将数据库的全部信息进行备份,包括数据库的数据文件、日志文件,还需要备份文件的存储位置以及数据库中的全部对象和相关信息。
- 差异备份
- 备份从最近的完全备份后对数据所做的修改,备份完全备份后变化了的数据文件、日志文件以及数据库中其他被修改的内容。
- 增量备份
- 增量备份是指在一次全备份或上一次增量备份后,以后每次的备份只需备份与前一次相比增加或者被修改的文件。
| 完全备份 | 差异备份 | 增量备份 | |
|---|---|---|---|
| 备份方法 | 备份所有文件 | 一次全备份后,备份与全备份差异的部分 | 一次全备份后,备份与上次备份的差异部分 |
| 备份速度 | 最慢 | 较快 | 最快 |
| 恢复速度 | 最快 | 较快 | 最慢 |
| 空间要求 | 最多 | 较多 | 最少 |
| 优势 | 最快的恢复速度,只需要上一次完全备份就能恢复 | 相比增量,更快也更简单并且只需要最近一次的完全备份和最后一次的差异备份就能恢复 | 备份速度快,较少的空间需求,没有重复的备份文件 |
| 劣势 | 最多的空间需求大量重复的备份 | 较慢的备份速度,仍然会存在许多的备份文件 | 最慢的恢复速度恢复需要最近一次完全备份和全部增量备份 |
备份的组合方式
- 完全备份与差异备份
以每周数据备份为例,可以在星期一进行完全备份,在星期二至星期六进行差异备份。如果在星期六数据被破坏了,则只需要还原星期一完全的备份和星期五的差异备份。
这种策略备份数据需要较多的时间,但还原数据使用较少的时间。
- 完全备份与增量备份
以每周数据备份为例,在星期一进行完全备份,在星期二至星期六进行增量备份。如果在星期六数据被破坏了,则需要还原星期一正常的备份和从星期二至星期五的所有增量备份。
这种策略备份数据需要较少的时间,但还原数据使用较长的时间。
MySQL冷备份和热备份
- 冷备份和热备份指的是,按照数据库的运行状态分类
冷备份
- 冷备份指的是当数据库进行备份时,数据库不能进行读写操作,即数据库要下线
冷备份的优点:
- 是操作比较方便的备份方法(只需拷贝文件)
- 低度维护,高度安全。
冷备份的缺点:
- 在实施备份的全过程中,数据库必须要作备份而不能作其它工作。
- 若磁盘空间有限,只能拷贝到磁带等其它外部存储设备上,速度比较慢慢。
- 不能按表或按用户恢复。
热备份
- 热备份是在数据库运行的情况下,备份数据库操作的sql语句,当数据库发生问题时,可以重新执行一遍备份的sql语句。
热备份的优点:
- 可在表空间或数据文件级备份,备份时间短。
- 备份时数据库仍可使用。
- 可达到秒级恢复(恢复到某一时间点上)。
热备份的缺点:
- 不能出错,否则后果严重。
- 因难维护,所以要特别仔细小心,不允许“以失败而告终”。
实战演练
冷备份实战
关闭SELinux
- 修改selinux配置文件,将SELINUX=enforcing改为SELINUX=disabled,保存后退出
1 | ini复制代码vim /etc/selinux/config |
- 修改后需要重启
1 | bash复制代码reboot # 重启命令 |
找到MySQL数据文件位置,停止MySQL服务
1 | mysql复制代码SHOW VARIABLES LIKE '%dir%'; |
进入到/mysql目录,执行打包命令 将数据文件打包备份
1 | bash复制代码cd /var/lib/ # 进入其上级目录 |
删除掉数据目录下的所有数据
1 | bash复制代码-- 删除原目录 |
恢复数据(使用tar命令)
1 | bash复制代码-- 解压 |
启动MySQL,然后登陆MySQL,查看数据是否丢失,如果数据正常代表冷备成功
1 | sql复制代码service mysqld start |
热备份实战
- mysqldump备份工具
mysqldump是MySQL数据库用来备份和数据转移的一个工具,一般在数据量很小的时候(几个G)可以用于备份。热备可以对多个库进行备份,可以对单张表或者某几张表进行备份。
- 备份单个数据库
- 创建文件夹,备份数据
1 | csharp复制代码[root@localhost ~]# mkdir databackup |
- 模拟数据丢失,删除数据库,然后重新创建一个新的库
1 | mysql复制代码DROP DATABASE lagou_edu; |
- 恢复数据
1 | csharp复制代码[root@localhost databackup]cd databackup |
- 备份数据库的某些表
- 备份表数据
1 | csharp复制代码[root@localhost databackup]# mysqldump -uroot -p lagou_edu course course_lesson |
- 模拟数据丢失,删除数据表
1 | mysql复制代码DROP TABLE course; |
- 恢复数据
1 | css复制代码mysql -uroot -p lagou_edu < backupTable.sql |
- 直接将MySQL数据库压缩备份
- 备份数据
1 | css复制代码mysqldump -uroot -p lagou_edu | gzip > lagou_edu.sql.gz |
- 模拟数据丢失,删除数据库
1 | mysql复制代码DROP DATABASE lagou_edu; |
- 恢复数据
1 | css复制代码gunzip < lagou_edu.sql.gz | mysql -uroot -p lagou_edu |
本文转载自: 掘金