面试题:什么是微服务 ?

news2024/11/15 5:52:40

文章目录

  • 微服务是什么
  • 微服务是什么样的
  • 服务化的前提
  • 服务的发布与引用
  • 声明式Restful API
  • 服务的注册与发现
    • 一致性与可用性
    • 注册方式
    • 存储结构
    • 服务健康监测
    • 状态变更通知
  • 服务的远程通信
    • 网络I/O的处理
    • 传输协议
    • 序列化方式
  • 注册中心如何保障稳定性
    • 节点信息的保障
    • 服务节点的摘除
    • 服务节点是可以随便摘除/变更的么
  • 服务消费者如何保障稳定性
    • 容错机制
    • 隔离
    • 降级
  • 服务提供者如何保障稳定性
    • 限流
    • 重启与回滚
    • 调度流量


微服务是什么

抛去教条性质的解释,从巨石应用到微服务应用,耦合度是其中最大的变化。或是将多个模块中重复的部分进行拆分,或是纯粹为了拆分膨胀的单体应用,这些拆分出来的部分独立成一个服务单独部署与维护,便是微服务了。

拆分后自然而然会催生出一些必要的需求:

  1. 从本地方法调用的关系衍变成远程过程调用的关系,那么可靠的通信功能是首要的。
  2. 随着拆分工作的推进,资源调度关系会变得错综复杂,这时候需要完善的服务治理。
  3. 调用关系网的整体复杂化还会给我们带来更大的风险,即链式反应导致服务雪崩的可能性,所以如何保障服务稳定性也是微服务架构中需要考虑的。
  4. 这点就不是内需而算是自我演进了,服务化后,如果能结合容器化、Devops技术实现服务运维一体化,将大大降低微服务维护的成本,不管是现在还是将来。

微服务是什么样的

从目前常见网站架构的宏观角度看,微服务处在中间的层次。红框圈出的部分都属于微服务的范畴。包括最基础的rpc框架、注册中心、配置中心,以及更广义角度的监控追踪、治理中心、调度中心等。
在这里插入图片描述
从微服务自身角度来看,则大致会包含以下这些模块:

  • 服务注册与发现
  • rpc远程调用
  • 路由与负载均衡
  • 服务监控
  • 服务治理
    在这里插入图片描述

服务化的前提

是不是只要套上微服务框架就算是一个微服务了呢?虽然这样有了微服务的表,但却没有微服务的实质,即”微“。微服务化的前提是服务拆分到足够”微“,足够单一职责,当然拆分程度与服务边界都需要结合业务自行把握。

广义的服务拆分即包含了应用拆分,也包含了数据拆分。

应用拆分后需要引入微服务框架来进行服务通信与服务治理,这也就是传统定义上的微服务。

数据拆分后同样需要引入一系列手段来进行保障,由于不是与微服务强相关的话题,在此只做简单阐述:

  • 分布式id
  • 新表优化
  • 数据迁移与数据同步
  • sql调用方案改造
  • 切库方案
  • 数据一致性

在我们对微服务架构有了整体的认识,并且具备了服务化的前提后,一个完整的微服务请求需要涉及到哪些内容呢?这其中包括了微服务框架所具备的三个基本功能:

  • 服务的发布与引用
  • 服务的注册与发现
  • 服务的远程通信

服务的发布与引用

首先我们面临的第一个问题是,如何发布服务和引用服务。具体一点就是,这个服务的接口名是啥,有哪些参数,返回值是什么类型等等,通常也就是接口描述信息。

常见的发布和引用的方式包括:

  • RESTful API / 声明式Restful API
  • XML
  • IDL

一般来讲,不管使用哪种方式,服务端定义接口与实现接口都是必要的,例如:

@exa(id = "xxx")
public interface testApi {

    @PostMapping(value = "/soatest/{id}")
    String getResponse(@PathVariable(value = "id") final Integer index, @RequestParam(value = "str") final String Data);
    }

具体实现如下:

public class testApiImpl implements testApi{

        @Override
        String getResponse(final Integer index, final String Data){
            return "ok";
        }
}

声明式Restful API

这种常使用HTTP或者HTTPS协议调用服务,相对来说,性能稍差。

