日期: 2021 年 5 月 17 日

Android Studio 模拟器的选择和安装

一、Android Studio 自带的AVD模拟器

Android Studio 程序可以在真机上调试运行,Android Studio 也提供了模拟器来调试运行,这时需要配置 AVD 来选择你调试程序的模拟环境。
%title插图%num1. 在 Intel CPU 的主机上启用 HAXM
在 Intel CPU 的主机上,为了加速AVD模拟器的运行速度,需要启用 HAXM 。如果在没有启用 HAXM 时就运行程序,调用AVD模拟器时会报如下错误:

emulator: ERROR: x86 emulation currently requires hardware acceleration!
Please ensure Intel HAXM is properly installed and usable.
CPU acceleration status: HAX kernel module is not installed!

出现此警告的原因是AVD模拟器调用X86架构的安卓虚拟机需要使用到Intel HAXM 引擎,而本机尚未进行安装导致。
此时,应该先进入 BIOS 启用 Virtualization Technology 选项。然后从 https://software.intel.com/en-us/android/articles/intel-hardware-accelerated-execution-manager/ 下载,也可以直接在 Android Studio 的 SDK 中下载,再安装即可。
%title插图%num
%title插图%num

2. 对于非 Intel CPU 的主机
对于非 Intel CPU 的主机或不能安装 HAXM 的主机(像我的云主机),则只能选择 arm 模拟器 或 选择其它第三方的模拟器 (像Genymotion)。
arm 模拟器的设置如下,不过一般情况下, arm 模拟器速度很慢 (像我的云主机启动它需要个吧小时),所以,应该尽量选用Genymotion等虚拟机。
%title插图%num

3. 设置
如果以上配置无误,就可以运行程序试试。如果出现类似如下面的警告:
emulator: WARNING: Requested RAM size of 1536MB is too large for your environment, and is reduced to 1152MB.
emulator: device fd:596
HAXM is not working and emulator runs in emulation mode
emulator: The memory needed by this AVD exceeds the max specified in your HAXM configuration.
emulator: AVD RAM size = 1152 MB
emulator: HAXM max RAM size = 1024 MB
emulator: You might want to adjust your AVD RAM size and/or HAXM configuration to run in fast virt mode.
Cannot set up guest memory ‘pc.ram’: Invalid argument
警告提示模拟器RAM过大,需要在 AVD 中将模拟器的RAM改为512~1024,这样模拟器才能正常启动。
%title插图%num

 

二、android studio外挂Genymotion模拟器

Genymotion虚拟机可以模拟Galaxy、SAMSUNG、SONY、HTC等主流手机,运行速度快,是现在广受欢迎的虚拟机。Genymotion依赖 VirtualBox 加载手机虚拟机。
1. 安装 VirtualBox
从 https://www.virtualbox.org/wiki/Downloads
下载 VirtualBox ,再安装,过程从略。
2. 安装 Genymotion
从 https://www.genymotion.com/download/ 下载 Genymotion ,下载是要用邮箱注册账号后才能下载。
官网提供了两个版本,带有VirtualBox的Genymotion整合包和不带VirtualBox的Genymotion安装包,可以根据需要下载相应版本安装。安装过程从略。
3. 下载 .ova 虚拟设备
启动 Genymotion,添加对应手机的 Virtual device ,如下图。
%title插图%num

不过如果因为墙的阻挡,也许会下载出错。如果Genymotion添加 Virtual device 时出现如下的错误:

Failed to deploy virtual device.
Unable to create virtual device:
Connection timeout occurred.

那就只好手工下载 Virtual device 的离线.ova文件了。
方法一:下载官方离线.ova文件
即使刚才下载失败了,但是它已经在 “C:\Users\用户主目录\AppData\Local\Genymobile\genymotion.log” 文件里保留了官方.ova文件的地址,打开该文件,找到类似 “http://files2.genymotion.com/dists/6.0.0/ova/genymotion_vbox86p_6.0_160114_090449.ova” 的路径,即您想要下载的.ova镜像文件URL;复制到浏览器或用第三方下载工具下载该文件。
方法二:贴吧下载
有些网友下载了一些.ova文件,大家可以在网上搜搜,也可以到百度贴吧里找找。像下面的地址里就有一些:
链接: http://pan.baidu.com/s/1jHfuJNg 密码: 222g
4. 安装下载的.ova 离线文件
下载好后拷贝到 “C:\Users\用户主目录\AppData\Local\Genymobile\Genymotion\ova” 文件夹下。
然后打开 VirtualBox -> 管理 -> 导入虚拟电脑 (快捷键:Ctrl+I ) -> 选择下载好的 .ova 文件 -> 下一步 -> 导入 。导入完成之后就OK了。这时候打开Genymotion就看到可以使用了。
5. 将Genymotion加入 Android Studio
如下图,在 Android Studio 的 Setting 中加入 Genymotion 的插件即可。
%title插图%num
%title插图%num
%title插图%num

6. 运行 Genymotion
现在可以点击 Android Studio 上的 Genymotion 图标来运行虚拟机,如果此时出现如下错误说明需要重装或升级显卡驱动。
make sure that your video card supports OpenGL 2.0 and update the drivers.
%title插图%num

五、运行
正常运行后,可以通过虚拟机的
Settings -> Language & input -> Language -> 中文(简体) 将虚拟手机中文化。
%title插图%num

然后,可以在虚拟机里调试程序了。
%title插图%num
 

三、真机模拟器

