数字农业WMS库存操作重构及思考

简介: 数字农业库存管理系统在2020年时,部门对产地仓生鲜水果生产加工数字化的背景下应运而生。项目一期的数农WMS中的各类库存操作均为单独编写。而伴随着后续的不断迭代,这些库存操作间慢慢积累了大量的共性逻辑:如参数校验、幂等性控制、操作明细构建、同步任务构建、数据库操作CAS重试、库存动账事件发布等等……大量重复或相似的代码不利于后续维护及高效迭代,因此我们决定借鉴并比较模板方法(Template Method)和回调(Callback)的思路进行重构:我们需要为各类库存操作搭建一个统一的框架,对其中固定不变的共性逻辑进行复用,而对会随场景变化的部分提供灵活扩展的能力支持。

image.png

作者 | 在田
来源 | 阿里技术公众号

一 问题背景

数字农业库存管理系统(以下简称数农WMS)是在2020年时,部门对产地仓生鲜水果生产加工数字化的背景下应运而生。项目一期的数农WMS中的各类库存操作(如库存增加、占用、转移等)均为单独编写。而伴随着后续的不断迭代,这些库存操作间慢慢积累了大量的共性逻辑:如参数校验、幂等性控制、操作明细构建、同步任务构建、数据库操作CAS重试、库存动账事件发布等等……大量重复或相似的代码不利于后续维护及高效迭代,因此我们决定借鉴并比较模板方法(Template Method)和回调(Callback)的思路进行重构:我们需要为各类库存操作搭建一个统一的框架,对其中固定不变的共性逻辑进行复用,而对会随场景变化的部分提供灵活扩展的能力支持。

二 模板方法

GoF的《设计模式》一书中对模板方法的定义是:「定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。」 —— 其核心是对算法或业务逻辑骨架的复用,以及其中部分操作的个性化扩展。在正式介绍对数农WMS库存操作的重构工作前,我们先以一个具体案例 —— AbstractQueuedSynchronizer(注1)(以下简称AQS) —— 来了解模板方法设计模式。虽然通过AQS这个相对复杂的例子来介绍模板方法显得有些小题大做,但由于AQS一方面是Java并发包的核心框架,另一方面也是模板方法在JDK中的现实案例,对它的剖析能使我们了解其背后精心的设计思路,同时与下文将介绍的回调的重构方式进行对比,值得我们多花一些时间研究。

《Java并发编程实战》中对AQS的描述是:AQS是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很容易并且高效地构造出来。不仅ReentrantLock和Semaphore是基于AQS构建的,还包括CountDownLatch、ReentrantReadWriteLock等。AQS解决了在实现同步器时涉及的大量细节问题(例如等待线程采用FIFO队列操作顺序)。在基于AQS构建的同步器类中,最基本的操作包括各种形式的「获取操作」和「释放操作」。在不同的同步器中可以定义一些灵活的标准,来判断某个线程是应该通过还是需要等待。比如当使用锁或信号量时,获取操作的含义就很直观,即「获取的是锁或者许可」。AQS负责管理同步器类中的状态(synchronization state),它管理了一个整数状态信息,用于表示任意状态。例如,ReentrantLock用它来表示所有者线程已经重复获取该锁的次数,Semaphore用它来表示剩余的可被获取的许可数量。

对照我们在前文中引用的GoF对模板模式的定义,这里提到的「锁和同步器的框架」即对应「算法的骨架」,「灵活的标准」即对应「重定义该算法的某些特定步骤」;而synchronization state(以下简称「同步状态」)可以说是这两者之间交互的桥梁。Doug Lea对AQS框架的「获取操作」和「释放操作」的算法骨架的基本思路描述如下方伪代码所示。可以看到,在获取和释放操作中,对同步状态的判断和更新,是算法骨架中可被各类同步器灵活扩展的部分;而相应的对操作线程的入队、阻塞、唤起和出队操作,则是算法骨架中被各类同步器所复用的部分。

// 「获取操作」伪代码
While(synchronization state does not allow acquire) { // * 骨架扩展点enqueue current thread if not already queued; // 线程结点入队possibly block current thread; // 阻塞当前线程
}
dequeue current thread if it was queued; // 线程结点出队// 「释放操作」伪代码
update synchronization state // * 骨架扩展点
if (state may permit a blocked thread to acquire) { // * 骨架扩展点unblock one or more queued threads; // 唤起被阻塞的线程
}