首先服务端如上定义接口并实现接口,随后服务提供者可以使用类似restEasy这样的框架通过servlet的方式发布服务,而服务消费者直接引用定义的接口调用。

除此之外还有一种类似feign的方式,即服务端的发布依赖于springmvc controller,框架只基于客户端模板化http请求调用。这种情况下需接口定义与服务端controller协商一致,这样客户端直接引用接口发起调用即可。

XML

使用私有rpc协议的都会选择xml配置的方式来描述接口,比较高效,例如dubbo、motan等。

同样服务端如上定义接口并实现接口,服务端通过server.xml将文件接口暴露出去。服务消费者则通过client.xml引用需要调用的接口。

但这种方式对业务代码入侵较高,xml配置有变更时候,服务消费者和服务提供者都需要更新。

IDL

IDL是接口描述语言,常用于跨语言之间的调用,最常用的IDL包括Thrift协议以及gRpc协议。例如gRpc协议使用Protobuf来定义接口,写好一个proto文件后,利用语言对应的protoc插件生成对应server端与client端的代码,便可直接使用。

但是如果参数字段非常多,proto文件会显得非常大难以维护。并且如果字段经常需要变更,例如删除字段,PB就无法做到向前兼容。

一些tips

不管哪种方式,在接口变更的时候都需要通知服务消费者。消费者对api的强依赖性是很难避免的,接口变更引起的各种调用失败也十分常见。所以如果有变更,尽量使用新增接口的方式,或者给每个接口定义好版本号吧。

在使用上,大多数人的选择是对外Restful,对内Xml,跨语言IDL。

一些问题

在实际的服务发布与引用的落地上,还会存在很多问题,大多和配置信息相关。例如一个简单的接口调用超时时间配置,这个配置应该配在服务级别还是接口级别?是放在服务提供者这边还是服务消费者这边?

在实践中,大多数服务消费者会忽略这些配置,所以服务提供者自身提供默认的配置模板是有必要的,相当于一个预定义的过程。每个服务消费者在继承服务提供者预定义好的配置后,还需要能够进行自定义的配置覆盖。

但是,比方说一个服务有100个接口,每个接口都有自身的超时配置,而这个服务又有100个消费者,当服务节点发生变更的时候,就会发生100*100次注册中心的消息通知,这是比较可怕的,就有可能引起网络风暴。

服务的注册与发现

假设你已经发布了服务,并在一台机器上部署了服务,那么消费者该怎样找到你的服务的地址呢?

也许有人会说是DNS,但DNS有许多缺陷:

  • 维护麻烦,更新延迟
  • 无法在客户端做负载均衡
  • 不能做到端口级别的服务发现

其实在分布式系统中,有个很重要的角色,叫注册中心,便是用于解决该问题。

在这里插入图片描述
使用注册中心寻址并调用的过程如下:

  • 服务启动时,向注册中心注册自身,并定期发送心跳汇报存活状态。
  • 客户端调用服务时,向注册中心订阅服务,并将节点列表缓存至本地,再与服务端建立连接(当然这儿可以lazy
    load)。发起调用时,在本地缓存节点列表中,基于负载均衡算法选取一台服务端发起调用。
  • 当服务端节点发生变更,注册中心能感知到后通知到客户端。

注册中心的实现主要需要考虑以下这些问题:

  • 自身一致性与可用性
  • 注册方式
  • 存储结构
  • 服务健康监测
  • 状态变更通知

一致性与可用性

一个老旧的命题,即分布式系统中的CAP(一致性、可用性、分区容错性)。我们知道同时满足CAP是不可能的,那么便需要有取舍。常见的注册中心大致分为CP注册中心以及AP注册中心。

CP注册中心

比较典型的就是zookeeper、etcd以及consul了,牺牲可用性来保证了一致性,通过zab协议或者raft协议来保证一致性。

AP注册中心

牺牲一致性来保证可用性,感觉只能列出eureka了。eureka每个服务器单独保存节点列表,可能会出现不一致的情况。

从理论上来说,仅用于注册中心,AP型是远比CP型合适的。可用性的需求远远高于一致性,一致性只要保证最终一致即可,而不一致的时候还可以使用各种容错策略进行弥补。

