系统性能提升利刃 | 缓存技术使用的实践与思考

导读

按照现在流行的互联网分层架构模型,最简单的架构当属Web响应层+DB存储层的架构。从最开始的单机混合部署Web和DB,到后来将二者拆分到不同物理机以避免共享机器硬件带来的性能瓶颈,再随着流量的增长,Web应用变为集群部署模式,而DB则衍生出主从机来保证高可用,同时便于实现读写分离。这一连串系统架构的升级,本质上是为了追求更高的性能,达到更低的延时。

高德作为一款国民级别的导航软件,导航路线的数据质量是由数据中心统一管理的。为了保证数据的鲜度,数据中心需要对不断变化的现实道路数据进行收集,将这些变化的信息保存到数据库中,从而保证导航数据的鲜度;另一方面数据中心内部多部门协调生产数据的时候,会产生海量请求查询最新生产的数据,这就要求数据的管理者要控制数据库连接数,降低请求的响应耗时,同时也需要保证返回数据的实时性。

在平衡数据鲜度和性能之间,高德数据中心针对不同的业务场景使用了不同的策略,达到了数据变更和缓存同步低延迟的目标,同时保障了系统的稳定性。

本文将提及的缓存技术则是提升性能的另一把利刃。然而任何技术都是有可为有可不为,没有最好的技术只有最适合的技术,因此在使用缓存之前,我们也需要了解下引入缓存模块所带来的好处和坏处。

缘起:为何使用缓存

在应用对外提供服务时,其稳定性受到诸多因素影响,其中比较重要的有CPU、内存、IO(磁盘IO、网络IO)等,这些硬件资源十分宝贵,因此对于那些需要经过复杂计算才能得到结果的,或者需要频繁读取磁盘数据的,最好将结果缓存起来,避免资源的重复消耗。

CPU瓶颈

如果项目中有很多正则表达式计算,或者某个计算结果是多次中间结果合并后才得出的,且CPU的使用率一直居高不下,那么就可以考虑是否应该将这些结果缓存起来,根据特定Key直接获取Value结果,减少中间链路的传递过程,减少CPU的使用率。

IO瓶颈

众所周知,从磁盘获取数据受到磁盘转速、寻道速度、磁盘缓冲区大小等诸多因素影响,这些因素决定了磁盘的IOPS,同时我们也知道对于数据的读写来说,CPU的缓存读写速度> 内存的读写速度 >磁盘的读写速度。虽然磁盘内部也配备了缓存以匹配内存的读写速度,但其容量毕竟是有限的,那么当磁盘的IOPS无法进一步提升的时候,便会想到将数据缓存到内存中,从而降低磁盘的访问压力。这一策略常被应用于缓解DB数据库的数据访问压力。

选择本地缓存和分布式缓存的考量点

既然可以使用缓存来提升系统吞吐能力,那么紧接着遇到的问题就是选择本地缓存,还是分布式缓存?什么时候需要使用多级缓存呢?接下来,让我们聊一聊在使用缓存优化项目的过程中,本地缓存和分布式缓存的应用场景和优缺点。

本地缓存的优缺点和应用场景

统一进程带来了以下优势:

  • 由于本地缓存和应用在同一个进程中,因而其稳定性很高,达到了和应用同生共死的境界;
  • 由于在同一进程中,避免了网络数据传输带来的消耗,所有缓存数据直接从进程所在的内存区域获取即可。

强耦合性也会导致以下这些劣势:

  • 本地缓存和应用共享一片JVM内存,争抢内存资源,无法水平扩展,且可能造成频繁的GC,影响线上应用的稳定性。
  • 由于没有持久化机制,在项目重启后缓存内数据就会丢失,对于高频访问数据,需要对数据进行预热操作。
  • 多份进程内缓存存储着同样的数据内容,造成内存使用浪费。
  • 同样的数据存储在不同的本地机器,数据变化后,很难保证数据的一致性。

结合以上优缺点,我们就会想到,如果有一种数据需要频繁访问,但一旦创建后就轻易不会改变,而且初始创建时就能预估占用的内存空间,那么这种类型的数据无疑是最适合用本地缓存存储了。

