Netty系列之Netty百万级推送服务设计要点

原文:http://www.infoq.com/cn/articles/netty-million-level-push-service-design-points

1. 背景

1.1. 话题来源

最近很多从事移动互联网和物联网开发的同学给我发邮件或者微博私信我,咨询推送服务相关的问题。问题五花八门,在帮助大家答疑解惑的过程中,我也对问题进行了总结,大概可以归纳为如下几类:

  1. Netty是否可以做推送服务器?
  2. 如果使用Netty开发推送服务,一个服务器最多可以支撑多少个客户端?
  3. 使用Netty开发推送服务遇到的各种技术问题。

由于咨询者众多,关注点也比较集中,我希望通过本文的案例分析和对推送服务设计要点的总结,帮助大家在实际工作中少走弯路。

1.2. 推送服务

移动互联网时代,推送(Push)服务成为App应用不可或缺的重要组成部分,推送服务可以提升用户的活跃度和留存率。我们的手机每天接收到各种各样的广告和提示消息等大多数都是通过推送服务实现的。

随着物联网的发展,大多数的智能家居都支持移动推送服务,未来所有接入物联网的智能设备都将是推送服务的客户端,这就意味着推送服务未来会面临海量的设备和终端接入。

1.3. 推送服务的特点

移动推送服务的主要特点如下:

  1. 使用的网络主要是运营商的无线移动网络,网络质量不稳定,例如在地铁上信号就很差,容易发生网络闪断;
  2. 海量的客户端接入,而且通常使用长连接,无论是客户端还是服务端,资源消耗都非常大;
  3. 由于谷歌的推送框架无法在国内使用,Android的长连接是由每个应用各自维护的,这就意味着每台安卓设备上会存在多个长连接。即便没有消息需要推送,长连接本身的心跳消息量也是非常巨大的,这就会导致流量和耗电量的增加;
  4. 不稳定:消息丢失、重复推送、延迟送达、过期推送时有发生;
  5. 垃圾消息满天飞,缺乏统一的服务治理能力。

为了解决上述弊端,一些企业也给出了自己的解决方案,例如京东云推出的推送服务,可以实现多应用单服务单连接模式,使用AlarmManager定时心跳节省电量和流量。

2. 智能家居领域的一个真实案例

2.1. 问题描述

智能家居MQTT消息服务中间件,保持10万用户在线长连接,2万用户并发做消息请求。程序运行一段时间之后,发现内存泄露,怀疑是Netty的Bug。其它相关信息如下:

  1. MQTT消息服务中间件服务器内存16G,8个核心CPU;
  2. Netty中boss线程池大小为1,worker线程池大小为6,其余线程分配给业务使用。该分配方式后来调整为worker线程池大小为11,问题依旧;
  3. Netty版本为4.0.8.Final。

2.2. 问题定位

首先需要dump内存堆栈,对疑似内存泄露的对象和引用关系进行分析,如下所示:

我们发现Netty的ScheduledFutureTask增加了9076%,达到110W个左右的实例,通过对业务代码的分析发现用户使用IdleStateHandler用于在链路空闲时进行业务逻辑处理,但是空闲时间设置的比较大,为15分钟。

Netty的IdleStateHandler会根据用户的使用场景,启动三类定时任务,分别是:ReaderIdleTimeoutTask、WriterIdleTimeoutTask和AllIdleTimeoutTask,它们都会被加入到NioEventLoop的Task队列中被调度和执行。

由于超时时间过长,10W个长链接链路会创建10W个ScheduledFutureTask对象,每个对象还保存有业务的成员变量,非常消耗内存。用户的持久代设置的比较大,一些定时任务被老化到持久代中,没有被JVM垃圾回收掉,内存一直在增长,用户误认为存在内存泄露。

事实上,我们进一步分析发现,用户的超时时间设置的非常不合理,15分钟的超时达不到设计目标,重新设计之后将超时时间设置为45秒,内存可以正常回收,问题解决。