保障高可用性其实还有很多办法,例如集群部署或者多IDC部署等。Consul就是多IDC部署保障可用性的典型例子,它使用了wan gossip来保持跨机房状态同步。

注册方式

有两种与注册中心交互的方式,一种是通过应用内集成sdk,另一种则是通过其他方式在应用外间接与注册中心交互。

应用内

这应该就是最常见的方式了,客户端与服务端都集成相关sdk与注册中心进行交互。例如选择zookeeper作为注册中心,那么就可以使用curator sdk进行服务的注册与发现。

应用外

consul提供了应用外注册的解决方案,consul agent或者第三方Registrator可以监听服务状态,从而负责服务提供者的注册或销毁。而Consul Template则可以做到定时从注册中心拉取节点列表,并刷新LB配置(例如通过Nginx的upstream),这样就相当于完成了服务消费者端的负载均衡。

存储结构

注册中心存储相关信息一般采取目录化的层次结构,一般分为服务-接口-节点信息。

同时注册中心一般还会进行分组,分组的概念很广,可以是根据机房划分也可以根据环境划分。

节点信息主要会包括节点的地址(ip和端口号),还有一些节点的其他信息,比如请求失败的重试次数、超时时间的设置等等。

当然很多时候,其实可能会把接口这一层给去掉,因为考虑到接口数量很多的情况下,过多的节点会造成很多问题,比如之前说的网络风暴。

服务健康监测

服务存活状态监测也是注册中心的一个必要功能。在zookeeper中,每个客户端都会与服务端保持一个长连接,并生成一个session,在session过期周期内,通过客户端定时向服务端发送心跳包来检测链路是否正常,服务端则重置下次session的过期时间,如果session过期周期内都没有检测到客户端的心跳包,那么就会认为它已经不可用了,将其从节点列表中移除。

状态变更通知

在注册中心具备服务健康检测能力后,还需要将状态变更通知到客户端。在zookeeper中,可以通过监听器watcher的process方法来获取服务变更。

服务的远程通信

在上面,服务消费者已经正确引用了服务,并发现了该服务的地址,那么如何向这个地址发起请求呢?要解决服务间的远程通信问题,我们需要考虑一些问题:

  • 网络I/O的处理
  • 传输协议
  • 序列化方式

网络I/O的处理

简单来说,就是客户端是怎么处理请求?服务端又是怎么处理请求的?

先从客户端来说,我们创建连接的时机可以是从注册中心获取到节点信息的时候,但更多时候,我们会选择在第一次请求发起调用的时候去创建连接。此外,我们往往会为该节点维护一个连接池,进行连接复用。

如果是异步的情况下,我们还需要为每一个请求编号,并维护一个请求池,从而在响应返回时找到对应的请求。当然这并不是必须的,很多框架会帮我们干好这些事情,比如rxNetty。

从服务端来说,处理请求的方式就可以追溯到unix的5种IO模型了。我们可以直接使用Netty、MINA等网络框架来处理服务端请求,或者如果你有十分的兴趣,可以自己实现一个通信框架。

传输协议

最常见的当然是直接使用Http协议,使用双方无需关注和了解协议内容,方便直接,但自然性能上会有所折损。

还有就是目前比较火热的http2协议,拥有二进制数据、头部压缩、多路复用等许多优良特性。但从自身的实践上看,http2要走到生产仍有一段距离,一个最简单的例子,升级到http2后所有的header names都变成小写,同时不是case-insenstive了,这时候就会有兼容性问题。

当然如果追求更高效与可控的传输,可以定制私有协议并基于tcp进行传输。私有协议的定制需要通信双方都了解其特性,设计上还需要注意预留好扩展字段,以及处理好粘包分包等问题。

序列化方式

在网络传输的前后,往往都需要在发送端进行编码,在服务端进行解码,这样主要是为了在网络传输时候减少数据传输量。

常用的序列化方式包括文本类的,例如XML/JSON,还有二进制类型的,例如Protobuf/Thrift等。在选择序列化的考虑上,一是性能,Protobuf的压缩大小和压缩速度都会比JSON快很多,性能也更好。二是兼容性上,相对来说,JSON的前后兼容性会强一些,可以用于接口经常变化的场景。

