iOS QQ粘性布局

iOS 仿照QQ未读消息提醒数字的粘性布局,实现了和QQ未读消息一样的功能,拖拽的时候会有粘性效果,在一定范围内拖拽松手还会回到原来的位置,超过一定距离之后就会播放一个动画。

效果图:

%title插图%num
核心代码:

//
// BageValueBtn.m
// QQ粘性布局
//
// Created by llkj on 2017/9/5.
// Copyright © 2017年 LayneCheung. All rights reserved.
//

#import “BageValueBtn.h”

@interface BageValueBtn()

@property (nonatomic, weak) UIView *smallCircle;
@property (nonatomic, weak) CAShapeLayer *shap;

@end
@implementation BageValueBtn

-(void)awakeFromNib{

[super awakeFromNib];
[self setUP];
}

-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self setUP];
}
return self;
}

-(CAShapeLayer *)shap{

if (_shap == nil) {
//形状图层
//它可以根据一个路径生成一个形状.
CAShapeLayer *shap = [CAShapeLayer layer];
//设置形状的填充颜色
shap.fillColor = [UIColor redColor].CGColor;
_shap = shap;
[self.superview.layer insertSublayer:shap atIndex:0];
}
return _shap;

}

//初始化
– (void)setUP{
//设置圆角
self.layer.cornerRadius = self.bounds.size.width * 0.5;
//设置背景颜色
[self setBackgroundColor:[UIColor redColor]];
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
self.titleLabel.font = [UIFont systemFontOfSize:12];

//添加手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];

//添加小圆
UIView *smallCircle = [[UIView alloc] init];
smallCircle.frame = self.frame;
smallCircle.backgroundColor = self.backgroundColor;
smallCircle.layer.cornerRadius = self.layer.cornerRadius;
self.smallCircle = smallCircle;
[self.superview insertSubview:smallCircle belowSubview:self];

}

//计算两个圆之间的距离
– (CGFloat)distanceWithSmallCircle:(UIView *)smallCircle bigCircle:(UIView *)bigCircle{

//X轴偏移量
CGFloat offsetX = bigCircle.center.x – smallCircle.center.x;
//Y轴偏移量
CGFloat offsetY = bigCircle.center.y – smallCircle.center.y;

return sqrtf(offsetX * offsetX + offsetY * offsetY);

}

//根据两个圆设置一个不规则的路径
– (UIBezierPath *)pathWithSmallCircle:(UIView *)smallCircle bigCircle:(UIView *)bigCircle{

CGFloat x1 = smallCircle.center.x;
CGFloat y1 = smallCircle.center.y;

CGFloat x2 = bigCircle.center.x;
CGFloat y2 = bigCircle.center.y;

CGFloat d = [self distanceWithSmallCircle:smallCircle bigCircle:self];

if (d <= 0) {
return nil;
}

CGFloat cosθ = (y2 – y1) / d;
CGFloat sinθ = (x2 – x1) / d;

CGFloat r1 = smallCircle.bounds.size.width * 0.5;
CGFloat r2 = bigCircle.bounds.size.width * 0.5;

CGPoint pointA = CGPointMake(x1 – r1 * cosθ, y1 + r1 * sinθ);
CGPoint pointB = CGPointMake(x1 + r1 * cosθ, y1 – r1 * sinθ);
CGPoint pointC = CGPointMake(x2 + r2 * cosθ, y2 – r2 * sinθ);
CGPoint pointD = CGPointMake(x2 – r2 * cosθ, y2 + r2 * sinθ);
CGPoint pointO = CGPointMake(pointA.x + d * 0.5 * sinθ, pointA.y + d * 0.5 * cosθ);
CGPoint pointP = CGPointMake(pointB.x + d * 0.5 * sinθ, pointB.y + d * 0.5 * cosθ);

UIBezierPath *path = [UIBezierPath bezierPath];
//AB
[path moveToPoint:pointA];
[path addLineToPoint:pointB];
//BC(曲线)
[path addQuadCurveToPoint:pointC controlPoint:pointP];
//CD
[path addLineToPoint:pointD];
//DA(曲线)
[path addQuadCurveToPoint:pointA controlPoint:pointO];

return path;

}

– (void)pan:(UIPanGestureRecognizer *)pan{

//frame,center,transform.

//移动.
CGPoint transP = [pan translationInView:self];
//修改transform值,并没有去修改center,它修改的frame
// self.transform = CGAffineTransformTranslate(self.transform, transP.x, transP.y);

CGPoint center = self.center;
center.x += transP.x;
center.y += transP.y;
self.center = center;

//复位
[pan setTranslation:CGPointZero inView:self];
//两个圆之间的距离
CGFloat distance = [self distanceWithSmallCircle:self.smallCircle bigCircle:self];

//让小圆的半径减去距离的比例
//获取小圆的半径
CGFloat smallR = self.bounds.size.width * 0.5;
smallR = smallR – distance / 10.0;
//要重设置小圆的尺寸
self.smallCircle.bounds = CGRectMake(0, 0, smallR * 2, smallR * 2);
//重新设置小圆的圆角
self.smallCircle.layer.cornerRadius = smallR;

//不规则的路径.

//如果小圆显示的时候再创建
if(self.smallCircle.hidden == NO){
UIBezierPath *path = [self pathWithSmallCircle:self.smallCircle bigCircle:self];
self.shap.path = path.CGPath;
}

//如果两个圆之间的距离超过某个范围.让小圆隐藏,shap移除
if(distance > 60){
self.smallCircle.hidden = YES;
[self.shap removeFromSuperlayer];
}
//当手指松开时,如果发现两个圆之间距离小于某个值时,大圆复位.
if(pan.state == UIGestureRecognizerStateEnded){
//如果发现两个圆之间距离小于某个值时,大圆复位.
if (distance < 60) {
//移除形状
[self.shap removeFromSuperlayer];

//添加一个弹性动画
[UIView animateWithDuration:0.25 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{

//大圆复位.
self.center = self.smallCircle.center;
} completion:^(BOOL finished) {
//让小圆显示
self.smallCircle.hidden = NO;
}];

}else{
//如果发现两个圆之间距离大于某个值时,播放动画,按钮从父控件当中移.

//添加一个UIImageView
UIImageView *imageV = [[UIImageView alloc] initWithFrame:self.bounds];

NSMutableArray *imageArray = [NSMutableArray array];
for (int i = 0; i < 8; ++i) {
NSString *imageName = [NSString stringWithFormat:@”%d”,i + 1];
UIImage *image = [UIImage imageNamed:imageName];
[imageArray addObject:image];
}

imageV.animationImages = imageArray;
//设置动画的执行时长
[imageV setAnimationDuration:1];
//开始动画
[imageV startAnimating];
[self addSubview:imageV];

//一秒钟后.把当前的按钮从父控件当中移.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//把当前的按钮从父控件当中移.
[self removeFromSuperview];
});

}

}

}