对于我这刚入门的菜鸟来说,我在配置Android的开发环境中,遇到的问题实在是太多了,都快花费我一个星期的时间了,在这期间出了*下载SDK之外,其他的世间都在搞Android virtual device出现的问题,由于本人的能力有限以及电脑配置的不给力,Android virtual device中出现的问题始终都没有得到解决,后来看了网上很多人都在说使用Google自带的Android virtual device来进行调试运行时会比较慢,性能也不是很好,然后使用真机模拟的话速度是比较快的,效果也不错。那么接下来我要讲的就是如何在Android studio中使用真机进行调试以及在这过程中遇到的一个问题

 

首先,你要先创建好一个项目

%title插图%num

 

 

接着在工具栏中找到APP这个按钮,选择Edit Configurations

%title插图%num

 

 

在弹出的对话框中,找到“ Deployment Target Options” 并选择“ USB Device ”,然后点击确定

%title插图%num

 

以上配置完之后,我们就可以进行调试了,在工具栏中找到绿色的三角符号按钮(或者按快捷键Shift+F10)运行项目

%title插图%num

 

启动run后,软件开始生成apk安装包,并自动安装到手机上,*次执行的话会慢一些,往后就快了。然后我们识别出来的设备可以再Android Monitor这一栏中看到

%title插图%num

 

安卓手机也自动安装好app并自动执行

%title插图%num

 

 

 

在以上的操作过程中,出现了一个问题,在Android Monitor这栏中,没有识别出我们的手机设备,显示No Connected Devices

 

%title插图%num

 

出现以上的原因是电脑中的驱动没有安装好,Android studio不能识别出我们的设备,然而解决方法很简单,我们只需要这我们的电脑中安装一个豌豆荚就可以了,这个应用可以帮我们直接搞定设备驱动安装的问题。

单独启动android studio模拟器

单独启动android studio的模拟器,需要自己写一个脚本,需要两个命令

*个:进入emulator.exe的目录,比如我的目录,他就在你的SDK目录下的tools下

E:\learn\androidSdk\tools\emulator.exe
第二个:指定你要启动的模拟器,如Pixel_API_25

-netdelay none -netspeed full -avd Pixel_API_25

%title插图%num
所以在左面创建一个runEmulator.bat的文件,里面输入下面命令即可,其中Pixel_API_25是你自己要启动的模拟器名字,

E:\learn\androidSdk\tools\emulator.exe -netdelay none -netspeed full -avd Pixel_API_25
提示:所有创建的虚拟手机均在C:\Users\Amarao\.android\avd下

%title插图%num

iOS的可变数组的执行copy操作后,添加元素出现crash

前要
copy这个属性,相信我们用的很多的吧,尤其集合类用的比较多,我们一般都会用到就是NSArray,NSMutableArray,NSString,NSMutableString等等,copy分为深拷贝和浅拷贝,深拷贝就是拷贝里面的内容(完全新的对象),浅拷贝只是拷贝对象地址(一样地址)。@property (copy, nonatomic) NSMutableArray *tempArray;一般都是这么写,然后使用懒加载的方式给这个实例变量进行初始化,但是我使用set方法直接赋值self.tempArray = [NSMutableArray array];然后数组添加元素的时候,就会出现crash。

原因分析
首先既然添加数组的时候crash,会不会是数组没有初始化或者数组不是可变数组的问题呢,以下是我的截图:

可以看出,执行了self.tempArray = [NSMutableArray array];之后,数组变成了不可变的数组,我们添加元素的话肯定有问题了呀,肯定会crash。肯定很多的why???我用的可变数组初始化的呀,为什么变成了不可变数组。其实我们换一种方式来解读一下

我们看出来了吧,我只是把[NSMutableArray array]执行了copy操作,但是one却变成了不可变数组了,那么我们再来看看mutableCopy会是什么结果。

是个可变数组。
再回头看tempArray的属性,是copy属性,这就必然会执行copy操作,然后重新生成了一个新的对象赋值给tempArray,又因为OC是动态语言,只有在执行的时候才知道是什么类型的对象,为什么懒加载就可以避免了这种crash。

– (NSMutableArray *)tempArray{

if (_tempArray) {
_tempArray = [NSMutableArray array];
}
return _tempArray;
}

我们看出来使用的tempArray的实例变量,实例变量是系统帮你生成的,不会存在属性那样的copy的属性,所以不会直接执行copy操作,把可变数组变成不可变数组,实例变量的自然也是可变的。

总结
copy证明可以修饰可变数组,但是会执行copy方法,生成的是个不可变的数组,所以只需要使用实例变量就可以完美解决了,也是常说的使用懒加载的模式来初始化数组对象,其实证明用retain和strong修饰的话,self.tempArray = [NSMutableArray array];这个方法生成的对象也是可变数组。retain引用计数+1;strong也就是强引用一次

IOS 拍照黑屏解决

之前好好的代码,现在拍照,等照片拍摄完毕,会出现黑屏(拍照结束了,看到的是黑屏状态。正常情况下是图片),但是还是可以获取并且保存照片到本地的。

结果发现是同事写的状态栏的statusBar造成的,去除就可以正常拍照了。这些也是受这篇文章的提点:

http://stackoverflow.com/questions/21538781/uiimagepickercontroller-camera-blank-snapshot-image

%title插图%num

#pragma mark — Camera And Picture

– (void)loadCameraChooseImage
{
UIActionSheet *sheet;

// 判断是否支持相机
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@”取消” destructiveButtonTitle:nil otherButtonTitles:@”拍照”,@”从相册选择”, nil];
}

else
{
sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@”取消” destructiveButtonTitle:nil otherButtonTitles:@”拍照”,@”从相册选择”, nil];
}

sheet.tag = 255;

[sheet showInView:self.view];
}