在此还是需要强调,使用每一种序列化都需要了解过其特性,并在接口变更的时候拿捏好边界。例如jackson的FAIL_ON_UNKNOW_PROPERTIES属性、kryo的CompatibleFieldSerializer、jdk序列化会严格比较serialVersionUID等等。

当一个单体应用改造成多个微服务之后,在请求调用过程中往往会出现更多的问题,通信过程中的每一个环节都可能出现问题。而在出现问题之后,如果不加处理,还会出现链式反应导致服务雪崩。服务治理功能就是用来处理此类问题的。我们将从微服务的三个角色:注册中心、服务消费者以及服务提供者一一说起。

注册中心如何保障稳定性

注册中心主要是负责节点状态的维护,以及相应的变更探测与通知操作。一方面,注册中心自身的稳定性是十分重要的。另一方面,我们也不能完全依赖注册中心,需要时常进行类似注册中心完全宕机后微服务如何正常运行的故障演练。

这一节,我们着重讲的并不是注册中心自身可用性保证,而更多的是与节点状态相关的部分。

节点信息的保障

我们说过,当注册中心完全宕机后,微服务框架仍然需要有正常工作的能力。这得益于框架内处理节点状态的一些机制。

本机内存

首先服务消费者会将节点状态保持在本机内存中。一方面由于节点状态不会变更得那么频繁,放在内存中可以减少网络开销。另一方面,当注册中心宕机后,服务消费者仍能从本机内存中找到服务节点列表从而发起调用。

本地快照

我们说,注册中心宕机后,服务消费者仍能从本机内存中找到服务节点列表。那么如果服务消费者重启了呢?这时候我们就需要一份本地快照了,即我们保存一份节点状态到本地文件,每次重启之后会恢复到本机内存中。

服务节点的摘除

现在无论注册中心工作与否,我们都能顺利拿到服务节点了。但是不是所有的服务节点都是正确可用的呢?在实际应用中,这是需要打问号的。如果我们不校验服务节点的正确性,很有可能就调用到了一个不正常的节点上。所以我们需要进行必要的节点管理。

对于节点管理来说,我们有两种手段,主要是去摘除不正确的服务节点。

注册中心摘除机制

一是通过注册中心来进行摘除节点。服务提供者会与注册中心保持心跳,而一旦超出一定时间收不到心跳包,注册中心就认为该节点出现了问题,会把节点从服务列表中摘除,并通知到服务消费者,这样服务消费者就不会调用到有问题的节点上。

服务消费者摘除机制

二是在服务消费者这边拆除节点。因为服务消费者自身是最知道节点是否可用的角色,所以在服务消费者这边做判断更合理,如果服务消费者调用出现网络异常,就将该节点从内存缓存列表中摘除。当然调用失败多少次之后才进行摘除,以及摘除恢复的时间等等细节,其实都和客户端熔断类似,可以结合起来做。

一般来说,对于大流量应用,服务消费者摘除的敏感度会高于注册中心摘除,两者之间也不用刻意做同步判断,因为过一段时间后注册中心摘除会自动覆盖服务消费者摘除。

服务节点是可以随便摘除/变更的么

上一节我们讲可以摘除问题节点,从而避免流量调用到该节点上。但节点是可以随便摘除的么?同时,这也包含"节点是可以随便更新的么?"疑问。

频繁变动

当网络抖动的时候,注册中心的节点就会不断变动。这导致的后果就是变更消息会不断通知到服务消费者,服务消费者不断刷新本地缓存。如果一个服务提供者有100个节点,同时有100个服务消费者,那么频繁变动的效果可能就是100*100,引起带宽打满。

这时候,我们可以在注册中心这边做一些控制,例如经过一段时间间隔后才能进行变更消息通知,或者打开开关后直接屏蔽不进行通知,或者通过一个概率计算来判断需要向哪些服务消费者通知。

增量更新

