崔小拽

崔小拽 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 cuihuan.net 编辑
编辑

靠谱,爱分享,屌丝小码农。

个人动态

崔小拽 分享了头条 · 2月16日

重读一周代码,关注一周的一些关键细节!

赞 1 收藏 0 评论 0

崔小拽 发布了文章 · 2019-05-12

字符编码那些事儿

身为一名要冲出国门的国际化码农🙃,字符编码是必备课题。小拽本文依次介绍下字节,ASCII,GB2312,GBK,GB18030,UNICODE,UTF8,UTF16,ICU 等到底是什么鬼?最后理论结合实际,研究下网站中经常出现的“锟斤拷,��,烫烫烫烫烫,屯屯屯屯屯屯”是什么神兵利器O(∩_∩)O?

一、二进制和字节

大概一百多年前,贝尔实验室制造了世界上的第一个晶体管,晶体管具有开合(0和1)的状态,这种01状态的变化被称为二进制位(bit)

过了几年,英特尔把八个可以开合的晶体管组合,做为一个基本的记录单元,这个单元被称作字节(byte),所以一个byte由8个bit构成,可以表达256种状态

又过几年,祖师爷冯诺依曼设计了一台可以存储和处理(冯诺依曼体系)字节变动的机器ENIAC,后来这个机器被称作计算机

二、标准ASCII

计算机运行是二进制,如何用二进制位来标识人类语言,就需要和计算机有一套约定关系。例如约定,0100 1111代表O,0100 1011代表K,那么存储为01001111 01001011的两个字节就代表OK,这套约定关系被称作字符编码

冯祖师爷是的德国人,二战去了美国设计了第一台计算机。起初,只有美国人能用计算机,山姆大叔就根据英语习惯设计了一套映射约定

  • 0-31 标识控制字符,例如换行[LF],删除[DEL],确认[ACK]等
  • 32-47 标识符号例如!@#$等
  • 48-57 标识0-9是个阿拉伯数字
  • 65-122 标识大小写字母

大家都按着这个约定来,交流表达起来没啥问题,呵呵,都挺好。于是这个方案就一致通过了,山姆大叔也给这个约定起了个名字ASCII编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文字符。

计算机一个标准字节8bit本身可以标识256个符号,但标准的ASCII的最高位去掉用做奇偶校验,用剩余7位标识128个符号,如下图

ASCII

三、ASCII 扩展字符集

随着计算机的发展,欧洲人开始逐步接触计算机了。

英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号

IBM牵头扩充了ASCII编码128-256位的标识字符,主要是一些欧洲的常用符号,这部分扩展映射被称为ASCII扩展字符集

四、GB2312

雄关漫道真如铁,而今迈步从头越,美帝国主义万万没有想到,二战后,大量第三世界的人民站起来了,逐步开始使用计算机。但问题是256个字符已经没啥可利用的字节状态来表示汉字了,更何况中华文明有6000多个常用汉字需要保存呢。

但是这难不倒智慧的中国人民,面对帝国主义的压迫,我们毫不客气的做了两件事情,并在1980年发表了这个声明

  • 互相尊重:尊重标准ASCII 规范中0-127位表示的标准字符。
  • 平等互利:ASCII的128-256位,我们用来标识中文,由于中文太多,我们要使用两个字节来表示一个中文^_^

中国人民觉的这个声明还不错,毕竟当时计算机的使用范围也不大,基本满足需求,于是就把这种汉字方案叫做 GB2312编码GB2312 是对 ASCII 的中文扩展

非专业人士可以忽略: GB2312如何组合,能表示多少个?
GB2312中用两个字节来标识一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,简单计算

0xA1:10*16 + 1 = 161  
0xF7:15*16 + 7 = 247 => 247-161 = 86
0xFE:15*16 + 14= 254 => 254-161 = 93
因此GB2312可以标识约86*93=7998 个汉字

实时上 GB2312 标准共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个。
同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。
几乎覆盖了大陆常用的99.75%汉字

这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的全角字符,而原来在127号以下的那些就叫半角字符了。

五、GBK

满足了基础和常用的汉字需求后,但依然会有很多人的生僻字名字打不出来,屌丝还好,但是一旦牵涉伟人名字打不出来那就坑爹了!改改改,抓紧改!

GB2312的编码,使用两个字符,每个都只用了后128位,不合理呀,干脆我们把低字节127号之后的内码我们也用了,不浪费

说干就干,于是扩展之后的编码方案被称为GBK标准(不知道K是不是扩展的缩写K^_^),GBK包括了GB2312的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号

非专业人士可以忽略: GBK如何组合,能表示多少个?
第一个字节的值从 0x81 到 0xFE保持不变,第二个字节的值扩展从 0x40 到 0xFE

0xFE-0x81:254 - 8*16+1 =125
0xFE-0x40:254 - 4*16 =190 

因此,GBK约标识了 125*190  = 23750 

GBK 共收入 21886 个汉字和图形符号,包括:GB2312 中的全部汉字、非汉字符号;BIG5中的全部汉字;ISO10646 相应的国家标准GB13000 中的其它 CJK 汉字
以上合计 20902 个汉字; 其它汉字、部首、符号,共计 984 个。

六、GB18030

中华民族大团结,后来少数民族也要用电脑了,于是我们需要再次扩展,又加了几千个新的少数民族的字,GBK扩成了GB18030。从此之后,中华民族的文化就可以完美的在计算机时代中传承了。

非专业人士忽略:
这一系列汉字编码的标准通为 `DBCS`(Double Byte Charecter Set 双字节字符集)。
在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,
必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了