– (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (actionSheet.tag == 255)
{
NSUInteger sourceType = 0;

// 判断是否支持相机
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
switch (buttonIndex)
{
case 0:
// 相机
sourceType = UIImagePickerControllerSourceTypeCamera;
break;

case 1:
// 相册
sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
break;

case 2:
// 取消
return;
}
}
else
{
if (buttonIndex == 0)
{
sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
else
{
return;
}
}

if (sourceType == UIImagePickerControllerSourceTypePhotoLibrary)
{
[self.navigationController pushViewController:_vc animated:YES];
}

else if (sourceType == UIImagePickerControllerSourceTypeCamera)
{
// 跳转到相机或相册页面
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.delegate = self;
imagePickerController.sourceType = sourceType;

if (iPad)//ipad拍照走下面的方法
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{

// [self presentViewController:imagePickerController animated:NO completion:nil];
[self presentViewController:imagePickerController animated:YES completion:^{}];
}];
}

else //iphone拍照走下面的方法
{
[self presentViewController:imagePickerController animated:YES completion:^{}];
}

}
}
}

#pragma mark – image picker delegte

– (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[picker dismissViewControllerAnimated:YES completion:^{}];

UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
/* 此处info 有六个值
* UIImagePickerControllerMediaType; // an NSString UTTypeImage)
* UIImagePickerControllerOriginalImage; // a UIImage 原始图片
* UIImagePickerControllerEditedImage; // a UIImage 裁剪后图片
* UIImagePickerControllerCropRect; // an NSValue (CGRect)
* UIImagePickerControllerMediaURL; // an NSURL
* UIImagePickerControllerReferenceURL // an NSURL that references an asset in the AssetsLibrary framework
* UIImagePickerControllerMediaMetadata // an NSDictionary containing metadata from a captured photo
*/

if (_photoArray.count > 0)
{
[_photoArray removeLastObject];
}

[_photoCollectionView reloadData];
}

– (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{ [self dismissViewControllerAnimated:YES completion:^{}];<span style=”font-family: Arial, Helvetica, sans-serif;”>}</span>

 

iOS终于知道为什么有时候打开相机是黑屏了

之前写一个相机,发现在有的手机上能够正常启动相机,而有的打开相机是黑屏。多处查询而未果,今天看到友盟微社区的代码,终于知道原因了。其实没有网上说的那么复杂,就是需要看一下在你手机的设置——隐私——相机中,本软件是不是允许访问相机,只要允许就可以正常打开。所以在写代码的时候要判断一下本程序是不是有访问相机的权限。代码如下:

注意:此方法只对ios7以上的系统有用,如果是在ios6的系统的话就直接崩溃了,况且ios6上也没有“设置–隐私–相机” 那一项

 

 

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
AVAuthorizationStatus authStatus =  [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];

if (authStatus == AVAuthorizationStatusDenied || authStatus == AVAuthorizationStatusRestricted)
{
[[[UIAlertView alloc] initWithTitle:nil message:@”本应用无访问相机的权限,如需访问,可在设置中修改” delegate:nil cancelButtonTitle:@”好的” otherButtonTitles:nil, nil] show];
return;
}
}else{
ALAuthorizationStatus author = [ALAssetsLibrary authorizationStatus];
if (author == kCLAuthorizationStatusRestricted || author == kCLAuthorizationStatusDenied)
{
[[[UIAlertView alloc] initWithTitle:nil message:@”本应用无访问相机的权限,如需访问,可在设置中修改” delegate:nil cancelButtonTitle:@”好的” otherButtonTitles:nil, nil] show];
return;
}
}

if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.sourceType =  UIImagePickerControllerSourceTypeCamera;
imagePicker.delegate = self;
imagePicker.allowsEditing = YES;
[self presentViewController:imagePicker animated:YES completion:^{

}];
}
— NORMAL —

Redis常见延迟问题排查手册!附33条优化建议

Redis作为内存数据库,拥有非常高的性能,单个实例的QPS能够达到10W左右。但我们在使用Redis时,经常时不时会出现访问延迟很大的情况,如果你不知道Redis的内部实现原理,在排查问题时就会一头雾水。

 

很多时候,Redis出现访问延迟变大,都与我们的使用不当或运维不合理导致的。

 

Redis变慢了?常见延迟问题定位与分析

 

下面我们就来分析一下Redis在使用过程中,经常会遇到的延迟问题以及如何定位和分析。

 

使用复杂度高的命令

 

 

如果在使用Redis时,发现访问延迟突然增大,如何进行排查?

 

首先,*步,建议你去查看一下Redis的慢日志。Redis提供了慢日志命令的统计功能,我们通过以下设置,就可以查看有哪些命令在执行时延迟比较大。

 

首先设置Redis的慢日志阈值,只有超过阈值的命令才会被记录,这里的单位是微妙,例如设置慢日志的阈值为5毫秒,同时设置只保留*近1000条慢日志记录:

 

 

# 命令执行超过5毫秒记录慢日志

CONFIG SET slowlog-log-slower-than 5000

# 只保留*近1000条慢日志

CONFIG SET slowlog-max-len 1000

 

设置完成之后,所有执行的命令如果延迟大于5毫秒,都会被Redis记录下来,我们执行SLOWLOG get 5查询*近5条慢日志:

 

 

127.0.0.1:6379> SLOWLOG get 5

1) 1) (integer) 32693       # 慢日志ID

2) (integer) 1593763337  # 执行时间

3) (integer) 5299        # 执行耗时(微妙)

4) 1) “LRANGE”           # 具体执行的命令和参数

2) “user_list_2000”

3) “0”

4) “-1”

2) 1) (integer) 32692

2) (integer) 1593763337

3) (integer) 5044

4) 1) “GET”

2) “book_price_1000”

 