既然有了上述的应用场景,我们反观技术开发中的诉求,发现其实很多优秀的框架已经在这样使用了,比如缓存类class的反射信息,包括field、method等。因为class的数量是有限的,且内容不会轻易改变,在使用时无需再使用反射机制,而只需要从本地缓存读取数据即可。

分布式缓存的优缺点和应用场景

优势:

  • 数据集中存储,消除冗余数据,解决整体内存的占用率,易于维护集群建缓存数据的一致性。
  • 缓存中间件可以对缓存进行统一管理,便于水平扩容。

劣势:

  • 依赖分布式缓存中间件稳定性,一旦挂了,容易造成缓存雪崩;
  • 由于是跨机器获取缓存数据,因此会造成数据传输的网络消耗,以及一些序列化/反序列化的时间开销。

对于上述缺点中,网络耗时等开销是难免的,而且这些操作耗费的时间在可接受范围内,而对于中间件的稳定性则可以通过服务降级、限流或者多级缓存思路来保证。我们主要看中的是它的优点,既然分布式缓存天然能保证缓存一致性,那么我们倾向于将需要频繁访问却又经常变化的数据存放于此。

选择缓存框架的衡量标准

在了解了何时使用缓存以及缓存的优缺点后,我们就准备大刀阔斧开始升级系统了,可紧接着的问题也随之出现,对于本地缓存和分布式缓存,到底应该使用什么框架才是最适用的呢?

现在的技术百花齐放,不同的技术解决的问题侧重点也不同,对于本地缓存来说,如果无资源竞争的代码逻辑,可以使用HashMap,而对于有资源竞争的多线程程序来说,则可以使用ConcurrentHashMap。但以上二者有个通病就是缓存占用只增不减,没有缓存过期机制、也没有缓存淘汰机制。

那么本地缓存是否有更高性能的框架呢?而对于分布式缓存,领域内常用的Redis和Memcache又应该怎样取舍呢?本小节期望通过横向对比的方式,分别给出一个比较通用的缓存框架方案,当然如果有个性化需求的,也可以根据不同缓存框架的特性来取舍。

不同本地缓存框架的横向对比,如下表所示:

 

总结:如果不需要淘汰算法则选择ConcurrentHashMap,如果需要淘汰算法和一些丰富的API,推荐选择Caffeine。

不同分布式缓存框架的横向对比,如下表所示:

对于存储容量而言,Memcache采用预先分配不同固定大小存储单元的方式,内存空间使用并不紧凑。如果存储Value对象大小最大为1MB,那么当一个对象有1000KB,那么会存储到大小最匹配1MB的单元中,因此会浪费24KB的内存;而Redis是使用之前才去申请空间,内存使用紧凑,但频繁对内存的扩容和收缩,可能造成内存碎片。

总结:由于Redis具有丰富的数据结构能满足不同的业务场景需求,同时Redis支持持久化,能有效地解决缓存中间件重启后的数据预加载问题,因此大多数应用场景中还是推荐使用Redis。

缓存框架使用过程的知识点

不论是本地缓存还是分布式缓存,在使用缓存提升性能的时候,必然会考虑缓存命中率的高低,考虑缓存数据的更新和删除策略,考虑数据一致性如何维护,本小节主要针对以上的问题来分析不同实现方案的优缺点。

缓存命中率

缓存命中率不仅是系统性能的一个侧面指标,也是优化缓存使用方案的一个重要依据。缓存命中率=请求命中数/请求总数。接下来的若干缓存使用策略所围绕的核心考量点就是在保证系统稳定性的同时,旨在提升缓存命中率。

缓存更新策略

主动请求DB数据,更新缓存

通过在集群中的每台机器都部署一套定时任务,每隔一段时间就主动向数据库DB请求最新数据,然后更新缓存。这样做的好处是可以避免缓存击穿的风险,在缓存失效前就主动请求加载DB数据,完成缓存数据更新的无缝连接。

但这样做也增加了机器的CPU和内存的占用率,因为即使有若干Key的缓存始终不被访问,可还是会被主动加载加载到内存中。也就是说,提高了业务抗风险能力,但对CPU和内存资源并不友好。

详情可参见下图,分布式缓存中存储着DB中的数据,每隔4.9s就会有定时任务执行去更新缓存,而缓存数据失效时间为5s,从而保证缓存中的数据永远存在,避免缓存击穿的风险。但对于Web请求来说,只会访问k1的缓存数据,也即对于k2和k3数据来说,是无效缓存。