从ASCII到GB2312,再到GBK,而后GB18030,中华民族终于完成了全量中文字符的编码,简单总结下

  • 第一阶段:中国人民通过对 ASCII 编码的中文扩充改造,产生了GB2312 编码,可以表示6000多个常用汉字。
  • 第二阶段:汉字实在是太多了,包括繁体和各种字符,于是产生了 GBK 编码,它包括了 GB2312 中的编码,同时扩充了很多。
  • 第三阶段:中国是个多民族国家,各个民族几乎都有自己独立的语言系统,为了表示那些字符,继续把 GBK 编码扩充为 GB18030 编码,完成全量中文字符编码。

六、UNICODE

之后的世界,百花齐放,百家争鸣,各国纷纷制造自己的编码规范,同时互相不去理解对方规范,即使同一种语言也区别巨大,例如台湾地区中文采用big5的繁体编码,名字也牛逼大了,叫大五码

各自为政引来了大量的问题,各个语言互不兼容,此时,一堆大佬看不下去了,勇敢的站了出来,着手解决这个问题,他们成立了一个类似于TC的组织,叫做ISO(International Organization for Standardization 国际标准化组织)。

他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它"Universal Multiple-Octet Coded Character Set",简称 UCS, 俗称 "UNICODE"。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码

UNICODE统一了各国,成为了实事上的大一统的编码规范。这种编码非常大,大到可以容纳世界上任何一个文字和标志。所以只要电脑上有 UNICODE 这种编码系统,无论是全球哪种文字,只需要保存文件的时候,保存成 UNICODE 编码就可以被其他电脑正常解释。

非专业人士忽略:unicode 编码
unicode开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。
于是ISO就直接规定:
1:必须用两个字节,也就是16位来统一表示所有的字符
2:对于ASCII里的那些“半角”字符,unicode包持其原编码不变,只是将其长度由原来的8位扩展为16位,
3:其他文化和语言的字符则全部重新统一编码。

由于”半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。

七、UTF,UTF8,UTF16

UNICODE很好的解决了不同语言统一编码的问题,但同样也不完美,有两个主要问题,

  • 字符识别:如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?
  • 存储浪费:我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储空间来说是极大的浪费,文本文件的大小会因此大出二三倍

此时UTF(unicode transfer format)标准出现了,顾名思义,是UNICODE在传输和存储过程中的格式化标准,其中使用最广的是utf8和utf16

UTF-16相对好理解,就是任何字符对应的数字都用两个字节来保存!我们通常对Unicode的理解就是把Unicode与UTF-16等同了。但是很显然如果都是英文字母这做有点浪费,明明用一个字节能表示一个字符为啥整两个啊。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,当字符在ASCII码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码做为它的一部分,注意的是unicode一个中文字符占2个字节,而UTF-8一个中文字符占3个字节)。从unicode到utf-8并不是直接的对应,而是要过一些算法和规则来转换。

非专业人士直接忽略:unicode 如何转换成utf-8
以小拽的"拽"字为例

Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
—————————————————————–
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
中文一般三个字节,前置标识位

举个栗子,中文:拽 unicode是25341
25341                    十进制 unicode
0x62fd                   十六进制 
0110 0010 1111 1101      二进制 

### 套上模板
0110      001011    111101      二进制  25341
1110xxxx  10xxxxxx  10xxxxxx    模板第三行
11100110  10001011  10111101    utf8 二进制
e   6     8   b     b   d       utf8 十六进制【一切为了节省】
最终utf8拽对应的就是0xe68bdb

UTF-8就是在互联网上使用最广的一种unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

简单对比下GB系列,UTF8,UTF16

  • UTF16:不推荐使用utf16,因为utf16最初能表示的字符数有6万多,看起来很多,但是实际上目前 Unicode5.0 收录的字符已经达到99024个字符,其实不够,有可能出现乱码
  • UTF8:国际化编码首推UTF8,兼容全量,唯一的问题是空间略有浪费!
  • GB系列:GB系列都是双字节字符集,相对节省空间,如果只是国内使用GB18030完全可以兼容所有

八、“锟斤拷��” 是什么

通过上面介绍,可以看出来,各个编码规则是不一样的,目前互联网浏览器默认传输和解析方式是UTF8,但是部分老的网页采用GB系列,就会出现传输过程UTF8解析不了,展示GB错乱问题。

UNIDCODE规定:当unicode遇到解释失败的字时,会尝试用 「U+FFFD」 来代替,「U+FFFD」乃是 unicode 的一个占位符, 显示为 �

而utf8识别为异常的传输字符后,传到页面转为双字节展示的GB会怎么样呢?

➜ xiaozhuai ✗ python
Python 2.7.10 (default, Aug 17 2018, 19:45:58)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> s = (u'\uFFFD'.encode('utf8')*2)
>>> print(s.decode('gbk'))
锟斤拷

也就产生了,传说中的"锟斤拷"神器!

另外还有几个神器:"烫烫烫烫烫,屯屯屯屯屯屯"
“烫” 主要出没于 windows 平台下,ms 的 vc++ 编译器中, 当你在栈内开辟新内存时, vc 会使用 0xcc 来初始化填充, 很多个 0xcc 连起来就成了 烫烫烫烫烫 同理在堆内开辟新内存时, 会用 0xcd 填充,这便是 屯屯屯屯屯屯
不管是 “锟斤拷” 还是 “烫” 都要求最后是用GB码输出。

九、ICU

在unicode的统治下,世界各国的基本编码不会出现乱码等异常。但当中华民族逐步强大,准备冲出中国统一世界的时候,发现各国的货币,时间,数字等表示灰常不统一,例如数字1234.5,英文表示1,234.5,葡语表示确是1.234,5,很是苦恼。

此时IBM站了出来,叫上google,apple等小伙伴,遵循"IBM公共许可证",开源了一套基于unicode的国际化组件ICU(International Component for Unicode
)。根据各地的风俗和语言习惯,实现对数字、货币、时间、日期、和消息的格式化、解析,对字符串进行大小写转换、整理、搜索和排序等功能,ICU4C提供了强大的BIDI算法,对阿拉伯语等BIDI语言提供了完善的支持。