同样是由于频繁变动可能引起的网络风暴问题,一个可行的方案是进行增量更新,注册中心只会推送那些变化的节点信息而不是全部,从而在频繁变动的时候避免网络风暴。

可用节点过少

当网络抖动,并进行节点摘除过后,很可能出现可用节点过少的情况。这时候过大的流量分配给过少的节点,导致剩下的节点难堪重负,罢工不干,引起恶化。而实际上,可能节点大多数是可用的,只不过由于网络问题与注册中心未能及时保持心跳而已。

这时候,就需要在服务消费者这边设置一个开关比例阈值,当注册中心通知节点摘除,但缓存列表中剩下的节点数低于一定比例后(与之前一段时间相比),不再进行摘除,从而保证有足够的节点提供正常服务。

这个值其实可以设置的高一些,例如百分之70,因为正常情况下不会有频繁的网络抖动。当然,如果开发者确实需要下线多数节点,可以关闭该开关。

服务消费者如何保障稳定性

一个请求失败了,最直接影响到的是服务消费者,那么在服务消费者这边,有什么可以做的呢?

超时

如果调用一个接口,但迟迟没有返回响应的时候,我们往往需要设置一个超时时间,以防自己被远程调用拖死。超时时间的设置也是有讲究的,设置的太长起的作用就小,自己被拖垮的风险就大,设置的太短又有可能误判一些正常请求,大幅提升错误率。

在实际使用中,我们可以取该应用一段时间内的P999的值,或者取p95的值*2。具体情况需要自行定夺。

在超时设置的时候,对于同步与异步的接口也是有区分的。对于同步接口,超时设置的值不仅需要考虑到下游接口,还需要考虑上游接口。而对于异步来说,由于接口已经快速返回,可以不用考虑上游接口,只需考虑自身在异步线程里的阻塞时长,所以超时时间也放得更宽一些。

容错机制

请求调用永远不能保证成功,那么当请求失败时候,服务消费者可以如何进行容错呢?通常容错机制分为以下这些:

  • FailTry:失败重试。就是指最常见的重试机制,当请求失败后视图再次发起请求进行重试。这样从概率上讲,失败率会呈指数下降。对于重试次数来说,也需要选择一个恰当的值,如果重试次数太多,就有可能引起服务恶化。另外,结合超时时间来说,对于性能有要求的服务,可以在超时时间到达前的一段提前量就发起重试,从而在概率上优化请求调用。当然,重试的前提是幂等操作。
  • FailOver:失败切换。和上面的策略类似,只不过FailTry会在当前实例上重试。而FailOver会重新在可用节点列表中根据负载均衡算法选择一个节点进行重试。
  • FailFast:快速失败。请求失败了就直接报一个错,或者记录在错误日志中,这没什么好说的。

另外,还有很多形形色色的容错机制,大多是基于自己的业务特性定制的,主要是在重试上做文章,例如每次重试等待时间都呈指数增长等。

第三方框架也都会内置默认的容错机制,例如Ribbon的容错机制就是由retry以及retry next组成,即重试当前实例与重试下一个实例。这里要多说一句,ribbon的重试次数与重试下一个实例次数是以笛卡尔乘积的方式提供的噢!

熔断

上一节将的容错机制,主要是一些重试机制,对于偶然因素导致的错误比较有效,例如网络原因。但如果错误的原因是服务提供者自身的故障,那么重试机制反而会引起服务恶化。这时候我们需要引入一种熔断的机制,即在一定时间内不再发起调用,给予服务提供者一定的恢复时间,等服务提供者恢复正常后再发起调用。这种保护机制大大降低了链式异常引起的服务雪崩的可能性。

在实际应用中,熔断器往往分为三种状态,打开、半开以及关闭。引用一张martinfowler画的原理图:

在这里插入图片描述
在普通情况下,断路器处于关闭状态,请求可以正常调用。当请求失败达到一定阈值条件时,则打开断路器,禁止向服务提供者发起调用。当断路器打开后一段时间,会进入一个半开的状态,此状态下的请求如果调用成功了则关闭断路器,如果没有成功则重新打开断路器,等待下一次半开状态周期。