被动请求DB数据,更新缓存

当有请求到达且发现缓存没数据时,就向DB请求最新数据并更新缓存。这种方案完全可以看做是方案一的互斥方案,它解决的是机器CPU和内存浪费的问题,内存中存储的数据始终是有用的,但却无法避免缓存失效的瞬间又突然流量峰值带来的缓存击穿问题,在业务上会有一定的风险。

详情见下图,缓存不会主动加载数据,而是根据Web请求懒加载数据。对于请求k1数据来说,发现缓存没有对应数据,到DB查询,然后放入Cache,这是常规流程;但如果有突发流量,大量请求同时访问k2数据,但Cache中没有数据时,请求就会同时落到DB上,可能压垮数据库。

缓存过期策略

依赖时间的过期策略

  • 定时删除

对于需要删除的每个Key都配备一个定时器,元素超时时间一到就删除元素,释放元素占用的内存,同时释放定时器自身资源。其优点是元素的删除很及时,但缺点也很明显,比如为每个Key配备定时器肯定会消耗CPU和内存资源,严重影响性能。这种策略只适合在小数据量且对过期时间又严格要求的场景能使用,一般生产环境都不会使用。

惰性删除

元素过期后并不会立马删除,而是等到该元素的下一次操作(如:访问、更新等)才会判断是否过期,执行过期删除操作。这样的好处是节约CPU资源,因为只有当元素真的过期了,才会将其删除,而不用单独管理元素的生命周期。但其对内存不友好,因为如果若干已经过期的元素一直不被访问的话,那就会一直占用内存,造成内存泄漏。

定期删除

以上两种元素删除策略各有优缺点,无非是对CPU友好,还是对内存友好。为了结合两者的优点,一方面减少了元素定时器的配备,只使用一个定时器来统一扫描过期元素;另一方面加速了判断元素过期的时间间隔,不是被动等待检测过期,而是间隔一段时间就主动执行元素过期检测任务。正是由于以上的改进点,此方案是元素过期检测的惯常手段。

我们假设一个场景,为了保护用户隐私,通常在用户电话和商家电话之间,会使用一个虚拟电话作为沟通的桥梁。业务使用中,往往同一个虚拟号码在一定时间内是可以对相同的用户和商家建立连接的,而当超出这个时间后,这个虚拟号码就不再维护映射关系了。

虚拟电话号码的资源是有限的,自然会想到创建一个虚拟号码资源池,管理虚拟号码的创建和释放。比如规定一个虚拟号码维持的关系每次能使用15分钟,那么过期后要释放虚拟号码,我们有什么方案呢?

A. 方案一:全量数据扫描,依次遍历判断过期时间

对于DB中存储的以上内容,每天记录都存储着虚拟号码的创建时间,以及经过expire_seconds就会删除此记录。那么需要配备一个定时任务扫描表中的所有记录,再判断current_time - create_time >expire_seconds,才会删除记录。

如果数据量很大的情况,就会导致数据删除延迟时间很长,这并不是可取的方案。那是否有方案能直接获取到需要过期的vr_phone,然后批量过期来解决上述痛点呢?来看看方案二吧。

B. 方案二:存储绝对过期时间+BTree索引,批量获取过期的vr_phone列表

将相对过期时间expire_seconds改为记录过期的时间戳expire_timestamp,同时将其添加BTree索引提高检索效率。仍然使用一个定时器,在获取待删除vr_phone列表时只需要select vr_phone from table where now()>=expire_timestamp即可。

对于空间复杂度增加了一个BTree数据结构,而基于BTree来考虑时间复杂度的话,对于元素的新增、修改、删除、查询的平均时间复杂度都是O(logN)。

此方案已经能满足业务使用需求了,那是否还有性能更好的方案呢?

d) 单层定时轮算法

我们继续讨论上面的案例,寻找更优的解题思路。下表是DB存储元素:

此时DB中不再存储和过期时间相关的数据,而专注于业务数据本身。对于过期的功能我们交给单层定时轮来解决。其本质是一个环形数组,数组每一格代表1秒,每次新加入的元素放在游标的上一格,而游标所指向的位置就是需要过期的vr_phone列表。

执行过程:

1、初始化:启动一个timer,每隔1s,在上述环形队列中移动一格,1->2->3...->29->750->1...有一个指针来标识有待过期的slot数据

2、新增数据:当有一个新的vr_phone创建时,存储到指针的上一个slot中。对于有slot冲突的场景,可以利用链表解决冲突,也可以利用数组解决冲突。链表和数组的考量标准还是依赖于单个slot的数据长度,如果数据过长,那么存储的数组会很长,则需要很大的内存空间才能满足,无法利用内存碎片的空间。

3、过期数据:指针每隔1秒移动一个slot,那么指针指向的slot就是需要过期的数据,因为新增的数据在环形slot转完一圈后,才会被指向到。

这样一种算法结构,将时间和空间巧妙地结合在了一起。新增元素的时间复杂度为O(1),直接插入待批量过期的slot的上一个位置即可;获取待删除元素列表时间复杂度也是O(1),就是待批量过期的slot位置。流行框架Netty、Kafka都有定时轮的影子。

当然,单层定时轮只适用于固定时间过期的场景,如果需要管理不同过期时间的元素,那么可以参考"多层定时轮算法",其实就是模拟现实世界的时针、分针、秒针的概念,建立多个单层定时轮,采用进位和退位的思想来管理元素的过期时间。

以上各种元素过期策略各有优缺点,可以根据业务的诉求来取舍。比如Memcache只是用了惰性删除,而Redis则同时使用了惰性删除和定期删除以结合二者的优点。

依赖空间的过期策略

此处只探讨最经典的三种策略FIFO、LRU、LFU的原理及实现方案,对于其它改进算法,感兴趣的同学可以自行查找。

a) FIFO:先进先出,当空间不足时,先进入的元素将会被移除。此方案并没有考虑元素的使用特性,可能最近频繁访问的一个元素会被移除,从而降低了缓存命中率。实现:基于LinkedHashMap的钩子函数实现FIFOMap。

// 链表头部是最近最少被访问的元素,需要被删除
public class FIFOMap<K, V> extends LinkedHashMap<K, V> {private int maxSize;//LinkedHashMap每次插入数据,默认都是链表tail;当accessOrder=false,元素被访问不会移动位置public FIFOMap(int maxSize) {super(maxSize, 0.75f, false);this.maxSize = maxSize;}//每次put和putAll新增元素的时候都会触发判断;当下面函数=true时,就删除链表head元素@Overrideprotected boolean removeEldestEntry(Map.Entry<K, V> eldest) {return size() > maxSize;}
}

b) LRU:最近最少使用算法,当下多次被访问的数据在以后被访问的概率会很大,因此保留最近访问的元素,提高命中率。可以应对流量突发峰值,因为存储的池子大小是固定的,因此内存占用不可能过多。但也有缺点:如果一个元素访问存在间歇规律,1分钟前访问1万次,后面30秒无访问,然后再访问一万次,这样就会导致被删除,降低了命中率。实现:基于LinkedHashMap的钩子函数实现LRUHashMap。

// 链表头部是最近最少被访问的元素,需要被删除
public class LRUMap<K, V> extends LinkedHashMap<K, V> {private int maxSize;//LinkedHashMap每次插入数据,默认都是链表tail;当accessOrder=true时,被访问的元素也会放到链表tailpublic LRUMap(int maxSize) {super(maxSize, 0.75f, true);this.maxSize = maxSize;}//每次put和putAll新增元素的时候都会触发判断;当下面函数=true时,就删除链表head元素@Overrideprotected boolean removeEldestEntry(Map.Entry<K, V> eldest) {return size() >= maxSize;}
}

c) LFU:最近最少频率使用,根据数据的历史访问频率来淘汰数据,其核心思想是"如果数据过去被访问多次,那么将来被访问的频率也更高"。这种算法针对LRU的缺点进行了优化,记录了元素访问的总次数,选出访问次数最小的元素进行删除。原本的LFU算法要求记录所有元素的访问次数,但考虑到内存成本,改进后的LFU是在有限队列中进行淘汰。

实现:Redis的优先级队列Zset实现,Zset存储元素的数量固定,Value是访问次数,超过size就删除访问次数最小的即可。但这种删除策略对于有时效性的数据却并不合适,对于排行榜类的数据,如果某个历史剧点击量特别高,那么就始终不会被淘汰,新剧就没有展示的机会。改进方案,可以将Value存储为入库时间戳+访问次数的值,这样随着时间流逝,历史老剧就可能被淘汰。