通过查看慢日志记录,我们就可以知道在什么时间执行哪些命令比较耗时,如果你的业务经常使用O(n)以上复杂度的命令,例如sort、sunion、zunionstore,或者在执行O(n)命令时操作的数据量比较大,这些情况下Redis处理数据时就会很耗时。

 

如果你的服务请求量并不大,但Redis实例的CPU使用率很高,很有可能是使用了复杂度高的命令导致的。

 

解决方案就是,不使用这些复杂度较高的命令,并且一次不要获取太多的数据,每次尽量操作少量的数据,让Redis可以及时处理返回。

 

存储大key

 

 

如果查询慢日志发现,并不是复杂度较高的命令导致的,例如都是SET、DELETE操作出现在慢日志记录中,那么你就要怀疑是否存在Redis写入了大key的情况。

 

Redis在写入数据时,需要为新的数据分配内存,当从Redis中删除数据时,它会释放对应的内存空间。

 

如果一个key写入的数据非常大,Redis在分配内存时也会比较耗时。同样的,当删除这个key的数据时,释放内存也会耗时比较久。

 

你需要检查你的业务代码,是否存在写入大key的情况,需要评估写入数据量的大小,业务层应该避免一个key存入过大的数据量。

 

那么有没有什么办法可以扫描现在Redis中是否存在大key的数据吗?

 

Redis也提供了扫描大key的方法:

 

 

redis-cli -h $host -p $port –bigkeys -i 0.01

 

使用上面的命令就可以扫描出整个实例key大小的分布情况,它是以类型维度来展示的。

 

需要注意的是当我们在线上实例进行大key扫描时,Redis的QPS会突增,为了降低扫描过程中对Redis的影响,我们需要控制扫描的频率,使用-i参数控制即可,它表示扫描过程中每次扫描的时间间隔,单位是秒。

 

使用这个命令的原理,其实就是Redis在内部执行scan命令,遍历所有key,然后针对不同类型的key执行strlen、llen、hlen、scard、zcard来获取字符串的长度以及容器类型(list/dict/set/zset)的元素个数。

 

而对于容器类型的key,只能扫描出元素*多的key,但元素*多的key不一定占用内存*多,这一点需要我们注意下。不过使用这个命令一般我们是可以对整个实例中key的分布情况有比较清晰的了解。

 

针对大key的问题,Redis官方在4.0版本推出了lazy-free的机制,用于异步释放大key的内存,降低对Redis性能的影响。即使这样,我们也不建议使用大key,大key在集群的迁移过程中,也会影响到迁移的性能,这个后面在介绍集群相关的文章时,会再详细介绍到。

 

集中过期

 

 

有时你会发现,平时在使用Redis时没有延时比较大的情况,但在某个时间点突然出现一波延时,而且报慢的时间点很有规律,例如某个整点,或者间隔多久就会发生一次。

 

如果出现这种情况,就需要考虑是否存在大量key集中过期的情况。

 

如果有大量的key在某个固定时间点集中过期,在这个时间点访问Redis时,就有可能导致延迟增加。

 

Redis的过期策略采用主动过期+懒惰过期两种策略:

 

  • 主动过期:Redis内部维护一个定时任务,默认每隔100毫秒会从过期字典中随机取出20个key,删除过期的key,如果过期key的比例超过了25%,则继续获取20个key,删除过期的key,循环往复,直到过期key的比例下降到25%或者这次任务的执行耗时超过了25毫秒,才会退出循环;
  • 懒惰过期:只有当访问某个key时,才判断这个key是否已过期,如果已经过期,则从实例中删除。

 

注意,Redis的主动过期的定时任务,也是在Redis主线程中执行的,也就是说如果在执行主动过期的过程中,出现了需要大量删除过期key的情况,那么在业务访问时,必须等这个过期任务执行结束,才可以处理业务请求。此时就会出现,业务访问延时增大的问题,*大延迟为25毫秒。

 

而且这个访问延迟的情况,不会记录在慢日志里。慢日志中只记录真正执行某个命令的耗时,Redis主动过期策略执行在操作命令之前,如果操作命令耗时达不到慢日志阈值,它是不会计算在慢日志统计中的,但我们的业务却感到了延迟增大。

 

此时你需要检查你的业务,是否真的存在集中过期的代码,一般集中过期使用的命令是expireat或pexpireat命令,在代码中搜索这个关键字就可以了。

 

如果你的业务确实需要集中过期掉某些key,又不想导致Redis发生抖动,有什么优化方案?

 

解决方案是,在集中过期时增加一个随机时间,把这些需要过期的key的时间打散即可。

 

伪代码可以这么写:

 

 

# 在过期时间点之后的5分钟内随机过期掉

redis.expireat(key, expire_time + random(300))

 

这样Redis在处理过期时,不会因为集中删除key导致压力过大,阻塞主线程。

 

另外,除了业务使用需要注意此问题之外,还可以通过运维手段来及时发现这种情况。

 

做法是我们需要把Redis的各项运行数据监控起来,执行info可以拿到所有的运行数据,在这里我们需要重点关注expired_keys这一项,它代表整个实例到目前为止,累计删除过期key的数量。

 

我们需要对这个指标监控,当在很短时间内这个指标出现突增时,需要及时报警出来,然后与业务报慢的时间点对比分析,确认时间是否一致,如果一致,则可以认为确实是因为这个原因导致的延迟增大。

 

实例内存达到上限

 

 

有时我们把Redis当做纯缓存使用,就会给实例设置一个内存上限maxmemory,然后开启LRU淘汰策略。

 

当实例的内存达到了maxmemory后,你会发现之后的每次写入新的数据,有可能变慢了。

 