断路器的实现中比较重要的一点是失败阈值的设置。可以根据业务需求设置失败的条件为连续失败的调用次数,也可以是时间窗口内的失败比率,失败比率通过一定的滑动窗口算法进行计算。另外,针对断路器的半开状态周期也可以做一些花样,一种常见的计算方法是周期长度随着失败次数呈指数增长。

具体的实现方式可以根据具体业务指定,也可以选择第三方框架例如Hystrix。

隔离

隔离往往和熔断结合在一起使用,还是以Hystrix为例,它提供了两种隔离方式:

  • 信号量隔离:使用信号量来控制隔离线程,你可以为不同的资源设置不同的信号量以控制并发,并相互隔离。当然实际上,使用原子计数器也没什么不一样。
  • 线程池隔离:通过提供相互隔离的线程池的方式来隔离资源,相对来说消耗资源更多,但可以更好地应对突发流量。

降级

降级同样大多和熔断结合在一起使用,当服务调用者这方断路器打开后,无法再对服务提供者发起调用了,这时候可以通过返回降级数据来避免熔断造成的影响。

降级往往用于那些错误容忍度较高的业务。同时降级的数据如何设置也是一门学问。一种方法是为每个接口预先设置好可接受的降级数据,但这种静态降级的方法适用性较窄。还有一种方法,是去线上日志系统/流量录制系统中捞取上一次正确的返回数据作为本次降级数据,但这种方法的关键是提供可供稳定抓取请求的日志系统或者流量采样录制系统。

参考:面试官:什么是熔断?什么是服务降级?

另外,针对降级我们往往还会设置操作开关,对于一些影响不大的采取自动降级,而对于一些影响较大的则需进行人为干预降级。

服务提供者如何保障稳定性

限流

限流就是限制服务请求流量,服务提供者可以根据自身情况(容量)给请求设置一个阈值,当超过这个阈值后就丢弃请求,这样就保证了自身服务的正常运行。

阈值的设置可以针对两个方面考虑,一是QPS即每秒请求数,二是并发线程数。从实践来看,我们往往会选择后者,因为QPS高往往是由于处理能力高,并不能反映出系统"不堪重负"。

除此之外,我们还有许多针对限流的算法。例如令牌桶算法以及漏桶算法,主要针对突发流量的状况做了优化。第三方的实现中例如guava rateLimiter就实现了令牌桶算法。在此就不就细节展开了。

重启与回滚

限流更多的起到一种保障的作用,但如果服务提供者已经出现问题了,这时候该怎么办呢?

这时候就会出现两种状况。一是本身代码有bug,这时候一方面需要服务消费者做好熔断降级等操作,一方面服务提供者这边结合DevOps需要有快速回滚到上一个正确版本的能力。

更多的时候,我们可能仅仅碰到了与代码无强关联的单机故障,一个简单粗暴的办法就是自动重启。例如观察到某个接口的平均耗时超出了正常范围一定程度,就将该实例进行自动重启。当然自动重启需要有很多注意事项,例如重启时间是否放在晚上,以及自动重启引起的与上述节点摘除一样的问题,都需要考虑和处理。

在事后复盘的时候,如果当时没有保护现场,就很难定位到问题原因。所以往往在一键回滚或者自动重启之前,我们往往需要进行现场保护。现场保护可以是自动的,例如一开始就给jvm加上打印gc日志的参数-XX:+PrintGCDetails,或者输出oom文件-XX:+HeapDumpOnOutOfMemoryError,也可以配合DevOps自动脚本完成,当然手动也可以。一般来说我们会如下操作:

  • 打印堆栈信息,jstak -l ‘java进程PID’
  • 打印内存镜像,jmap -dump:format=b,file=hprof ‘java进程PID’
  • 保留gc日志,保留业务日志

调度流量

除了以上这些措施,通过调度流量来避免调用到问题节点上也是非常常用的手段。

当服务提供者中的一台机器出现问题,而其他机器正常时,我们可以结合负载均衡算法迅速调整该机器的权重至0,避免流量流入,再去机器上进行慢慢排查,而不用着急第一时间重启。

如果服务提供者分了不同集群/分组,当其中一个集群出现问题时,我们也可以通过路由算法将流量路由到正常的集群中。这时候一个集群就是一个微服务分组。