2.3. 问题总结

如果是100个长连接,即便是长周期的定时任务,也不存在内存泄露问题,在新生代通过minor GC就可以实现内存回收。正是因为十万级的长连接,导致小问题被放大,引出了后续的各种问题。

事实上,如果用户确实有长周期运行的定时任务,该如何处理?对于海量长连接的推送服务,代码处理稍有不慎,就满盘皆输,下面我们针对Netty的架构特点,介绍下如何使用Netty实现百万级客户端的推送服务。

3. Netty海量推送服务设计要点

作为高性能的NIO框架,利用Netty开发高效的推送服务技术上是可行的,但是由于推送服务自身的复杂性,想要开发出稳定、高性能的推送服务并非易事,需要在设计阶段针对推送服务的特点进行合理设计。

3.1. 最大句柄数修改

百万长连接接入,首先需要优化的就是Linux内核参数,其中Linux最大文件句柄数是最重要的调优参数之一,默认单进程打开的最大句柄数是1024,通过ulimit -a可以查看相关参数,示例如下:

[root@lilinfeng ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 256324
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024......后续输出省略

当单个推送服务接收到的链接超过上限后,就会报“too many open files”,所有新的客户端接入将失败。

通过vi /etc/security/limits.conf 添加如下配置参数:修改之后保存,注销当前用户,重新登录,通过ulimit -a 查看修改的状态是否生效。

*  soft  nofile  1000000
*  hard  nofile  1000000

需要指出的是,尽管我们可以将单个进程打开的最大句柄数修改的非常大,但是当句柄数达到一定数量级之后,处理效率将出现明显下降,因此,需要根据服务器的硬件配置和处理能力进行合理设置。如果单个服务器性能不行也可以通过集群的方式实现。

3.2. 当心CLOSE_WAIT

从事移动推送服务开发的同学可能都有体会,移动无线网络可靠性非常差,经常存在客户端重置连接,网络闪断等。

在百万长连接的推送系统中,服务端需要能够正确处理这些网络异常,设计要点如下:

  1. 客户端的重连间隔需要合理设置,防止连接过于频繁导致的连接失败(例如端口还没有被释放);
  2. 客户端重复登陆拒绝机制;
  3. 服务端正确处理I/O异常和解码异常等,防止句柄泄露。

最后特别需要注意的一点就是close_wait 过多问题,由于网络不稳定经常会导致客户端断连,如果服务端没有能够及时关闭socket,就会导致处于close_wait状态的链路过多。close_wait状态的链路并不释放句柄和内存等资源,如果积压过多可能会导致系统句柄耗尽,发生“Too many open files”异常,新的客户端无法接入,涉及创建或者打开句柄的操作都将失败。

下面对close_wait状态进行下简单介绍,被动关闭TCP连接状态迁移图如下所示:

图3-1 被动关闭TCP连接状态迁移图

close_wait是被动关闭连接是形成的,根据TCP状态机,服务器端收到客户端发送的FIN,TCP协议栈会自动发送ACK,链接进入close_wait状态。但如果服务器端不执行socket的close()操作,状态就不能由close_wait迁移到last_ack,则系统中会存在很多close_wait状态的连接。通常来说,一个close_wait会维持至少2个小时的时间(系统默认超时时间的是7200秒,也就是2小时)。如果服务端程序因某个原因导致系统造成一堆close_wait消耗资源,那么通常是等不到释放那一刻,系统就已崩溃。

导致close_wait过多的可能原因如下:

  1. 程序处理Bug,导致接收到对方的fin之后没有及时关闭socket,这可能是Netty的Bug,也可能是业务层Bug,需要具体问题具体分析;
  2. 关闭socket不及时:例如I/O线程被意外阻塞,或者I/O线程执行的用户自定义Task比例过高,导致I/O操作处理不及时,链路不能被及时释放。

下面我们结合Netty的原理,对潜在的故障点进行分析。

设计要点1:不要在Netty的I/O线程上处理业务(心跳发送和检测除外)。Why? 对于Java进程,线程不能无限增长,这就意味着Netty的Reactor线程数必须收敛。Netty的默认值是CPU核数 * 2,通常情况下,I/O密集型应用建议线程数尽量设置大些,但这主要是针对传统同步I/O而言,对于非阻塞I/O,线程数并不建议设置太大,尽管没有最优值,但是I/O线程数经验值是[CPU核数 + 1,CPU核数*2 ]之间。

假如单个服务器支撑100万个长连接,服务器内核数为32,则单个I/O线程处理的链接数L = 100/(32 * 2) = 15625。 假如每5S有一次消息交互(新消息推送、心跳消息和其它管理消息),则平均CAPS = 15625 / 5 = 3125条/秒。这个数值相比于Netty的处理性能而言压力并不大,但是在实际业务处理中,经常会有一些额外的复杂逻辑处理,例如性能统计、记录接口日志等,这些业务操作性能开销也比较大,如果在I/O线程上直接做业务逻辑处理,可能会阻塞I/O线程,影响对其它链路的读写操作,这就会导致被动关闭的链路不能及时关闭,造成close_wait堆积。

设计要点2:在I/O线程上执行自定义Task要当心。Netty的I/O处理线程NioEventLoop支持两种自定义Task的执行:

  1. 普通的Runnable: 通过调用NioEventLoop的execute(Runnable task)方法执行;
  2. 定时任务ScheduledFutureTask:通过调用NioEventLoop的schedule(Runnable command, long delay, TimeUnit unit)系列接口执行。

为什么NioEventLoop要支持用户自定义Runnable和ScheduledFutureTask的执行,并不是本文要讨论的重点,后续会有专题文章进行介绍。本文重点对它们的影响进行分析。

在NioEventLoop中执行Runnable和ScheduledFutureTask,意味着允许用户在NioEventLoop中执行非I/O操作类的业务逻辑,这些业务逻辑通常用消息报文的处理和协议管理相关。它们的执行会抢占NioEventLoop I/O读写的CPU时间,如果用户自定义Task过多,或者单个Task执行周期过长,会导致I/O读写操作被阻塞,这样也间接导致close_wait堆积。

所以,如果用户在代码中使用到了Runnable和ScheduledFutureTask,请合理设置ioRatio的比例,通过NioEventLoop的setIoRatio(int ioRatio)方法可以设置该值,默认值为50,即I/O操作和用户自定义任务的执行时间比为1:1。

我的建议是当服务端处理海量客户端长连接的时候,不要在NioEventLoop中执行自定义Task,或者非心跳类的定时任务。

设计要点3:IdleStateHandler使用要当心。很多用户会使用IdleStateHandler做心跳发送和检测,这种用法值得提倡。相比于自己启定时任务发送心跳,这种方式更高效。但是在实际开发中需要注意的是,在心跳的业务逻辑处理中,无论是正常还是异常场景,处理时延要可控,防止时延不可控导致的NioEventLoop被意外阻塞。例如,心跳超时或者发生I/O异常时,业务调用Email发送接口告警,由于Email服务端处理超时,导致邮件发送客户端被阻塞,级联引起IdleStateHandler的AllIdleTimeoutTask任务被阻塞,最终NioEventLoop多路复用器上其它的链路读写被阻塞。

对于ReadTimeoutHandler和WriteTimeoutHandler,约束同样存在。

3.3. 合理的心跳周期

百万级的推送服务,意味着会存在百万个长连接,每个长连接都需要靠和App之间的心跳来维持链路。合理设置心跳周期是非常重要的工作,推送服务的心跳周期设置需要考虑移动无线网络的特点。

当一台智能手机连上移动网络时,其实并没有真正连接上Internet,运营商分配给手机的IP其实是运营商的内网IP,手机终端要连接上Internet还必须通过运营商的网关进行IP地址的转换,这个网关简称为NAT(NetWork Address Translation),简单来说就是手机终端连接Internet 其实就是移动内网IP,端口,外网IP之间相互映射。

GGSN(GateWay GPRS Support Note)模块就实现了NAT功能,由于大部分的移动无线网络运营商为了减少网关NAT映射表的负荷,如果一个链路有一段时间没有通信时就会删除其对应表,造成链路中断,正是这种刻意缩短空闲连接的释放超时,原本是想节省信道资源的作用,没想到让互联网的应用不得以远高于正常频率发送心跳来维护推送的长连接。以中移动的2.5G网络为例,大约5分钟左右的基带空闲,连接就会被释放。

由于移动无线网络的特点,推送服务的心跳周期并不能设置的太长,否则长连接会被释放,造成频繁的客户端重连,但是也不能设置太短,否则在当前缺乏统一心跳框架的机制下很容易导致信令风暴(例如微信心跳信令风暴问题)。具体的心跳周期并没有统一的标准,180S也许是个不错的选择,微信为300S。

在Netty中,可以通过在ChannelPipeline中增加IdleStateHandler的方式实现心跳检测,在构造函数中指定链路空闲时间,然后实现空闲回调接口,实现心跳的发送和检测,代码如下:

public void initChannel({@link Channel} channel) {channel.pipeline().addLast("idleStateHandler", new {@link   IdleStateHandler}(0, 0, 180));channel.pipeline().addLast("myHandler", new MyHandler());
}
拦截链路空闲事件并处理心跳:public class MyHandler extends {@link ChannelHandlerAdapter} {{@code @Override}public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {if (evt instanceof {@link IdleStateEvent}} {//心跳处理}}}

3.4. 合理设置接收和发送缓冲区容量

对于长链接,每个链路都需要维护自己的消息接收和发送缓冲区,JDK原生的NIO类库使用的是java.nio.ByteBuffer,它实际是一个长度固定的Byte数组,我们都知道数组无法动态扩容,ByteBuffer也有这个限制,相关代码如下:

public abstract class ByteBufferextends Bufferimplements Comparable
{final byte[] hb; // Non-null only for heap buffersfinal int offset;boolean isReadOnly;

容量无法动态扩展会给用户带来一些麻烦,例如由于无法预测每条消息报文的长度,可能需要预分配一个比较大的ByteBuffer,这通常也没有问题。但是在海量推送服务系统中,这会给服务端带来沉重的内存负担。假设单条推送消息最大上限为10K,消息平均大小为5K,为了满足10K消息的处理,ByteBuffer的容量被设置为10K,这样每条链路实际上多消耗了5K内存,如果长链接链路数为100万,每个链路都独立持有ByteBuffer接收缓冲区,则额外损耗的总内存 Total(M) = 1000000 * 5K = 4882M。内存消耗过大,不仅仅增加了硬件成本,而且大内存容易导致长时间的Full GC,对系统稳定性会造成比较大的冲击。

实际上,最灵活的处理方式就是能够动态调整内存,即接收缓冲区可以根据以往接收的消息进行计算,动态调整内存,利用CPU资源来换内存资源,具体的策略如下:

  1. ByteBuffer支持容量的扩展和收缩,可以按需灵活调整,以节约内存;
  2. 接收消息的时候,可以按照指定的算法对之前接收的消息大小进行分析,并预测未来的消息大小,按照预测值灵活调整缓冲区容量,以做到最小的资源损耗满足程序正常功能。

幸运的是,Netty提供的ByteBuf支持容量动态调整,对于接收缓冲区的内存分配器,Netty提供了两种:

  1. FixedRecvByteBufAllocator:固定长度的接收缓冲区分配器,由它分配的ByteBuf长度都是固定大小的,并不会根据实际数据报的大小动态收缩。但是,如果容量不足,支持动态扩展。动态扩展是Netty ByteBuf的一项基本功能,与ByteBuf分配器的实现没有关系;
  2. AdaptiveRecvByteBufAllocator:容量动态调整的接收缓冲区分配器,它会根据之前Channel接收到的数据报大小进行计算,如果连续填充满接收缓冲区的可写空间,则动态扩展容量。如果连续2次接收到的数据报都小于指定值,则收缩当前的容量,以节约内存。

相对于FixedRecvByteBufAllocator,使用AdaptiveRecvByteBufAllocator更为合理,可以在创建客户端或者服务端的时候指定RecvByteBufAllocator,代码如下:

 Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)

如果默认没有设置,则使用AdaptiveRecvByteBufAllocator。

另外值得注意的是,无论是接收缓冲区还是发送缓冲区,缓冲区的大小建议设置为消息的平均大小,不要设置成最大消息的上限,这会导致额外的内存浪费。通过如下方式可以设置接收缓冲区的初始大小:

/*** Creates a new predictor with the specified parameters.* * @param minimum*            the inclusive lower bound of the expected buffer size* @param initial*            the initial buffer size when no feed back was received* @param maximum*            the inclusive upper bound of the expected buffer size*/public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) 

对于消息发送,通常需要用户自己构造ByteBuf并编码,例如通过如下工具类创建消息发送缓冲区:

图3-2 构造指定容量的缓冲区

3.5. 内存池

推送服务器承载了海量的长链接,每个长链接实际就是一个会话。如果每个会话都持有心跳数据、接收缓冲区、指令集等数据结构,而且这些实例随着消息的处理朝生夕灭,这就会给服务器带来沉重的GC压力,同时消耗大量的内存。

最有效的解决策略就是使用内存池,每个NioEventLoop线程处理N个链路,在线程内部,链路的处理时串行的。假如A链路首先被处理,它会创建接收缓冲区等对象,待解码完成之后,构造的POJO对象被封装成Task后投递到后台的线程池中执行,然后接收缓冲区会被释放,每条消息的接收和处理都会重复接收缓冲区的创建和释放。如果使用内存池,则当A链路接收到新的数据报之后,从NioEventLoop的内存池中申请空闲的ByteBuf,解码完成之后,调用release将ByteBuf释放到内存池中,供后续B链路继续使用。

使用内存池优化之后,单个NioEventLoop的ByteBuf申请和GC次数从原来的N = 1000000/64 = 15625 次减少为最少0次(假设每次申请都有可用的内存)。

下面我们以推特使用Netty4的PooledByteBufAllocator进行GC优化作为案例,对内存池的效果进行评估,结果如下:

垃圾生成速度是原来的1/5,而垃圾清理速度快了5倍。使用新的内存池机制,几乎可以把网络带宽压满。

Netty4之前的版本问题如下:每当收到新信息或者用户发送信息到远程端,Netty 3均会创建一个新的堆缓冲区。这意味着,对应每一个新的缓冲区,都会有一个new byte[capacity]。这些缓冲区会导致GC压力,并消耗内存带宽。为了安全起见,新的字节数组分配时会用零填充,这会消耗内存带宽。然而,用零填充的数组很可能会再次用实际的数据填充,这又会消耗同样的内存带宽。如果Java虚拟机(JVM)提供了创建新字节数组而又无需用零填充的方式,那么我们本来就可以将内存带宽消耗减少50%,但是目前没有那样一种方式。

在Netty 4中实现了一个新的ByteBuf内存池,它是一个纯Java版本的 jemalloc (Facebook也在用)。现在,Netty不会再因为用零填充缓冲区而浪费内存带宽了。不过,由于它不依赖于GC,开发人员需要小心内存泄漏。如果忘记在处理程序中释放缓冲区,那么内存使用率会无限地增长。

Netty默认不使用内存池,需要在创建客户端或者服务端的时候进行指定,代码如下:

Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)

使用内存池之后,内存的申请和释放必须成对出现,即retain()和release()要成对出现,否则会导致内存泄露。

值得注意的是,如果使用内存池,完成ByteBuf的解码工作之后必须显式的调用ReferenceCountUtil.release(msg)对接收缓冲区ByteBuf进行内存释放,否则它会被认为仍然在使用中,这样会导致内存泄露。

3.6. 当心“日志隐形杀手”

通常情况下,大家都知道不能在Netty的I/O线程上做执行时间不可控的操作,例如访问数据库、发送Email等。但是有个常用但是非常危险的操作却容易被忽略,那便是记录日志。

通常,在生产环境中,需要实时打印接口日志,其它日志处于ERROR级别,当推送服务发生I/O异常之后,会记录异常日志。如果当前磁盘的WIO比较高,可能会发生写日志文件操作被同步阻塞,阻塞时间无法预测。这就会导致Netty的NioEventLoop线程被阻塞,Socket链路无法被及时关闭、其它的链路也无法进行读写操作等。

以最常用的log4j为例,尽管它支持异步写日志(AsyncAppender),但是当日志队列满之后,它会同步阻塞业务线程,直到日志队列有空闲位置可用,相关代码如下:

 synchronized (this.buffer) {while (true) {int previousSize = this.buffer.size();if (previousSize < this.bufferSize) {this.buffer.add(event);if (previousSize != 0) break;this.buffer.notifyAll(); break;}boolean discard = true;if ((this.blocking) && (!Thread.interrupted()) && (Thread.currentThread() != this.dispatcher)) //判断是业务线程{try{this.buffer.wait();//阻塞业务线程discard = false;}catch (InterruptedException e){Thread.currentThread().interrupt();}}

类似这类BUG具有极强的隐蔽性,往往WIO高的时间持续非常短,或者是偶现的,在测试环境中很难模拟此类故障,问题定位难度非常大。这就要求读者在平时写代码的时候一定要当心,注意那些隐性地雷。

3.7. TCP参数优化

常用的TCP参数,例如TCP层面的接收和发送缓冲区大小设置,在Netty中分别对应ChannelOption的SO_SNDBUF和SO_RCVBUF,需要根据推送消息的大小,合理设置,对于海量长连接,通常32K是个不错的选择。

另外一个比较常用的优化手段就是软中断,如图所示:如果所有的软中断都运行在CPU0相应网卡的硬件中断上,那么始终都是cpu0在处理软中断,而此时其它CPU资源就被浪费了,因为无法并行的执行多个软中断。

图3-3 中断信息

大于等于2.6.35版本的Linux kernel内核,开启RPS,网络通信性能提升20%之上。RPS的基本原理:根据数据包的源地址,目的地址以及目的和源端口,计算出一个hash值,然后根据这个hash值来选择软中断运行的cpu。从上层来看,也就是说将每个连接和cpu绑定,并通过这个hash值,来均衡软中断运行在多个cpu上,从而提升通信性能。

3.8. JVM参数

最重要的参数调整有两个:

  • -Xmx:JVM最大内存需要根据内存模型进行计算并得出相对合理的值;
  • GC相关的参数: 例如新生代和老生代、永久代的比例,GC的策略,新生代各区的比例等,需要根据具体的场景进行设置和测试,并不断的优化,尽量将Full GC的频率降到最低。

4. 作者简介

李林锋,2007年毕业于东北大学,2008年进入华为公司从事高性能通信软件的设计和开发工作,有6年NIO设计和开发经验,精通Netty、Mina等NIO框架。Netty中国社区创始人,《Netty权威指南》作者。

联系方式:新浪微博 Nettying 微信:Nettying

转载于:https://www.cnblogs.com/zhwl/p/4748507.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/358366.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

上架APPStore需要准备哪些材料?

前端时间用敏捷式开发平台开发了一款APP应用&#xff0c;应用名称我就不说啦&#xff0c;这篇文章主要讲述一下上架苹果应用商店APPStore需要准备哪些材料&#xff0c;有相关的困扰欢迎私信我。一、上架流程1. 注册苹果企业账号2. 创建测试证书&#xff0c;发布证书 (使用Mac)3…

浅谈C++设计模式之工厂方法(Factory Method)

为什么要用设计模式&#xff1f;根本原因是为了代码复用&#xff0c;增加可维护性。 面向对象设计坚持的原则&#xff1a;开闭原则&#xff08;Open Closed Principle&#xff0c;OCP&#xff09;、里氏代换原则&#xff08;Liskov Substitution Principle&#xff0c;LSP&…

上架Android应用到腾讯应用包、百度手机助手、华为应用市场、小米应用商店、阿里应用分发平台需要准备哪些材料?...

前端时间用敏捷式开发平台开发了一款APP应用&#xff0c;应用名称我就不说啦&#xff0c;这篇文章主要讲述一下上架各大安卓应用商店&#xff08;腾讯应用宝、阿里应用商店、百度手机助手、华为应用市场、小米应用商店&#xff09;需要准备哪些材料&#xff0c;有相关的困扰欢迎…

【APICloud系列|18】上架Android应用到腾讯应用包、百度手机助手、华为应用市场、小米应用商店、阿里应用分发平台需要准备哪些材料?

前端时间用敏捷式开发平台开发了一款APP应用,应用名称我就不说啦,这篇文章主要讲述一下上架各大安卓应用商店(腾讯应用宝、阿里应用商店、百度手机助手、华为应用市场、小米应用商店)需要准备哪些材料,有相关的困扰欢迎私信我。 一、应用商店选择 推荐平台(六选五) 1.…

Activiti 6中的可插拔持久性

在过去的几年中&#xff0c;我们经常听到&#xff08;来自社区和我们的客户&#xff09;关于如何将Activiti的持久性逻辑从关系数据库交换到其他内容的请求。 当我们宣布Activiti 6时&#xff0c; 我们做出的承诺之一就是我们将实现这一目标。 深入研究Activiti引擎代码的人会…

src漏洞类型总结

本文转载于https://blog.csdn.net/qq_33942040/article/details/111831536 这三类存在漏洞可能更大 越他娘丑的站&#xff0c;越有可能存在洞。 Asp aspx 存在漏洞的可能更大 登陆口没得验证码的可能存在一,未授权访问 常见28种服务器或者中间协议未授权访问 易出现处 ①照片…

【APICloud系列|19】上架APPStore需要准备哪些材料?

前端时间用敏捷式开发平台开发了一款APP应用,应用名称我就不说啦,这篇文章主要讲述一下上架苹果应用商店APPStore需要准备哪些材料,有相关的困扰欢迎私信我。 一、上架流程 1. 注册苹果企业账号 2. 创建测试证书,发布证书 (使用Mac) 3. 使用xcode 上传应用到APP Store (…

Json注入

一、Json简介 JSON 是存储和交换文本信息的语法&#xff0c;是轻量级的文本数据交换格式。类似xml&#xff0c;但JSON 比 XML 更小、更快&#xff0c;更易解析。所以现在接口数据传输都采用json方式进行。JSON 文本的 MIME 类型是 “application/json”。 json语法 数据在名…

国行 lg g3 D858 刷 lg g3 D858hk 教程(备忘)

纯手打&#xff0c;转载请注明出处~ 刷机有风险&#xff0c;出现问题概不负责&#xff01; 本着自娱自乐的宗旨 &#xff0c;分享一下&#xff0c;出了问题不负责&#xff01; 准备的材料&#xff1a; 1&#xff0c;手机一枚&#xff08;废话&#xff09;国行lg g3 d858 2&am…

渗透测试-验证码的爆破与绕过

【验证码机制原理】 客户端发起请求->服务端响应并创建一个新的SessionID同时生成随机验证码&#xff0c;将验证码和SessionID一并返回给客户端->客户端提交验证码连同SessionID给服务端->服务端验证验证码同时销毁当前会话&#xff0c;返回给客户端结果。 【客户端可…

最近对项目代码做的一些更改和感想

最近对项目代码做了一些更改&#xff0c;主要的改动是对整个界面框架的改变&#xff0c;因为以前写代码的时候&#xff0c;为了完成功能&#xff0c;没有从上帝视角来思考软件的界面设计&#xff0c;完全是需要这个功能了&#xff0c;怎么可以做到&#xff1f;好&#xff0c;就…

CSS常见的四种垂直居中的方法

面试中不管是笔试题还是面试题,一般很容易被问到如何实现垂直水平居中,这里总结四种方法作为参考 (1)margin:auto法 css: div{ width: 400px; height: 400px; position: relative; border: 1px solid #465468; } img{ position: absolute; margin: auto; top: 0; left: 0; …

对安卓应用进行加固签名,为上架各大应用市场做准备

上架安卓各大应用市场之前需要对自己的应用进行签名加固&#xff0c;签名是为了证明你是这个应用的开发者&#xff0c;软著也是一种方式&#xff0c;这是不做介绍&#xff0c;加固是为了从安全角度给安装包加一个保护层&#xff0c;防止被恶意破解及攻击。下面简单介绍一下签名…

大前端最强vscode教程(基础篇)

这段时间入职了一家外包公司的前端工程师岗位,前端编辑器用起来,前端一般会用到几个编译器,VScode、sublime text3、webstorm、Hbuid等,这里主要介绍VScode. 初次使用vscode时各种不适应,所有需要用到的功能貌似都需要单独安装插件才能用。这让很多初次使用vscode的朋友有…

edusrc0day挖掘技巧

网瑞达web资源管理系统0day ps&#xff1a; 作为在edusrc的小白&#xff0c;经常看见大师傅们的刷屏&#xff0c;我也很向往能像大师们一样有一次刷屏的机会&#xff0c;于是有了这一次的渗透之旅。 思路&#xff1a;要想刷屏上分&#xff0c;就得找系统来挖掘&#xff0c;对…

个人或者公司如何写版权认证的证明文件?

项目场景&#xff1a; 现在好多平台都在做知识付费&#xff0c;比如百度文库、CSDN、微博、头条、知乎等等&#xff0c;因此我想给大家做一些付费文档&#xff0c;想上传到百度文库的知识店铺 问题描述&#xff1a; 一般人不知道这个版权认证文件怎么写&#xff0c;怎么弄&am…

Bypass WAF实战总结

0X00前言 上个月刷了一波洞&#xff0c;然后这个月初远程支持了一个HW&#xff0c;在文件上传getshell的时候&#xff0c;碰到个各式各样的云waf&#xff0c;通过一个月的实战&#xff0c;总结了几个比较实用的技巧&#xff0c;文章总结的不全&#xff0c;只是基于我实战中用到…

Git教程学习总结(分享给热爱学习的你,团队的协作离不开你呀)

目录 Git 教程 Git 安装配置 Git 工作流程 Git 工作区、暂存区和版本库 Git 创建仓库 Git 基本操作 Git 分支管理 Git 查看提交历史 git log git blame Git 标签 Git 远程仓库(Github) Git Gitee Git 服务器搭建 Git 教程 Git 是一个开源的分布式版本控制系统&…

Linux的shell编写

-eq //等于 -ne //不等于 -gt //大于 -lt //小于 ge //大于等于 le //小于等于实验中遇到的问题&#xff1a; 1.NAMEuser1 中间不能有空格 2.[ xxx ] xxx前面和后面要有空格 任务1&#xff1a;使用case语句编…

为啥这么多程序员大佬学习Cortex-M3

Cortex-M3是一个32位处理器内核。内部的数据路径是32位的&#xff0c;寄存器是32位的&#xff0c;存储器接口也是32位的。CM3采用了哈佛结构&#xff0c;拥有独立的指令总线和数据总线&#xff0c;可以让取指与数据访问并行不悖。这样一来数据访问不再占用指令总线&#xff0c;…