其他影响命中率的因素

缓存穿透

对于数据库中本就不存在的值,缓存中肯定也不会存在,此类数据的查询一定会落到DB上。为了减少DB访问压力,我们期望将这些数据都可以在缓存中cover住,以下是两种解法。

  • 解法一:缓存null值:

该方法对于元素是否存在于DB有精准的判断,可如果存在海量null值的数据,则会对内存过度占用。

  • 布隆过滤:

使用场景是海量数据,且不要求精准判断和过滤数据。其思路是借助Hash和bit位思想,将Key值映射成若干Hash值存储到bit数组中。

B. 新增元素时,将元素的Key根据预设的若干Hash函数解析成若干整数,然后定位到bit位数组中,将对应的bit位都改为1。

C. 判断元素是否存在,也是将元素的Key根据Hash函数解析成整数,查询若干bit位的值。只要有一个bit位是0,那么这个Key肯定是新元素,不存在;如果所有bit位全都是1,那么这个Key很大概率是已经存在的元素,但也有极小的概率是Key3经过若干Hash函数定位到bit数组后都是Hash冲突的,可能造成误判。

缓存击穿

缓存中原本一批数据有值,但恰好都同时过期了,此时有大量请求过来就都会落到DB上。避免这种风险也有两种解法。

  • 解法一:随机缓存失效时间:

对缓存中不同的Key设置不同的缓存失效时间,避免缓存同时失效带来大量请求都落到DB上的情况。

  • 解法二:主动加载更新缓存策略,替代缓存过期删除策略:

在缓存失效之前就主动到DB中加载最新的数据放到缓存中,从而避免大量请求落到DB的情况。

缓存雪崩

大量缓存同时过期,或者缓存中间件不可用,导致大量请求落到DB,系统停止响应。解法是对缓存设置随机失效时间,同时增加缓存中间件健康度监测。

保证业务数据一致性的策略

在分析了影响缓存命中率的若干策略和方案后,我们结合实际开发诉求,来分析下缓存是如何降低DB的访问压力,以及DB和缓存中业务数据的一致性如何保证?

维护数据一致性常用的方案有两种:先操作DB,再操作Cache;先操作Cache,再操作DB。而以上两步操作都期望是全部成功,才能保证操作是原子性的。如果不依赖事务,那么对数据怎样操作才能保证即使流程异常中断,对业务影响也是最小呢?

对于读取操作

因为只是读取,不涉及数据修改,因此先读缓存,Cache miss后,读DB数据,然后set cache就足够通用。

对于写入操作

先操作DB,再操作(delete/update)缓存

当DB数据操作成功,但缓存数据(不论是delete还是update)操作失败,就会导致在未来一段时间内,缓存中的数据都是历史旧数据,并没有保证操作的原子性,无法接受。

先操作(delete/update)缓存,再操作DB

  • 第一种方案:当update缓存成功,但操作DB失败,虽然缓存中的数据是最新的了,但这个最新的数据最终并没有更新到DB中,当缓存失效后,还是会从DB中读取到旧的数据,这样就会导致上下游依赖的数据出现错误,无法接受。
  • 第二种方案:先delete缓存,再操作DB数据,我们详细讨论下这种方案:
  1. 如果delete就失败了,整体操作失败,相当于事务回滚;
  2. 如果delete成功,但DB操作失败,此时会引起一次cache miss,紧接着还是会从DB加载旧数据,相当于整体无操作,事务回滚,代价只是一次cache miss;
  3. 如果delete成功,且DB操作成功,那么整体成功。

结论:先delete缓存,再操作DB,能尽可能达到两步处理的原子性效果,即使流程中断对业务影响也是最小的。

小结

对于缓存的使用没有绝对的黄金标准,都是根据业务的使用场景来决定什么缓存框架或者缓存策略是最适合的。但对于通用的业务场景来说,以下的缓存框架选择方法应该可以满足大部分场景。

  • 对于本地缓存,如果缓存的数量是可估计的,且不会变化的,那么可使用JDK自带的HashMap或ConcurrentHashMap来存储。
  • 对于有按时间过期、自动刷新需求的本地缓存可以使用Caffeine。
  • 对于分布式缓存且要求有丰富数据结构的,推荐使用Redis。


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

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

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