而当机房炸了、光缆被偷了等IDC故障时,我们又部署了多IDC,也可以通过一些方式将流量切换到正常的IDC,以供服务继续正常运行。切换流量同样可以通过微服务的路由实现,但这时候一个IDC对应一个微服务分组了。除此之外,使用DNS解析进行流量切换也是可以的,将对外域名的VIP从一个IDC切换到另一个IDC。

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

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

相关文章

微信分销小程序商城的作用是什么

众人拾柴火焰高,分销商城值得是商家通过一定比列的佣金或奖品激励会员或用户分销商品/服务的手段,以低成本获得高收益,也成为不少线上线下商家寻求增长的一种方法。 商家难以完善“引流拓客-转化-留存-复购-裂变-分享”等路径,导…

游戏反虚拟框架检测方案

游戏风险环境,是指独立于原有设备或破坏设备原有系统的环境。常见的游戏风险环境有:iOS越狱、安卓设备root、虚拟机、虚拟框架、云手机等。 因为这类风险环境可以为游戏外挂、破解提供所需的高级别设备权限,所以当游戏处于这些设备环境下&am…

Element UI 密码输入框--可切换显示隐藏,自定义图标

<el-form-item prop"password"><el-inputkeyup.enter.native"login"placeholder"密码"v-model"formData.password":type"showPassword ? text : password"><i slot"suffix" click"switchPas…

策略模式在社会中的应用

文章目录 &#x1f31f; 如何将设计模式策略模式运用到社会当中&#x1f34a; 什么是策略模式&#x1f34a; 策略模式在社会中的应用&#x1f389; 1. 政治选举&#x1f389; 2. 商业竞争&#x1f389; 3. 教育培训 &#x1f34a; 策略模式的优缺点&#x1f389; 优点&#x1f…

SSM - Springboot - MyBatis-Plus 全栈体系(三十三)

第八章 项目实战 终极实战&#xff1a;SpringBoot 版微头条实战 一、微头条案例介绍 1. 微头条业务简介 用户功能 注册功能登录功能jwt 实现 头条新闻 新闻的分页浏览通过标题关键字搜索新闻查看新闻详情新闻的修改和删除 2. 技术栈介绍 2.1 前端技术栈 ES6 作为基础 J…

操作系统——信号量机制(王道视频p30、课本ch6)

1.记忆有技巧&#xff1a;PV操作 —— wait,signal ——肯定是先申请/等待&#xff0c;再释放 2.这里特别指出 记录型信号量就是 后面会重点研究的内容

软件测试肖sir__python之ui自动化实战和讲解(03)

python之ui自动化实战和讲解 一、讲解常见控件定位 链接&#xff1a;http://cms.duoceshi.cn/cms/manage/login.do 1、定位文本框&#xff0c;密码框&#xff0c;按钮 2. 输入 &#xff1a;send_keys()方法 3、点击 &#xff1a;click&#xff08;&#xff09; 方法 案例&…

uniapp在线预约到家按摩系统安装部署二次开发 可以搭建H5公众号+小程序+APP

后端Thinkphp框架开发。前端采用uni-app开发&#xff0c;适配多端&#xff08;小程序&#xff0b;公众号H5&#xff0b;APP&#xff09;源码全开源。 Nginx1.22.1Mysql5.6php7.3redis扩展 支持h5、公众号、小程序、APP&#xff0c;提供相关教程和详细功能文档&#xff01; 含开…

LeetCode算法刷题(python) Day41|09动态规划|理论基础、509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

目录 动规五部曲LeetCode 509. 斐波那契数LeetCode 70. 爬楼梯LeetCode 746. 使用最小花费爬楼梯 动规五部曲 确定dp数组以及下标的含义确定递归公式dp数组如何初始化确定遍历顺序举例推导dp数组 LeetCode 509. 斐波那契数 力扣题目链接 本题最直观是用递归方法 class Sol…

GIL全局解释器锁