导致变慢的原因是,当Redis内存达到maxmemory后,每次写入新的数据之前,必须先踢出一部分数据,让内存维持在maxmemory之下。

 

这个踢出旧数据的逻辑也是需要消耗时间的,而具体耗时的长短,要取决于配置的淘汰策略:

 

  • allkeys-lru:不管key是否设置了过期,淘汰*近*少访问的key;
  • volatile-lru:只淘汰*近*少访问并设置过期的key;
  • allkeys-random:不管key是否设置了过期,随机淘汰;
  • volatile-random:只随机淘汰有设置过期的key;
  • allkeys-ttl:不管key是否设置了过期,淘汰即将过期的key;
  • noeviction:不淘汰任何key,满容后再写入直接报错;
  • allkeys-lfu:不管key是否设置了过期,淘汰访问频率*低的key(4.0+支持);
  • volatile-lfu:只淘汰访问频率*低的过期key(4.0+支持)。

 

具体使用哪种策略,需要根据业务场景来决定。

 

我们*常使用的一般是allkeys-lru或volatile-lru策略,它们的处理逻辑是,每次从实例中随机取出一批key(可配置),然后淘汰一个*少访问的key,之后把剩下的key暂存到一个池子中,继续随机取出一批key,并与之前池子中的key比较,再淘汰一个*少访问的key。以此循环,直到内存降到maxmemory之下。

 

如果使用的是allkeys-random或volatile-random策略,那么就会快很多,因为是随机淘汰,那么就少了比较key访问频率时间的消耗了,随机拿出一批key后直接淘汰即可,因此这个策略要比上面的LRU策略执行快一些。

 

但以上这些逻辑都是在访问Redis时,真正命令执行之前执行的,也就是它会影响我们访问Redis时执行的命令。

 

另外,如果此时Redis实例中有存储大key,那么在淘汰大key释放内存时,这个耗时会更加久,延迟更大,这需要我们格外注意。

 

如果你的业务访问量非常大,并且必须设置maxmemory限制实例的内存上限,同时面临淘汰key导致延迟增大的的情况,要想缓解这种情况,除了上面说的避免存储大key、使用随机淘汰策略之外,也可以考虑拆分实例的方法来缓解,拆分实例可以把一个实例淘汰key的压力分摊到多个实例上,可以在一定程度降低延迟。

 

fork耗时严重

 

 

如果你的Redis开启了自动生成RDB和AOF重写功能,那么有可能在后台生成RDB和AOF重写时导致Redis的访问延迟增大,而等这些任务执行完毕后,延迟情况消失。

 

遇到这种情况,一般就是执行生成RDB和AOF重写任务导致的。

 

生成RDB和AOF都需要父进程fork出一个子进程进行数据的持久化,在fork执行过程中,父进程需要拷贝内存页表给子进程,如果整个实例内存占用很大,那么需要拷贝的内存页表会比较耗时,此过程会消耗大量的CPU资源,在完成fork之前,整个实例会被阻塞住,无法处理任何请求,如果此时CPU资源紧张,那么fork的时间会更长,甚至达到秒级。这会严重影响Redis的性能。

 

具体原理也可以参考我之前写的文章:Redis持久化是如何做的?RDB和AOF对比分析。

 

我们可以执行info命令,查看*后一次fork执行的耗时latest_fork_usec,单位微妙。这个时间就是整个实例阻塞无法处理请求的时间。

 

除了因为备份的原因生成RDB之外,在主从节点*次建立数据同步时,主节点也会生成RDB文件给从节点进行一次全量同步,这时也会对Redis产生性能影响。

 

要想避免这种情况,我们需要规划好数据备份的周期,建议在从节点上执行备份,而且*好放在低峰期执行。如果对于丢失数据不敏感的业务,那么不建议开启AOF和AOF重写功能。

 

另外,fork的耗时也与系统有关,如果把Redis部署在虚拟机上,那么这个时间也会增大。所以使用Redis时建议部署在物理机上,降低fork的影响。

 

绑定CPU

 

 

很多时候,我们在部署服务时,为了提高性能,降低程序在使用多个CPU时上下文切换的性能损耗,一般会采用进程绑定CPU的操作。

 

但在使用Redis时,我们不建议这么干,原因如下。

 

绑定CPU的Redis,在进行数据持久化时,fork出的子进程,子进程会继承父进程的CPU使用偏好,而此时子进程会消耗大量的CPU资源进行数据持久化,子进程会与主进程发生CPU争抢,这也会导致主进程的CPU资源不足访问延迟增大。

 

所以在部署Redis进程时,如果需要开启RDB和AOF重写机制,一定不能进行CPU绑定操作!

 

开启AOF

 

 

上面提到了,当执行AOF文件重写时会因为fork执行耗时导致Redis延迟增大,除了这个之外,如果开启AOF机制,设置的策略不合理,也会导致性能问题。

 

开启AOF后,Redis会把写入的命令实时写入到文件中,但写入文件的过程是先写入内存,等内存中的数据超过一定阈值或达到一定时间后,内存中的内容才会被真正写入到磁盘中。

 

AOF为了保证文件写入磁盘的安全性,提供了3种刷盘机制:

 

  • appendfsync always:每次写入都刷盘,对性能影响*大,占用磁盘IO比较高,数据安全性*高;
  • appendfsync everysec:1秒刷一次盘,对性能影响相对较小,节点宕机时*多丢失1秒的数据;
  • appendfsync no:按照操作系统的机制刷盘,对性能影响*小,数据安全性低,节点宕机丢失数据取决于操作系统刷盘机制。

 

当使用*种机制appendfsync always时,Redis每处理一次写命令,都会把这个命令写入磁盘,而且这个操作是在主线程中执行的。

 