ICU成为了目前国际化组件的实事标准,底层依赖UNICODE和CLDR,官方提供了C/C++和JAVA的SDK,ICU4C和ICU4J,同时,各个语言在此基础上开发了各个语言的版本,例如php的intl组件。

十、实事标准

字符编码的从产生,发展,到国际化一步一步走来,逐步形成了下列实事标准

  • 字符集:UNICODE
  • 字节编码:UTF8
  • 国际化:ICU
需要注意的是,mysql的utf8并不完全兼容标准的utf8编码,后续推出了utf8mb4完全兼容,所以推荐采用utf8mb4

参考网站:

clipboard.png

【转载请注明:字符编码那些事儿 | 靠谱崔小拽

查看原文

赞 2 收藏 1 评论 0

崔小拽 发布了文章 · 2019-04-03

数据归档那些事儿

热点账户问题和常用解决方案【中】这篇文章中提到,解决热点读性能的一个非常通用方式是数据归档。本篇小拽总结下在操作数据归档过程中遇到的一些问题和经验!

一、数据归档

所谓数据归档就是把部分低频访问的历史数据从线上库迁移到归档库的过程。在设计数据归档方案的时候通常需要思考三个问题

  • 归档前:如何进行存储选型
  • 归档中:如何保证迁移准确
  • 归档后:如何处理数据完整性破坏所引起的问题

下面也着重从这三部分来聊聊

二、存储选型

存储选型是归档前要做的最重要的一件事情,目前市面上的存储方式多如牛毛,如何选择能够支撑当前业务环境的存储选型,就非常重要!

2.1 归档的数据特点

既然是要选型数据归档的存储,首先来需要梳理下归档数据的特点

  • 读性能:归档数据对读性能没啥要求,能够读出来就可以
  • 写性能:尽可能好的批量写入性能,能够批量1w+达标
  • 压缩比:尽可能的节省空间,采用高压缩比的存储引擎
  • 分布式:最好能够分布式,考虑到目前单片都40T了,非分也可
  • 数据量级:上限尽可能高,考虑到实际情况,10TB+目前达标
  • 一致性保证:归档是兜底,尽可能高的保证数据不会出现异常丢失

2.2 通用选型因素

除了考虑归档数据的特点,还要考虑一些通用因素,例如

  • 公司是否运维支持:大厂这个因素很重要,如果运维支持背书,最好不过!
  • 开源活跃程度:活跃度太低不能选
  • 普遍使用场景:跳出存储给的通用场景的不能选

2.3 备选存储的特性

也初步总结和梳理了下可能用到的集中存储的特性

archive_1

结合归档数据的特点和不同存储的优势,最终选用了

  • rocksDB:作为存储归档数据引擎,性能和数据压缩比都不错,最主要是公司DBA愿意支持
  • ES:作为在线查询,公司运维支持
  • HIVE:作为财务数仓核心数据和全量数据中心,哈哈,为下一篇财务数据中台做铺垫^_^
  • fusion:作为幂等健破坏后的幂等健KV池

三、一致性保证

归档过程存在会删除线上数据,是个非常高危的操作,所以操作过程中和操作之后都需要特别注意数据一致性的保证。

对于操作过程的一致性保证相对简单,过程通常两步
step1 插入确认:查询线上库->插入归档库->查询归档库->确认插入
step2 删除确认:删除线上库->查询线上库->确认删除
注意:过程中尽可能的保证读取和写入的时间,删除会锁库,大批量读会抢网络和IO,防止对线上业务造成压力,尽可能调优批量数据,推荐条目在200-1000条一次,数据量在5M-100M一次

四、归档后问题

数据归档后,必然破坏了数据的完整性,会造成下面几个问题,,需要提前考虑

4.1 读数据穿透问题

低频历史数据归档后,造成线上数据缺失,查询数据穿透和范围关系查询损失都会存在。因此,数据归档后,对于读操作有两种处理方式

  • 归档数据不读:最简单,但是对于某些场景可能确实不太合适。
  • 读proxy兼容:通过读proxy,穿透性的选择各种存储截止。

针对读数据,还有一种比较特殊的情况,就是跨区间范围关系聚合,这样就需要有一份完整数据来满足极端需求,目前财务系统对于这类需求统一走离线财务数仓来解决!

4.2 写幂等破坏问题

对于写数据最大的问题就是幂等健被破坏,归档了数据后,rds写入唯一健破坏,在极端情况下,可能会造成duplicate。考虑到问题的出现概率和实现成本,初期可以忽略,采用人工干预的方式,归档最终要写入全量,写不进去就是duplicate了;后面可以采用前置幂等健组来挡,做到最终一致!

4.3 数据一致性问题

无论是数仓数据还是归档数据,作为财务数据,一旦提供资金服务,那么就必须保证强一致性,财务目前采用离线分天统计数据的金额和数量,来保证宏观上的一致性。这里面也有需要小坑,例如数据飘移,时间gap等,关于财务数仓中遇到的坑和解决方案,后续专项讨论

五、最终归档方案

分析了归档前选型,归档中数据转移,归档后数据完整性问题,初步的归档方案如下图

archive_2

简单梳理下核心流程

  • 写数据流:写数据写入online rds[备注:目前没有前置幂等拦截,后面择机完善],写入后通过binlog准实时写入es,提供线上读服务;通过binlog小时级入hive,作为分析数据和全量数据存储;通过天级归档脚本,将历史数据导入rocksdb归档。同时,如果有日切,也会天级进行数据日切和新表创建。
  • 读数据流:读数据过proxy,非归档期间数据直接读取es,归档数据和es没有的数据都会穿透到rocksdb。
  • 监控流:天级监控hive,es,rocksdb,三个不同来源的数据条目和总金额,保证一致性。

六、总结和不足

本篇主要总结了小拽在数据归档过程中,如何选型,如何归档以及在归档数据后引起的问题如何处理。