相关文章

Android 控件 - Button

1、Button 1.1、新建 mybutton 模块 在原有项目基础上新建 mybutton项目 1.2、Button基础 在源码中Button继承TextView&#xff0c;所以TextView有的功能Button基本都有&#xff0c;重复功能不在赘述 1.2.1 设置button背景颜色 当使用background不起作用时&#xff0c…

通过SQL即可让监控分析更简单更高效

1.前言 阿里时序时空数据库TSDB最新推出TSQL&#xff0c;支持标准SQL的语法和函数。用户使用熟悉的SQL&#xff0c;不仅仅查询更简单易用&#xff0c;用户还可以利用SQL强大的功能&#xff0c;实现更加复杂的计算分析。 2. 为什么需要用SQL做时序查询&#xff1f; 2.1 SQL拥…

深度好文 | 战“疫”上云正当时:打开云计算的正确姿势

作者 | 马超责编 | Carol封图 | CSDN 付费下载于视觉中国4月29日&#xff0c;谷歌的母公司Alphabet正式发布了2020年第一季度财报&#xff0c;报告显示&#xff0c;Alphabet比去年同期的363.39亿美元增长13%&#xff0c;不计入汇率变动的影响为同比增长15%&#xff1b;在业绩公…

Windows批处理文件(.bat文件和.cmd文件)简单使用

cmd文件和bat文件的区别&#xff0c;从文件描述中的区别是&#xff0c;cmd文件叫做&#xff1a;Windows命令脚本&#xff0c;bat文件叫&#xff1a;批处理文件&#xff0c;两者都可以使用任意一款文本编辑器进行创建、编辑和修改&#xff0c;只是在cmd中支持的命令要多于bat。 …

AnalyticDB for MySQL:PB级云数仓核心技术和场景解析

2019阿里云峰会上海开发者大会于7月24日盛大开幕&#xff0c;本次峰会与未来世界的开发者们分享开源大数据、IT基础设施云化、数据库、云原生、物联网等领域的技术干货&#xff0c;共同探讨前沿科技趋势。本文整理自数据库专场中阿里云智能高级技术专家南仙的精彩演讲&#xff…

UML科普文,一篇文章掌握14种UML图

来源 | 如逆水行舟责编 | Carol封图 | CSDN 付费下载于视觉中国什么是UML&#xff1f;UML是Unified Model Language的缩写&#xff0c;中文是统一建模语言&#xff0c;是由一整套图表组成的标准化建模语言。为什么要用UML&#xff1f;通过使用UML使得在软件开发之前&#xff0c…

企业级数据库新型研发模式——数据管理DMS实践

2019阿里云峰会上海开发者大会于7月24日盛大开幕&#xff0c;本次峰会与未来世界的开发者们分享开源大数据、IT基础设施云化、数据库、云原生、物联网等领域的技术干货&#xff0c;共同探讨前沿科技趋势。本文整理自数据库专场中阿里云智能技术专家王天振 (为知)的精彩演讲&…

linux-centos7环境搭建

1、下载centos7 官网地址&#xff1a; http://isoredirect.centos.org/centos/7/isos/x86_64/ 阿里云&#xff1a; http://mirrors.aliyun.com/centos/ 以下针对各个版本的ISO镜像文件&#xff0c;进行一一说明&#xff1a; CentOS-7-x86_64-DVD-1708.iso 标准安装版&#x…

揭秘!机器人和你对话时在想什么?

阿里妹导读&#xff1a;为什么聊天机器人越来越普及&#xff1f;聊天机器人不仅可以节省时间&#xff0c;提升效率&#xff0c;还能一天24小时提供服务&#xff0c;更是可以减少误差。聊天机器人背后的问题原理是什么&#xff1f;效率如何提升&#xff1f;就是今天我们要了解的…

阿里云与A站在一起后,悄悄干了件大事

八月盛夏&#xff0c;“AcFun弹幕视频网站”&#xff08;简称“A站”&#xff09;的视频服务器全面迁移上阿里云&#xff08;此处应有掌声&#xff09;&#xff01; A站去年与阿里云达成此项合作。在迁移过程中&#xff0c;阿里云提供专业技术解决方案团队&#xff0c;为A站建立…

