有赞零售客户端的用户是需要经营线下门店的商家,在商家的经营时间内,如果软件不能保证正常使用会导致经营效率下降,甚至客户流失。因此除了不断优化软件性能,降低崩溃率,还要做好异常情况的降级处理,比如遇到网络故障和服务器故障等情况时,软件要保证核心功能的可用性,此时软件的工作模式被称为离线模式。
在离线模式下,客户端不能和服务端进行正常的数据通信,所有的数据存储和计算逻辑都要客户端独立完成。目前有赞零售客户端在离线模式下支持登录、收银支付、订单管理、会员积分、部分营销活动等核心功能,即使在极端情况下,有赞零售客户端依然保证商户的经营活动正常进行。
在客户端离线解决方案中主要有两个问题:
1、如何准确及时的触发或退出离线模式。
2、离线场景下的各个业务如何进行数据处理和同步。
本文针对第一个问题,以 iOS 零售客户端为例对其技术实现细节展开分析。
首先我们要清楚商家在什么场景下需要切换到离线模式。试想一下这样的场景:
小明是一名门店收银员,此时正是店里的客流高峰期,收银机前等待结账的顾客已经排起了队,不凑巧的是,此时店里的网络信号很不稳定,开单会等待很久才能有结果,有时会提示网络超时,收银效率很低,排队等待的顾客开始有些不耐烦了,小明只能一边安抚顾客,一边尝试着进行开单收银操作。后来网络彻底断掉,软件无法进行开单操作了,排队的顾客放下准备买的东西离开了,小明只能干等着技术人员来修复网络。
如果以上场景,收银软件能够切换成离线模式,脱离对网络的依赖,确保现有的顾客都能顺利进行结账,收银效率和顾客体验都不会受到影响。此外也存在服务器出现故障的情况,导致客户端的数据请求失败,此时也需要切换到离线模式。因此有赞零售客户端设计了两种切换离线的功能:手动切换能力和针对断网和服务故障的自动切换能力。
为此我们设计一个离线模块用来实现离线模式的触发和退出,它位于业务层和网络层的中间。业务层中各业务模块通过依赖注入的方式获取离线的状态变化和原因,而离线场景下的具体功能由各业务模块实现。业务层通过网络层发送业务数据请求,如果返回的数据出现异常,网络模块会将错误分别发送给业务层和离线模块,离线模块分析接口信息和返回的数据,进而启动服务故障识别功能。
离线模块主要提供三个能力:
是否处于离线状态是由三个因子共同决定的:
其中标记离线是用户想要主动启动离线模式时,点击指定按钮触发离线模式。网络故障和服务故障是触发离线状态的另外两个条件。我们通过有限状态机维护离线状态的变化,只有当标记离线、网络故障和服务故障的状态都是 False,才会恢复在线状态,否则一直是离线状态。
标记离线通过本地文件缓存来保存状态,因此软件崩溃并不会影响离线状态的管理。对于网络故障,细分为蜂窝移动网络故障还是 WIFI 故障。服务故障解析是根据报错的接口判断出是哪个业务域出的问题。网络故障和服务故障都是实时监测获取状态,具体的监测手段会在下文展开介绍。
要实现网络状态的实时监测, 我们首先考虑采用苹果官方提供的 Reachability 库,Reachability 的实现依赖于系统提供的 SCNetworkReachability 类,SCNetworkReachability 允许应用程序获取当前系统的网络配置情况,包括网络类型和当前的连接状态。
Reachability 的使用分为同步模式和异步模式。在同步模式下,应用程序主动调用 Reachability 的 currentReachabilityStatus
方法获取当前的网络连接状态。
- (NetworkStatus)currentReachabilityStatus
{
NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef");
NetworkStatus returnValue = NotReachable;
SCNetworkReachabilityFlags flags;
// 同步模式获取网络连接状态
if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
{
returnValue = [self networkStatusForFlags: flags];
}
return returnValue;
}
在异步模式下,应用程序需要调用 Reachability 的 startNotifier
方法开启一个 RunLoop 去监听网络状态的变化,当网络状态发生变化时,Reachability 会执行 ReachabilityCallback
回调函数,在这个回调函数里会发送 kReachabilityChangedNotification
通知,应用程序监听这个通知就可以感知网络状态的变化。源码如下:
- (BOOL)startNotifier
{
BOOL returnValue = NO;
SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
// 构造一个监听网络连接状态的上下文信息,详细说明见下面
// 通过调用 SCNetworkReachabilitySetCallback 函数(并传入 Reachability 对象的 ref,以及根据 SCNetworkReachabilityCallBack 自定义的一个回调函数和上述 context)设置 ref 的网络连接状态变化时对应的回调函数为 ReachabilityCallback
if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context))
{
// 通过调用 SCNetworkReachabilityScheduleWithRunLoop 函数设置 Reachability 对象的 ref 在 Current Runloop 中对应的模式(kCFRunLoopDefaultMode)开始监听网络状态
if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
{
returnValue = YES;
}
}
// 如果监听成功,返回 YES,否则返回 NO
return returnValue;
}
...
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
#pragma unused (target, flags)
NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback");
NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback");
Reachability* noteObject = (__bridge Reachability *)info;
// 因为上述 context 传入的是 self(Reachability 对象),所以这里的 info 为 Reachability 对象类型
// 发送一个全局通知告诉监听者网络连接状态已发生改变,可通过 noteObject 获取状态
[[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject];
}
然而 Reachability 存在一个缺陷,当应用程序可以将一个数据包发出时,SCNetworkReachability 就认为网络是可达的,但是这个数据包是否到达目标地址,SCNetworkReachability 并不清楚,所以它并不能真正检测服务可达性。
于是我们采用一个三方开源库 RealReachability 的方案,这个库是在 Reachability 的基础上进行改进,借助系统的 Socket 库实现的 ping 功能,通过不断对目标地址发送 ping 请求,根据返回的结果判断目标地址是否可达,因而可以更准确的识别当前的网络状态。
在实际应用中,会遇到网络状况时好时坏的情况,RealReachability 的方案会造成业务层频繁地在离线模式和正常模式间来回切换,影响用户体验,有的场景下甚至会导致反复刷新页面,进而引起卡顿。为了解决这个问题,我们在 RealReachability 上再一次进行优化,加入网络防抖功能,它的机制是网络状态的变化不会实时影响离线状态,而是设置一个时间缓冲值 T1,当网络断开时,我们会等待 T1 时间再检查网络是否断开,如果此时网络已经恢复,不会触发离线模式,如果此时网络仍然是断开的,就触发离线模式。在这个缓冲时间内的网络反复变化不会影响离线状态,因此就不会造成频繁的离线切换。
此外 RealReachability ping 检测的时间间隔为 T2,当发现网络连接断开时,为了及时地检测到网络的恢复,我们会以更快的频率进行 ping 操作,网络恢复后检测的时间间隔也会恢复到 T2。
服务故障的检测流程如下图所示:
第一阶段是网络层错误分发,业务层通过网络层发送请求给相应的服务端,当返回的数据出现异常,网络层会启动错误处理的流程,一方面会把错误信息返回给业务层,另一方面把错误信息和请求接口信息一起发给离线模块。
第二阶段是错误信息的本地解析,离线模块维护一份核心业务白名单,这里会配置客户端使用的各个核心业务的接口信息,根据这个白名单可以判断请求的接口所属的业务领域,如果当前报错的接口命中了白名单,离线模块会认为核心业务存在故障风险。
第三阶段是服务端的故障检测,离线模块会请求 QoS 智能决策系统,它根据不同业务后端系统的报警信息来判断是否发生服务故障。如果它判断发生了服务故障,离线模块会收到通知,进而更新本地的离线状态。
第四阶段是故障恢复的检测,离线模块会启动后台线程定期去轮询 QoS 智能决策系统,直到它判断服务故障已经恢复,停止轮询并更新离线状态。
本文介绍了零售客户端离线切换解决方案,接下来的问题是业务层如何进行数据处理和同步,以收银开单流程为例,涉及到账号、商品、营销、会员、支付、订单等多个业务模块,各个模块在离线状态下如何存储和处理数据,保证用户在离线状态下可以完成收银功能,以上这些问题我们会在后续文章里详细介绍,敬请期待。
欢迎关注我们的公众号
Original url: Access
Created at: 2019-09-26 14:19:25
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
java windows火焰图_mob64ca12ec8020的技术博客_51CTO博客 - 在windows下不可行,不知道作者是怎样搞的 监听SpringBoot 服务启动成功事件并打印信息_监听springboot启动完毕-CSDN博客 SpringBoot中就绪探针和存活探针_management.endpoint.health.probes.enabled-CSDN博客 u2u转换板 - 嘉立创EDA开源硬件平台 Spring Boot 项目的轻量级 HTTP 客户端 retrofit 框架,快来试试它!_Java精选-CSDN博客 手把手教你打造一套最牛的知识笔记管理系统! - 知乎 - 想法有重合-理论可参考 安宇雨 闲鱼 机械键盘 客制化 开贴记录 文本 linux 使用find命令查找包含某字符串的文件_beijihukk的博客-CSDN博客_find 查找字符串 ---- mac 也适用 安宇雨 打字音 记录集合 B站 bilibili 自行搭建 开坑 真正的客制化 安宇雨 黑苹果开坑 查找工具包maven pom 引用地 工具网站 Dantelis 介绍的玩轴入坑攻略 --- 关于轴的一些说法 --- 非官方 ---- 心得而已 --- 长期开坑更新 [本人问题][新开坑位]关于自动化测试的工具与平台应用 机械键盘 开团 网站记录 -- 能做一个收集的程序就好了 不过现在没时间 -- 信息大多是在群里发的 - 你要让垃圾佬 都去一个地方看难度也是很大的 精神支柱 [超级前台]sprinbboot maven superdesk-app 记录 [信息有用] [环境准备] [基本完成] [sebp/elk] 给已创建的Docker容器增加新的端口映射 - qq_30599553的博客 - CSDN博客 [正在研究] Elasticsearch, Logstash, Kibana (ELK) Docker image documentation elasticsearch centos 安装记录 及 启动手记 正式服务器 39 elasticsearch 问题合集 不断更新 6.1.1 | 6.5.1 两个版本 博客程序 - 测试 - bug记录 等等问题 laravel的启动过程解析 - lpfuture - 博客园 OAuth2 Server PHP 用 Laravel 搭建带 OAuth2 验证的 RESTful 服务 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区 利用Laravel 搭建oauth2 API接口 附 Unauthenticated 解决办法 - 煮茶的博客 - SegmentFault 思否 使用 OAuth2-Server-php 搭建 OAuth2 Server - 午时的海 - 博客园 基于PHP构建OAuth 2.0 服务端 认证平台 - Endv - 博客园 Laravel 的 Artisan 命令行工具 Laravel 的文件系统和云存储功能集成 浅谈Chromium中的设计模式--终--Observer模式 浅谈Chromium中的设计模式--二--pre/post和Delegate模式 浅谈Chromium中的设计模式--一--Chromium中模块分层和进程模型 DeepMind 4 Hacking Yourself README.md update 20211011
Laravel China 简书 知乎 博客园 CSDN博客 开源中国 Go Further Ryan是菜鸟 | LNMP技术栈笔记 云栖社区-阿里云 Netflix技术博客 Techie Delight Linkedin技术博客 Dropbox技术博客 Facebook技术博客 淘宝中间件团队 美团技术博客 360技术博客 古巷博客 - 一个专注于分享的不正常博客 软件测试知识传播 - 测试窝 有赞技术团队 阮一峰 语雀 静觅丨崔庆才的个人博客 软件测试从业者综合能力提升 - isTester IBM Java 开发 使用开放 Java 生态系统开发现代应用程序 pengdai 一个强大的博主 HTML5资源教程 | 分享HTML5开发资源和开发教程 蘑菇博客 - 专注于技术分享的博客平台 个人博客-leapMie 流星007 CSDN博客 - 舍其小伙伴 稀土掘金 Go 技术论坛 | Golang / Go 语言中国知识社区
最新评论