通过数据归档,更清楚的划定了不同存储介质的功能边界,是进行数据中台搭建,赋能业务的前置准备!

【转载请注明:数据归档那些事儿 | 靠谱崔小拽

查看原文

赞 2 收藏 2 评论 0

崔小拽 发布了文章 · 2019-04-01

热点账户问题和常用解决方案【中】

话接上回,上篇阐述了什么是热点账户,基本财务账户如何设计,幂等健和链式设计!本篇将针对热点账户在实践中引发的问题,梳理和拆解业务流,分析问题点,提出七种常用解决方案。

一、性能问题初现

上线初期数据量较小,运行正常!
一次大促后,账户流水的总数目接近亿级别,初现性能问题:系统整体的qps也就10+,但热点账户写入失败率偏高,并且随数据量增加失败率逐步升高;整个账户系统全靠上游有redo标识位不断重试,才能保证最终写入成功!

哈哈,作为一名拥有三年工作经验的老码农,面对问题,要做的第一件事,就是,抽根烟静静,准备开搞!

二、数据流拆解

拿到问题,抽根烟静一下之后,分析问题需要三步:梳理数据流,拆解过程,定位问题点。先对财务账户更新的数据流进行拆解

finance_hot_account_3

链式锁后的基本账户操作过程,分为如下五阶段

  • 请求阶段:账户操作请求。
  • 查询阶段:查询当前账户信息。主要获取当前链,资金数据等!
  • 计算阶段:对链和操作的资金进行计算,判定资金操作合规,同时保证幂等和并发!
  • 写入阶段:事务同步写入流水和余额
  • 响应阶段:告知上游回调

三、链路分析

梳理数据流后,接下来分析每个阶段可能引发的问题。按照优先级,先分析业务问题区域(读取阶段,计算阶段,写入阶段),通常问题会出现在业务阶段;之后,再分析框架问题区域(请求阶段和回调阶段),该区域出现问题的情况偏小,但是一旦出现问题,就是比较有意思^_^!

3.1 业务问题区域分析

读取阶段,计算阶段,写入阶段三个阶段是具体的业务操作,从并发和耗时两个角度来分析下可能的问题点

3.2.1 耗时分析

耗时分为三块

  • 查询耗时:RDS拥有亿级别数据量,查询未中primary,但命中索引,业务数据体并未完全在索引中,因此访问数据走index match;数据主键聚簇,唯一健索引查询获取数据,page极难命中cache,也不会命中磁盘电梯算法优化!结合实际情况,查询耗时在10-100ms级别
  • 写入耗时:insert 包含了自增,理论上在数据落盘是追加写,即使uniq_key去创建索引的情况下,耗时在ms级
  • 过程耗时:长连接情况下,init conn时间基本可以忽略,但是读写两次往返数据库的链路时间还是需要考虑,整体预估在1-2ms之间

从整体上看,预估该阶段的耗时在10-100+ms,从实际失败率来看也基本一致!

3.2.2 并发分析

  • 天级QPS:当时分析天级几十万单,天级QPS不到10,不高!
  • 瞬间QPS:每个订单拆解到资金流后,会同时操作多次热点账户,瞬间qps相对很高,理论qps就可能达到语言上限,由于上游链路限流1024,按照10级别操作拆分,理论上满池QPS在万级别。考虑实际单量,瞬间QPS=单量(10)*拆解量(10),实际的满额预估QPS可能到100+ !

按照上面分析,在瞬时QPS达到10+的情况下,热点账户整体延时在10-100+ms,由于DB在写入uniq_key保证链点唯一,所以出现并发写入失败也在情理之中;并且随着数据量的提升,读取延时增加,写入失败率会继续增加。

3.2 框架问题区域

请求阶段做为入口,一般也分为三个小阶段

  • webserver接收请求
  • 框架加载和路由
  • 基础校验

请求阶段核心耗时一般存在于框架加载和路由,高并发场景webserver和upstream之间的调用也是一个可能出问题点!当时财务系统,采用欢总封装的go-thrift,并且其他模块并未出现请求阶段问题,所以并未对这个阶段的latency和并发做一个衡量,重点分析了业务模块!

四、解决方案

4.1 读取和写入阶段优化

通过上面分析,目前问题的痛点是并发读取热点账户数据高延时引发写入失败,提升读性能成为了关键

读性能提升有两个基本思路:读的时效快和读的次数少

针对上面两个基本思路,结合财务账户情况提出了五种提升读性能的解决方案

  • 【读快】持久化last record:不从全量数据里面读,抽离子账户的最新信息,持久化到单独的表中或者内存中,降低整体数据量,提升了读性能。缺点是要保证持久化信息的准确性,引入事务写。
  • 【读快】纵向切分-时间分库分表:按照时间进行纵向切分,降低查询范围内的数据量,提升读性能。缺点是跨时间读不友好,开发量也不小
  • 【读快】纵向切分-归档:历史数据归档是实现相对简单,跨时间读也比较友好,随着数据量的提升,也是必须要做,之后会详细介绍归档方案和选型。
  • 【读快】横向切分-业务分库分表:按照账户类型或者城市分库分表,可以优化读写数据量,同时,跨表读负担也会较小。但对于热点账户或者热点城市,依然聚簇,效果不是很明显。同时,再次对热点账户进行横向分库分表也是极度不推荐,引入的极高的读写成本均。
  • 【读少】阶段快照:一定量或者一定时间内的数据,持久化一次。优势是极大的降低读写次数;缺点是需要复杂的逻辑来保证undo操作和数据一致性!

五种解决方案各有千秋,作为一个初期的财务系统推荐采用持久化last record和数据归档来保证写入读性能和整体读的数据量。如果系统发展到了中期,推荐按照时间分库分表。如果发展到了双11或者春晚某些极端场景,牺牲掉部分准确性,采用阶段快照也是可以的。