科大讯飞营收破百亿,员工涨薪27%,羡慕这个AI“老大哥”​了!

科大讯飞&#xff0c;中国AI公司“老大哥”&#xff0c;交出2019年成绩单。营收达到100.79亿&#xff0c;首次破百亿&#xff1b;净利润同比增长51.12%&#xff0c;达到8.19亿&#xff0c;日均盈利224万元&#xff0c;创下历史最佳业绩。与此同时&#xff0c;5大厂2020年应届生…

Knative Serving 之路由管理和 Ingress

Knative 默认会为每一个 Service 生成一个域名&#xff0c;并且 Istio Gateway 要根据域名判断当前的请求应该转发给哪个 Knative Service。Knative 默认使用的主域名是 example.com&#xff0c;这个域名是不能作为线上服务的。本文我首先介绍一下如何修改 默认主域名&#xff…

linux-centos7 关机命令、系统目录结构介绍

1、关机命令 关机指令 shutdown; sync # 将数据由内存同步到硬盘中&#xff0c;一般关机前需要同步一下&#xff0c;防止数据丢失shutdown # 关机指令&#xff0c;会在一定时间后关机&#xff0c;我试的是一分钟后关机shutdown -h 10 # 十分钟后关机 shutdown -h 10 # 十分…

SprinBoot2.X 集成 Flowable6.6 工作流引擎

上一篇&#xff1a;SpringBoot2.x Flowable 6.4.2 开源项目 码云开源地址&#xff1a;https://gitee.com/lwj/flow GitHub开源地址&#xff1a;https://github.com/ecnice/flow flowable学习 可以入群&#xff1a;633168411 说明:此项目是我师傅为了帮助更多小伙伴们入门工作流…

OceanBase高可用实践

背景 高可用是构建分布式系统的基石。一方面&#xff0c;出于成本考虑&#xff0c; 分布式系统往往采取比较廉价的硬件&#xff0c;其可靠性相对于小型机、专有硬件有很大的不足&#xff0c; 而分布式系统的规模一般比较大&#xff0c;假如硬件的可靠性只有三个9(99.9%)&#…

咦,拆分个字符串都这么讲究?

来源 | 沉默王二封图 | CSDN 付费下载于视觉中国提到拆分字符串&#xff0c;我猜你十有八九会撂下一句狠话&#xff0c;“这有什么难的&#xff0c;直接上 String 类的 split() 方法不就拉到了&#xff01;”假如你真的这么觉得&#xff0c;那可要注意了&#xff0c;事情远没这…

linux-centos7 常用的基本命令--目录管理、基本属性

一、目录管理 1、cd &#xff08;切换目录&#xff09; cd 路径 &#xff1a;切换路径命令&#xff0c;路径可以是绝对路径&#xff0c;也可以是相对路径 ./ : 当前目录 返回上级目录&#xff1a; cd … 返回用户目录&#xff1a; cd ~ 2、ls&#xff08;列出目录&#xff…

开源考试系统 - 本地代码调试运行

文章目录一、后端部署1. 图形化克隆项目2. 命令克隆项目3. 创建数据库&#xff0c;初始化数据库脚本4. IntelliJ IDEA打开项目5. 数据库连接和redis配置6. 启动redis和后端程序6. 浏览器访问二、前端部署2.1. 打开源码安装依赖2.2. 依次启动admin端和student端2.3. 浏览器访问补…

KDD 2019论文解读:异构信息网络上的对抗生成学习

前言 网络表示学习是一种在低维空间中表示网络数据的方法&#xff0c;在异构信息网络分析中得到了广泛的应用。现有的异构信息网络表示学习方法虽然在一定程度上实现了性能的提高&#xff0c;但仍然存在一些主要的不足。最重要的是&#xff0c;它们通常采用负抽样的方法从网络…

剖析疫情环境下的国内云市场:大势所趋,正是大展拳脚的好时机!

作者 | 马超责编 | Carol封图 | CSDN 付费下载于视觉中国4月29日&#xff0c;谷歌的母公司Alphabet正式发布了2020年第一季度财报&#xff0c;报告显示&#xff0c;Alphabet比去年同期的363.39亿美元增长13%&#xff0c;不计入汇率变动的影响为同比增长15%&#xff1b;在业绩公…