内存中的的数据写入磁盘,这个会加重磁盘的IO负担,操作磁盘成本要比操作内存的代价大得多。如果写入量很大,那么每次更新都会写入磁盘,此时机器的磁盘IO就会非常高,拖慢Redis的性能,因此我们不建议使用这种机制。

 

与*种机制对比,appendfsync everysec会每隔1秒刷盘,而appendfsync no取决于操作系统的刷盘时间,安全性不高。因此我们推荐使用appendfsync everysec这种方式,在*坏的情况下,只会丢失1秒的数据,但它能保持较好的访问性能。

 

当然,对于有些业务场景,对丢失数据并不敏感,也可以不开启AOF。

 

使用Swap

 

 

如果你发现Redis突然变得非常慢,每次访问的耗时都达到了几百毫秒甚至秒级,那此时就检查Redis是否使用到了Swap,这种情况下Redis基本上已经无法提供高性能的服务。

 

我们知道,操作系统提供了Swap机制,目的是为了当内存不足时,可以把一部分内存中的数据换到磁盘上,以达到对内存使用的缓冲。

 

但当内存中的数据被换到磁盘上后,访问这些数据就需要从磁盘中读取,这个速度要比内存慢太多!

 

尤其是针对Redis这种高性能的内存数据库来说,如果Redis中的内存被换到磁盘上,对于Redis这种性能*其敏感的数据库,这个操作时间是无法接受的。

 

我们需要检查机器的内存使用情况,确认是否确实是因为内存不足导致使用到了Swap。

 

如果确实使用到了Swap,要及时整理内存空间,释放出足够的内存供Redis使用,然后释放Redis的Swap,让Redis重新使用内存。

 

释放Redis的Swap过程通常要重启实例,为了避免重启实例对业务的影响,一般先进行主从切换,然后释放旧主节点的Swap,重新启动服务,待数据同步完成后,再切换回主节点即可。

 

可见,当Redis使用到Swap后,此时的Redis的高性能基本被废掉,所以我们需要提前预防这种情况。

 

我们需要对Redis机器的内存和Swap使用情况进行监控,在内存不足和使用到Swap时及时报警出来,及时进行相应的处理。

 

网卡负载过高

 

 

如果以上产生性能问题的场景,你都规避掉了,而且Redis也稳定运行了很长时间,但在某个时间点之后开始,访问Redis开始变慢了,而且一直持续到现在,这种情况是什么原因导致的?

 

之前我们就遇到这种问题,特点就是从某个时间点之后就开始变慢,并且一直持续。这时你需要检查一下机器的网卡流量,是否存在网卡流量被跑满的情况。

 

网卡负载过高,在网络层和TCP层就会出现数据发送延迟、数据丢包等情况。Redis的高性能除了内存之外,就在于网络IO,请求量突增会导致网卡负载变高。

 

如果出现这种情况,你需要排查这个机器上的哪个Redis实例的流量过大占满了网络带宽,然后确认流量突增是否属于业务正常情况,如果属于那就需要及时扩容或迁移实例,避免这个机器的其他实例受到影响。

 

运维层面,我们需要对机器的各项指标增加监控,包括网络流量,在达到阈值时提前报警,及时与业务确认并扩容。

 

以上我们总结了Redis中常见的可能导致延迟增大甚至阻塞的场景,这其中既涉及到了业务的使用问题,也涉及到Redis的运维问题。

 

可见,要想保证Redis高性能的运行,其中涉及到CPU、内存、网络,甚至磁盘的方方面面,其中还包括操作系统的相关特性的使用。

 

作为开发人员,我们需要了解Redis的运行机制,例如各个命令的执行时间复杂度、数据过期策略、数据淘汰策略等,使用合理的命令,并结合业务场景进行优化。

 

作为DBA运维人员,需要了解数据持久化、操作系统fork原理、Swap机制等,并对Redis的容量进行合理规划,预留足够的机器资源,对机器做好完善的监控,才能保证Redis的稳定运行。

 

Redis的*佳实践方式:业务层面和运维层面

 

在上文中,主要讲解了 Redis 常见的导致变慢的场景以及问题定位和分析,主要是由业务使用不合理和运维不当导致的。

 

我们在了解了导致Redis变慢的原因之后,针对性地优化,就可以让Redis稳定发挥出更高性能。

 

接着就来总结一下,在使用Redis时的*佳实践方式,主要包含两个层面:业务层面、运维层面。

 

由于我之前写过很多UGC后端服务,在大量场景下用到了Redis,这个过程中也踩过很多坑,所以在使用过程中也总结了一套合理的使用方法。

 

后来做基础架构,开发Codis、Redis相关的中间件,在这个阶段关注领域从使用层面下沉到Redis的开发和运维,更多聚焦在Redis的内部实现和运维过程中产生的各种问题,在这块也积累了一些经验。

 

下面就针对这两块,分享一下我认为比较合理的Redis使用和运维方法,不一定*全面,也可能与你使用Redis的方法不同,但以下这些方法都是我在踩坑之后总结的实际经验,供你参考。

 

业务层面

 

 

业务层面主要是开发人员需要关注,也就是开发人员在写业务代码时,如何合理地使用Redis。开发人员需要对Redis有基本的了解,才能在合适的业务场景使用Redis,从而避免业务层面导致的延迟问题。

 