4.2 计算阶段优化

存在计算阶段造成的最大影响也就是引起了两次数据传输,通常是不可避免的,但是如果真的是要进行提升有一种方案通用方案

  • DB计算通过存储计算,转嫁计算成本给DB,减少一次链路请求。但不太推荐,复杂的sql往往有坑,insert computer from select 还会造成大面积的数据隔离,很容易引起死锁。

4.3 请求和回调阶段优化

请求阶段一般有三种形式:同步调用,异步调用和伪同步调用
前两种调用非常常见:同步爆池的情况,一般采用限流来降压,采用漏桶,令牌桶等等策略;异步调用通常采用消息队列来削峰填谷;这里重点阐述对于支付和财务系统在请求阶段经常采用的伪同步的方式

伪同步流量较早出现在innodb,leveldb等存储引擎为了利用追加写提升写入性能,采用类WAL日志来持久化数据。通常伪同步方案采用三件套:WAL日志+校验位+广播消息来完成一次完整的请求!流程图一般如下

finance_hot_account_4

  • 请求阶段:同步请求调用,核心要素追加写入wal日志,变更校验位,完成同步调用!此处追加写保证了快速写入,校验位来保证数据的最终写入成功。图中1,2
  • 异步阶段:通过读取wal日志的核心数据,进行复杂事务处理,如果成功进入下一阶段;如果失败,没问题,通过外部trigger来触发redo操作!如果多次redo依然失败,那么通过undo来回滚数据
  • 回调阶段:如果成功,更改校验位,同时发布成功广播消息,关注结果和时效性的模块,可以获取最终成功的标识!如果undo回滚数据,则发布失败广播消息,告知结果失败!

在伪同步的模式下指标衡量:

  • QPS:伪同步模式,采用WAL核心要素追加写,所以写性能可以极大提升,进而满额QPS相对直接同步调用也大量提升
  • 时效性:伪同步并非完全同步,所以结果需要监听回调。对于结果强一致的请求,必须监听回调,确保一致,时效性降低;对于弱一致可以认为同步回调即成功,时效性提升。
  • 失败率:操作知识核心要素追加写入,真正的操作通过异步保证,整体成功率提升!

对于资金处理过程,大量采用伪同步的请求方式来保证快速写入和最终一致性

4.4 解决方案总结

总的来说,归结了七种优化方式(哈哈,上篇写的八种优化,当时总结的,现在愣是想不到还有啥了^_^)。其中请求和回调的伪同步方式,是在架构层面优化,这个在多数的财务系统和财务系统的内部数据流中都会用到;而读写和计算阶段的优化,可以跟进实际业务情况进行选型处理。

五、事故复盘

面对各种优化方案,需要结合实际情况做出取舍,有的是长期方案,有的是快速方案,但一定需要想清楚了再开搞,过程中有一个对小拽之后影响很大的事故,引以为戒。

翻车过程:当时觉的读->计算->写这个过程,两次读DB操作,下沉计算过程到DB后,通过DB计算,可以减少一次数据库请求。于是合并了一个大SQL,也就是所谓的insert ( field computer from select),觉的写了个狂赚酷炫吊炸天的SQL,一上线,库锁死了!幸好有前置的redo flag,全量redo数据恢复,要不然估计直接祭天了!

对于这个复杂大SQL事故,小拽总结了三个方面

莫炫技:没啥好说的,解决问题的成就感要远大于炫技!
简单设计:简单的设计通常意味着可依赖;复杂的设计要尽可能的拆解,想清楚,队友都理解不了的设计,那就别上线了,可能真的需要再思考拆解下
尊重线上:核心服务基本上线流程一定要遵守,测试,监控和回滚方案缺一不可

六、小结

本篇主要针对热点账户问题提出了七种常用的解决方案,下篇将继续引申探索下,各种解决方案在不规则高并发场景,例如双十一,微博热点事件中如何套用

预知后事如何,下回再聊!

【转载请注明:热点账户问题和常用解决方案【中】 | 靠谱崔小拽

查看原文

赞 2 收藏 0 评论 0

崔小拽 评论了文章 · 2019-03-28

phpredis单例模式封装

通过单例模式实现对phpredis连接的封装。

直接上代码

<?php

/**
 * Class RedisConnManager
 *
 * 单例模式对redis实例的操作的进一步封装
 * 主要目的:防止过多的连接,一个页面只能存在一个声明连接
 * 
 * @author :cuihuan
 */
class RedisManager
{
    private static $redisInstance;

    /**
     * 私有化构造函数
     * 原因:防止外界调用构造新的对象
     */
    private function __construct(){}

    /**
     * 获取redis连接的唯一出口
     */
    static public function getRedisConn(){
        if(!self::$redisInstance instanceof self){
            self::$redisInstance = new self;
        }


        // 获取当前单例
        $temp = self::$redisInstance;
        // 调用私有化方法
        return $temp->connRedis();
    }

    /**
     * 连接ocean 上的redis的私有化方法
     * @return Redis
     */
    static private function connRedis()
    {
        try {
            $redis_ocean = new Redis();
            $redis_ocean->connect(G::$conf['redis-host'], G::$conf['redis-port']);
            $redis_ocean->auth(G::$conf['redis-pass']);

        }catch (Exception $e){
            echo $e->getMessage().'<br/>';
        }

        return $redis_ocean;
    }

}

【转载请注明:phpredis单例模式封装 | 靠谱崔小拽

查看原文

崔小拽 发布了文章 · 2019-02-13

热点账户问题和常用解决方案【上】

热点账户问题由来已久,一直是账户系统设计中的一个难点和瓶颈!
小拽将通过上中下三篇文章,分别介绍下热点账户的产生,解决方案和延伸应用!
本篇主要介绍下什么是热点账户?通用财务账户系统如何设计?以及其中的幂等健和链式设计等