//取消高亮状态
-(void)setHighlighted:(BOOL)highlighted{
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
– (void)drawRect:(CGRect)rect {
// Drawing code
}
*/

@end

IOS6+ 下,使用position:sticky实现粘性布局

回顾一下

开通博客之后,潦草的写了几篇,之后由于没时间,加上文笔不好等等(好吧,都是借口),基本上就没怎么写过了,其实平时也做了一些记录,但就是犯懒,没有去整理。现在打算重新开始了,记录一些自己学习过程中,对一些新的知识的理解,不一定对,目的就是鞭策自己不断的去反省,总结。


正文

场景

OK,回归正题。相信大家在移动端页面里,经常会遇到一个场景,页面里有个导航栏,当用户向下滑动的时候,将导航栏固定到页面顶部。

这种布局其实实现起来非常简单,通常我们都会监听window的scoll事件,当页面滚动到某个位置时,将导航条的position设为fixed,否则,取消fixed。

问题来了

但是~~在安卓设备里,没毛病,在iOS设备里,问题就来了,在iOS上会发现,页面向下滑,手指离开页面,还会有一段滚动,但是滚到相应的位置时,我们的导航条并没有fix到顶部,而是当页面停止滚动的时候,fixed才会生效。所以这时候,导航栏会有个跳动的效果,刚开始测试妹子给我提bug,我一度认为这尼玛不是bug啊。。。后来研究了一下

原因及解决方案

在iOS设备上,iOS下的浏览器会在页面滚动的时候,会暂停所有js的执行,直到滚动停止才会继续去执行js(注意暂停了所有js的执行,所以考虑用setTimeout或setInterval也是没有用的)。所以页面滚动时,scroll事件在iOS的浏览器下并不会持续被触发,而是在页面滚动停止后,才会去触发一次scroll事件。 这就是问题的根源。

然后,万能的CSS,position还有一个不常见的属性:sticky,使用position:sticky,可以完美的解决这个问题,但这个属性支持度并不好,因此可以先通过以下代码先判断是否支持:

  1. _supportSticky() {
  2. let e, i = “-webkit-sticky”, s = document.createElement(“i”);
  3. s.style.position = i;
  4. e = s.style.position;
  5. s = null;
  6. return e === i
  7. }

如果支持,就给设置position: -webkit-sticky,top:0;这样,就可以完美实现fixed效果,但是注意:如父容器设置属性overflow:hidden,父容器根本就不能滚动,所以sticky元素也不会有滚动然后固定的情况。sticky效果就不会生效。

这样,我们在iOS平台里,先判断是否支持sticky属性是否支持,如果支持,就使用sticky,完美~

一次解决ios中z-index属性失效问题的经历

在一次用h5实现app页面功能中,ios系统发生如下bug

%title插图%num

问题
整个页面都被遮罩盖住,但是我检查了代码,发现遮罩的z-index=2,而全部科目区域的z-index=3,按道理讲,全部科目的区域应该是在遮罩的上方,但是在ios中显示错误,在安卓手机上显示正常。
然后查了一些资料,其中看到如下博客,才解决了问题
https://blog.csdn.net/zilvzsy/article/details/86022612

*主要的原因是全局样式-webkit-overflow-scrolling属性设置为touch导致的
-webkit-overflow-scrolling: touch;

一检查代码发现我没有设置这个属性,想了想,应该是某插件的样式设置了这个属性的,于是仔细查找了使用的插件,发现之前为了实现列表的下拉刷新的功能,使用了mescroll.js插件,里面的css样式有

%title插图%num

,如此原因找到

解决方法:在页面中直接-webkit-overflow-scrolling: unset样式覆盖

%title插图%num

完美解决

z-index在ios失效问题记录

一直以为只要z-index的值够大,元素肯定在*上层。实际不然,想要实现的效果如下左图,but~效果是下右图:

%title插图%num

查了z-inex失效的原因,原来是两个元素不在同一楼层

%title插图%num

分析:

元素1是元素2前一个元素的子元素,因为1的父元素在负一楼,所以无论1的z-index多大,都在负一楼,体现为z-index失效了

方案:

将1、2修改为平级元素,再设置1的position为absolute(非static),z-index高于元素2,即可实现1在2的上方。

修改后的bug:元素3和1、2元素的父级(一楼)同级,position:absolute,在二楼,在*上层

%title插图%num

方案:

蒙层2出现时,将3的z-index:-1,即可实现3在2的下方

到此,问题解决。

iOS crash 崩溃问题的追踪方法

在调试程序的时候,总是碰到crash的bug,而且一追踪就是一些汇编的代码,让人特别疑惑。

一般情况下可以通过增加两天断点来解决此问题,方法介绍如下:

基本上有错误分为以下几种类型:

signal(SIGABRT, MySignalHandler);

signal(SIGILL, MySignalHandler);

signal(SIGSEGV, MySignalHandler);

signal(SIGFPE, MySignalHandler);

signal(SIGBUS, MySignalHandler);

signal(SIGPIPE, MySignalHandler);

SIGABRT和EXC_BAD_ACCESS较为特殊,算是比较好跟进。

SIGABRT是系统报错,在memery warning之后,系统会把程序强制退出,报的就是这个错误。

EXC_BAD_ACCESS 大多数时候是内存提前释放而引起的问题,或者访问的方法不存在引起的。

 

追踪程序的调用stacktrace的方法,跟踪问题:

一般出错了之后出现的常见界面,再熟悉不过了。。

%title插图%num%title插图%num

切换到breakpoint界面,拖动底端的slider按钮,显示调用堆栈

%title插图%num%title插图%num

虽然调用堆栈已经出来了,但是都是一堆的汇编代码,无法给予明确的信息。。

剩下的就是今天要讲的问题:添加Symbol breakpoint  Exception breakpoint

Exception breakpoint添加 的添加步骤

一、Exception breakpoint 的添加。

1、切换到breakpoint 视图界面

%title插图%num%title插图%num

2、点击*底端的”+”按钮,添加Add Exception BreakPoint,这个就是捕获所有的exception, 貌似stackoverflow上说,bad_access那种错误无法捕获的,这个用于捕获那些SIGSEGV 的错误。

%title插图%num%title插图%num

3、添加完成之后的界面。

%title插图%num%title插图%num

 

二、Symbolic breakpoint的添加

前两步和一 基本是一样的,不截图了,只是在第二步选择的时候选 Add Symbolic BreakPoint

第三步截图;添加完成之后添加上objc_exception_throw

%title插图%num%title插图%num

完成,添加完成只两个断点之后,程序中很多异常也可以捕获了,直接定位到出问题的位置。

当然程序中*好也加上异常处理的代码,可以参考这个处理,一般都是这个方式处理的

http://www.cocoachina.com/newbie/tutorial/2012/0829/4672.html

2021年二季度和上半年国内生产总值(GDP)初步核算结果

  根据有关基础资料和国民经济核算方法,我国2021年二季度和上半年国内生产总值(以下简称GDP)初步核算主要结果如下: 表1 2021年2季度和上半年GDP初步核算数据    *对额(亿元) 比上年同期增长(%) 2季度 上半年 2季度 上半年 GDP 282857 532167 7.9 12.7 *产业 17070 28402 7.6 7.8 第二产业 114531 207154 7.5 14.8 第三产业 151257 296611 8.3 11.8           农林牧渔业 17864 29751 7.5 7.7 工业 93635 174606 8.8 15.9 #制造业 80037 148523 9.2 17.0 建筑业 21318 33335 1.8 8.6 批发和零售业 26861 50895 9.6 17.0 交通运输、仓储和邮政业 12637 22704 12.7 21.0 住宿和餐饮业 4169 8228 17.1 29.1 金融业 22563 45637 4.1 4.7 房地产业 20282 39742 7.1 13.6 信息传输、软件和信息技术服务业 11868 23187 19.5 20.3 租赁和商务服务业 7714 15464 5.8 6.9 其他行业 43945 88617 6.2 7.5 注: 1.*对额按现价计算,增长速度按不变价计算; 2.三次产业分类依据国家统计局2018年修订的《三次产业划分规定》; 3.行业分类采用《国民经济行业分类(GB/T 4754-2017)》; 4.本表GDP总量数据中,有的不等于各产业(行业)之和,是由于数值修约误差所致,未作机械调整。  表2  GDP同比增长速度                                              单位:% 年份 1季度 2季度 3季度 4季度 2016 6.9 6.8 6.8 6.9 2017 7.0 7.0 6.9 6.8 2018 6.9 6.9 6.7 6.5 2019 6.3 6.0 5.9 5.8 2020 -6.8 3.2 4.9 6.5 2021 18.3 7.9     注:同比增长速度为与上年同期对比的增长速度。  表3  GDP环比增长速度                                              单位:% 年份 1季度 2季度 3季度 4季度 2016 1.5 1.8 1.7 1.6 2017 1.8 1.8 1.6 1.6 2018 1.8 1.7 1.3 1.5 2019 1.6 1.4 1.2 1.5 2020 -8.7 10.0 2.8 3.0 2021 0.4 1.3     注:环比增长速度为经季节调整后与上一季度对比的增长速度。    其他相关核算结果详见国家统计局数据库(http://data.stats.gov.cn)。   附件: 中国GDP季度核算说明   1.季度GDP核算概述   1.1 基本概念   GDP是一个国家所有常住单位在一定时期内生产活动的*终成果。GDP是国民经济核算的核心指标,也是衡量一个国家经济状况和发展水平的重要指标。   GDP核算有三种方法,即生产法、收入法和支出法,三种方法从不同的角度反映国民经济生产活动成果。生产法是从生产过程中创造的货物和服务价值中,剔除生产过程中投入的中间货物和服务价值,得到增加值的一种方法。国民经济各行业生产法增加值计算公式如下:增加值=总产出-中间投入。将国民经济各行业生产法增加值相加,得到生产法国内生产总值。收入法是从生产过程形成收入的角度,对生产活动成果进行核算。按照这种计算方法,增加值由劳动者报酬、生产税净额、固定资产折旧和营业盈余四个部分组成。计算公式为:增加值=劳动者报酬+生产税净额+固定资产折旧+营业盈余。国民经济各行业收入法增加值之和等于收入法国内生产总值。支出法是从生产活动成果*终使用的角度计算国内生产总值的一种方法。*终使用包括*终消费支出、资本形成总额及货物和服务净出口三部分。   国家统计局发布的季度GDP是以生产法为基础核算的结果。   1.2 核算范围   1.2.1 生产范围   GDP核算的生产范围包括以下四个部分:*,生产者提供或准备提供给其他单位的货物或服务的生产;第二,生产者用于自身*终消费或固定资本形成的所有货物的自给性生产;第三,生产者为了自身*终消费或固定资本形成而进行的知识载体产品的自给性生产,但不包括住户部门所从事的类似的活动;第四,自有住房提供的住房服务,以及雇佣有酬家庭服务人员提供的家庭和个人服务的自给性生产。生产范围不包括没有报酬的家庭和个人服务、没有单位控制的自然活动(如野生的、未经培育的森林、野果或野浆果的自然生长,公海中鱼类数量的自然增长)等。   1.2.2 生产活动主体范围   GDP生产活动主体范围包括了中国经济领土范围内具有经济利益中心的所有常住单位。本报告中的季度GDP数据是由国家统计局负责核算的全国数据,未包括香港、澳门特别行政区和台湾省的地区生产总值数据。   1.3 核算单位   GDP核算主要以法人单位作为核算单位,在核算中依据法人单位从事的主要活动将其划分到不同的行业,分别计算各个行业的增加值,再将各行业增加值汇总得到GDP。   1.4 核算频率   核算频率为季度。中国从1992年1季度开始到2015年2季度,采用累计核算方式核算季度GDP,即分别计算各年1季度,1-2季度,1-3季度和1-4季度的GDP数据,1-4季度GDP初步核算即为年度GDP初步核算。从2015年3季度开始改为分季核算方式,即分别计算各年1季度,2季度,3季度和4季度的GDP数据,累计数据通过当季数据相加得到。   从2011年1季度开始,国家统计局正式对外发布各季GDP环比增长速度。   1.5 法律依据和制度规定   GDP核算严格遵守《中华人民共和国统计法》的规定。目前,中国GDP是按照《中国国民经济核算体系(2016)》的要求进行测算的,该体系采纳了联合国《国民账户体系(2008)》的基本核算原则、内容和方法。   1.6 保密性   依照《中华人民共和国统计法》*章第九条的规定,统计机构和统计人员对在统计工作中知悉的国家秘密、商业秘密和个人信息,应当予以保密。   国民经济核算人员在进行GDP核算时对所使用的未经公开的专业统计数据和行政记录数据严格保密,在GDP核算数据发布前对当期GDP数据也严格保密。   1.7 用户需求   季度GDP数据的国内用户主要是政府部门、研究机构、大学、行业协会、媒体以及社会公众。此外,国家统计局定期向联合国、国际货币基金组织、经济合作与发展组织、亚洲开发银行等国际组织提供中国季度GDP数据。   2.季度GDP核算方法   2.1 分类体系   在季度GDP核算中,行业划分依据中国国民经济行业分类标准和三次产业划分标准,并采用两种分类方式。   *种分类是国民经济行业分类,采用国家标准管理部门2017年颁布的《国民经济行业分类(GB/T 4754-2017)》。在实际核算中采用两级分类。   *级分类以国民经济行业分类中的门类为基础,分为农、林、牧、渔业,工业,建筑业,批发和零售业,交通运输、仓储和邮政业,住宿和餐饮业,金融业,房地产业,信息传输、软件和信息技术服务业,租赁和商务服务业,其他行业等11个行业。其中工业包含采矿业,制造业,电力、热力、燃气及水生产和供应业3个门类行业;其他行业包含科学研究和技术服务业,水利、环境和公共设施管理业,居民服务、修理和其他服务业,教育,卫生和社会工作,文化、体育和娱乐业,公共管理、社会保障和社会组织等7个门类行业。   第二级分类在*级分类的基础上,将国民经济行业分类中的一部分门类细化为行业大类。   第二种分类是三次产业分类,依据国家统计局2018年修订的《三次产业划分规定》,分为*产业、第二产业和第三产业。*产业是指农、林、牧、渔业(不含农、林、牧、渔专业及辅助性活动);第二产业是指采矿业(不含开采专业及辅助性活动),制造业(不含金属制品、机械和设备修理业),电力、热力、燃气及水生产和供应业,建筑业;第三产业即服务业,是指除*产业、第二产业以外的其他行业(剔除国际组织)。   2.2 资料来源   在季度GDP核算时,将所有可以在核算时获得的、适用的经济统计调查数据都用于GDP核算。资料来源主要包括两部分:   一是国家统计调查资料,指由国家统计系统实施的统计调查获得的各种统计资料,如农林牧渔业、工业、建筑业、批发和零售业、住宿和餐饮业、房地产业、规模以上服务业等统计调查资料、人口与劳动工资统计资料、价格统计资料等。   二是行政管理部门的行政记录资料,主要包括:财政部、人民银行、税务总局、银保监会、证监会等行政管理部门的相关数据,例如人民银行的金融机构本外币信贷收支情况、税务总局分行业的税收资料等。   2.3 核算方法   2.3.1 现价增加值核算方法   根据资料来源情况,季度现价增加值核算主要采用增加值率法、相关价值量指标推算法以及利用不变价推算现价等方法。   2.3.1.1 相关价值量指标速度推算法   相关价值量指标速度推算法是利用相关价值量指标的现价增长速度推算现价增加值的增长速度,然后用上年同期现价增加值乘以推算出的现价增加值增长速度得出当期现价增加值,计算公式为:   现价增加值=上年同期现价增加值×(1+现价增加值增长速度)   其中,现价增加值增长速度,根据本期相关价值量指标现价增长速度,以及以前年度现价增加值增长速度和相关价值量指标的现价增长速度之间的数量关系确定。   2.3.1.2 利用不变价推算现价方法   先利用物量指数外推法求得本期不变价增加值,再根据相关价格指数推算现价增加值。计算公式为:   现价增加值=不变价增加值×价格指数   2.3.2 不变价增加值核算方法   不变价增加值是把按当期价格计算的增加值换算成按某个固定期(基期)价格计算的价值,从而剔除价格变化因素的影响,以使不同时期的价值可以比较。不变价增加值采用固定基期方法计算,目前每5年更换一次基期,2021年至2025年不变价增加值的基期是2020年。   季度不变价增加值核算主要采用价格指数缩减法和相关物量指数外推法。   2.3.2.1 价格指数缩减法   利用相关价格指数直接缩减现价增加值,计算不变价增加值,计算公式为:   某行业不变价增加值=该行业现价增加值÷价格指数   2.3.2.2 物量指数外推法   利用相关物量指标的增长速度推算不变价增加值的增长速度,然后用上年同期不变价增加值和推算出的不变价增加值增长速度计算得出当期不变价增加值,计算公式为:   某行业不变价增加值=该行业上年同期不变价增加值 ×(1+该行业不变价增加值增长速度)   其中,不变价增加值增长速度根据本期相关物量指标增长速度,以及以前年度不变价增加值增长速度和相关物量指标的增长速度之间的数量关系确定。   2.4 季节调整   GDP环比增长速度是季度增加值与上一个季度增加值数据对比的结果。在测算时,须剔除季节性因素对时间序列的影响,利用国家统计局版季节调整软件(NBS-SA)对时间序列进行季节调整。NBS-SA是在目前国际上比较常用的季节调整软件的基础上,考虑了中国特有的季节因素研制而成的。该软件添加了处理中国特有的季节因素的新模块,有效剔除了中国特有的季节因素,包括春节、端午、中秋等移动假日因素、周工作天数从原来的6天制到5天制转变的因素、假期变动及调休带来的变化因素等。   3.季度GDP数据修订   3.1 修订的必要性   季度GDP初步核算对时效性要求很强,一般在季后15天左右公布,这时,GDP核算所需要的基础资料不能全部获得,因此季度GDP初步核算利用专业统计进度资料和相关指标推算得到。之后,随着可以获得的基础资料不断增加和完善,会利用更加完整的基础资料,例如,专业统计年报、行业财务资料和财政决算资料对GDP数据进行修订,使其更加准确地反映经济发展实际情况。   3.2 修订程序   按照国家统计局*新改革的GDP核算和数据发布制度规定,中国季度GDP核算分为初步核算和*终核实两个步骤。通常,年度GDP*终核实后,要对季度数据进行修订,称为常规修订;在开展全国经济普查,发现对GDP数据有较大影响的新的基础资料,或计算方法及分类标准发生变化后而对年度GDP历史数据进行修订后,也要对季度GDP历史数据进行相应修订,称为全面修订。   3.3 修订方法   3.3.1 当季数据的修订   中国目前对季度GDP数据修订的方法是比例衔接法,即利用年度基准值与年内四个季度汇总数的差率调整季度数据的方法。比例衔接法的基本做法是:首先对国民经济各行业现价和不变价增加值分别进行衔接,GDP和三次产业增加值是衔接后的行业增加值的加总。不变价GDP和不变价三次产业增加值的衔接方法与现价相同。   3.3.2 环比数据的修订   由于季节调整的对象是时间序列数据,因此,当时间序列中任何一个季度数据发生变化时,都会影响季节调整的结果;在时间序列中加入*新的一个季度的数据,也会使以前季度的环比数据或多或少地发生变化,这是模型自动修正的结果。根据季节调整原理,一般情况下,离*新数据时间较近的时期,数据受影响较大;离*新数据时间较远的时期,数据受影响较小。为便于用户使用,在发布当期环比数据的同时,会通过国家统计局网站发布修订后的以前季度的环比数据。   4.季度GDP数据质量评估   4.1 对基础数据的评估   对于GDP核算所使用的各专业统计数据和行政记录数据,有关部门都会对其质量进行检验,确保数据合理反映经济发展实际情况。当GDP核算部门得到这些基础数据后,会再次对数据的完整性和准确性进行检验,确保这些数据符合GDP核算的概念和要求。   4.2 对核算方法的评估   在GDP核算中,GDP核算部门会根据不断发展的中国经济实际情况,依据不断完善的国民经济核算标准,对中国的季度GDP核算方法进行修订,以确保核算方法的合理性。   4.3 对核算结果的评估   在得到季度GDP核算结果后,要对GDP各构成项目数据、GDP数据与相关专业、部门统计数据以及宏观数据的协调性进行检验,保证GDP数据和其他主要数据的相互协调和匹配。正在建立以国民经济核算为核心框架,对各专业和部门基础统计数据进行评估的制度。   4.4 数据的可比性   《中国国民经济核算体系(2016)》采纳了联合国《国民账户体系(2008)》的基本核算原则、内容和方法,因而GDP数据具有国际可比性。   在开展全国经济普查或计算方法及分类标准发生变化后对季度GDP历史数据进行了修订,因此1992年1季度以来的季度GDP时间序列具有可比性。   5.季度GDP数据发布   5.1 发布时间   季度GDP初步核算数一般于季后15日左右发布,季度GDP*终核实数一般于隔年1月份发布。对于主要统计指标的发布,国家统计局会在年初发布的《主要统计信息发布日程表》中说明发布日期,GDP数据将按规定日程发布。   5.2 发布方式   季度GDP初步核算数在季度国民经济运行情况新闻发布会、国家统计局网站(www.stats.gov.cn)、《中国经济景气月报》上公布;季度GDP*终核实数在国家统计数据库(http://data.stats.gov.cn/)、《中国经济景气月报》上公布。对于1992年1季度以来的季度GDP数据时间序列,可以通过国家统计数据库(http://data.stats.gov.cn/)进行查询。 

干货!一文搞懂无状态服务

 

事故的发生是量的积累的结果,任何事情都没有表面看起来那么简单,在软件运行的过程中,随着用户量的增加,不考虑高可用,迟早有一天会发生故障,不得事先考虑高可用设计,而高可用是一门庞大的学问。

在设计一个高可用系统要考虑哪些内容?

  • 考虑方案选型会带来哪些坑,*差的情况下需要考虑故障发生的紧急解决方案
  • 需要监控系统,在故障发生时、发生时有所感知
  • 需要自动化恢复方案,自动化提前处理预警方案
  • 在代码层面需要考虑处理速度、代码性能、报错处理
  • 还要考虑把故障降低到*小:服务降级、限流、熔断
  • 其他

这篇文章主要介绍无状态服务在架构层面,如何保证可高用。

无状态服务的高可用旨在任何情况下数据都不丢失,服务都不发生故障,在某些服务发生故障时保证影响*小,并可以快速恢复。

可以从这几个方面考虑:

  • 冗余部署:至少多部署一个节点,避免单点问题
  • 垂直扩展:增加单机性能
  • 水平扩展:流量激增可快速扩容

  冗余部署

在单点架构中,随着数据数据量增加,单点负载压力过大,容易产生服务崩溃不可用的情形,对于无状态服务,可以考虑部署多个节点的服务来分散压力。

对于如何调度来临的请求,可以参考负载均衡的方式,尽可能的保证充分的利用服务器的资源。

%title插图%num

  • 无状态服务:不需要存储数据的服务,即使节点挂掉再重启,不会发生数据丢失
  • 负载均衡:把大量请求分散到不同节点上的一种算法

  无状态服务的负载均衡

可以使用负载均衡中提供的几种算法:

  • 随机均衡算法:已知后端服务器列表,随机请求,数据量越大越趋近于均衡
  • 轮询算法:轮流请求后端服务器

前两种算法存在的问题是后端服务器在负载压力不同或服务器配置不同时,不能保证压力小的多分配,压力大的小分配,于是引入。

%title插图%num

  • 加权轮循算法:按照后端服务器的抗压能力,负载情况分配更高的权重,更容易命中,减少宕机风险,按权重顺序的分配到后端服务器上
  • 加权随机法:和加权轮训算法一样,不同的是分配是按权重随机的,比如有多台权重一致的情况,随机访问,那就和随机算法有同样的问题,数据量大时才趋近于均衡,数据量小时有可能重复访问同一台权重一致的机器
  • [加权]*小连接数算法:这是*智能的一种算法,根据服务器当前的连接数来选取,更容易命中处理速度快的服务器

上面的算法使用于无状态应用,假如要保存通信状态,需要使用

  • 源地址哈希算法:对源地址做hash,可以保证相同的请求*终都是落在同一台机器上,不需要重复建立连接

  负载均衡算法如何选择?

首先抛弃随机算法,*简单的配置可以使用基本的轮训算法,它适用于服务器配置一致,比如使用虚拟机,可以动态调整服务器配置的场景,同时要保证专用虚拟机,上面不会部署其他应用的情况。

但是服务器上往往会安装多个应用,那就要考虑在加权轮训*小连接数中做选择。

%title插图%num

加权轮训适用于短连接场景,比如HTTP服务,在k8s中因为每个pod都是独立的,默认service策略是非加权轮训。

  • *小连接数适用于长连接,比如FTP等

如果系统架构中考虑到无cookie功能的场景,可以用源地址hash算法,把源IP一直映射到同一台rs上,在k8s中叫会话保持模式,每次转发到同一个pod上。

建议:

  • 如果上了容器直接交给k8s来做调度,使用cookie做会话保持,算法使用默认轮训,具体调度未来k8s文章里会做详细介绍
  • 使用长连接的应用(FTP、socket,或者用于下载连接),选择加权*小连接数
  • 短连接应用(静态网站、微服务组件等),选择加权轮训,用cookie来做会话保持,减少session的设计,不仅会提高代码复杂度,也会增加服务端负载情况,不利于分布式应用

  高并发应用的识别

主要指标QPS每秒处理响应数,比如每天有10w的pv

公式 (100000 * 80%) / (86400*20%) = 4.62 QPS(峰值QPS)

公式原理:每天80%的访问集中在20%的时间里,这20%时间叫做峰值时间。

比如我做的系统托管了*高5w台机器,每台机器每次分钟有一次PV,时间比较均匀那就是:

((60*24)*50000)/(86400)=833 QPS

一般上百的量级就可以叫高并发了,网上查到的资料微博每天1亿多pv的系统一般也就1500QPS,5000QPS峰值。

除了QPS还有服务响应时间、并发用户数指标可以参考。在服务器负载高的时候,表现在处理速度变慢、网络断连、服务处理失败、异常报错等问题,具体问题要具体分析,不可一概而论。

可以通过监控,来获得服务器性能状态,动态调整、重试,达到服务可用性的保证,减少维护成本,通常单纯服务器压力大的时候可以考虑垂直扩展。

  垂直扩展

垂直扩展是增加服务器单机的处理能力,主要有三种方式

  • 服务器升配:集中在CPU、内存、swap、磁盘容量或者网卡等
  • 硬件性能:磁盘SSD、调整系统参数等
  • 架构调整:软件层面使用异步、缓存、无锁结构等

%title插图%num

增强单机性能的方式是*快*容易的方式,但是单机性能之中是存在*限,同时单机部署时如果产生故障,对应用来说打击是致命的,我们应该保证应用时刻处于可用的状态,也就是俗话说的保证5个9的可靠性。

  水平自动伸缩

知道了单机的局限以后,考虑使用水平伸缩的方式。

水平伸缩就是压力增加的时候,增加新的节点来分担压力,但仅仅多点部署还是不够的,对于持续增长的业务,始终有一天会突破服务的压力上限,如果遇到流量激增的场景,人工应对肯定会措手不及,所以需要一种自动伸缩的手段。

  • 对于私有云部署来说可以手动实现调度器,检测系统状态,连通iaas层实现伸缩
  • 也可以直接使用云服务器提供的弹性伸缩服务
  • 对于容器而言,可以在iaas层弹性伸缩的情况下或者有充足node节点的情况下,配置自动伸缩和调度的策略,预防单机故障

注意:弹性伸缩针对的业务场景是无状态服务

另外无状态机器不足以承载请求流量,需要进行水平扩展的阈值一般QPS是千级,同时在这里对数据库也会有压力,所以建议水平伸缩的服务器不要部署有状态服务。

对于有状态服务压力分散在后续的文章会有所介绍。

  CDN和OSS

对于一个网站来说,用户交互页面,是一个特殊的服务,包含很多静态资源,比如图片、视频、页面(html/css/js),这些资源在用户请求的时候需要现场下载,下载速度决定了加载速度,在web服务故障的时候,同样应该对用户做出响应。

%title插图%num

在这一层面可以考虑使用CDN内容分发网络的方式,详见[xxx],把前端静态数据缓存到边缘服务器上。(边缘服务器(边缘节点),可以理解为和用户交互的服务器,也可理解为靠近用户的服务器节点,因为靠近用户,减少了网络传输使用的时间)

使用了CDN的web服务,可以把https证书绑定到cdn上,在回源策略可以配置回源超时、回源跟随301/302状态码等配置,还可以智能压缩网页、自定义错误页面,非常方便。

oss是一种特殊的存储方案,以对象的形式进行存储,理论上可以存储无限的文件。考虑使用oss对象存储并结合cdn,把媒体资源存储在对象存储上面,也可以把冷数据压缩归档到oss上。常见的视频网站大多会用到oss,微博n年以前的数据应该就是归档到对象存储中了。

  总结

本文介绍的无状态服务常见的高可用架构设计,他们是

  • 冗余部署
  • 负载均衡的6种算法与算法选择
  • 垂直扩展的好处与弊端
  • 水平扩展与水平自动伸缩
  • 哪些服务可以使用CDN和OSS

要注意无状态应用不应该存储session,也不存储数据。

云原生除了K8S、微服务,还有…?

云原生(Cloud Native)是*近技术圈一个比较火的名词,相信大家或多或少都听说过。不过对于大多数普通研发朋友来说,”云原生”这个词多少可能还是有些陌生,以至于刚开始听到这个词时可能还会一脸懵逼的问”这到底是一个什么技术,我用过吗?”这样的问题。

其实这并不奇怪,因为对于*大多数普通开发者来说,我们大部分时间都是在别人构建的基础设施里专注于业务代码的开发,而很少关心业务应用运行所依赖的基础设施环境,但这恰恰也是构建云原生应用的核心意义所在。在今天的文章中,就和大家聊一聊关于云原生的话题!

%title插图%num

云原生的概念

什么是云原生?对于这个问题我们需要理解,云原生并不是指某一项具体的技术,而是一组技术体系、概念及系统设计原则的集合。例如我们常讨论的微服务架构、Kubernetes容器编排、Devops等内容都是云原生体系的组成部分。

从这个角度看,对于目前已经实现了云服务部署、Spring Cloud微服务架构体系、Kubernetes容器化部署、且构建起了一套自动化发布系统的公司来说,事实上就已经是在践行云原生架构理念了。所以,你看是不是很多公司其实都已经在实施云原生架构了呢?

根据CNCF(云原生计算基金会)的官方描述,云原生技术是指有利于在公有云、私有云或混合云等新型动态环境下,实现应用可弹性伸缩部署的技术体系。云原生的代表技术主要包括容器、服务网格、微服务、不可变基础设施及声明式API。利用这些技术可以构建出容错性更好、更易于管理和观察的松耦合系统,再加上一些可靠的自动化技术及完备的监控预警体系,云原生技术将使开发人员能更快速、轻松地迭代和交付软件系统。

所以从上述描述看,云原生技术实际上并不是突然才流行起来的概念,而是随着云计算、微服务架构、服务网格等分布式应用架构技术普及流行,以及在以Docker、Kubernetes为代表的容器化技术的推动下,逐步被业界所认可的一种系统架构理念及设计原则的抽象总结。

%title插图%num

云原生技术图谱

这里我总结了一份关于云原生架构的技术图谱供大家参考,如下图所示:

%title插图%num

如上图所示,你会发现所谓的云原生简直就是一个技术大杂烩,它几乎囊括目前大部分流行的后端技术,甚至还延伸到了AI、机器学习、边缘计算等领域。但从实际应用场景来说云原生架构主要特征还是体现在云端环境、微服务架构、服务网格、Devops自动化交付、容器化部署这几个方面。

云端环境就是要使用云服务器,对于大部分公司来说就是使用阿里云、腾讯云之类的公有云服务来部署应用,而不是自己在额外维护一套复杂服务器机房。这样做的好处就在于利用云服务的弹性及分布式优势,可以大大降低运维成本,并且提升服务的稳定性。

而面向微服务的架构,能将原先耦合度高的单体系统,在遵循软件“高内聚、低耦合”设计原则的前提下,以独立业务能力为边界拆分为一个个原子系统。这样做的好处是,每个子系统都可以独立交付部署,从而能实现更敏捷的软件迭代效果。目前以Spring Cloud为代表的微服务技术,几乎已成为事实上的软件构建标准;而以Istio、Linkerd为代表的下一代服务网格技术也在快速发展,这一切都为云原生架构理念的普及作了有效地铺垫。

关于Devops,它强调的是以开发运维的视角,去构建一套高效完备的CI/CD流程,并通过自动化构建工具及发布系统,来实现软件生命周期的管理。从而使得普通开发人员,能够更快、更频繁地交付更加稳定的软件代码。例如我在本专栏发表的<<Kubernetes微服务自动化发布系统>>实际上就是一种Devops思想的具体实践案例,感兴趣的朋友可以参考下。

此外基于Kubernetes的容器化编排技术,已经事实上成为微服务运行的标准基础架构环境,也正是Kubernetes的流行,才真正推动了云原生架构理念的普及,Kubernetes可以说就是云原生架构的核心承载平台。关于Kubernetes的基本原理及具体实践本专栏也有一些文章可供参考,感兴趣的朋友可以阅读下。

%title插图%num

总结

以上内容给大家大致介绍了下云原生的概念,并总结了目前云原生所涉及的主流技术栈图谱。从宏观上看云原生架构是一个非常庞大的体系,它几乎能包含目前软件后端技术领域的方方面面,但从细节上看它却又是我们现阶段工作中都多少能接触到的技术,例如Spring Cloud微服务、服务熔断限流、Kubernetes容器编排等等。

所以从某种程度上讲,云原生是一个抽象又具体的存在。它不是一个具体的产品,而是一套技术体系和一套方法论,随着围绕着云原生架构的各类开源技术的进一步发展,云原生技术体系必将成为主流,进而影响到每一个技术人员、每一个企业和行业。

存储基础:磁盘 IO 为什么总叫你对齐?

存储 IO 重要的一个知识点

划重点:存储 IO 要对齐。

资深存储人员为啥总叫你注意 IO 对齐的?机械磁盘 IO 为什么要 512 对齐呢,SSD 盘为啥要 4K 对齐?不对齐又会如何?

重要的知识点

  1. 机械盘的 IO 要扇区对齐(*大部分的扇区是 512 字节大小),磁盘的读写*小单元就是扇区;
  2. SSD 盘的 IO 要 4K 对齐,SSD 盘的读写单元是 page,一个 page 为 4K 大小;

如果不对齐,会有问题问题?

  1. 性能*差;
  2. SSD 寿命缩短;

那你就会想了,出现这个问题的原因又是啥?

本质原因是内部 IO 流量和次数的放大。

那你又会想了,怎么会这样呢,我不就写 1 个字节数据而已嘛,放大个啥?

这个秘密就在于前面提到的,机械盘和 SSD 盘的 IO 都有*小单元的概念。机械盘是以扇区为*小的空间单位,SSD 盘则是 4k 的 page 作为 IO 的*小单元。所以当在机械盘上读 1 个字节的数据,本质上是至少读一个扇区,写 1 个字节则更复杂,先要把这个扇区读出来,然后在内存里 update,*后又把这个扇区写回到磁盘。

你可能又有疑问了:为啥机械盘和 SSD 盘都要按照一个单元来管理呢,为啥不 1 字节 1 字节管理呢?如果这样的话,业务读写不就方便了嘛。

答案是:开销。 考虑以下两个方面:

  • *个是存储的开销:数据存储到磁盘上还有 SSD 盘上是会有校验的(比如 ECC 校验),如果每个字节都对应一个校验,存 1T 的数据有 1T 的校验?那这个存储开销无比的大。那如果是现在 512 字节对应一个 1 字节的校验,则开销完全可控;
  • 第二个是性能的开销:物理硬件是跟真实世界的设计匹配的,对于机械磁盘的写是利用磁化介质来存储数据,如果磁盘上是1 个字节,甚至 1 个 bit 来独立存储,那磁盘磁头的次数将无比庞大,性能也将无比的下降(每一次都是写数据+校验)。SSD 的写则更复杂,因为 SSD 由于自身的存储特点,是无法覆盖写的,每次写都是写新位置,旧的位置则是作为垃圾等待后台 GC ,粒度太小则会导致擦写的次数无比的多,性能和寿命都将不可接受;

所以说,在综合因素的权衡下,硬件对于空间管理会划分固定单元,并对 IO 也提出单元对齐的要求。至于磁盘固定单元为 512 字节,SSD 的 IO 单元为 4K 则是经过综合的科学测试和验证的一个数值而已。

举个不严谨的例子

在京东上买东西,你买 1 块钱的货,京东要收费 7 块的运费。对你来讲是不是划不来?因为对京东来讲,如果真是单独送你这 1 块钱的东西也划不来。所以京东要你买的东西满 79 元才给你免运费。

如果说真的有个愣头青,每次都单独买 1 块钱的货,买它 100 次,划得来来不?

当然划不来,运费都要 700 了,加上货本身,总费用 800 元(哈哈,可能京东被你折腾的更惨)。

那如果是 1 次买 100 件这个货呢?

你只需要 100 块。

所以,你再思考下为什么磁盘本身一定有固定大小的存储单元,通俗话叫做:量大从优,专业一点叫做:减少边际效应。

%title插图%num

对齐有哪些方面

对齐其实很简单,只有两个维度:

  • 偏移对齐
  • 长度对齐

是不是很简单呀。偏移和长度就是我们常说的 offsetlength,对齐通常就是既要保证偏移对齐也要保证长度对齐。

%title插图%num

IO 读写的真实样子

下面以机械盘扇区对齐和非对齐的举例。为了简化,以下例子讨论的的对齐都是偏移和长度双重对齐的。

1   扇区对齐场景

场景:读 512 个字节数据是怎么做的?

步骤如下:

  1. 磁头摆到指定偏移;
  2. 读取一个扇区的数据到内存;

%title插图%num

开销:只需要一次磁盘读 IO。

场景:写 512 个字节数据是怎么做的?

步骤如下:

  1. 磁头摆到指定偏移;
  2. 覆盖写一个扇区的数据到磁盘;

%title插图%num

开销:只需要一次磁盘写 IO。

2   扇区非对齐场景

场景:读 1 个字节数据是怎么做的?

步骤如下:

  1. 磁头摆到这 1 个字节数据所在扇区位置,对齐到扇区开始的偏移;
  2. 读取 1 个完整扇区( 512 字节)的数据到内存;
  3. 从这 512 字节的内存中,copy 出用户要用的 1 个字节,给到用户;

%title插图%num

开销:读放大。如果是读 512 字节的数据,但是偏移不对齐,那么可能导致 2 次 IO。

场景:读 512 个字节,但是偏移不对齐?

步骤如下:

  1. 磁头摆到这 512 个字节数据所在 2 个扇区位置,对齐到扇区开始的偏移;
  2. 读取 2 个完整扇区(1024 Bytes)的数据到内存;
  3. 从这 1024 字节的内存中,copy 出用户要用的 512 个字节,给到用户;

%title插图%num

开销:读放大。虽然读的是 1 个扇区的数据,但是偏移却没对齐,所以必须要读 2 个扇区,也放大了一倍的流量。

场景:写 1 个字节数据是怎么做的?

写是比较复杂的,用户现在手握 1 个字节的数据,想要写到磁盘,但是磁盘的 IO 单元是扇区,所以本质上是读改写的方式。步骤如下:

  1. 先要把这要改动的 1 字节所在的扇区完整的读到内存;
    1. 磁头摆动到扇区偏移;
    2. 读一个扇区,读到内存;
  2. 然后在内存中,把这 1 个字节对应的位置的数据修改;
  3. 然后重新写回扇区;
    1. 磁盘寻道道扇区偏移;
    2. 覆盖写着一个扇区;

%title插图%num

开销:一次磁盘读 IO,内存合并,*后再一次磁盘写 IO。明明是写,却必须要先读,IO 即放大了流量,又放大了次数

所以你看出来了吗,非对齐的 IO 多了很多步骤。存在 IO 流量和次数的放大。这样会*度拖累性能。

本来只需要一次 IO 就能完成的操作,放大了一次,那么性能就至少下降一倍。就这么简单。

场景:写 1 个扇区的数据,但是偏移不对齐会怎么样?

操作步骤

  1. 读 2 个扇区的数据(1024 Bytes)上来;
  2. 合并内存中的数据;
  3. 把这 2 个新的扇区数据,写到磁盘;

%title插图%num

开销:所以,我们看到由于不对齐,读的时候导致多读了一个扇区,写的时候导致多写了 1 个扇区。性能自然是*差的。

%title插图%num

经常被忽略的事

1   IO 的代价比你预期的要大

机械盘来说,随机 iops 就是几百的样子,带宽也就几百兆。一次寻道都是 10ms 级别的。所以磁盘读写数据的代价远比你想象中的大。

这个有多慢?cpu 执行指令都是纳秒和微秒级别的,一次 IO 性能和 cpu 或者内存的操作相差十万百千里。所以,但凡你能省 1 次 IO 都是非常大的性能提升。

而对于 SSD 来说,如果你 IO 不对齐,很有可能峰值能跑 5 万 iops 的盘只能跑 2 万甚至更低。大家一定要有个意识:IO 能节省就节省,多一次 IO 性能可能慢 1 倍。

2   为什么我编程的时候从来不需要注意对齐?

这个时候你可能反问了,我读写文件从没考虑过对齐的问题呀?

是的,你是没考虑过,那是因为有一个苦逼帮你把这个活干了,谁呢?

文件系统。这个世界并不简单,只是有人替你负重前行。

*大部分的程序员都是基于文件系统之上操作磁盘。文件系统则会使用 buffer cache 自动帮用户对齐 IO ,然后再下发磁盘。一旦你想要深入优化 IO 的性能,IO 对齐一定是*道要考量的坎。

还有一点要特别注意,一个特别重要的环节:文件系统格式化化的时候。

格式化的时候,一定要注意对齐。 格式化文件系统的时候,一定要注意对齐的偏移,不要故意搞成非对齐的偏移,不然一旦文件系统格式化的时候都不对齐,后面谁也救不了,除非你重新格式化文件系统。

举个例子,本来文件系统从偏移磁盘 0 这个位置格式化,你偏不,你偏要从 3 字节偏移的位置开始格式化。那后面的所有的貌似对齐的 IO 偏移都将是不对齐的,性能也自然是下降的。

%title插图%num

总结

  1. IO 对齐是做存储必须要考量的一个因素;
  2. IO 对齐的两个核心是:偏移和长度;
  3. 非 IO 对齐的请求会导致内部 IO 流量和次数的放到,从而性能下降(SSD 则会因为放到而导致擦写次数过多,更会影响寿命);
  4. 程序员一般很少要主动对齐,因为文件系统帮你 hold 住了一层。但是如果程序员自己不注意,还是会踩坑。比如明明是 SSD 盘,你偏要每次发送 512 字节的 IO 请求,那性能肯定惨不忍睹;
  5. 对齐的 IO 简简单单,不对齐的 IO 千奇百怪。