在开发过程中,业务层面的优化建议如下:

 

  • key的长度尽量要短,在数据量非常大时,过长的key名会占用更多的内存;
  • 一定避免存储过大的数据(大value),过大的数据在分配内存和释放内存时耗时严重,会阻塞主线程;
  • Redis 4.0以上建议开启lazy-free机制,释放大value时异步操作,不阻塞主线程;
  • 建议设置过期时间,把Redis当做缓存使用,尤其在数量很大的时,不设置过期时间会导致内存的无限增长;
  • 不使用复杂度过高的命令,例如SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE,使用这些命令耗时较久,会阻塞主线程;
  • 查询数据时,一次尽量获取较少的数据,在不确定容器元素个数的情况下,避免使用LRANGE key 0 -1,ZRANGE key 0 -1这类操作,应该设置具体查询的元素个数,推荐一次查询100个以下元素;
  • 写入数据时,一次尽量写入较少的数据,例如HSET key value1 value2 value3…,控制一次写入元素的数量,推荐在100以下,大数据量分多个批次写入;
  • 批量操作数据时,用MGET/MSET替换GET/SET、HMGET/MHSET替换HGET/HSET,减少请求来回的网络IO次数,降低延迟,对于没有批量操作的命令,推荐使用pipeline,一次性发送多个命令到服务端;
  • 禁止使用KEYS命令,需要扫描实例时,建议使用SCAN,线上操作一定要控制扫描的频率,避免对Redis产生性能抖动
  • 避免某个时间点集中过期大量的key,集中过期时推荐增加一个随机时间,把过期时间打散,降低集中过期key时Redis的压力,避免阻塞主线程;
  • 根据业务场景,选择合适的淘汰策略,通常随机过期要比LRU过期淘汰数据更快;
  • 使用连接池访问Redis,并配置合理的连接池参数,避免短连接,TCP三次握手和四次挥手的耗时也很高;
  • 只使用db0,不推荐使用多个db,使用多个db会增加Redis的负担,每次访问不同的db都需要执行SELECT命令,如果业务线不同,建议拆分多个实例,还能提高单个实例的性能;
  • 读的请求量很大时,推荐使用读写分离,前提是可以容忍从节数据更新不及时的问题;
  • 写请求量很大时,推荐使用集群,部署多个实例分摊写压力。

 

运维层面

 

 

运维层面主要是DBA需要关注的,目的是合理规划Redis的部署和保障Redis的稳定运行,主要优化如下:

 

  • 不同业务线部署不同的实例,各自独立,避免混用,推荐不同业务线使用不同的机器,根据业务重要程度划分不同的分组来部署,避免某一个业务线出现问题影响其他业务线;
  • 保证机器有足够的CPU、内存、带宽、磁盘资源,防止负载过高影响Redis性能;
  • 以master-slave集群方式部署实例,并分布在不同机器上,避免单点,slave必须设置为readonly;
  • master和slave节点所在机器,各自独立,不要交叉部署实例,通常备份工作会在slave上做,做备份时会消耗机器资源,交叉部署会影响到master的性能;
  • 推荐部署哨兵节点增加可用性,节点数量至少3个,并分布在不同机器上,实现故障自动故障转移;
  • 提前做好容量规划,一台机器部署实例的内存上限,*好是机器内存的一半,主从全量同步时会占用*多额外一倍的内存空间,防止网络大面积故障引发所有master-slave的全量同步导致机器内存被吃光;
  • 做好机器的CPU、内存、带宽、磁盘监控,在资源不足时及时报警处理,Redis使用Swap后性能急剧下降,网络带宽负载过高访问延迟明显增大,磁盘IO过高时开启AOF会拖慢Redis的性能;
  • 设置*大连接数上限,防止过多的客户端连接导致服务负载过高;
  • 单个实例的使用内存建议控制在10G以下,过大的实例会导致备份时间久、资源消耗多,主从全量同步数据时间阻塞时间更长;
  • 设置合理的slowlog阈值,推荐10毫秒,并对其进行监控,产生过多的慢日志需要及时报警;
  • 设置合理的复制缓冲区repl-backlog大小,适当调大repl-backlog可以降低主从全量复制的概率;
  • 设置合理的slave节点client-output-buffer-limit大小,对于写入量很大的实例,适当调大可以避免主从复制中断问题;
  • 备份时推荐在slave节点上做,不影响master性能;
  • 不开启AOF或开启AOF配置为每秒刷盘,避免磁盘IO消耗降低Redis性能;
  • 当实例设置了内存上限,需要调大内存上限时,先调整slave再调整master,否则会导致主从节点数据不一致;
  • 对Redis增加监控,监控采集info信息时,使用长连接,频繁的短连接也会影响Redis性能;
  • 线上扫描整个实例数时,记得设置休眠时间,避免扫描时QPS突增对Redis产生性能抖动;
  • 做好Redis的运行时监控,尤其是expired_keys、evicted_keys、latest_fork_usec指标,短时间内这些指标值突增可能会阻塞整个实例,引发性能问题。

 

以上就是我在使用Redis和开发Redis相关中间件时,总结出来Redis推荐的实践方法,以上提出的这些方面,都或多或少在实际使用中遇到过。

 

可见,要想稳定发挥Redis的高性能,需要在各个方面做好工作,但凡某一个方面出现问题,必然会影响到Redis的性能,这对我们使用和运维提出了更高的要求。

iOS可变数组添加元素 以及可变数组操作

今天在项目工程中想往可变数组中添加从服务器获取的元素,我用了一下方法:

方法一:

[self.arrM_Data addObjectsFromArray:arr];

方法二:

[self.arrM_DataSource insertObjects:arr atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.arrM_DataSource.count, arr.count)]];

方法三:

for (int i=0; i<arr.count; i++) {

[self.arrM_DataSource insertObject:[arr objectAtIndex:i] atIndex:self.arrM_DataSource.count];

}