一、热点账户问题

1.1 什么是热点账户

热点账户:顾名思义,热点账户就是会被高频操作的账户!相较于普通的账户,热点账户数量不多,但操作频率极高!

热点账户从产生来源可分两大类:

  • 富二代型:从产生之初就是热点账户,非常稳定。例如财务中公司的账户,每一笔资金操作都要经过公司出金账户,自然而然操作就会灰常频繁,此类账户还包括:大V账户大KA账户等等,此类账户所引起的问题是本文重点要解决的
  • 暴发户型:本身是普通账户,由于热点问题变为热点帐户。例如微博出轨女猪脚账户诺贝尔奖获得者等等,由于热点事件造成的短时间内访问暴增!此类热点账户防不胜防,超出本文的攻击范围,暂不讨论。

1.2 热点账户问题

热点账户一旦产生便伴随着高并发,流量分布不均匀,高一致性等等问题。在实际场景中是热点账户必然存在,常常成为用户系统的瓶颈!
同时,热点账户问题也是高并发问题的延展,由于热点的不规则性,如何在高并发情况下,削峰填谷,弹性抗压也是很有挑战性的一个方向!

1.3 热点账户通用解决方案的价值

热点账户除了是账户体系的一个通用问题,在高并发,流量分布不均匀,异常峰值等其他问题上,也有一定的通用性。例如微博热点问题,支付宝双11弹性变更,高频抢购问题等等。期望通过学习热点账户的八种解决方案,能够举一反三,应用于不同场景!

二、如何设计一个财务账户

在解决热点账户问题之前,先来看下如何设计一个简单的财务账户,来保障资金记账的安全!

2.1 业务场景分析

从业务上看,财务账户需要准确记录用户的资金变动过程和结果!因此设计一个简单财务账户至少要能包括两个部分:账户余额账户流水

便于理解,来张传统的账本,看下什么是流水,什么是余额
finance_hot_account_1

账户流水:账户流水也就是通俗意义上的帐或者账单!针对某个账户,每一笔资金的变更都需要记录下来,并且保障准确,不可更改!同时如图所示,流水中需要包含单据产生的原因,来源,变更额等等

账户余额:账户余额记录用户某个场景账户的当前资金额度!在复杂的业务场景中往往需要拆分出不同的子账户和账户模型。例如,未结算子账户,可提现子账户,冻结子账户,授信账户等等。

从业务场景上一个账户系统核心需要准确记录余额和流水,同时,必须保障记录的准确,完备,不可变更!

2.2 技术层面拆解

2.2.1 基本表方案

通过业务场景初步分析,基本的账户系统,需要三张基本表

账户基本信息:账户信息表
子账户余额信息:账户余额表
账户流水信息:账户流水表

三张表基本关系
账户信息表 1:N 账户余额表
账户余额表 1:N 账户流水表

## 具体账户和用户的关联可以参考三户模型 

2.2.2 表字段设计

从技术层面看,设计具体表细节关键要解决以下几个问题

  1. 防重:幂等健设计
  2. 防改:链式设计
  3. 防错:销账设计

先上结果,简单的,能够满足上述需求的设计可以参考innodb mvcc,核心表字段如下

finance_hot_account_2

2.2.3 表字段解读

2.2.3.1 幂等健设计

通过三个属性资金凭证号+版本号+rollback三个字段作为uniq key来保证幂等!

资金凭证号:来自业务方,业务方发起资金操作的唯一财务凭证,必须可追溯上游凭证和对账!
版本号:每次获取DB最新流水n后,版本号n+1插入,保障在并发情况下,每个子账户只有唯一一个版本号:n+1条记录能够插入成功!
rollback:回滚标识,保证每条记录能且只能销账一次

对于幂等建设计此处有三条小技巧

  1. 上游产生:每一个幂等健如果可能的话,尽可能的上游产生,这样可以最大限度的避免自产生幂等健的重复问题。如果确实不能上游产生,例如订单ID,提现单ID,那么也尽可能的分阶段产生,例如提现时,先生成提现单ID,真正提现操作的时候,一定是带着提现单ID和信息来的,防止重复造成资损!
  2. 业务关联:幂等健的产生可以用ice生成,但是,最好能够和业务关联,因为通过业务强关联的幂等健可以无限回溯来容灾!比如,a用户的b订单进行c操作,uniq_key = a_b_c的话,也就是在任何情况下,无论多少次回溯,重试也只会有一个唯一的a_b_c,而ice生成则可能造成自回溯的时候插入多条!
  3. 写库保证:这条原则是高一致高并发的基本原则!因为读取a,校验a,然后插入,必然会存在读写之间a变了,或者主从延时a已经变了,读了历史a。因此,幂等一定要通过写库保证或者最底层保证
2.2.3.2 链式设计

链式设计是保证操作精准不可篡改的非常有效手段!
通过资金的before info,after info,版本号三个要素来保证一条资金记录一旦插入成功,前后置信息固化!

链式设计的情况下单条修改是不可能的,多条修改需要在保证条目不变的情况下重组资金,但是,整体资金不可变

解决多条修改的一般方案:分布式存储,选举来判定最终正确的链,来确认是否某条链发生了过程修改,这种设计有一个很时髦的名字:区块链!而每条流水的核心信息加密后也有了一个更加时髦的名字:比特币

2.2.3.3 销账设计

销账设计在账户系统中是一直存在的,现实财务系统可以红销蓝抵,线上财务系统加了链式之后,基本上就只能采用蓝抵
通过增加rollback字段,并且严格限制0|1,保证一条账务流水只能被抵销一次!

具体三张表详细字段,需要脱敏,就不贴了,参考上面,其中索引,字段大小,联合索引等设计根据自身业务场景兼容即可!

小结:欲知后事如何,且听下回分解