下面我们以大家熟悉的ReentrantLock为例具体分析。ReentrantLock实例内部维护了一个AQS的具体实现,用户的lock/unlock请求最终是借助AQS实例的acquire/release方法实现。同时,AQS实例在被构造时有两种选择:非公平性锁实现和公平性锁实现。我们来看下AQS算法骨架部分的代码:

// AQS acquire/release 操作算法骨架代码
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {// 同步状态 synchronization state private volatile int state; // 排他式「获取操作」public final void acquire(int arg) {if (!tryAcquire(arg) && // * 骨架扩展点acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 线程结点入队selfInterrupt();}// 针对已入队线程结点的排他式「获取操作」final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) { // * 骨架扩展点setHead(node); // 线程结点出队(队列head为哑结点)p.next = null;failed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) // 阻塞当前线程interrupted = true;}} finally {if (failed)cancelAcquire(node);}}// 排他式「释放操作」public final boolean release(int arg) {if (tryRelease(arg)) { // * 骨架扩展点Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h); // 唤起被阻塞的线程return true;}return false;}// * 排他式「获取操作」骨架扩展点protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}// * 排他式「释放操作」骨架扩展点protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}}

可以看到,AQS骨架代码为其子类的具体实现封装并屏蔽了复杂的FIFO队列和线程控制逻辑。ReentrantLock中的AQS实例只需实现其中的个性化逻辑部分:tryAcquire和tryRelease方法。比如在tryAcquire方法中,如果发现同步状态为0,会尝试以CAS的方式更新同步状态为1,以获取锁;如果发现同步状态大于0,且当前线程就是持有锁的线程,则会将同步状态加1,表示锁的重入;否则方法返回false,表示获取锁失败。而其中非公平性锁(ReentrantLock.NonfairSync)和公平性锁(ReentrantLock.FairSync)的区别主要在于,公平性锁在尝试获取锁时,会检查是否已有其他线程先于当前线程等待获取锁,如果没有,才会按照前述的方式尝试加锁。下图是ReentrantLock中AQS具体实现的类图(中间有一层额外的ReentrantLock.Sync,主要是为了部分代码的复用而设计)。

image.png

三 回调方式

但是,数农WMS最终使用的重构方式,实际上并不是模板方法模式,而是借鉴了Spring的风格,基于回调(Callback)的方式实现算法骨架中的扩展点。维基百科中对回调的定义是:「一段可执行代码被作为参数传递到另一段代码中,并将在某个时机被这段代码回调(执行)」。回调虽然不属于GoF的书中总结的某种特定的设计模式,但是在观察者(Observer)、策略(Strategy)和访问者(Visitor)这些模式中都可以发现它的身影(注2),可以说是一种常见的编程方式。

如下述RedisTemplate中的管道模式命令执行方法,其中的RedisCallback< ?> action参数即是作为函数式回调接口,接收用户传入的具体实现(自定义Redis命令操作),并在管道模式下进行回调执行(action.doInRedis或session.execute)。同时,管道的打开和关闭(connection.openPipeline/connection.closePipeline)也支持不同的实现方式:如我们熟悉的JedisConnection和Spring Boot 2开始默认使用的LettuceConnection。值得注意的是,虽然在Spring框架中存在各类以Template后缀命名的类(如RedisTemplate、TransactionTemplate、JdbcTemplate等),但是仔细观察可以发现,它们实际上使用的并不是模板方法,而是回调的方式(注3)。

public class RedisTemplate< K, V> extends RedisAccessor implements RedisOperations< K, V>, BeanClassLoaderAware {// 管道模式命令执行,RedisCallback@Overridepublic List< Object> executePipelined(RedisCallback< ?> action, @Nullable RedisSerializer< ?> resultSerializer) {return execute((RedisCallback< List< Object>>) connection -> {connection.openPipeline(); // * 扩展点:开启管道模式boolean pipelinedClosed = false;try {Object result = action.doInRedis(connection); // * 扩展点:回调执行用户自定义操作if (result != null) {throw new InvalidDataAccessApiUsageException("Callback cannot return a non-null value as it gets overwritten by the pipeline");}List< Object> closePipeline = connection.closePipeline(); // * 扩展点:关闭管道模式pipelinedClosed = true;return deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer);} finally {if (!pipelinedClosed) {connection.closePipeline();}}});}// 事务+管道模式命令执行@Overridepublic List< Object> executePipelined(SessionCallback< ?> session, @Nullable RedisSerializer< ?> resultSerializer) {// 具体代码省略}}

类似地,在数农WMS的库存操作重构中,我们定义了ContainerInventoryOperationTemplate「模板类」,作为承载库存操作业务逻辑的框架。下述为其中的库存操作核心代码片段。可以看到,框架统一定义了库存操作流程,并对其中的通用逻辑提供了支持,使各类不同的库存操作得以复用:如构建库存操作明细、持久化操作明细及同步任务、并发冲突重试等;而对于其中随不同库存操作类型变动的逻辑 —— 如操作库存数据、确认前置操作、持久化库存数据等 —— 则通过对ContainerInventoryOperationHandler接口实例的回调实现,它们可以被看作是库存操作框架代码中的扩展点。接口由不同类型的库存操作分别实现,如库存增加、库存占用、库存转移、库存释放等等。如此,如果我们后续需要添加某种新类型的库存操作,只需要实现ContainerInventoryOperationHandler接口中定义的个性化逻辑即可;而如果我们需要对整个库存操作流程进行迭代,也只需要修改ContainerInventoryOperationTemplate中的框架代码,而不是像先前那样,需要同时修改多处代码(这里模板类和库存操作handler的命名均以Container作为前缀,是因为数农WMS以容器托盘作为基本的库存管理单元)。

@Service
public class ContainerInventoryOperationTemplate {private Boolean doOperateInTransaction(OperationContext context) {final Boolean transactionSuccess = transactionTemplate.execute(transactionStatus -> {try {ContainerInventoryOperationHandler handler = context.getHandler(); // 库存操作回调handlerhandler.getAndCheckCurrentInventory(context); // 获取并校验库存数据buildInventoryDetail(context); // 构建库存操作明细handler.operateInventory(context); // * 扩展点:操作库存数据handler.confirmPreOperationIfNecessary(context); // * 扩展点:确认前置操作(如库存占用)    handler.persistInventoryOperation(context); // * 扩展点:持久化库存数据persistInventoryDetailAndSyncTask(context); // 持久化操作明细及同步任务        doSyncOperationIfNecessary(context); // 库存同步操作return Boolean.TRUE;} catch (WhException we) {context.setWhException(we);// 遇到并发冲突异常,需要重试if (Objects.equals(we.getErrorCode(), ErrorCodeEnum.CAS_SAVE_ERROR.getCode())) {context.setCanRetry(true);}}// 省略部分代码transactionStatus.setRollbackOnly();return Boolean.FALSE;});// 省略部分代码return transactionSuccess;}}

四 组合与继承

为什么我们选择了基于回调,而非模板方法的方式,来实现数农WMS的库存操作重构呢?由于回调是基于对象之间的组合关系(composition)实现,而模板方法是基于类之间的继承关系(inheritance)实现,我们结合系统实际情况,并基于「组合优先于继承」的考量,最终选择了使用回调的方式进行代码重构。其原因大致如下:

  1. 继承打破封装性:《Effective Java》在《第18条:复合优先于继承》中提到,继承是实现代码重用的有力手段,但它并非永远是完成这项工作的最佳工具。使用不当会导致软件变得很脆弱。与方法调用不同的是,继承打破了封装性。换句话说,子类依赖于其超类中特定功能的实现细节。超类的实现有可能会随着发行版本的不同而有所变化,如果真的发生了变化,子类可能会遭到破坏,即使它的代码完全没有改变。同时,子类可能继承了定义在父类,但其自身并不需要的方法,有违最小知识原则(Least Knowledge Principle)。子类可能因此错误地覆盖并改变了父类中的方法实现,导致父类功能的封装性被破坏。而如果我们使用对象间组合的方式,则可以避免此类问题的出现。
  2. 接口优于抽象类:仍旧是《Effective Java》,在《第20条:接口优于抽象类》中提到,因为Java只允许单继承,所以用抽象类(模板方法便是基于抽象类实现)作为类型定义受到了限制。而现有的类可以很容易被更新,以实现新的接口。接口是定义混合类型(mixin)的理想选择,允许构造非层次结构的类型框架。与之相反的做法是编写一个臃肿的类层次,对于每一种要被支持的属性组合,都包含一个单独的类。如果整个类型系统中有n个属性,那么就必须支持2n种可能的组合,这种现象被称为「组合爆炸」,即需要定义过多的类。
  3. 组合替代继承:最后,王争的《设计模式之美》中提到,继承主要有三个作用:表示is-a关系,支持多态性,以及代码复用。而这三个作用都可以通过其他手段达成:is-a关系可以通过组合和接口的has-a关系来替代;多态性可以利用接口来实现;代码复用则可以通过组合和委托来实现。因此从理论上讲,通过组合、接口、委托三个技术手段,我们可以替换掉继承,在项目中不用或者少用复杂的继承关系。这种对象间组合的设计方式比类间继承的方式更加符合开闭原则(Open-Closed Principle)(注4)。

结合我们前文中介绍的AbstractQueuedSynchronizer的案例,仔细阅读其源码可以发现,作者通过代码上的精心设计规避了上文提到的「继承打破封装性」的问题。比如,为了不使模板中的骨架逻辑错误地被子类覆盖,相关方法(如acquire和release)均使用了final关键字进行修饰;而对于某些必须由子类实现的扩展点,在AQS抽象类中均会抛出UnsupportedOperationException异常。然而此处不将扩展点定义为抽象方法,而是提供抛出异常的默认实现的原因,个人认为是由于AQS中定义了不同形式的获取和释放操作,而其锁和同步器的具体实现虽然会继承所有这些方法,但依据自身的应用场景往往只关心其中某种版本。比如ReentrantLock中的AQS实现仅关心排他式的版本(即tryAcquire和tryRelease),而Semaphore中的AQS实现仅关心共享式的版本(即tryAcquireShared和tryReleaseShared)。解决这类问题的另一种思路便是对这些不同形式的扩展方法进行拆分,归置到不同的接口,并以回调的方式进行具体功能实现,从而避免暴露不必要的方法。

此外,AQS内部维护的等待线程队列采用的是基于CLH思想实现的FIFO队列。如果我们同时需要一种优先级队列的内部实现(注5),并严格按照模板方法的模式对AQS进行扩展,则最终可能得到的是一个稍显臃肿的类层次,如下图所示:

image.png

AQS作为JDK的底层并发框架,应用场景相对固定,且更加侧重性能方面的考虑,其扩展性较低无可厚非。而对于如Spring的上层框架,在设计时就必须更多地考虑可扩展性的支持。如前文提到的RedisTemplate,借助其维护的RedisConnectionFactory即可获得不同类型的底层Redis连接实现;而对于其不同形式的管道执行方法(管道/事务+管道),用户只需要实现并传入对应的回调接口(RedisCallback/SessionCallback)即可,而不必感知其不需要的方法定义。这两点便是通过组合委托和回调的方式实现的,相较AQS而言显得更加灵活简洁,如下图所示:

image.png

五 再论重构

回到我们的数农WMS库存操作重构,虽然ContainerInventoryOperationTemplate与ContainerInventoryOperationHandler之间的关系非常接近策略模式(Strategy),但由于我们的「模板类」使用Spring的单例模式进行管理,其中并没有单独维护某个指定的库存操作handler,而是通过方法传参的方式触达它们,因此笔者更倾向于使用回调描述两者之间的代码结构。不过读者不必对两者命名的差异过于纠结,因为它们的思路是非常相近的。

随着数农WMS代码重构的推进,以及对更多库存操作业务场景的覆盖,我们不断发现这套重构后的代码框架具备优秀的可扩展性。例如,当我们需要为上游系统提供「库存增加并占用」的库存操作原子能力支持时,我们发现可以使用组合委托的方式复用「库存增加」和「库存占用」的基本库存操作能力,从而简洁高效地完成功能开发。而这点若是单纯基于模板方法的类间继承的方式是无法实现的。具体代码和类图如下:

// 库存增加并占用
@Component
public class IncreaseAndOccupyOperationHandler implements ContainerInventoryOperationHandler {@Resourceprivate IncreaseOperationHandler increaseOperationHandler; // 组合「库存增加」操作handler@Resourceprivate OccupyOperationHandler occupyOperationHandler; // 组合「库存占用」操作handler// 委托「库存占用」操作handler进行前置操作校验,判断是否单据占用已存在@Overridepublic void checkPreOperationIfNecessary(ContainerInventoryOperationTemplate.OperationContext context) {occupyOperationHandler.checkPreOperationIfNecessary(context); }// 委托「库存增加」操作handler进行库存信息校验@Overridepublic void getAndCheckCurrentInventory(ContainerInventoryOperationTemplate.OperationContext context) {increaseOperationHandler.getAndCheckCurrentInventory(context);}// 委托「库存增加」、「库存占用」操作handler进行「库存增加并占用」操作@Overridepublic void operateInventory(ContainerInventoryOperationTemplate.OperationContext context) {increaseOperationHandler.operateInventory(context);occupyOperationHandler.operateInventory(context);}// 其余代码略}

image.png

最后,无论是基于模板方法还是回调的方式对库存操作进行重构,虽然我们可以获得代码复用以及扩展便利的好处,但是「模板类」中骨架逻辑的复杂性,其实是所有库存操作复杂性的总和(个人认为这一点在Spring框架的代码中也有所体现)。比如,库存增加操作在某些场景下需要在开启数据库事务前获取分布式锁,库存占用操作需要判断相关单据是否已经占用了库存等。而模板代码中的骨架逻辑需要为所有这些流程分支提供扩展点,从而支持各种类型的库存操作。此外,修改模板骨架逻辑的代码时也需要小心谨慎,因为一旦模板代码本身出错,可能会影响所有的库存操作。这些都对我们代码编写的质量和可维护性提出更高的要求。

六 结语

代码重构并且总结成文的过程要求不断地学习、思辨和实践,也让自己获益良多。

注解

  1. 对AQS使用了模板方法设计模式的「官方论断」可见于其作者Doug Lea在The java.util.concurrent Synchronizer Framework一文中的论述:Class AbstractQueuedSynchronizer ties together the above functionality and serves as a "template method pattern" base class for synchronizers. Subclasses define only the methods that implement the state inspections and updates that control acquire and release. 此外,文中还包含了对等待线程FIFO队列(CLH变体)、公平性、框架性能等方面的详细讨论。 http://gee.cs.oswego.edu/dl/papers/aqs.pdf
  2. 参考维基百科Callback词条:In object-oriented programming languages without function-valued arguments, such as in Java before its 8 version, callbacks can be simulated by passing an instance of an abstract class or interface, of which the receiver will call one or more methods, while the calling end provides a concrete implementation. Such objects are effectively a bundle of callbacks, plus the data they need to manipulate. They are useful in implementing various design patterns such as Visitor, Observer, and Strategy.
    https://en.wikipedia.org/wiki/Callback_(computer_programming)
  3. Stack Overflow上的某个问答可作为参考:I concur - JdbcTemplate isn't an example of template method design pattern. The design pattern used is callback. Note that the goal and effect of both patterns is very similar, the main difference is that template method uses inheritance while callback uses composition (sort of) - by Jiri Tousekh. java - Why is JdbcTemplate an example of the Template method design pattern - Stack Overflow
  4. 参考维基百科Strategy pattern词条:The strategy pattern uses composition instead of inheritance. In the strategy pattern, behaviors are defined as separate interfaces and specific classes that implement these interfaces. This allows better decoupling between the behavior and the class that uses the behavior. The behavior can be changed without breaking the classes that use it, and the classes can switch between behaviors by changing the specific implementation used without requiring any significant code changes. This is compatible with the open/closed principle (OCP), which proposes that classes should be open for extension but closed for modification. https://en.wikipedia.org/wiki/Strategy_pattern
  5. Doug Lea在The java.util.concurrent Synchronizer Framework中提到:The heart of the framework is maintenance of queues of blocked threads, which are restricted here to FIFO queues. Thus, the framework does not support priority-based synchronization.
    http://gee.cs.oswego.edu/dl/papers/aqs.pdf

参考资料

  • 《设计模式》

设计模式 (豆瓣)

  • The java.util.concurrent Synchronizer Framework

http://gee.cs.oswego.edu/dl/papers/aqs.pdf

  • 《Java并发编程实战》

Java并发编程实战 (豆瓣)

  • 维基百科Callback词条

https://en.wikipedia.org/wiki/Callback_(computer_programming)

  • why is jdbctemplate an example of the template method design pattern

java - Why is JdbcTemplate an example of the Template method design pattern - Stack Overflow

  • 《Effective Java 3》

Effective Java (豆瓣)

  • 《设计模式之美》

设计模式之美_设计模式_代码重构-极客时间

  • 维基百科Strategy pattern词条

https://en.wikipedia.org/wiki/Strategy_pattern

原文链接
本文为阿里云原创内容,未经允许不得转载。 

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

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

相关文章

数字营销行业大数据平台云原生升级实战

简介&#xff1a; 加和科技CTO 王可攀&#xff1a;技术是为业务价值而服务 王可攀 加和科技CTO 本文将基于加和科技大数据平台升级过程中面临的问题和挑战、如何调整数据平台架构以及调整后的变化&#xff0c;为大家介绍数字营销行业大数据平台云原生升级实战经验。主要分为以…

场景模型驱动自动化测试在盒马的探索及实践

简介&#xff1a; 盒马业务有如下几个特点&#xff1a;线上线下一体化、仓储配送一体化、超市餐饮一体化、经营作业一体化、多业态与平台化。在以上的种种原因&#xff0c;生鲜及物流体验是盒马的特点&#xff0c;但仓储配送一体化作业中&#xff0c;如何能更高效的提升测试效率…

基于 KubeVela 的 GitOps 交付

简介&#xff1a; KubeVela 是一个简单、易用、且高可扩展的云原生应用管理和交付平台&#xff0c;KubeVela 背后的 OAM 模型天然解决了应用构建过程中对复杂资源的组合、编排等管理问题&#xff0c;同时也将后期的运维策略模型化&#xff0c;这意味着 KubeVela 可以结合 GitOp…

BCS2022大会将提前至5月 网络安全产业空间扩容将成热门话题

年度网络安全的盛会即将开启。 2022年3月30日&#xff0c;2022年北京网络安全大会&#xff08;BCS2022&#xff09;新闻发布会在北京奇安信安全中心召开&#xff0c;宣布2022年北京网络安全大会“提档”至5月24日至26日&#xff0c;并与北辰集团国家会议中心达成战略合作&#…

基于 Istio 的全链路灰度方案探索和实践

简介&#xff1a; 本文介绍的基于“流量打标”和“按标路由” 能力是一个通用方案&#xff0c;基于此可以较好地解决测试环境治理、线上全链路灰度发布等相关问题&#xff0c;基于服务网格技术做到与开发语言无关。同时&#xff0c;该方案适应于不同的7层协议&#xff0c;当前已…

图像检索在高德地图POI数据生产中的应用

简介&#xff1a; 高德通过自有海量的图像源&#xff0c;来保证现实世界的每一个新增的POI及时制作成数据。在较短时间间隔内&#xff08;小于月度&#xff09;&#xff0c;同一个地方的POI 的变化量是很低的。 作者 | 灵笼、怀迩 来源 | 阿里技术公众号 一 背景 POI 是 Poin…

Redis HyperLogLog 是什么?这些场景使用它~

作者 | 就是码哥呀来源 | 码哥字节在移动互联网的业务场景中&#xff0c;数据量很大&#xff0c;我们需要保存这样的信息&#xff1a;一个 key 关联了一个数据集合&#xff0c;同时对这个数据集合做统计。统计一个 APP 的日活、月活数&#xff1b;统计一个页面的每天被多少个不…

matlab三角形分割,MATLAB 2014b及以上版本中带有画家渲染器的三角形拆分补丁

在解决实际问题之前,这是一个值得怀疑的解决方法&#xff1a;对角线只是三角形之间的空白区域,所以我们看到的是补丁后面的白色空间.愚蠢的想法&#xff1a;让我们用匹配的颜色填充该空间而不是白色.为此,我们将复制所有对象,并通过一个tiiiiny位来抵消新对象.码&#xff1a;hi…

网易云音乐音视频算法的 Serverless 探索之路

简介&#xff1a; 网易云音乐最初的音视频技术大多都应用在曲库的数据处理上&#xff0c;基于音视频算法服务化的经验&#xff0c;云音乐曲库团队与音视频算法团队一起协作&#xff0c;一起共建了网易云音乐音视频算法处理平台&#xff0c;为整个云音乐提供统一的音视频算法处理…

小小的 likely 背后却大有玄机!

作者 | 张彦飞allen来源 | 开发内功修炼今天我给大家分享一个内核中常用的提升性能的小技巧。理解了它对你一定大有好处。在内核中很多地方都充斥着 likely、unlikely 这一对儿函数的使用。随便揪两处&#xff0c;比如在 TCP 连接建立的过程中的这两个函数。//file: net/ipv4/t…

阿里云马涛:因云进化的基础软件

简介&#xff1a; 基础软件的云原生化。 编者按&#xff1a;2021 年10 月20 日&#xff0c;在2021 云栖大会云计算产业升级峰会上&#xff0c;阿里云“因云而生”云原生心智大图正式发布&#xff0c;包含弹性计算、云网络、基础产品、基础设施、操作系统、云安全、开放平台等7个…

阿里云ECI如何6秒扩容3000容器实例?

简介&#xff1a; 2021年云栖大会现场&#xff0c;阿里云工程师演示了在6秒时间内成功启动3000个ECI&#xff0c;并全部进入到Running状态。本文将为你揭开阿里云ECI是如何做到极速扩容的。 引言 根据最新CNCF报告&#xff0c;有超过90%的用户在生产环境使用容器&#xff0c;…

巧用友盟+U-APM 实现移动端性能优化—启动速度

简介&#xff1a; 移动端性能对用户体验、留存有着至关重要的影响&#xff0c;作为开发者是不是被这样吐槽过&#xff0c;“这个 APP 怎么这么大&#xff1f;”、“怎么一直在 APP 封面图转悠&#xff0c;点不进去”、“进入详情效果有些卡”、“用 4G 使用你们的 APP&#xff…

第25版 OpenStack Yoga 已发布

OpenStack社区今日正式发布第25版-Yoga&#xff0c;该版本通过支持先进的硬件技术如SmartNIC DPUs&#xff0c;优化与云原生软件如Kubernetes、Prometheus等的集成以及减少技术债等方式来保持OpenStack内核的稳定性与可靠性。 OpenStack作为开源基础设施即服务&#xff08;Iaa…

项目实战总结以及接入U-APM

简介&#xff1a; 导致 App 性能低下的原因有很多&#xff0c;除去设备硬件和软件的外部因素&#xff0c;其中大部分是开发者错误地使用线、系统函数、编程范式、数据结构等导致的。即便是较有经验的程序员&#xff0c;也很难在开发时就能避免所有导致性能低下的“坑”&#xf…

oracle redo 200mb,Oracle的redo log在各场景下的恢复

Oracle的redo log非常重要&#xff0c;redo log损坏将导致数据库开法开启或数据丢失&#xff0c;针对redo log在各种场景下如何打开或恢复数据库&#xff0c;特别模拟测试说明&#xff1a;各场景包括如下(共6个场景):场景一.非归档下inactive状态的redo 恢复场景二.非归档下act…

站在原地就是退步——除了死磕通道,云通讯服务商还该做些什么?

受访嘉宾&#xff1a;吴佳钊&#xff0c;杭州云片网络科技有限公司联合创始人、CTO 当前&#xff0c;全球通信云已经步入2.0时代&#xff0c;最大的变化在于通信形式的变革&#xff1a;传统短信语音的通信形式将逐步向包括即时通讯IM实时音视频RTC的互联网通信转变。尤其在5G时…

Cube 技术解读 | 详解「支付宝」全新的卡片技术栈

简介&#xff1a; 魔方卡片&#xff08;Cube&#xff09;&#xff0c;让 App 首页实现敏捷更新。 CodeHub#7 正式落幕&#xff0c;来自蚂蚁集团的技术专家「京君」与掘金社区的开发者们分享了「支付宝」全新的卡片技术栈——魔方卡片&#xff08;Cube&#xff09;。 京君围绕 C…

庖丁解InnoDB之REDO LOG

简介&#xff1a; 数据库故障恢复机制的前世今生一文中提到&#xff0c;今生磁盘数据库为了在保证数据库的原子性(A, Atomic) 和持久性(D, Durability)的同时&#xff0c;还能以灵活的刷盘策略来充分利用磁盘顺序写的性能&#xff0c;会记录REDO和UNDO日志&#xff0c;即ARIES方…

Web 自动化神器,批量下载美图,可直接导入使用

‍‍作者 | 小碗汤来源 | 进击云原生今天为大家分享一款前端自动化操作神器: Automa。Automa介绍它是一款 Chrome 插件&#xff0c;即使你不会写代码&#xff0c;也能按照自己的需求&#xff0c;完成一系列自动化操作。利用它&#xff0c;你可以将一些重复性的任务实现自动化、…