但是结果都是一样的Crash  ,报了如下错误(不知道是不是我的方法有问题):
*** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object’

*** First throw call stack:

(0x281d012 0x1e55e7e 0x281cdeb 0x289d9a5 0x27e1a90 0x27e1893 0x27e1480 0x9d848 0x1e696b0 0x36ecb 0x1e696b0 0x1896035 0x27a0f3f 0x27a096f 0x27c3734 0x27c2f44 0x27c2e1b 0x25717e3 0x2571668 0xd9d65c 0x235a 0x2265)

libc++abi.dylib: terminate called throwing an exception

*后翻看其他网友的解决方案 ,给顺利解决了,贴上解决方案:

NSMutableArray *arrM = [[NSMutableArray alloc] init];

[arrM addObjectsFromArray:self.arrM_Data];

[arrM addObjectsFromArray:arr];

self.arrM_Data = arrM;

[arrM release];

NSmutableArray 常用方法总结
array 创建一个空数组
+(id)arrayWithCapacity:size 创建一个数组,指定容量为size
-(id)initWithCapacity:size 初始化一个新分配的数组,指定容量为size
-(void)addObject:obj 将对象obj添加到数组末尾
-(void)insertObject:obj atIndex:i 将对象 obj 插入到索引为 i 的位置
-(void)replaceObject:obj atIndex:i 将数组中索引为 i 处的元素用obj 置换
-(void)removeObject:obj 从数组中删除所有是 obj 的对象
-(void)removeObjectAtIndex:i 从数组中删除索引为 i 的对像
-(void)sortUsingSelector:(SEL)selector 用 selector 只是的比较方法将数组排序

 

[IOS]——判断一个数组是否为空的方法

判断数组是否为空

有人说可以用([array count]==0 )来判断是否为空,都是坑,如果array为空的话,执行count就会直接报错,程序崩溃退出。

正确判断NSArray是否为空的方法:用 (!array)

if (array != nil && ![array isKindOfClass:[NSNull class]] && array.count != 0){

//执行array不为空时的操作

}

ubuntu20.04设置配置静态ip

ubuntu20.04设置配置静态ip方法

1. 前言

本教程将会演示Ubuntu20.04 Server版系统的静态固定IP地址。

2. 确认你要修改的网卡号

先确认你要修改的网卡号,假设你的服务器有多张网卡:

1 ubuntu2004:~$ ip addr

我的服务器配置如下:

1

2

3

4

5

6

7

8

9

10

11

12

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

inet 127.0.0.1/8 scope host lo

valid_lft forever preferred_lft forever

inet6 ::1/128 scope host

valid_lft forever preferred_lft forever

2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000

link/ether 00:0c:29:f1:b5:e1 brd ff:ff:ff:ff:ff:ff

inet 172.16.87.140/24 brd 172.16.87.255 scope global dynamic ens33

valid_lft 1500sec preferred_lft 1500sec

inet6 fe80::20c:29ff:fef1:b5e1/64 scope link

valid_lft forever preferred_lft forever

 

%title插图%num

 

 

3. 默认的网卡配置文件

默认情况下,网络使用DHCP

1

2

3

4

5

6

7

8

9

10

ubuntu1804:~$ cat /etc/netplan/50-cloud-init.yaml

配置文件内容如下

 

network:

    ethernets:

        ens33:

            dhcp4: yes

            addresses: []

 

    version: 2

 

 

 

 

4. ubuntu20.04设置静态IP

需要把配置文件修改为以下内容:

1 ubuntu1804:~$ sudo vi /etc/netplan/50-cloud-init.yaml

假设IP地址修改为192.168.1.100,子网掩码24位即255.255.255.0,网关设置为192.168.1.1,DNS1:223.5.5.5,DNS2:223.6.6.6

1

2

3

4

5

6

7

8

9

10

11

network:

    ethernets:

        ens33:

            dhcp4: false

            addresses: [192.168.1.100/24]

            optional: true

            gateway4: 192.168.1.1

            nameservers:

                    addresses: [223.5.5.5,223.6.6.6]

 

    version: 2

 

ubuntu20.04配置静态ip

 

 

5. 应用新配置

1 ubuntu1804:~$ sudo netplan apply

 

使用ip addr检查新地址

1 ubuntu2004:~$ ip addr

 

ubuntu20.04设置静态ip

 

 

6. 测试网络连通性

ubuntu1804:~$ ping 192.168.1.100

ubuntu20.04配置静态ip

ubuntu20.04设置静态ip方法

[IOS]——设置tableView的footerView

效果图

%title插图%num
代码实现
首先需要新建一个类来存放这个底部view(方法多样,这只是我的做法)

%title插图%num
介绍一下单独创造一个xib的做法(因为有些类创建时是不能同时创建xib的,就像我上面那个图的类是继承于UIView,需要自己再创建一个xib

%title插图%num

创建完之后记得要设置这个xib从属于哪个类

%title插图%num
具体代码
.h文件
#import <UIKit/UIKit.h>

@interface upLoaddata : UIView
+(instancetype)setfooter;
@end

.m文件
#import “upLoaddata.h”

@implementation upLoaddata

+(instancetype)setfooter
{
return [[[NSBundle mainBundle] loadNibNamed:@”upLoaddata” owner:nil options:nil] lastObject];
}
//这是设置了view的点击事件
– (IBAction)button:(UIButton *)sender {
//发布通知来监听按钮的点击
//发布通知
NSNotification *post = [NSNotification notificationWithName:@”dowmloadmore” object:nil];
[[NSNotificationCenter defaultCenter]postNotification:post];
}

@end

调用的操作
记得先导入这个类的头文件
self.tableView.tableFooterView = [upLoaddata setfooter];

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速