本部分简单介绍了什么是热点账户和账户的基本设计,涵盖幂等健设计,链式设计等等!
下一篇重点分析下热点账户在链式设计下的问题,产生原因和八种基本解决方案

【转载请注明:热点账户问题和常用解决方案【上】 | 公众号:靠谱崔小拽 |】

查看原文

赞 1 收藏 1 评论 0

崔小拽 评论了文章 · 2019-01-09

财务系统设计【序】

新年伊始,组织团队小伙伴进行了一次头脑风暴,畅想了下财务系统2019的愿景,自己也思考颇多,决定针对【财务系统设计】做个专栏,落笔为记!

一、一次头脑风暴

头脑风暴前,除了准备泡面,花生,矿泉水,还列了几个比较现实的问题

  • 公司:你做的事情,值多少钱?公司凭什么给你升职,涨薪?财务系统到底还能给公司带来多少收益?
  • 自己:为什么要留下来?干一年财务系统,到底能给每个人带来多大成长?
  • 目标:下次跳槽时,你希望自己成长成什么样子?
  • 落地:规划每个人的模块方向,如何把自身成长需求和业务成长绑票?

与我个人而言,期望通过这次头脑风暴,让团队小伙伴们能够对自己的模块有个规划,能够在业务成长的过程中实现个人技能成长,能够通过促进模块收益来提升自己薪资职级!

无他,也希望各位看官能够思考一下

二、财务系统设计专栏

头脑风暴后,小拽也一直在思考,2018年干了一年财务系统了,2019年如何搞?

具体的需求拆解,模块设计,架构图,暂时先不祭出来了,毕竟还需要深入的拆解和剖析!
但结合年初的flag,小拽决定2019年完善【财务系统设计】专栏^_^,期望能够通过专栏,自己能够体系化的梳理下财务系统,抽象出更通用的解决方案!

废话不提先列下2018年亏欠的文章和目录,今年一定补上^_^!

  • 热点账户问题思考和常用解决方案
  • 数据最终一致性保证
  • 幂等健设计原则
  • 全局ID生成思考和解决方案
  • 财务系统异步和同步的思考
  • 国际化账务系统思考
  • 财务数仓有哪些坑?
  • 通用账单分级模型设计
  • 账户模型设计和思考
  • 账户流水设计和思考

三、专栏目录

长远的看,小拽的财务模块设计最终会把所有文章落到各个模块中,暂时先梳理了下目录!

财务系统专栏
├── 在线系统设计和实现
│   ├── 分账模块
│   ├── 提现模块
│   ├── 收银模块
│   ├── 结算模块
│   ├── 记账模块
│   └── 账户模块
├── 支撑系统设计和实现
│   ├── MIS系统
│   ├── openAPI
│   ├── 任务系统
│   ├── 财务网关
│   ├── 数据质量中心
│   └── 监控预警系统
├── 数据中心设计和实现
│   ├── ARCHIVE
│   ├── GraphDB
│   ├── HBASE
│   ├── HIVE
│   ├── KV
│   ├── RDS
│   └── TSDB
└── 离线系统设计和实现
    ├── 对账引擎
    ├── 经营分析
    ├── 结算引擎
    ├── 财务报表
    ├── 资金安全
    └── 预算引擎

【转载请注明:财务系统设计【序】 | 靠谱崔小拽

查看原文

崔小拽 发布了文章 · 2019-01-06

财务系统设计【序】

新年伊始,组织团队小伙伴进行了一次头脑风暴,畅想了下财务系统2019的愿景,自己也思考颇多,决定针对【财务系统设计】做个专栏,落笔为记!

一、一次头脑风暴

头脑风暴前,除了准备泡面,花生,矿泉水,还列了几个比较现实的问题

  • 公司:你做的事情,值多少钱?公司凭什么给你升职,涨薪?财务系统到底还能给公司带来多少收益?
  • 自己:为什么要留下来?干一年财务系统,到底能给每个人带来多大成长?
  • 目标:下次跳槽时,你希望自己成长成什么样子?
  • 落地:规划每个人的模块方向,如何把自身成长需求和业务成长绑票?

与我个人而言,期望通过这次头脑风暴,让团队小伙伴们能够对自己的模块有个规划,能够在业务成长的过程中实现个人技能成长,能够通过促进模块收益来提升自己薪资职级!

无他,也希望各位看官能够思考一下

二、财务系统设计专栏

头脑风暴后,小拽也一直在思考,2018年干了一年财务系统了,2019年如何搞?

具体的需求拆解,模块设计,架构图,暂时先不祭出来了,毕竟还需要深入的拆解和剖析!
但结合年初的flag,小拽决定2019年完善【财务系统设计】专栏^_^,期望能够通过专栏,自己能够体系化的梳理下财务系统,抽象出更通用的解决方案!

废话不提先列下2018年亏欠的文章和目录,今年一定补上^_^!

  • 热点账户问题思考和常用解决方案
  • 数据最终一致性保证
  • 幂等健设计原则
  • 全局ID生成思考和解决方案
  • 财务系统异步和同步的思考
  • 国际化账务系统思考
  • 财务数仓有哪些坑?
  • 通用账单分级模型设计
  • 账户模型设计和思考
  • 账户流水设计和思考

三、专栏目录

长远的看,小拽的财务模块设计最终会把所有文章落到各个模块中,暂时先梳理了下目录!

财务系统专栏
├── 在线系统设计和实现
│   ├── 分账模块
│   ├── 提现模块
│   ├── 收银模块
│   ├── 结算模块
│   ├── 记账模块
│   └── 账户模块
├── 支撑系统设计和实现
│   ├── MIS系统
│   ├── openAPI
│   ├── 任务系统
│   ├── 财务网关
│   ├── 数据质量中心
│   └── 监控预警系统
├── 数据中心设计和实现
│   ├── ARCHIVE
│   ├── GraphDB
│   ├── HBASE
│   ├── HIVE
│   ├── KV
│   ├── RDS
│   └── TSDB
└── 离线系统设计和实现
    ├── 对账引擎
    ├── 经营分析
    ├── 结算引擎
    ├── 财务报表
    ├── 资金安全
    └── 预算引擎