文章目录 GIL全局解释器锁一、引入&#xff1a;二、GIL介绍三、GIL与Lock四、GIL与多线程总结 GIL全局解释器锁 一、引入&#xff1a; 首先要明白&#xff0c;GIL并不是Python的一个特性&#xff0c;其实在我们通常所称呼的Python解释器&#xff0c;其实是CPython解释器&…

excel, 熟悉了一般应用的函数,为何还有必要学vba

背景&#xff1a; 1. win10, office 2016, hp Elitebook笔记本电脑 2. 统计数量时&#xff0c;在sheet &#xff08;工作表&#xff09;页面使用函数 sum通过类似 sum(c6:c8) 进行求和&#xff0c;其中C7未被计算在内&#xff0c;例子见图片或附件。即使使用 “数据透视表”…

基于QT的学生考勤系统

低价有偿-获取代码&#xff1a; 知识付费时代&#xff0c;低价有偿获取代码&#xff0c;请理解&#xff01; (1) 下载链接: 后补。 (2) 添加博主微信获取&#xff08;有偿&#xff09;,备注来源: mryang511688 项目描述 技术&#xff1a;C、QT等 摘要&#xff1a; 信息技术的迅…

【Andriod】Appium的不同版本(Appium GUI、Appium Desktop、Appium Server )的安装教程

文章目录 前言一.Appium GUI二.Appium Desktop三.Appium Server 命令行版本1.安装node.js2.安装Appium Server 前言 Appium 安装提供两2方式&#xff1a;桌面版和命令行版。其中桌面版又分为 Appium GUI 和 Appium Desktop。 建议&#xff1a;使用Appium Desktop 一.Appium …

ubuntu安装Anaconda

下载 Anaconda 进入 Ubuntu&#xff0c;自己新建下载路径&#xff0c;输入以下命令开始下载 注意&#xff0c;如果不是 x86_64&#xff0c;需要去镜像看对应的版本&#xff08;https://mirrors.bfsu.edu.cn/anaconda/archive/?CM&OA&#xff09; wget https://mirrors.…

【Java 进阶篇】深入了解 Bootstrap 表格和菜单

表格和菜单是网页设计中的重要组成部分&#xff0c;它们用于展示数据、导航和用户交互。Bootstrap 是一个强大的前端框架&#xff0c;提供了丰富的表格样式和菜单组件&#xff0c;使开发者能够轻松创建功能丰富的网页。在本文中&#xff0c;我们将深入探讨 Bootstrap 中表格和菜…

火热的“出海潮”背后,你还应该知道些什么?

点击文末“阅读原文”即可报名参会 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩北京录音间 联合制作 / RTE开发者社区 过去几十年&#xff0c;入华的科技企业“走进来”&#xff0c;加速了国内企业的数…

VM虚拟机创建centos7 64位系统提示此主机不支持64位客户机操作系统,此系统无法运行

VM虚拟机创建centos7 64位系统提示此主机不支持64位客户机操作系统,此系统无法运行 背景解决方案 背景 本身系统是window10 64位专业版系统&#xff0c;理论上不应该不支持64位的。 解决方案 最近安装docker开启了虚拟化hyper-v&#xff0c;关闭即可。 打开cmd&#xff08;…

Vue中的v-if和v-show指令有什么区别?

在Vue中,v-if和v-show是两个常用的指令,用于根据条件控制元素的显示和隐藏。虽然它们都可以根据条件来切换元素的可见性,但它们的实现和行为有一些区别。 1:编译时机: v-if是在编译阶段进行条件判断,如果条件为false,则在DOM中不会渲染该元素及其子元素;v-show则是在运…

MongoDB深度学习

MongoDB的简介 什么是MongoDB? MongoDB是一个基于分布式文件存储的数据库&#xff0c;由C语言编写。MongoDB是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据库的&#xff0c;它支持的数据结构非常松散&…

解码eBPF可观测性:eBPF如何改变我们所知的观测性

让我们来看看eBPF——这项技术到底是什么&#xff0c;它如何影响观测性&#xff0c;它与现有的观测性实践有什么区别&#xff0c;未来可能会发生什么变化&#xff1f; 在过去的两年里&#xff0c;云原生社区一直在热烈讨论eBPF。eBPF曾是KubeCon、eBPF Days和eBPF Summit的主题…