【转载请注明:财务系统设计【序】 | 靠谱崔小拽

查看原文

赞 2 收藏 2 评论 2

崔小拽 发布了文章 · 2019-01-02

2018回顾

岁月不居,时光荏苒,本想写个2018的总结,结果2019都过2天了,三十功名已经泡汤,顺道写些2019展望吧!

工作方面

  • 工作方面,18年离开混了四年的狼厂母校,加入滴滴,没有了小伙伴和团队的呵护,只能独立面对,干了几件还算可以的事儿!花了四个月,从0到1的设计,开发了外卖财务在线系统;又花了四个月,搭建了财务离线系统和支撑系统;最后四个月,财务系统国际化改造!整体来说,成长很快,日子很燃,业务波动大!对于自己,无论从技术架构、业务成长、团队建设,既是机遇也是挑战。2019支撑业务发展的同时,需要加深技术的深度和体系化,增强技能外的视野^_^!
  • 19年个人成长立个flag吧,一年多没更新的博客重新捡起来,做到每月至少一篇!总计达标20篇吧,其中至少有两篇是技能外的

投资方面

  • 18年可谓过山车,年中股票整体收益高达150%,年底落到了20%多,总算没亏。教训就是,止损一定要果断!果断!果断!经济低迷的环境下,基金逐步加仓,等待周期!投资个小桶装水厂,也是亏了,经验一句话:做生意,人非常重要,一定想清楚了再干!18年看好半导体,19年目前比较看好健康体检,旅游领域。19年的核心调整自己的资产配置,按着二八原则递归!实体行业暂时先观望
  • 19年投资方面flag不变,继续保持每周读经济周刊!

生活方面

  • 18年有些波澜不惊,在媳妇的组织下,爸妈哥嫂全家旅游了一趟,毕业后一直没有好好陪过父母,多少弥补了些遗憾。年底和媳妇儿去了趟台湾,互联网方面太落后了,吃老本而已,以后也不会再去了!年末赶上天朝洪恩,买了共有产权房,位置很喜欢,上下班方便!大事儿就这些,哈哈,整体感觉自己变懒了,很多事情都是媳妇儿在张罗维持,需要反思!2019期望,自己能够腾出更多的时间和媳妇在国内好好逛逛,毕竟三人世界很快就要替代二人世界了!
  • 19年在生活方面立个flag,哈哈,就不在这里说了,感恩家庭

兴趣方面

  • 2018新get两个技能,一个是无人机,很喜欢,换一个角度看看世界,很不一样,后面也逐步学习下原理和拍摄技巧;另一个勉强算是羽毛球了,哈哈,不过现在还是苍蝇拍,也没啥提升的兴趣了,挺好的健身途径,目标保持体重!2019,期望新增一门技能:做饭,最好能考个证(初步查了下新东方厨师周末班都要6000,真贵),毕竟程序员的未来都是开饭店,提前做好准备!
  • 2019 年兴趣爱好方面立个flag,能做一桌菜!

想说的很多,但是没喝点酒,吹不起来,18年先这样子吧O(∩_∩)O

最后致谢,感谢老婆和家人的支持,有了你们坚实后盾,怎么走都是路!感谢工作中小伙伴,有了你们的信任和配合,稳如狗!

2018,不管愿不愿意,已经成为过去,让我们在2019继续努力,以梦为马,不负韶华!

【转载请注明:2018回顾 | 靠谱崔小拽

查看原文

赞 2 收藏 1 评论 0

崔小拽 发布了文章 · 2017-10-13

【GO学习一】 Hello World

最近项目中需要开发抗并发的db proxy,API GATEWAY等;同时,随着虚拟化的过程中出现各种问题。作为一个老程序员,go语言的学习,已经刻不容缓。

一、基础背景

Go是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言

对于go语言的特性,网上大牛总结,对于个人来说特别看重语言交互和并发性

  • 自动垃圾回收
  • 更丰富的内置类型
  • 函数多返回值
  • 错误处理
  • 匿名函数和闭包
  • 类型和接口
  • 并发编程
  • 反射
  • 语言交互性

二、安装

建议参考:
http://dmdgeeker.com/goBook/d...

需要注意的是 gopath一定要配置,配置到自己的workspace即可:

# go path change by cuihuan
export GOPATH=/Users/cuixiaohuan/Desktop/workspace/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOPATH

workspace的基本目录规范可以参考:https://go-zh.org/doc/code.html

  • src 目录包含Go的源文件,它们被组织成包(每个目录都对应一个包),
  • pkg 目录包含包对象,
  • bin 目录包含可执行命令。

三、hello world

代码

package main
import "fmt"

func main() {
   fmt.Println("Hello World")
}

语言简述:
1:package 是必须的,对于独立运行的执行文件,必须是package main
2:import 表示引入的包,或者库
3:程序中的主函数
4:执行函数

运行:

cuixiaozhuai:main cuixiaohuan$ go build hello.go
cuixiaozhuai:main cuixiaohuan$ ./hello
Hello World

编译和运行都非常简单,而且比较方便的是跨平台编译

# mac 下编译
cuixiaozhuai:main cuixiaohuan$ env GOOS=linux GOARCH=amd64 GOARM=7 go build hello.go 

# linux 开发机运行
[work@xx.com ~]$ ./hello
Hello World

【转载请注明:【GO学习一】 Hello World | 靠谱崔小拽

查看原文

赞 1 收藏 3 评论 0

认证与成就

  • 获得 251 次点赞
  • 获得 46 枚徽章 获得 3 枚金徽章, 获得 12 枚银徽章, 获得 31 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-09-07
个人主页被 1.9k 人浏览